Пример #1
0
class BarRendererStyle(BaseXYRendererStyle):
    """ Styling object for customizing line renderers.
    """
    #: Name of the
    renderer_type = "bar"

    #: Width of each bar. Leave as 0 to have it computed programmatically.
    bar_width = Float

    #: Color of the contours of the bars
    line_color = ColorTrait(DEFAULT_RENDERER_COLOR)

    #: Color of the inside of the bars
    fill_color = ColorTrait(DEFAULT_RENDERER_COLOR)

    def traits_view(self):
        view = self.view_klass(
            VGroup(
                Item('bar_width'),
                HGroup(
                    Item('color'),
                    Item('alpha', label="Transparency"),
                ),
                Item("orientation", label="Y-axis"),
            ), )
        return view

    def _color_changed(self, new):
        # Needed so plot style can control the renderer colors
        self.fill_color = new
        self.line_color = new

    def _dict_keys_default(self):
        return ["bar_width", "line_color", "fill_color", "alpha"]
Пример #2
0
class LassoOverlay(AbstractOverlay):
    """ Draws a lasso selection region on top of a plot.

    LassoOverlay gets its data from a LassoSelection.
    """

    # The LassoSelection that provides the data for this overlay.
    lasso_selection = Instance('chaco.tools.lasso_selection.LassoSelection')
    # The fill color for the selection region.
    selection_fill_color = ColorTrait('lightskyblue')
    # The border color for the selection region.
    selection_border_color = ColorTrait('dodgerblue')
    # The transparency level for the selection fill color.
    selection_alpha = Float(0.8)
    # The width of the selection border.
    selection_border_width = Float(2.0)
    # The line style of the selection border.
    selection_border_dash = LineStyle

    # The background color (overrides AbstractOverlay).
    bgcolor = 'clear'

    def overlay(self, other_component, gc, view_bounds=None, mode="normal"):
        """ Draws this component overlaid on another component.

        Implements AbstractOverlay.
        """
        with gc:
            c = other_component
            gc.clip_to_rect(c.x, c.y, c.width, c.height)
            self._draw_component(gc, view_bounds, mode)
        return

    def _updated_changed_for_lasso_selection(self):
        self.component.invalidate_draw()
        self.component.request_redraw()

    def _draw_component(self, gc, view_bounds=None, mode='normal'):
        """ Draws the component.

        This method is preserved for backwards compatibility with _old_draw().
        Overrides PlotComponent.
        """
        with gc:
            # We may need to make map_screen more flexible in the number of dimensions
            # it accepts for ths to work well.
            for selection in self.lasso_selection.disjoint_selections:
                points = self.component.map_screen(selection)
                if len(points) == 0:
                    return
                points = concatenate((points, points[0, newaxis]), axis=0)
                gc.set_line_width(self.border_width)
                gc.set_line_dash(self.selection_border_dash_)
                gc.set_fill_color(self.selection_fill_color_)
                gc.set_stroke_color(self.selection_border_color_)
                gc.set_alpha(self.selection_alpha)
                gc.lines(points)
                gc.draw_path()
Пример #3
0
class SelectableOverlayPlotContainer(OverlayPlotContainer):
    """
    An OverlayPlotContainer that can show a selection region on top of it.
    """

    # Screen position of the start of the selection, which can be in the x- or
    # y-dimension, depending on **selection_direction**.
    selection_screen_start = Float(0.0)
    # Screen position of the end of the selection, which can be in the x- or
    # y-dimension, depending on **selection_direction**.
    selection_screen_end = Float(0.0)
    # Is there an active selection?
    selection_active = Bool(False)
    # The direction of the selection.
    selection_direction = Enum('v', 'h')
    # The color to use to fill the selected region.
    selection_fill_color = ColorTrait('lightskyblue')
    # The color to use to draw the border of the selected region.
    selection_border_color = ColorTrait('dodgerblue')
    # The transparency of the **selection_fill_color**.
    selection_alpha = Float(0.3)

    def _draw_overlays(self, gc, view_bounds=None, mode='normal'):
        """ Method for backward compatability with old drawing scheme.

        Overrides BasePlotContainer.
        """
        self._draw_selection(gc, view_bounds=view_bounds, mode=mode)
        return

    def _draw_selection(self, gc, view_bounds=None, mode='normal'):
        """ Renders a selected subset of a component's data.

        Overrides PlotComponent.
        """
        if self.selection_active:
            if self.selection_direction == 'h':
                x1 = self.selection_screen_start
                x2 = self.selection_screen_end
                y1 = self.y
                y2 = self.position[1] + self.bounds[1] - 1
            else:
                x1 = self.x
                x2 = self.position[0] + self.bounds[0] - 1
                y1 = self.selection_screen_start
                y2 = self.selection_screen_end
            lowerleft = array((min(x1, x2), min(y1, y2)), float64)
            upperright = array((max(x1, x2), max(y1, y2)), float64)
            with gc:
                gc.translate_ctm(*self.position)
                gc.set_fill_color(self.selection_fill_color_)
                gc.set_stroke_color(self.selection_border_color_)
                gc.set_alpha(self.selection_alpha)
                gc.rect(lowerleft[0], lowerleft[1], upperright[0],
                        upperright[1])
                gc.draw_path()
        return
Пример #4
0
class Region(PlotComponent, DragTool):

    color = ColorTrait("lightblue")
    draw_layer = "plot"
    resizable = ""
    event_states = Enum("normal", "dragging")
    _offset = Tuple

    def __init__(self, color=None, **kw):
        super().__init__(**kw)
        if color:
            self.color = color
        if "bounds" not in kw:
            self.bounds = [100, 100]

    def _draw_plot(self, gc, view_bounds=None, mode="normal"):
        with gc:
            gc.set_fill_color(self.color_)
            gc.rect(self.x, self.y, self.width, self.height)
            gc.fill_path()

    def drag_start(self, event):
        self._offset = (event.x - self.x, event.y - self.y)
        event.handled = True

    def dragging(self, event):
        self.position = [event.x - self._offset[0], event.y - self._offset[1]]
        event.handled = True
        self.request_redraw()
Пример #5
0
class ScatterPlotTraits(HasTraits):

    plot = Instance(Plot)
    color = ColorTrait("blue")
    marker = marker_trait
    marker_size = Int(4)

    traits_view = View(
        Group(Item('color', label="Color", style="custom"),
              Item('marker', label="Marker"),
              Item('marker_size', label="Size"),
              Item('plot', editor=ComponentEditor(), show_label=False),
                   orientation="vertical"),
              width=800, height=600, resizable=True, title="Dynamic Plot")

    def __init__(self):
        super(ScatterPlotTraits, self).__init__()

        x = linspace(-14, 14, 100)
        y = sin(x) * x ** 3
        plotdata = ArrayPlotData(x=x, y=y)

        plot = Plot(plotdata)

        self.renderer = plot.plot(("x", "y"), type="scatter", color="blue")[0]
        self.plot = plot

    def _color_changed(self):
        self.renderer.color = self.color

    def _marker_changed(self):
        self.renderer.marker = self.marker

    def _marker_size_changed(self):
        self.renderer.marker_size = self.marker_size
Пример #6
0
    def __init__(self, **traits):
        super(PlotColorEditor, self).__init__(**traits)

        for i, (name, collection) in enumerate(self.collection_list.items()):
            # In case an experiment/simulation is loaded without data:
            if not collection.logs:
                continue

            # The color control will be always visible only if the collection
            # stems from an experiment
            visibility_trait = "plot_always_visible_{}".format(i)
            self.add_trait(visibility_trait, Bool)
            always_visible = collection.source_type == "experiment"
            self.trait_set(**{visibility_trait: always_visible})

            # Grab the current color
            props = collection.logs.values()[0].renderer_properties
            color = props["color"]

            # Create traits for the collection name and color
            color_trait = "plot_color_{}".format(i)
            self.add_trait(color_trait, ColorTrait(color))
            self.on_trait_change(self.color_modified, color_trait)
            self.add_trait("plot_name_{}".format(i), Str(name))

            # Connect the name to color trait for lookup
            self.collection_map[name] = color_trait
Пример #7
0
class EventTracer(AbstractOverlay):
    """ Draws a marker under the mouse cursor where an event is occurring. """

    x = Float
    y = Float

    color = ColorTrait("red")
    size = Float(5)
    angle = Float(0.0)  # angle in degrees

    def normal_mouse_move(self, event):
        self.x = event.x
        self.y = event.y
        self.component.request_redraw()

    def overlay(self, component, gc, view_bounds, mode):
        with gc:
            gc.translate_ctm(self.x, self.y)
            if self.angle != 0:
                gc.rotate_ctm(self.angle * 3.14159 / 180.)
            gc.set_stroke_color(self.color_)
            gc.set_line_width(1.0)
            gc.move_to(-self.size, 0)
            gc.line_to(self.size, 0)
            gc.move_to(0, -self.size)
            gc.line_to(0, self.size)
            gc.stroke_path()
Пример #8
0
class BaseXYRendererStyle(BaseRendererStyle):
    """ Styling object for customizing scatter renderers.
    """
    #: Color of the renderer
    color = ColorTrait(DEFAULT_RENDERER_COLOR)

    #: Transparency of the renderer
    alpha = Range(value=1., low=0., high=1.)

    #: Which y-axis to be displayed along
    orientation = Enum([STYLE_L_ORIENT, STYLE_R_ORIENT])

    # Traits property getter/setter -------------------------------------------

    def _get_general_view_elements(self):
        elements = (VGroup(
            HGroup(
                Item('color'),
                Item('alpha', label="Transparency"),
            ),
            Item("orientation", label="Y-axis"),
        ), )
        return elements

    def _dict_keys_default(self):
        return ["color", "alpha"]
class CustomOverlay(AbstractOverlay):
    x = Float(10, editor=RangeEditor(low=1.0, high=600, mode="slider"))
    y = Float(10, editor=RangeEditor(low=1.0, high=500, mode="slider"))
    width = Range(10.0,
                  300,
                  editor=RangeEditor(low=10.0, high=300, mode="slider"))
    height = Range(10.0,
                   300,
                   editor=RangeEditor(low=10.0, high=300, mode="slider"))
    color = ColorTrait("red")

    traits_view = View(
        Group(Item("x"),
              Item("y"),
              Item("width"),
              Item("height"),
              Item("color"),
              orientation="vertical"))

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        gc.set_fill_color(self.color_)
        x = self.x + component.x
        y = self.y + component.y
        gc.rect(x, y, self.width, self.height)
        gc.fill_path()

    def _anytrait_changed(self):
        self.component.request_redraw()
class RendererEditor(HasTraits):

    plot = Instance(Plot)
    color = ColorTrait('blue')

    renderers = Property(List, depends_on='plot.plots')
    selected_renderer = Any

    def _get_renderers(self):
        return self.plot.plots.keys()

    def _selected_renderer_default(self):
        return self.renderers[0]

    def _color_changed(self):
        renderer = self.plot.plots[self.selected_renderer][0]
        renderer.color = self.color

    def _selected_renderer_changed(self):
        renderer = self.plot.plots[self.selected_renderer][0]
        self.color = renderer.color

    traits_view = View(
        HGroup(
            Item('selected_renderer',
                 editor=EnumEditor(name='renderers'),
                 show_label=False),
            Item('color', show_label=False),
        ))
class CustomOverlay(AbstractOverlay):
    x = Float(10, editor=RangeEditor(low=1.0, high=600, mode="slider"))
    y = Float(10, editor=RangeEditor(low=1.0, high=500, mode="slider"))
    width = Range(10.0, 300, editor=RangeEditor(low=10.0, high=300, mode="slider"))
    height = Range(10.0, 300, editor=RangeEditor(low=10.0, high=300, mode="slider"))
    color = ColorTrait("red")
    dataspace = Bool(False)

    _anchor = CArray

    traits_view = View(Group(
                        Item("x"), Item("y"), Item("width"), Item("height"),
                        Item("color"),
                        Item("dataspace", label="Data space?"),
                        orientation = "vertical"
                        ))

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        if self.dataspace:
            self.x, self.y = component.map_screen(self._anchor)
        gc.set_fill_color(self.color_)
        x = self.x + component.x
        y = self.y + component.y
        gc.rect(x, y, self.width, self.height)
        gc.fill_path()

    def _anytrait_changed(self):
        self.component.request_redraw()

    def _dataspace_changed(self):
        if self.dataspace:
            # Map our current x,y point into data space
            self._anchor = self.component.map_data((self.x, self.y))
Пример #12
0
class EVDataSet(HasTraits):
    """Metadata for an Explained Variance line plot.

    * A color for each line
    """
    # (0.8, 0.2, 0.1, 1.0)
    color = ColorTrait('darkviolet')
    view_data = DataSet()
Пример #13
0
class RegressionOverlay(LassoOverlay):

    line_color = ColorTrait("black")
    line_style = LineStyle("dash")
    line_width = Float(2.0)

    _label = Instance(Label,
                      kw=dict(bgcolor="white",
                              border_color="black",
                              font="modern 14",
                              border_width=1))

    def _draw_component(self, gc, view_bounds=None, mode="normal"):
        LassoOverlay._draw_component(self, gc, view_bounds, mode)
        selection = self.lasso_selection

        if selection.fit_params is not None:
            # draw the label overlay
            self._label.component = self.component
            c = self.component

            if selection.fit_params[1] < 0:
                operator = "-"
            else:
                operator = "+"
            self._label.text = "%.2fx "%selection.fit_params[0] + operator + \
                               " %.2f" % fabs(selection.fit_params[1])
            w, h = self._label.get_width_height(gc)
            x = (c.x + c.x2) / 2 - w / 2
            y = c.y + 5  # add some padding on the bottom
            with gc:
                gc.translate_ctm(x, y)
                self._label.draw(gc)

            # draw the line
            slope, y0 = selection.fit_params
            f = lambda x: slope * x + y0
            cx, cy = c.map_screen([selection.centroid])[0]
            left = c.x
            right = c.x2

            left_x = c.map_data([left, c.y])[0]
            right_x = c.map_data([right, c.y])[0]
            left_y = f(left_x)
            right_y = f(right_x)

            left_pt, right_pt = c.map_screen([[left_x, left_y],
                                              [right_x, right_y]])

            with gc:
                gc.set_line_dash(self.line_style_)
                gc.set_stroke_color(self.line_color_)
                gc.set_line_width(self.line_width)
                gc.move_to(*left_pt)
                gc.line_to(*right_pt)
                gc.stroke_path()

        return
Пример #14
0
class RangeKnobsOverlay(RangeSelectionOverlay):
    radius = Float(3)
    low_color = ColorTrait("red")
    high_color = ColorTrait("red")

    # Override the default alpha and border color, inherited from
    # RangeSelectionOverlay; these are more appropriate for our application.
    alpha = Float(0.8)
    border_color = ColorTrait("black")

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        mid_y = component.position[1] + component.bounds[1] / 2
        # Draw each of a possibly disjoint set of selections
        coords = self._get_selection_screencoords()
        for coord in coords:
            start, end = coord
            gc.save_state()
            try:
                gc.set_alpha(self.alpha)
                gc.set_stroke_color(self.border_color_)
                gc.set_line_width(self.border_width)

                gc.rect(start + self.radius, mid_y - 1,
                        (end - start - 2 * self.radius), 2)
                gc.draw_path()

                gc.set_fill_color(self.low_color_)
                self._circle(gc, start, mid_y, self.radius)
                # Have to stroke/fill the path before we change out the
                # fill color
                gc.draw_path()

                gc.set_fill_color(self.high_color_)
                self._circle(gc, end, mid_y, self.radius)
                gc.draw_path()
            finally:
                gc.restore_state()

    def _circle(self, gc, x, y, radius):
        gc.save_state()
        gc.translate_ctm(x, y)
        gc.arc(0, 0, 2 * radius, 0, 2 * pi)
        gc.restore_state()
Пример #15
0
class PCDataSet(HasTraits):
    """Metadata for a PC plot set.

    * A list of labels for each datapoint
    """
    labels = List()
    # colors: chocolate, blue, brown, coral, darkblue, darkcyan, darkgoldenrod,
    # darkgreen, darkolivegreen, darkorange, darksalmon, darkseagreen
    # from: /usr/share/pyshared/enable/colors.py
    color = ColorTrait('darkviolet')
    expl_vars = List()
    selected = List()
    pc_ds = DataSet()
Пример #16
0
class Centroid(DisplayPlugin):

    # These are the results of the calculation
    _centroid = Tuple(Float(), Float())

    # These control the visualization
    color = ColorTrait('white')

    view = View(VGroup(Item('active'), label='Centroid', show_border=True))

    def __init__(self, **traits):
        super(Centroid, self).__init__(**traits)
        self.screen.data_store['centroid_x'] = N.array([])
        self.screen.data_store['centroid_y'] = N.array([])
        renderers = self.screen.plot.plot(('centroid_x', 'centroid_y'),
                                          type='scatter',
                                          marker_size=2.0,
                                          color=self.color,
                                          marker='circle')
        self._centroid_patch = renderers[0]
        self._centroid_patch.visible = self.active

        # Connect handlers
        self.on_trait_change(self._move_centroid, '_centroid', dispatch='ui')
        self.on_trait_change(self._update_hud, '_centroid', dispatch='ui')

    def _move_centroid(self):
        self.screen.data_store['centroid_x'] = N.array([self._centroid[0]])
        self.screen.data_store['centroid_y'] = N.array([self._centroid[1]])

    def _update_hud(self):
        self.screen.hud(
            'centroid',
            'Centroid: {0[0]:.1f}, {0[1]:.1f}\n'.format(self._centroid))

    def _process(self, frame):
        bw = (len(frame.shape) == 2)
        if not bw:
            # Use standard NTSC conversion formula
            frame = N.array(0.2989 * frame[..., 0] + 0.5870 * frame[..., 1] +
                            0.1140 * frame[..., 2])

        self._centroid = _calculate_centroid(frame)

    def activate(self):
        self._centroid_patch.visible = True

    def deactivate(self):
        self.screen.hud('centroid', None)
        self._centroid_patch.visible = False
Пример #17
0
class MappingCanvas(Canvas):
    """
    An infinite tiled canvas for showing maps
    """

    tile_cache = Instance(ITileManager)

    tile_alpha = Range(0.0, 1.0, 1.0)

    bgcolor = ColorTrait("lightsteelblue")

    # FIXME This is a hack - remove when viewport is fixed
    _zoom_level = Int(0)

    _blank_tile = Instance(Image)

    def __blank_tile_default(self):
        import pkg_resources
        import Image as pil
        import ImageDraw
        import ImageFont

        im = pil.new('RGB', (256, 256), (234, 224, 216))

        text = 'Image not available'
        try:
            font_file = pkg_resources.resource_filename(
                'mapping.enable', 'fonts/Verdana.ttf')
            font = ImageFont.truetype(font_file, 18)
        except IOError:
            font = ImageFont.load_default()
        size = font.getsize(text)
        pos = (256 - size[0]) // 2, (256 - size[1]) // 2

        draw = ImageDraw.Draw(im)
        draw.text(pos, text, fill=(200, 200, 200), font=font)
        del draw

        tile = StringIO()
        im.save(tile, format='png')
        return Image(StringIO(tile.getvalue()))

    def _tile_cache_changed(self, new):
        new.process_raw = lambda d: Image(StringIO(d))

    @on_trait_change('tile_cache:tile_ready')
    def _tile_ready(self, (zoom, row, col)):
        self.request_redraw()
Пример #18
0
class ScatterPlotTraits(HasTraits):

    plot = Instance(Plot)
    color = ColorTrait("blue")
    marker = marker_trait
    marker_size = Int(4)
    renderer = Property(depends_on=['plot'])

    traits_view = \
        View(
            VGroup(
                Item('color', label="Color", style="custom"),
                Item('marker', label="Marker"),
                Item('marker_size', label="Size",
                     editor=RangeEditor(low=1, high=20)),
                Item('plot', editor=ComponentEditor(), show_label=False),
            ),
            width=800, height=600, resizable=True,
            title="Chaco Plot"
        )

    def _plot_default(self):
        # Create the data and the PlotData object
        x = linspace(-14, 14, 100)
        y = sin(x) * x ** 3
        plotdata = ArrayPlotData(x=x, y=y)
        # Create a Plot and associate it with the PlotData
        plot = Plot(plotdata)
        # Create a line plot in the Plot.  Give it a name for easy access.
        plot.plot(("x", "y"), type="scatter", color="blue", name="XY")
        return plot

    def _get_renderer(self):
        return self.plot.plots['XY'][0]

    def _color_changed(self):
        self.renderer.color = self.color

    def _marker_changed(self):
        self.renderer.marker = self.marker

    def _marker_size_changed(self):
        self.renderer.marker_size = self.marker_size
Пример #19
0
class Box(Component):
    color = ColorTrait("red")

    delay = Float(0.50)

    def _draw_mainlayer(self, gc, view=None, mode="default"):
        if self.event_state == "clicked":
            print("waiting %0.4f seconds... " % self.delay, end=' ')
            time.sleep(self.delay)
            print("done.")

            with gc:
                gc.set_fill_color(self.color_)
                gc.rect(*(self.position + self.bounds))
                gc.fill_path()

        else:
            with gc:
                gc.set_stroke_color(self.color_)
                gc.set_fill_color(self.color_)
                gc.set_line_width(1.0)
                gc.rect(*(self.position + self.bounds))
                gc.stroke_path()

                gc.set_font(font)
                x, y = self.position
                dx, dy = self.bounds
                tx, ty, tdx, tdy = gc.get_text_extent(str(self.delay))
                gc.set_text_position(x + dx / 2 - tdx / 2,
                                     y + dy / 2 - tdy / 2)
                gc.show_text(str(self.delay))

    def normal_left_down(self, event):
        self.event_state = "clicked"
        event.handled = True
        self.request_redraw()

    def clicked_left_up(self, event):
        self.event_state = "normal"
        event.handled = True
        self.request_redraw()
class ScatterPlotTraits(traits.api.HasTraits):

    plot = traits.api.Instance(chaco.api.Plot)
    color = ColorTrait("blue")
    marker = chaco.api.marker_trait
    marker_size = traits.api.Int(4)

    traits_view = traitsui.api.View(
        traitsui.api.Group(traitsui.api.Item('color', label="Color", style="custom"),
                           traitsui.api.Item('marker', label="Marker"),
                           traitsui.api.Item('marker_size', label='Size'),
                           traitsui.api.Item('plot', editor=ComponentEditor()),
                           orientation="vertical"),
        width=800, height=600, resizable=True, title="Ye olde plot")

    def __init__(self):
        super(ScatterPlotTraits, self).__init__()
        x = np.arange(100)
        y = np.sin(x / 40)
        plotdata = chaco.api.ArrayPlotData(x=x, y=y)
        plot = chaco.api.Plot(plotdata)

        # append tools to pan, zoom, and drag
        plot.tools.append(chaco.tools.api.PanTool(plot))
        plot.tools.append(chaco.tools.api.ZoomTool(plot))
        plot.tools.append(chaco.tools.api.DragZoom(plot, drag_button="right"))

        self.renderer = plot.plot(("x", "y"), type="scatter", color="blue")[0]
        self.plot = plot
        
        

    def _color_changed(self):
        self.renderer.color = self.color

    def _marker_changed(self):
        self.renderer.marker = self.marker

    def _marker_size_changed(self):
        self.renderer.marker_size = self.marker_size
Пример #21
0
class MFnPolarPlotItem(Item):
    """ A Traits UI Item for a Chaco plot, for use in Traits UI Views.

    NOTE: ComponentEditor is preferred over this class, as it is more
    flexible.
    """
    # Name of the trait that references the index data source.
    index = Str
    # Name of the trait that references the value data source.
    value_list = List(Str)

    # Foreground olor of the plot.
    color = ColorTrait("green")
    # Background color of the plot.
    bgcolor = white_color_trait

    # Width of the plot border
    border_width = Int(1)
    # Is the border visible?
    border_visible = false
    # Color of the border.
    border_color = black_color_trait

    # The type of the plot as a string.
    type_trait = Str

    def __init__(self, index, value_list, type="line", **traits):
        self.index = index
        self.value_list = value_list
        #        self.type = type
        self.name = "Plot"

        super(Item, self).__init__(**traits)
        self.editor = MFnPolarEditorFactory()
        self.editor.plotitem = self

        return
Пример #22
0
class Adjuster(HasTraits):
    plot = Instance(Plot)
    color = ColorTrait("blue")
    marker = marker_trait
    marker_size = Int(4)
    function = Enum("j0", "j1", "j2", "cos", "sin")
    xleft = Int(-14)
    xright = Int(14)
    num_points = Int(100)

    def _plot_default(self):
        pass

    def _function_changed(self, old, new):
        pass

    def _color_changed(self):
        pass

    def _marker_changed(self):
        pass

    def _marker_size_changed(self):
        pass
Пример #23
0
class DataLabel(ToolTip):
    """ A label on a point in data space.

    Optionally, an arrow is drawn to the point.
    """

    # The symbol to use if **marker** is set to "custom". This attribute must
    # be a compiled path for the given Kiva context.
    custom_symbol = Any

    # The point in data space where this label should anchor itself.
    data_point = Trait(None, None, Tuple, List, Array)

    # The location of the data label relative to the data point.
    label_position = LabelPositionTrait

    # The format string that determines the label's text.  This string is
    # formatted using a dict containing the keys 'x' and 'y', corresponding to
    # data space values.
    label_format = Str("(%(x)f, %(y)f)")

    # The text to show on the label, or above the coordinates for the label, if
    # show_label_coords is True
    label_text = Str

    # Flag whether to show coordinates with the label or not.
    show_label_coords = Bool(True)

    # Does the label clip itself against the main plot area?  If not, then
    # the label draws into the padding area (where axes typically reside).
    clip_to_plot = Bool(True)

    # The center x position (average of x and x2)
    xmid = Property(Float, depends_on=['x', 'x2'])

    # The center y position (average of y and y2)
    ymid = Property(Float, depends_on=['y', 'y2'])

    # 'box' is a simple rectangular box, with an arrow that is a single line
    # with an arrowhead at the data point.
    # 'bubble' can be given rounded corners (by setting `corner_radius`), and
    # the 'arrow' is a thin triangular wedge with its point at the data point.
    # When label_style is 'bubble', the following traits are ignored:
    #    arrow_size, arrow_color, arrow_root, and arrow_max_length.
    label_style = Enum('box', 'bubble')

    #----------------------------------------------------------------------
    # Marker traits
    #----------------------------------------------------------------------

    # Mark the point on the data that this label refers to?
    marker_visible = Bool(True)

    # The type of marker to use.  This is a mapped trait using strings as the
    # keys.
    marker = MarkerTrait

    # The pixel size of the marker (doesn't include the thickness of the
    # outline).
    marker_size = Int(4)

    # The thickness, in pixels, of the outline to draw around the marker.
    # If this is 0, no outline will be drawn.
    marker_line_width = Float(1.0)

    # The color of the inside of the marker.
    marker_color = ColorTrait("red")

    # The color out of the border drawn around the marker.
    marker_line_color = ColorTrait("black")

    #----------------------------------------------------------------------
    # Arrow traits
    #----------------------------------------------------------------------

    # Draw an arrow from the label to the data point?  Only
    # used if **data_point** is not None.
    arrow_visible = Bool(True)  # FIXME: replace with some sort of ArrowStyle

    # The length of the arrowhead, in screen points (e.g., pixels).
    arrow_size = Float(10)

    # The color of the arrow.
    arrow_color = ColorTrait("black")

    # The position of the base of the arrow on the label.  If this
    # is 'auto', then the label uses **label_position**.  Otherwise, it
    # treats the label as if it were at the label position indicated by
    # this attribute.
    arrow_root = Trait("auto", "auto", "top left", "top right", "bottom left",
                       "bottom right", "top center", "bottom center",
                       "left center", "right center")

    # The minimum length of the arrow before it will be drawn.  By default,
    # the arrow will be drawn regardless of how short it is.
    arrow_min_length = Float(0)

    # The maximum length of the arrow before it will be drawn.  By default,
    # the arrow will be drawn regardless of how long it is.
    arrow_max_length = Float(inf)

    #----------------------------------------------------------------------
    # Bubble traits
    #----------------------------------------------------------------------

    # The radius (in screen coordinates) of the curved corners of the "bubble".
    corner_radius = Float(10)

    #-------------------------------------------------------------------------
    # Private traits
    #-------------------------------------------------------------------------

    # Tuple (sx, sy) of the mapped screen coordinates of **data_point**.
    _screen_coords = Any

    _cached_arrow = Any

    # When **arrow_root** is 'auto', this determines the location on the data
    # label from which the arrow is drawn, based on the position of the label
    # relative to its data point.
    _position_root_map = {
        "top left": "bottom right",
        "top right": "bottom left",
        "bottom left": "top right",
        "bottom right": "top left",
        "top center": "bottom center",
        "bottom center": "top center",
        "left center": "right center",
        "right center": "left center"
    }

    _root_positions = {
        "bottom right": ("x2", "y"),
        "bottom left": ("x", "y"),
        "top right": ("x2", "y2"),
        "top left": ("x", "y2"),
        "top center": ("xmid", "y2"),
        "bottom center": ("xmid", "y"),
        "left center": ("x", "ymid"),
        "right center": ("x2", "ymid"),
    }

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        """ Draws the tooltip overlaid on another component.

        Overrides and extends ToolTip.overlay()
        """
        if self.clip_to_plot:
            gc.save_state()
            c = component
            gc.clip_to_rect(c.x, c.y, c.width, c.height)

        self.do_layout()

        if self.label_style == 'box':
            self._render_box(component, gc, view_bounds=view_bounds, mode=mode)
        else:
            self._render_bubble(component,
                                gc,
                                view_bounds=view_bounds,
                                mode=mode)

        # draw the marker
        if self.marker_visible:
            render_markers(gc, [self._screen_coords], self.marker,
                           self.marker_size, self.marker_color_,
                           self.marker_line_width, self.marker_line_color_,
                           self.custom_symbol)

        if self.clip_to_plot:
            gc.restore_state()

    def _render_box(self, component, gc, view_bounds=None, mode='normal'):
        # draw the arrow if necessary
        if self.arrow_visible:
            if self._cached_arrow is None:
                if self.arrow_root in self._root_positions:
                    ox, oy = self._root_positions[self.arrow_root]
                else:
                    if self.arrow_root == "auto":
                        arrow_root = self.label_position
                    else:
                        arrow_root = self.arrow_root
                    pos = self._position_root_map.get(arrow_root, "DUMMY")
                    ox, oy = self._root_positions.get(
                        pos,
                        (self.x + self.width / 2, self.y + self.height / 2))

                if type(ox) == str:
                    ox = getattr(self, ox)
                    oy = getattr(self, oy)
                self._cached_arrow = draw_arrow(gc, (ox, oy),
                                                self._screen_coords,
                                                self.arrow_color_,
                                                arrowhead_size=self.arrow_size,
                                                offset1=3,
                                                offset2=self.marker_size + 3,
                                                minlen=self.arrow_min_length,
                                                maxlen=self.arrow_max_length)
            else:
                draw_arrow(gc,
                           None,
                           None,
                           self.arrow_color_,
                           arrow=self._cached_arrow,
                           minlen=self.arrow_min_length,
                           maxlen=self.arrow_max_length)

        # layout and render the label itself
        ToolTip.overlay(self, component, gc, view_bounds, mode)

    def _render_bubble(self, component, gc, view_bounds=None, mode='normal'):
        """ Render the bubble label in the graphics context. """
        # (px, py) is the data point in screen space.
        px, py = self._screen_coords

        # (x, y) is the lower left corner of the label.
        x = self.x
        y = self.y
        # (x2, y2) is the upper right corner of the label.
        x2 = self.x2
        y2 = self.y2
        # r is the corner radius.
        r = self.corner_radius

        if self.arrow_visible:
            # FIXME: Make 'gap_width' a configurable trait (and give it a
            #        better name).
            max_gap_width = 10
            gap_width = min(max_gap_width, abs(x2 - x - 2 * r),
                            abs(y2 - y - 2 * r))
            region = find_region(px, py, x, y, x2, y2)

            # Figure out where the "arrow" connects to the "bubble".
            if region == 'left' or region == 'right':
                gap_start = py - gap_width / 2
                if gap_start < y + r:
                    gap_start = y + r
                elif gap_start > y2 - r - gap_width:
                    gap_start = y2 - r - gap_width
                by = gap_start + 0.5 * gap_width
                if region == 'left':
                    bx = x
                else:
                    bx = x2
            else:
                gap_start = px - gap_width / 2
                if gap_start < x + r:
                    gap_start = x + r
                elif gap_start > x2 - r - gap_width:
                    gap_start = x2 - r - gap_width
                bx = gap_start + 0.5 * gap_width
                if region == 'top':
                    by = y2
                else:
                    by = y
            arrow_len = sqrt((px - bx)**2 + (py - by)**2)

        arrow_visible = (self.arrow_visible
                         and (arrow_len >= self.arrow_min_length))

        with gc:
            if self.border_visible:
                gc.set_line_width(self.border_width)
                gc.set_stroke_color(self.border_color_)
            else:
                gc.set_line_width(0)
                gc.set_stroke_color((0, 0, 0, 0))
            gc.set_fill_color(self.bgcolor_)

            # Start at the lower left, on the left edge where the curved
            # part of the box ends.
            gc.move_to(x, y + r)

            # Draw the left side and the upper left curved corner.
            if arrow_visible and region == 'left':
                gc.line_to(x, gap_start)
                gc.line_to(px, py)
                gc.line_to(x, gap_start + gap_width)
            gc.arc_to(x, y2, x + r, y2, r)

            # Draw the top and the upper right curved corner.
            if arrow_visible and region == 'top':
                gc.line_to(gap_start, y2)
                gc.line_to(px, py)
                gc.line_to(gap_start + gap_width, y2)
            gc.arc_to(x2, y2, x2, y2 - r, r)

            # Draw the right side and the lower right curved corner.
            if arrow_visible and region == 'right':
                gc.line_to(x2, gap_start + gap_width)
                gc.line_to(px, py)
                gc.line_to(x2, gap_start)
            gc.arc_to(x2, y, x2 - r, y, r)

            # Draw the bottom and the lower left curved corner.
            if arrow_visible and region == 'bottom':
                gc.line_to(gap_start + gap_width, y)
                gc.line_to(px, py)
                gc.line_to(gap_start, y)
            gc.arc_to(x, y, x, y + r, r)

            # Finish the "bubble".
            gc.draw_path()

            self._draw_overlay(gc)

    def _do_layout(self, size=None):
        """Computes the size and position of the label and arrow.

        Overrides and extends ToolTip._do_layout()
        """
        if not self.component or not hasattr(self.component, "map_screen"):
            return

        # Call the parent class layout.  This computes all the label
        ToolTip._do_layout(self)

        self._screen_coords = self.component.map_screen([self.data_point])[0]
        sx, sy = self._screen_coords

        if isinstance(self.label_position, str):
            orientation = self.label_position
            if ("left" in orientation) or ("right" in orientation):
                if " " not in orientation:
                    self.y = sy - self.height / 2
                if "left" in orientation:
                    self.outer_x = sx - self.outer_width - 1
                elif "right" in orientation:
                    self.outer_x = sx
            if ("top" in orientation) or ("bottom" in orientation):
                if " " not in orientation:
                    self.x = sx - self.width / 2
                if "bottom" in orientation:
                    self.outer_y = sy - self.outer_height - 1
                elif "top" in orientation:
                    self.outer_y = sy
            if "center" in orientation:
                if " " not in orientation:
                    self.x = sx - (self.width / 2)
                    self.y = sy - (self.height / 2)
                else:
                    self.x = sx - (self.outer_width / 2) - 1
                    self.y = sy - (self.outer_height / 2) - 1
        else:
            self.x = sx + self.label_position[0]
            self.y = sy + self.label_position[1]

        self._cached_arrow = None
        return

    def _data_point_changed(self, old, new):
        if new is not None:
            self._create_new_labels()

    def _label_format_changed(self, old, new):
        self._create_new_labels()

    def _label_text_changed(self, old, new):
        self._create_new_labels()

    def _show_label_coords_changed(self, old, new):
        self._create_new_labels()

    def _create_new_labels(self):
        pt = self.data_point
        if pt is not None:
            if self.show_label_coords:
                self.lines = [
                    self.label_text, self.label_format % {
                        "x": pt[0],
                        "y": pt[1]
                    }
                ]
            else:
                self.lines = [self.label_text]

    def _component_changed(self, old, new):
        for comp, attach in ((old, False), (new, True)):
            if comp is not None:
                if hasattr(comp, 'index_mapper'):
                    self._modify_mapper_listeners(comp.index_mapper,
                                                  attach=attach)
                if hasattr(comp, 'value_mapper'):
                    self._modify_mapper_listeners(comp.value_mapper,
                                                  attach=attach)
        return

    def _modify_mapper_listeners(self, mapper, attach=True):
        if mapper is not None:
            mapper.on_trait_change(self._handle_mapper,
                                   'updated',
                                   remove=not attach)
        return

    def _handle_mapper(self):
        # This gets fired whenever a mapper on our plot fires its
        # 'updated' event.
        self._layout_needed = True

    @on_trait_change("arrow_size,arrow_root,arrow_min_length," +
                     "arrow_max_length")
    def _invalidate_arrow(self):
        self._cached_arrow = None
        self._layout_needed = True

    @on_trait_change("label_position,position,position_items,bounds," +
                     "bounds_items")
    def _invalidate_layout(self):
        self._layout_needed = True

    def _get_xmid(self):
        return 0.5 * (self.x + self.x2)

    def _get_ymid(self):
        return 0.5 * (self.y + self.y2)
Пример #24
0
class BaseCandlePlot(BaseXYPlot):
    """ Represents the base class for candle- and bar-type plots that are
    multi-valued at each index point, and optionally have an extent in the
    index dimension.

    Implements the rendering logic and centralizes a lot of the visual
    attributes for these sorts of plots.  The gather and culling and
    clipping of data is up to individual subclasses.
    """

    #------------------------------------------------------------------------
    # Appearance traits
    #------------------------------------------------------------------------

    # The fill color of the marker.
    color = ColorTrait("black")

    # The fill color of the bar
    bar_color = Alias("color")

    # The color of the rectangular box forming the bar.
    bar_line_color = Alias("outline_color")

    # The color of the stems reaching from the bar ends to the min and max
    # values.  Also the color of the endcap line segments at min and max.  If
    # None, this defaults to **bar_line_color**.
    stem_color = Trait(None, None, ColorTrait("black"))

    # The color of the line drawn across the bar at the center values.
    # If None, this defaults to **bar_line_color**.
    center_color = Trait(None, None, ColorTrait("black"))

    # The color of the outline to draw around the bar.
    outline_color = ColorTrait("black")

    # The thickness, in pixels, of the outline to draw around the bar.  If
    # this is 0, no outline is drawn.
    line_width = Float(1.0)

    # The thickness, in pixels, of the stem lines.  If None, this defaults
    # to **line_width**.
    stem_width = Trait(None, None, Int(1))

    # The thickeness, in pixels, of the line drawn across the bar at the
    # center values.  If None, this defaults to **line_width**.
    center_width = Trait(None, None, Int(1))

    # Whether or not to draw bars at the min and max extents of the error bar
    end_cap = Bool(True)

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    # Override the base class definition of this because we store a list of
    # arrays and not a single array.
    _cached_data_pts = List()

    #------------------------------------------------------------------------
    # BaseXYPlot interface
    #------------------------------------------------------------------------

    def get_screen_points(self):
        # Override the BaseXYPlot implementation so that this is just
        # a pass-through, in case anyone calls it.
        pass

    #------------------------------------------------------------------------
    # Protected methods (subclasses should be able to use these directly
    # or wrap them)
    #------------------------------------------------------------------------

    def _render(self, gc, right, left, min, bar_min, center, bar_max, max):
        stack = column_stack

        with gc:
            widths = right - left
            bar_vert_center = left + widths / 2.0

            # Draw the stem lines for min to max.  Draw these first so we can
            # draw the boxes on top.
            # A little tricky: we need to account for cases when either min or max
            # are None.  To do this, just draw to bar_min or from bar_max instead
            # of drawing a single line from min to max.
            if min is not None or max is not None:
                if self.stem_color is None:
                    stem_color = self.outline_color_
                else:
                    stem_color = self.stem_color_
                gc.set_stroke_color(stem_color)

                if self.stem_width is None:
                    stem_width = self.line_width
                else:
                    stem_width = self.stem_width
                gc.set_line_width(stem_width)

                if min is None:
                    gc.line_set(stack((bar_vert_center, bar_max)),
                                stack((bar_vert_center, max)))
                    if self.end_cap:
                        gc.line_set(stack((left, max)), stack((right, max)))
                elif max is None:
                    gc.line_set(stack((bar_vert_center, min)),
                                stack((bar_vert_center, bar_min)))
                    if self.end_cap:
                        gc.line_set(stack((left, min)), stack((right, min)))
                else:
                    gc.line_set(stack((bar_vert_center, min)),
                                stack((bar_vert_center, max)))
                    if self.end_cap:
                        gc.line_set(stack((left, max)), stack((right, max)))
                        gc.line_set(stack((left, min)), stack((right, min)))
                gc.stroke_path()

            # Draw the candlestick boxes
            boxes = stack((left, bar_min, widths, bar_max - bar_min))
            gc.set_antialias(False)
            gc.set_stroke_color(self.outline_color_)
            gc.set_line_width(self.line_width)
            gc.rects(boxes)
            if self.color in ("none", "transparent", "clear"):
                gc.stroke_path()
            else:
                gc.set_fill_color(self.color_)
                gc.draw_path()

            # Draw the center line
            if center is not None:
                if self.center_color is None:
                    gc.set_stroke_color(self.outline_color_)
                else:
                    gc.set_stroke_color(self.center_color_)
                if self.center_width is None:
                    gc.set_line_width(self.line_width)
                else:
                    gc.set_line_width(self.center_width)
                gc.line_set(stack((left, center)), stack((right, center)))
                gc.stroke_path()

    def _render_icon(self, gc, x, y, width, height):
        min = array([y + 1])
        max = array([y + height - 1])
        bar_min = array([y + height / 3])
        bar_max = array([y + height - (height / 3)])
        center = array([y + (height / 2)])
        self._render(gc, array([x + width / 4]), array([x + 3 * width / 4]),
                     min, bar_min, center, bar_max, max)
Пример #25
0
class Turtle(AbstractOverlay):
    x = Float
    y = Float
    angle = Range(0.0, 360.0, value=90.0)  # degrees, clockwise
    color = ColorTrait("blue")
    line_color = ColorTrait("green")
    size = Float(10.0)
    path = Array

    _pen = Enum("down", "up")

    view = View(
        Group("x",
              "y",
              "angle",
              Item("color", style="custom"),
              Item("line_color", style="custom"),
              "size",
              orientation="vertical"))

    def __init__(self, component=None, **traits):
        super(Turtle, self).__init__(component=component, **traits)
        if 'path' not in traits:
            self.path = array([self.x, self.y], ndmin=2)

    def overlay(self, other_component, gc, view_bounds=None, mode="normal"):
        self.render(gc, other_component)

    def render_turtle(self, gc, component):
        with gc:
            x, y = component.map_screen(array([self.x, self.y], ndmin=2))[0]
            gc.translate_ctm(x, y)
            angle = self.angle * pi / 180.0
            gc.rotate_ctm(angle)
            gc.set_stroke_color(self.color_)
            gc.set_fill_color(self.color_)
            gc.begin_path()
            gc.lines([[-0.707 * self.size, 0.707 * self.size],
                      [-0.707 * self.size, -0.707 * self.size],
                      [self.size, 0.0]])
            gc.fill_path()

    def render(self, gc, component):
        # Uses the component to map our path into screen space
        nan_mask = invert(isnan(self.path[:, 0])).astype(int)
        blocks = [
            b for b in arg_find_runs(nan_mask, "flat") if nan_mask[b[0]] != 0
        ]
        screen_pts = component.map_screen(self.path)
        with gc:
            gc.clip_to_rect(component.x, component.y, component.width,
                            component.height)
            gc.set_stroke_color(self.line_color_)
            for start, end in blocks:
                gc.begin_path()
                gc.lines(screen_pts[start:end])
                gc.stroke_path()
            self.render_turtle(gc, component)

    def pendown(self):
        self._pen = "down"
        self.path = vstack((self.path, [self.x, self.y]))

    def penup(self):
        self.path = vstack((self.path, [nan, nan]))
        self._pen = "up"

    def forward(self, amt):
        angle = self.angle * pi / 180.0
        self.x += amt * cos(angle)
        self.y += amt * sin(angle)
        if self._pen == "down":
            self.path = vstack((self.path, [self.x, self.y]))

    def back(self, amt):
        self.forward(-amt)

    def left(self, angle):
        self.angle = (self.angle + angle) % 360

    def right(self, angle):
        self.angle = ((self.angle - angle) + 360) % 360

    def clear(self):
        self.path = array([self.x, self.y], ndmin=2)

    def reset(self):
        self.x = self.y = 0.0
        self.angle = 90.0
        self.clear()

    def _anytrait_changed(self, trait, val):
        self.component.request_redraw()
Пример #26
0
class DataBox(AbstractOverlay):
    """
    An overlay that is a box defined by data space coordinates.  This can be
    used as a base class for various kinds of zoom boxes.  Unlike the
    "momentary" zoom box drawn for the ZoomTool, a ZoomBox is a more permanent
    visual component.
    """

    data_position = Property
    data_bounds = Property

    # Should the zoom box stay attached to the image or to the screen if the
    # component moves underneath it?
    # TODO: This basically works, but the problem is that it responds to both
    # changes in X and Y independently.  The DataRange2D needs to be updated
    # to reflect changes from its two DataRange1Ds.  The PanTool and ZoomTool
    # need to be improved that they change both dimensions at once.
    affinity = Enum("image", "screen")

    #-------------------------------------------------------------------------
    # Appearance properties (for Box mode)
    #-------------------------------------------------------------------------

    # The color of the selection box.
    color = ColorTrait("lightskyblue")

    # The alpha value to apply to **color** when filling in the selection
    # region.  Because it is almost certainly useless to have an opaque zoom
    # rectangle, but it's also extremely useful to be able to use the normal
    # named colors from Enable, this attribute allows the specification of a
    # separate alpha value that replaces the alpha value of **color** at draw
    # time.
    alpha = Trait(0.3, None, Float)

    # The color of the outside selection rectangle.
    border_color = ColorTrait("dodgerblue")

    # The thickness of selection rectangle border.
    border_size = Int(1)

    #-------------------------------------------------------------------------
    # Private Traits
    #-------------------------------------------------------------------------

    _data_position = CList([0, 0])
    _data_bounds = CList([0, 0])
    _position_valid = False
    _bounds_valid = False

    # Are we in the middle of an event handler or a property setter
    _updating = Bool(False)

    def __init__(self, *args, **kw):
        super(DataBox, self).__init__(*args, **kw)
        if hasattr(self.component, "range2d"):
            self.component.range2d._xrange.on_trait_change(
                self.my_component_moved, "updated")
            self.component.range2d._yrange.on_trait_change(
                self.my_component_moved, "updated")
        elif hasattr(self.component, "x_mapper") and hasattr(
                self.component, "y_mapper"):
            self.component.x_mapper.range.on_trait_change(
                self.my_component_moved, "updated")
            self.component.y_mapper.range.on_trait_change(
                self.my_component_moved, "updated")
        else:
            raise RuntimeError(
                "DataBox cannot find a suitable mapper on its component.")
        self.component.on_trait_change(self.my_component_resized, "bounds")
        self.component.on_trait_change(self.my_component_resized,
                                       "bounds_items")

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        if not self._position_valid:
            tmp = self.component.map_screen([self._data_position])
            if len(tmp.shape) == 2:
                tmp = tmp[0]
            self._updating = True
            self.position = tmp
            self._updating = False
            self._position_valid = True

        if not self._bounds_valid:
            data_x2 = self._data_position[0] + self._data_bounds[0]
            data_y2 = self._data_position[1] + self._data_bounds[1]
            tmp = self.component.map_screen((data_x2, data_y2))
            if len(tmp.shape) == 2:
                tmp = tmp[0]
            x2, y2 = tmp
            x, y = self.position
            self._updating = True
            self.bounds = [x2 - x, y2 - y]
            self._updating = False
            self._bounds_valid = True

        with gc:
            gc.set_antialias(0)
            gc.set_line_width(self.border_size)
            gc.set_stroke_color(self.border_color_)
            gc.clip_to_rect(component.x, component.y, component.width,
                            component.height)
            rect = self.position + self.bounds

            if self.color != "transparent":
                if self.alpha:
                    color = list(self.color_)
                    if len(color) == 4:
                        color[3] = self.alpha
                    else:
                        color += [self.alpha]
                else:
                    color = self.color_
                gc.set_fill_color(color)
                gc.rect(*rect)
                gc.draw_path()
            else:
                gc.rect(*rect)
                gc.stroke_path()

        return

    #-------------------------------------------------------------------------
    # Property setters/getters, event handlers
    #-------------------------------------------------------------------------

    def _get_data_position(self):
        return self._data_position

    def _set_data_position(self, val):
        self._data_position = val
        self._position_valid = False
        self.trait_property_changed("data_position", self._data_position)

    def _get_data_bounds(self):
        return self._data_bounds

    def _set_data_bounds(self, val):
        self._data_bounds = val
        self._bounds_valid = False
        self.trait_property_changed("data_bounds", self._data_bounds)

    @on_trait_change('position,position_items')
    def _update_position(self):
        if self._updating:
            return
        tmp = self.component.map_data(self.position)
        if len(tmp.shape) == 2:
            tmp = tmp.T[0]
        self._data_position = tmp
        self.trait_property_changed("data_position", self._data_position)

    @on_trait_change('bounds,bounds_items')
    def _update_bounds(self):
        if self._updating:
            return
        data_x2, data_y2 = self.component.map_data((self.x2, self.y2))
        data_pos = self._data_position
        self._data_bounds = [data_x2 - data_pos[0], data_y2 - data_pos[1]]
        self.trait_property_changed("data_bounds", self._data_bounds)

    def my_component_moved(self):
        if self.affinity == "screen":
            # If we have screen affinity, then we need to take our current position
            # and map that back down into data coords
            self._update_position()
            self._update_bounds()
        self._bounds_valid = False
        self._position_valid = False

    def my_component_resized(self):
        self._bounds_valid = False
        self._position_valid = False
Пример #27
0
class DataLabel(ToolTip):
    """ A label on a point in data space, optionally with an arrow to the point.
    """

    # The symbol to use if **marker** is set to "custom". This attribute must
    # be a compiled path for the given Kiva context.
    custom_symbol = Any

    # The point in data space where this label should anchor itself.
    data_point = Trait(None, None, Tuple, List, Array)

    # The location of the data label relative to the data point.
    label_position = LabelPositionTrait

    # The format string that determines the label's text.  This string is
    # formatted using a dict containing the keys 'x' and 'y', corresponding to
    # data space values.
    label_format = Str("(%(x)f, %(y)f)")

    # The text to show on the label, or above the coordinates for the label, if
    # show_label_coords is True
    label_text = Str

    # Flag whether to show coordinates with the label or not.
    show_label_coords = Bool(True)

    # Does the label clip itself against the main plot area?  If not, then
    # the label draws into the padding area (where axes typically reside).
    clip_to_plot = Bool(True)

    # The center x position (average of x and x2)
    xmid = Property(Float, depends_on=['x', 'x2'])

    # The center y position (average of y and y2)
    ymid = Property(Float, depends_on=['y', 'y2'])

    #----------------------------------------------------------------------
    # Marker traits
    #----------------------------------------------------------------------

    # Mark the point on the data that this label refers to?
    marker_visible = Bool(True)

    # The type of marker to use.  This is a mapped trait using strings as the
    # keys.
    marker = MarkerTrait

    # The pixel size of the marker (doesn't include the thickness of the outline).
    marker_size = Int(4)

    # The thickness, in pixels, of the outline to draw around the marker.  If
    # this is 0, no outline will be drawn.
    marker_line_width = Float(1.0)

    # The color of the inside of the marker.
    marker_color = ColorTrait("red")

    # The color out of the border drawn around the marker.
    marker_line_color = ColorTrait("black")

    #----------------------------------------------------------------------
    # Arrow traits
    #----------------------------------------------------------------------

    # Draw an arrow from the label to the data point?  Only
    # used if **data_point** is not None.
    arrow_visible = Bool(True)  # FIXME: replace with some sort of ArrowStyle

    # The length of the arrowhead, in screen points (e.g., pixels).
    arrow_size = Float(10)

    # The color of the arrow.
    arrow_color = ColorTrait("black")

    # The position of the base of the arrow on the label.  If this
    # is 'auto', then the label uses **label_position**.  Otherwise, it treats
    # the label as if it were at the label position indicated by this attribute.
    arrow_root = Trait("auto", "auto", "top left", "top right", "bottom left",
                       "bottom right", "top center", "bottom center",
                       "left center", "right center")

    # The minimum length of the arrow before it will be drawn.  By default,
    # the arrow will be drawn regardless of how short it is.
    arrow_min_length = Float(0)

    # The maximum length of the arrow before it will be drawn.  By default,
    # the arrow will be drawn regardless of how long it is.
    arrow_max_length = Float(inf)

    #-------------------------------------------------------------------------
    # Private traits
    #-------------------------------------------------------------------------

    # Tuple (sx, sy) of the mapped screen coordinates of **data_point**.
    _screen_coords = Any

    _cached_arrow = Any

    # When **arrow_root** is 'auto', this determines the location on the data label
    # from which the arrow is drawn, based on the position of the label relative
    # to its data point.
    _position_root_map = {
        "top left": "bottom right",
        "top right": "bottom left",
        "bottom left": "top right",
        "bottom right": "top left",
        "top center": "bottom center",
        "bottom center": "top center",
        "left center": "right center",
        "right center": "left center"
    }

    _root_positions = {
        "bottom right": ("x2", "y"),
        "bottom left": ("x", "y"),
        "top right": ("x2", "y2"),
        "top left": ("x", "y2"),
        "top center": ("xmid", "y2"),
        "bottom center": ("xmid", "y"),
        "left center": ("x", "ymid"),
        "right center": ("x2", "ymid"),
    }

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        """ Draws the tooltip overlaid on another component.

        Overrides and extends ToolTip.overlay()
        """
        if self.clip_to_plot:
            gc.save_state()
            c = component
            gc.clip_to_rect(c.x, c.y, c.width, c.height)

        self.do_layout()

        # draw the arrow if necessary
        if self.arrow_visible:
            if self._cached_arrow is None:
                if self.arrow_root in self._root_positions:
                    ox, oy = self._root_positions[self.arrow_root]
                else:
                    if self.arrow_root == "auto":
                        arrow_root = self.label_position
                    else:
                        arrow_root = self.arrow_root
                    ox, oy = self._root_positions.get(
                        self._position_root_map.get(arrow_root, "DUMMY"),
                        (self.x + self.width / 2, self.y + self.height / 2))

                if type(ox) == str:
                    ox = getattr(self, ox)
                    oy = getattr(self, oy)
                self._cached_arrow = draw_arrow(gc, (ox, oy),
                                                self._screen_coords,
                                                self.arrow_color_,
                                                arrowhead_size=self.arrow_size,
                                                offset1=3,
                                                offset2=self.marker_size + 3,
                                                minlen=self.arrow_min_length,
                                                maxlen=self.arrow_max_length)
            else:
                draw_arrow(gc,
                           None,
                           None,
                           self.arrow_color_,
                           arrow=self._cached_arrow,
                           minlen=self.arrow_min_length,
                           maxlen=self.arrow_max_length)

        # layout and render the label itself
        ToolTip.overlay(self, component, gc, view_bounds, mode)

        # draw the marker
        if self.marker_visible:
            render_markers(gc, [self._screen_coords], self.marker,
                           self.marker_size, self.marker_color_,
                           self.marker_line_width, self.marker_line_color_,
                           self.custom_symbol)

        if self.clip_to_plot:
            gc.restore_state()

    def _do_layout(self, size=None):
        """Computes the size and position of the label and arrow.

        Overrides and extends ToolTip._do_layout()
        """
        if not self.component or not hasattr(self.component, "map_screen"):
            return

        # Call the parent class layout.  This computes all the label
        ToolTip._do_layout(self)

        self._screen_coords = self.component.map_screen([self.data_point])[0]
        sx, sy = self._screen_coords

        if isinstance(self.label_position, str):
            orientation = self.label_position
            if ("left" in orientation) or ("right" in orientation):
                if " " not in orientation:
                    self.y = sy - self.height / 2
                if "left" in orientation:
                    self.outer_x = sx - self.outer_width - 1
                elif "right" in orientation:
                    self.outer_x = sx
            if ("top" in orientation) or ("bottom" in orientation):
                if " " not in orientation:
                    self.x = sx - self.width / 2
                if "bottom" in orientation:
                    self.outer_y = sy - self.outer_height - 1
                elif "top" in orientation:
                    self.outer_y = sy
            if "center" in orientation:
                if " " not in orientation:
                    self.x = sx - (self.width / 2)
                    self.y = sy - (self.height / 2)
                else:
                    self.x = sx - (self.outer_width / 2) - 1
                    self.y = sy - (self.outer_height / 2) - 1
        else:
            self.x = sx + self.label_position[0]
            self.y = sy + self.label_position[1]

        self._cached_arrow = None
        return

    def _data_point_changed(self, old, new):
        if new is not None:
            self._create_new_labels()

    def _label_format_changed(self, old, new):
        self._create_new_labels()

    def _label_text_changed(self, old, new):
        self._create_new_labels()

    def _show_label_coords_changed(self, old, new):
        self._create_new_labels()

    def _create_new_labels(self):
        pt = self.data_point
        if pt is not None:
            if self.show_label_coords:
                self.lines = [
                    self.label_text, self.label_format % {
                        "x": pt[0],
                        "y": pt[1]
                    }
                ]
            else:
                self.lines = [self.label_text]

    def _component_changed(self, old, new):
        for comp, attach in ((old, False), (new, True)):
            if comp is not None:
                if hasattr(comp, 'index_mapper'):
                    self._modify_mapper_listeners(comp.index_mapper,
                                                  attach=attach)
                if hasattr(comp, 'value_mapper'):
                    self._modify_mapper_listeners(comp.value_mapper,
                                                  attach=attach)
        return

    def _modify_mapper_listeners(self, mapper, attach=True):
        if mapper is not None:
            mapper.on_trait_change(self._handle_mapper,
                                   'updated',
                                   remove=not attach)
        return

    def _handle_mapper(self):
        # This gets fired whenever a mapper on our plot fires its 'updated' event.
        self._layout_needed = True

    @on_trait_change("arrow_size,arrow_root,arrow_min_length,arrow_max_length")
    def _invalidate_arrow(self):
        self._cached_arrow = None
        self._layout_needed = True

    @on_trait_change(
        "label_position,position,position_items,bounds,bounds_items")
    def _invalidate_layout(self):
        self._layout_needed = True

    def _get_xmid(self):
        return 0.5 * (self.x + self.x2)

    def _get_ymid(self):
        return 0.5 * (self.y + self.y2)
Пример #28
0
class Button(Component):

    color = ColorTrait((0.6, 0.6, 0.6, 1.0))

    down_color = ColorTrait("gray")

    border_color = ColorTrait((0.4, 0.4, 0.4, 1.0))

    # important for rendering rounded buttons properly, since the default for
    # the Component parent class is 'white'
    bgcolor = "clear"

    label = Str

    label_font = KivaFont("modern 11 bold")

    label_color = ColorTrait("white")

    label_shadow = ColorTrait("gray")

    shadow_text = Bool(True)

    label_padding = Int(5)

    height = Int(20)

    button_state = Enum("up", "down")

    end_radius = Int(10)

    # Default size of the button if no label is present
    bounds=[32,32]

    # Cached value of the measured sizes of self.label
    _text_extents = Tuple

    def perform(self, event):
        """
        Called when the button is depressed.  'event' is the Enable mouse event
        that triggered this call.
        """
        pass

    def _draw_mainlayer(self, gc, view_bounds, mode="default"):
        if self.button_state == "up":
            self.draw_up(gc, view_bounds)
        else:
            self.draw_down(gc, view_bounds)
        return

    def draw_up(self, gc, view_bounds):
        with gc:
            gc.set_fill_color(self.color_)
            self._draw_actual_button(gc)
        return

    def draw_down(self, gc, view_bounds):
        with gc:
            gc.set_fill_color(self.down_color_)
            self._draw_actual_button(gc)
        return

    def _draw_actual_button(self, gc):
        gc.set_stroke_color(self.border_color_)
        gc.begin_path()

        gc.move_to(self.x + self.end_radius, self.y)

        gc.arc_to(self.x + self.width, self.y,
                self.x + self.width,
                self.y + self.end_radius, self.end_radius)
        gc.arc_to(self.x + self.width,
                self.y + self.height,
                self.x + self.width - self.end_radius,
                self.y + self.height, self.end_radius)
        gc.arc_to(self.x, self.y + self.height,
                self.x, self.y,
                self.end_radius)
        gc.arc_to(self.x, self.y,
                self.x + self.width + self.end_radius,
                self.y, self.end_radius)

        gc.draw_path()
        self._draw_label(gc)

    def _draw_label(self, gc):
        if self.label != "":
            if self._text_extents is None or len(self._text_extents) == 0:
                self._recompute_font_metrics()
            x,y,w,h = self._text_extents
            gc.set_font(self.label_font)
            text_offset = 0.0

            if self.shadow_text:
                # Draw shadow text
                gc.set_fill_color(self.label_shadow_)
                x_pos = self.x + (self.width-w-x)/2 + 0.5
                y_pos = self.y + (self.height-h-y)/2 - 0.5
                gc.show_text_at_point(self.label, x_pos, y_pos)
                text_offset = 0.5

            # Draw foreground text to button
            gc.set_fill_color(self.label_color_)
            x_pos = self.x + (self.width-w-x)/2 - text_offset
            y_pos = self.y + (self.height-h-y)/2 + text_offset
            gc.show_text_at_point(self.label, x_pos, y_pos)

        return

    def normal_left_down(self, event):
        self.button_state = "down"
        self.request_redraw()
        event.handled = True
        return

    def normal_left_up(self, event):
        self.button_state = "up"
        self.request_redraw()
        self.perform(event)
        event.handled = True
        return

    def _recompute_font_metrics(self):
        if self.label != "":
            metrics = font_metrics_provider()
            metrics.set_font(self.label_font)
            self._text_extents = metrics.get_text_extent(self.label)

    def _label_font_changed(self, old, new):
        self._recompute_font_metrics()

    def _label_changed(self, old, new):
        self._recompute_font_metrics()
Пример #29
0
class QuiverPlot(ScatterPlot):

    #: Determines how to interpret the data in the **vectors** data source.
    #:   "vector": each tuple is a (dx, dy)
    #:   "radial": each tuple is an (r, theta)
    data_type = Enum("vector", "radial") # TODO: implement "radial"

    #: A datasource that returns an Nx2 array array indicating directions
    #: of the vectors.  The interpretation of this array is dependent on
    #: the setting of the **data_type** attribute.
    #:
    #: Usually this will be a MultiArrayDataSource.
    vectors = Instance(AbstractDataSource)

    #------------------------------------------------------------------------
    # Visual attributes of the vector
    #------------------------------------------------------------------------

    #: The color of the lines
    line_color = ColorTrait("black")

    #: The width of the lines
    line_width = Float(1.0)

    #: The length, in pixels, of the arrowhead
    arrow_size = Int(5)

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    _cached_vector_data = Array
    _selected_vector_data = Array

    def _gather_points_old(self):
        # In addition to the standard scatterplot _gather_points, we need
        # to also grab the vectors that fall inside the view range
        super(QuiverPlot, self)._gather_points_old()

        if not self.index or not self.value:
            return

        if len(self._cached_point_mask) == 0:
            self._cached_vector_data = []
            return

        vectors = self.vectors.get_data()
        self._cached_vector_data = compress(self._cached_point_mask, vectors, axis=0)

        if self._cached_selected_pts is not None:
            indices = self._cached_selection_point_mask
            self._selected_vector_data = compress(indices, vectors, axis=0)
        else:
            self._selected_vector_data = None
        return


    def _render(self, gc, points, icon_mode=False):
        if len(points) < 1:
            return

        with gc:
            gc.clip_to_rect(self.x, self.y, self.width, self.height)

            gc.set_stroke_color(self.line_color_)
            gc.set_line_width(self.line_width)

            # Draw the body of the arrow
            starts = points
            ends = points + self._cached_vector_data
            gc.begin_path()
            gc.line_set(starts, ends)
            gc.stroke_path()

            if self.arrow_size > 0:
                vec = self._cached_vector_data
                unit_vec = vec / sqrt(vec[:,0] ** 2 + vec[:,1] ** 2)[:, newaxis]
                a = 0.707106781   # sqrt(2)/2

                # Draw the left arrowhead (for an arrow pointing straight up)
                arrow_ends = ends - array(unit_vec * matrix([[a, a], [-a, a]])) * self.arrow_size
                gc.begin_path()
                gc.line_set(ends, arrow_ends)
                gc.stroke_path()

                # Draw the left arrowhead (for an arrow pointing straight up)
                arrow_ends = ends - array(unit_vec * matrix([[a, -a], [a, a]])) * self.arrow_size
                gc.begin_path()
                gc.line_set(ends, arrow_ends)
                gc.stroke_path()
Пример #30
0
class TextBoxOverlay(AbstractOverlay):
    """ Draws a box with text in it.
    """

    #### Configuration traits #################################################

    #: The text to display in the box.
    text = Str

    #: The font to use for the text.
    font = KivaFont("modern 12")

    #: The background color for the box (overrides AbstractOverlay).
    bgcolor = ColorTrait("transparent")

    #: The alpha value to apply to **bgcolor**
    alpha = Trait(1.0, None, Float)

    #: The color of the outside box.
    border_color = ColorTrait("dodgerblue")

    #: The color of the text.
    text_color = ColorTrait("black")

    #: The thickness of box border.
    border_size = Int(1)

    #: The border visibility. Defaults to true to duplicate previous behavior.
    border_visible = Bool(True)

    #: Number of pixels of padding around the text within the box.
    padding = Int(5)

    #: The maximum width of the displayed text. This affects the width of the
    #: text only, not the text box, which includes margins around the text and
    #: `padding`.
    #: A `max_text_width` of 0.0 means that the width will not be restricted.
    max_text_width = Float(0.0)

    #: Alignment of the text in the box:
    #:
    #: * "ur": upper right
    #: * "ul": upper left
    #: * "ll": lower left
    #: * "lr": lower right
    align = Enum("ur", "ul", "ll", "lr")

    #: This allows subclasses to specify an alternate position for the root
    #: of the text box.  Must be a sequence of length 2.
    alternate_position = Any

    #### Public 'AbstractOverlay' interface ###################################

    def overlay(self, component, gc, view_bounds=None, mode="normal"):
        """ Draws the box overlaid on another component.

        Overrides AbstractOverlay.
        """

        if not self.visible:
            return

        # draw the label on a transparent box. This allows us to draw
        # different shapes and put the text inside it without the label
        # filling a rectangle on top of it
        label = Label(text=self.text, font=self.font, bgcolor="transparent",
                      color=self.text_color, max_width=self.max_text_width,
                      margin=5)
        width, height = label.get_width_height(gc)

        valign, halign = self.align

        if self.alternate_position:
            x, y = self.alternate_position
            if valign == "u":
                y += self.padding
            else:
                y -= self.padding + height

            if halign == "r":
                x += self.padding
            else:
                x -= self.padding + width
        else:
            if valign == "u":
                y = component.y2 - self.padding - height
            else:
                y = component.y + self.padding

            if halign == "r":
                x = component.x2 - self.padding - width
            else:
                x = component.x + self.padding

        # attempt to get the box entirely within the component
        x_min, y_min, x_max, y_max = (component.x,
                                      component.y,
                                      component.x + component.width,
                                      component.y + component.height)
        if x + width > x_max:
            x = max(x_min, x_max - width)
        if y + height > y_max:
            y = max(y_min, y_max - height)
        elif y < y_min:
            y = y_min

        # apply the alpha channel
        color = self.bgcolor_
        if self.bgcolor != "transparent":
            if self.alpha:
                color = list(self.bgcolor_)
                if len(color) == 4:
                    color[3] = self.alpha
                else:
                    color += [self.alpha]

        with gc:
            gc.translate_ctm(x, y)

            gc.set_line_width(self.border_size)
            gc.set_stroke_color(self.border_color_)
            gc.set_fill_color(color)

            if self.border_visible:
                # draw a rounded rectangle.
                x = y = 0
                end_radius = 8.0
                gc.begin_path()
                gc.move_to(x + end_radius, y)
                gc.arc_to(x + width, y,
                          x + width,
                          y + end_radius, end_radius)
                gc.arc_to(x + width,
                          y + height,
                          x + width - end_radius,
                          y + height, end_radius)
                gc.arc_to(x, y + height,
                          x, y,
                          end_radius)
                gc.arc_to(x, y,
                          x + width + end_radius,
                          y, end_radius)
                gc.draw_path()

            label.draw(gc)