Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
chaco / overlays / simple_inspector_overlay.py
Size: Mime:
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

"""A simple inspector overlay for plots

This module provides the SimpleInspectorOverlay for displaying
information gathered from an inspector tool in a TextGrid.  By default
it is configured to work with a SimpleInspectorTool.

The module also provides some helper factory functions for creating text
formatters for dictionary values.
"""

from numpy import array

from traits.api import Any, List, Callable, Enum, Bool

from .text_grid_overlay import TextGridOverlay


def basic_formatter(key, decimals):
    """Create a basic '<key>: <value>' formatting function

    This factory creates a function that formats a specified key and with a
    numerical value from a dictionary into a string.

    Parameters
    ----------

    key
        The dictionary key to format.
    decimals
        The number of decimal places to show.

    Returns
    -------

    format
        A factory function that takes a dictionary and returns a string.
    """
    format_string = "%s: %%(%s).%df" % (key, key, decimals)

    def format(**kwargs):
        return format_string % kwargs

    return format


def datetime_formatter(key, time_format="%Y/%m/%d %H:%M:%S"):
    """Create a datetime formatting function

    This factory creates a function that formats a specified key and with a
    timestamp value from a dictionary into a string.

    Parameters
    ----------

    key
        The dictionary key to format.  The corresponding value should be a
        timestamp.
    time_format
        A format string suitable for strftime().

    Returns
    -------

    format
        A factory function that takes a dictionary and returns a string.
    """
    import datetime

    def format(**kwargs):
        dt = datetime.datetime.fromtimestamp(kwargs[key])
        return key + ": " + dt.strftime(time_format)

    return format


def time_formatter(key):
    """Create a time formatting function

    This factory creates a function that formats a specified key and with a
    timestamp value from a dictionary into a 'HH:MM:SS' format string.

    Parameters
    ----------

    key
        The dictionary key to format.  The corresponding value should be a
        timestamp.

    Returns
    -------

    format
        A factory function that takes a dictionary and returns a string.
    """
    return datetime_formatter(key, time_format="%H:%M:%S")


def date_formatter(key):
    """Create a date formatting function

    This factory creates a function that formats a specified key and with a
    timestamp value from a dictionary into a 'yyyy/mm/dd' format string.

    Parameters
    ----------

    key
        The dictionary key to format.  The corresponding value should be a
        timestamp.

    Returns
    -------

    format
        A factory function that takes a dictionary and returns a string.
    """
    return datetime_formatter(key, time_format="%Y/%m/%d")


class SimpleInspectorOverlay(TextGridOverlay):
    """Simple inspector overlay for plots

    This is a simple overlay that listens for new_value events on a
    SimpleInspectorTool and displays formatted values in a grid.

    By default this displays the 'x' and 'y' values provided by the
    SimpleInspectorTool, but instances can provide a field_formatters
    trait which is a list of lists of callables which extract values
    from a dictionary and formats them.  Each callable corresponds to a
    cell in the underlying TextGrid component.

    Although by default this works with the SimpleInspectorTool, with
    appropriate field_formatters this class can be used with any inspector
    tool that follows the same API.
    """

    # XXX We should probably refactor this into a BaseInspectorOverlay
    # which handles the visibility and basic event handling, and smaller
    # version of this class which handles inserting values into a text grid

    # the inspector that I am listening to.  This should have a new_value
    # event and a visible trait for me to listen to.
    inspector = Any

    # fields to display
    field_formatters = List(List(Callable))

    # Anchor the text to the mouse?  (If False, then the text is in one of the
    # corners.)  Use the **align** trait to determine which corner.
    tooltip_mode = Bool(False)

    # The default state of the overlay is visible.
    visible = True

    # Whether the overlay should auto-hide and auto-show based on the
    # tool's location, or whether it should be forced to be hidden or visible.
    visibility = Enum("auto", True, False)

    #########################################################################
    # Traits Handlers
    #########################################################################

    def _field_formatters_default(self):
        return [[basic_formatter("x", 2)], [basic_formatter("y", 2)]]

    def _new_value_updated(self, event):
        new_value_event = event.new
        if new_value_event is None:
            self.text_grid = array()
            if self.visibility == "auto":
                self.visibility = False
        elif self.visibility == "auto":
            self.visible = True

        if self.tooltip_mode:
            self.alternate_position = self.inspector.last_mouse_position

        d = new_value_event
        text = []
        self.text_grid.string_array = array(
            [
                [formatter(**d) for formatter in row]
                for row in self.field_formatters
            ]
        )

        self.text_grid.request_redraw()

    def _visible_changed(self):
        if self.component:
            self.request_redraw()

    def _inspector_changed(self, old, new):
        if old:
            old.observe(self._new_value_updated, "new_value", remove=True)
            old.observe(self._tool_visible_changed, "visible", remove=True)
        if new:
            new.observe(self._new_value_updated, "new_value")
            new.observe(self._tool_visible_changed, "visible")
            self._tool_visible_changed()

    def _tool_visible_changed(self, event=None):
        self.visibility = self.inspector.visible
        if self.visibility != "auto":
            self.visible = self.visibility