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"]
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()
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
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()
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
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
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()
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))
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()
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
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()
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()
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
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()
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
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
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
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
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)
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)
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()
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
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)
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()
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()
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)