def _value_scale_changed(self, old, new): if old is None: return if new == old: return if not self.range2d: return if self.value_scale == "linear": vmap = LinearMapper(range=self.value_range, screen_bounds=self.value_mapper.screen_bounds, stretch_data=self.value_mapper.stretch_data) else: vmap = LogMapper(range=self.value_range, screen_bounds=self.value_mapper.screen_bounds, stretch_data=self.value_mapper.stretch_data) self.value_mapper = vmap for key in self.plots: for plot in self.plots[key]: if not isinstance(plot, BaseXYPlot): raise ValueError("log scale only supported on XY plots") if self.value_scale == "linear": vmap = LinearMapper(range=plot.value_range, screen_bounds=plot.value_mapper.screen_bounds, stretch_data=self.value_mapper.stretch_data) else: vmap = LogMapper(range=plot.value_range, screen_bounds=plot.value_mapper.screen_bounds, stretch_data=self.value_mapper.stretch_data) plot.value_mapper = vmap
def __init__(self, x_type="linear", y_type="linear", range=None, **kwargs): # TODO: This is currently an implicit assumption, i.e. that the range # will be passed in to the constructor. It would be impossible to # create the xmapper and ymapper otherwise. However, this should be # changed so that the mappers get created or modified in response to # the .range attribute changing, instead of requiring the range to # be passed in at construction time. self.range = range if "_xmapper" not in kwargs: if x_type == "linear": self._xmapper = LinearMapper(range=self.range.x_range) elif x_type == "log": self._xmapper = LogMapper(range=self.range.x_range) else: raise ValueError("Invalid x axis type: %s" % x_type) else: self._xmapper = kwargs.pop("_xmapper") if "_ymapper" not in kwargs: if y_type == "linear": self._ymapper = LinearMapper(range=self.range.y_range) elif y_type == "log": self._ymapper = LogMapper(range=self.range.y_range) else: raise ValueError("Invalid y axis type: %s" % y_type) else: self._ymapper = kwargs.pop("_ymapper") # Now that the mappers are created, we can go to the normal HasTraits # constructor, which might set values that depend on us having a valid # range and mappers. super(GridMapper, self).__init__(**kwargs)
def add_xy_plot(self, index_name, value_name, renderer_factory, name=None, origin=None, **kwds): """ Add a BaseXYPlot renderer subclass to this Plot. Parameters ---------- index_name : str The name of the index datasource. value_name : str The name of the value datasource. renderer_factory : callable The callable that creates the renderer. name : string (optional) The name of the plot. If None, then a default one is created (usually "plotNNN"). origin : string (optional) Which corner the origin of this plot should occupy: "bottom left", "top left", "bottom right", "top right" **kwds : Additional keywords to pass to the factory. """ if name is None: name = self._make_new_plot_name() if origin is None: origin = self.default_origin index = self._get_or_create_datasource(index_name) self.index_range.add(index) value = self._get_or_create_datasource(value_name) self.value_range.add(value) if self.index_scale == "linear": imap = LinearMapper(range=self.index_range) else: imap = LogMapper(range=self.index_range) if self.value_scale == "linear": vmap = LinearMapper(range=self.value_range) else: vmap = LogMapper(range=self.value_range) renderer = renderer_factory(index=index, value=value, index_mapper=imap, value_mapper=vmap, orientation=self.orientation, origin=origin, **kwds) self.add(renderer) self.plots[name] = [renderer] self.invalidate_and_redraw() return self.plots[name]
def _init_components(self): # Since this is called after the HasTraits constructor, we have to make # sure that we don't blow away any components that the caller may have # already set. if not self.range2d: self.range2d = DataRange2D() if not self.index_mapper: if self.index_scale == "linear": imap = LinearMapper(range=self.range2d.x_range) else: imap = LogMapper(range=self.range2d.x_range) self.index_mapper = imap if not self.value_mapper: if self.value_scale == "linear": vmap = LinearMapper(range=self.range2d.y_range) else: vmap = LogMapper(range=self.range2d.y_range) self.value_mapper = vmap # make sure the grid and bgcolor are not the same color grid_color = 'lightgray' if color_table[self.bgcolor] == color_table[grid_color]: grid_color = 'white' if not self.x_grid and self.auto_grid: self.x_grid = PlotGrid(mapper=self.x_mapper, orientation="vertical", line_color=grid_color, line_style="dot", component=self) if not self.y_grid and self.auto_grid: self.y_grid = PlotGrid(mapper=self.y_mapper, orientation="horizontal", line_color=grid_color, line_style="dot", component=self) if not self.x_axis and self.auto_axis: self.x_axis = PlotAxis(mapper=self.x_mapper, orientation="bottom", component=self) if not self.y_axis and self.auto_axis: self.y_axis = PlotAxis(mapper=self.y_mapper, orientation="left", component=self)
def candle_plot(self, data, name=None, value_scale="linear", origin=None, **styles): """ Adds a new sub-plot using the given data and plot style. Parameters ---------- data : list(string), tuple(string) The names of the data to be plotted in the ArrayDataSource. The number of arguments determines how they are interpreted: (index, bar_min, bar_max) filled or outline-only bar extending from **bar_min** to **bar_max** (index, bar_min, center, bar_max) above, plus a center line of a different color at **center** (index, min, bar_min, bar_max, max) bar extending from **bar_min** to **bar_max**, with thin bars at **min** and **max** connected to the bar by a long stem (index, min, bar_min, center, bar_max, max) like above, plus a center line of a different color and configurable thickness at **center** name : string The name of the plot. If None, then a default one is created. value_scale : string The type of scale to use for the value axis. If not "linear", then a log scale is used. Styles ------ These are all optional keyword arguments. bar_color : string, 3- or 4-tuple The fill color of the bar; defaults to "auto". bar_line_color : string, 3- or 4-tuple The color of the rectangular box forming the bar. stem_color : string, 3- or 4-tuple (default = bar_line_color) The color of the stems reaching from the bar to the min and max values. center_color : string, 3- or 4-tuple (default = bar_line_color) The color of the line drawn across the bar at the center values. line_width : int (default = 1) The thickness, in pixels, of the outline around the bar. stem_width : int (default = line_width) The thickness, in pixels, of the stem lines center_width : int (default = line_width) The width, in pixels, of the line drawn across the bar at the center values. end_cap : bool (default = True) Whether or not to draw bars at the min and max extents of the error bar. Returns ------- [renderers] -> list of renderers created in response to this call. """ if len(data) == 0: return self.value_scale = value_scale if name is None: name = self._make_new_plot_name() if origin is None: origin = self.default_origin # Create the datasources if len(data) == 3: index, bar_min, bar_max = map(self._get_or_create_datasource, data) self.value_range.add(bar_min, bar_max) center = None min = None max = None elif len(data) == 4: index, bar_min, center, bar_max = map(self._get_or_create_datasource, data) self.value_range.add(bar_min, center, bar_max) min = None max = None elif len(data) == 5: index, min, bar_min, bar_max, max = \ map(self._get_or_create_datasource, data) self.value_range.add(min, bar_min, bar_max, max) center = None elif len(data) == 6: index, min, bar_min, center, bar_max, max = \ map(self._get_or_create_datasource, data) self.value_range.add(min, bar_min, center, bar_max, max) self.index_range.add(index) if styles.get("bar_color") == "auto" or styles.get("color") == "auto": self._auto_color_idx = \ (self._auto_color_idx + 1) % len(self.auto_colors) styles["color"] = self.auto_colors[self._auto_color_idx] if self.index_scale == "linear": imap = LinearMapper(range=self.index_range, stretch_data=self.index_mapper.stretch_data) else: imap = LogMapper(range=self.index_range, stretch_data=self.index_mapper.stretch_data) if self.value_scale == "linear": vmap = LinearMapper(range=self.value_range, stretch_data=self.value_mapper.stretch_data) else: vmap = LogMapper(range=self.value_range, stretch_data=self.value_mapper.stretch_data) cls = self.renderer_map["candle"] plot = cls(index = index, min_values = min, bar_min = bar_min, center_values = center, bar_max = bar_max, max_values = max, index_mapper = imap, value_mapper = vmap, orientation = self.orientation, origin = self.origin, **styles) self.add(plot) self.plots[name] = [plot] return [plot]
def plot(self, data, type="line", name=None, index_scale="linear", value_scale="linear", origin=None, **styles): """ Adds a new sub-plot using the given data and plot style. Parameters ---------- data : string, tuple(string), list(string) The data to be plotted. The type of plot and the number of arguments determines how the arguments are interpreted: one item: (line/scatter) The data is treated as the value and self.default_index is used as the index. If **default_index** does not exist, one is created from arange(len(*data*)) two or more items: (line/scatter) Interpreted as (index, value1, value2, ...). Each index,value pair forms a new plot of the type specified. two items: (cmap_scatter) Interpreted as (value, color_values). Uses **default_index**. three or more items: (cmap_scatter) Interpreted as (index, val1, color_val1, val2, color_val2, ...) type : comma-delimited string of "line", "scatter", "cmap_scatter" The types of plots to add. name : string The name of the plot. If None, then a default one is created (usually "plotNNN"). index_scale : string The type of scale to use for the index axis. If not "linear", then a log scale is used. value_scale : string The type of scale to use for the value axis. If not "linear", then a log scale is used. origin : string Which corner the origin of this plot should occupy: "bottom left", "top left", "bottom right", "top right" styles : series of keyword arguments attributes and values that apply to one or more of the plot types requested, e.g.,'line_color' or 'line_width'. Examples -------- :: plot("my_data", type="line", name="myplot", color=lightblue) plot(("x-data", "y-data"), type="scatter") plot(("x", "y1", "y2", "y3")) Returns ------- [renderers] -> list of renderers created in response to this call to plot() """ if len(data) == 0: return if isinstance(data, basestring): data = (data,) self.index_scale = index_scale self.value_scale = value_scale # TODO: support lists of plot types plot_type = type if name is None: name = self._make_new_plot_name() if origin is None: origin = self.default_origin if plot_type in ("line", "scatter", "polygon", "bar", "filled_line"): # Tie data to the index range if len(data) == 1: if self.default_index is None: # Create the default index based on the length of the first # data series value = self._get_or_create_datasource(data[0]) self.default_index = ArrayDataSource(arange(len(value.get_data())), sort_order="none") self.index_range.add(self.default_index) index = self.default_index else: index = self._get_or_create_datasource(data[0]) if self.default_index is None: self.default_index = index self.index_range.add(index) data = data[1:] # Tie data to the value_range and create the renderer for each data new_plots = [] simple_plot_types = ("line", "scatter") for value_name in data: value = self._get_or_create_datasource(value_name) self.value_range.add(value) if plot_type in simple_plot_types: cls = self.renderer_map[plot_type] # handle auto-coloring request if styles.get("color") == "auto": self._auto_color_idx = \ (self._auto_color_idx + 1) % len(self.auto_colors) styles["color"] = self.auto_colors[self._auto_color_idx] elif plot_type in ("polygon", "filled_line"): cls = self.renderer_map[plot_type] # handle auto-coloring request if styles.get("edge_color") == "auto": self._auto_edge_color_idx = \ (self._auto_edge_color_idx + 1) % len(self.auto_colors) styles["edge_color"] = self.auto_colors[self._auto_edge_color_idx] if styles.get("face_color") == "auto": self._auto_face_color_idx = \ (self._auto_face_color_idx + 1) % len(self.auto_colors) styles["face_color"] = self.auto_colors[self._auto_face_color_idx] elif plot_type == 'bar': cls = self.renderer_map[plot_type] # handle auto-coloring request if styles.get("color") == "auto": self._auto_color_idx = \ (self._auto_color_idx + 1) % len(self.auto_colors) styles["fill_color"] = self.auto_colors[self._auto_color_idx] else: raise ValueError("Unhandled plot type: " + plot_type) if self.index_scale == "linear": imap = LinearMapper(range=self.index_range, stretch_data=self.index_mapper.stretch_data) else: imap = LogMapper(range=self.index_range, stretch_data=self.index_mapper.stretch_data) if self.value_scale == "linear": vmap = LinearMapper(range=self.value_range, stretch_data=self.value_mapper.stretch_data) else: vmap = LogMapper(range=self.value_range, stretch_data=self.value_mapper.stretch_data) plot = cls(index=index, value=value, index_mapper=imap, value_mapper=vmap, orientation=self.orientation, origin = origin, **styles) self.add(plot) new_plots.append(plot) if plot_type == 'bar': # For bar plots, compute the ranges from the data to make the # plot look clean. def custom_index_func(data_low, data_high, margin, tight_bounds): """ Compute custom bounds of the plot along index (in data space). """ bar_width = styles.get('bar_width', cls().bar_width) plot_low = data_low - bar_width plot_high = data_high + bar_width return plot_low, plot_high if self.index_range.bounds_func is None: self.index_range.bounds_func = custom_index_func def custom_value_func(data_low, data_high, margin, tight_bounds): """ Compute custom bounds of the plot along value (in data space). """ plot_low = data_low - (data_high-data_low)*0.1 plot_high = data_high + (data_high-data_low)*0.1 return plot_low, plot_high if self.value_range.bounds_func is None: self.value_range.bounds_func = custom_value_func self.index_range.tight_bounds = False self.value_range.tight_bounds = False self.index_range.refresh() self.value_range.refresh() self.plots[name] = new_plots elif plot_type == "cmap_scatter": if len(data) != 3: raise ValueError("Colormapped scatter plots require (index, value, color) data") else: index = self._get_or_create_datasource(data[0]) if self.default_index is None: self.default_index = index self.index_range.add(index) value = self._get_or_create_datasource(data[1]) self.value_range.add(value) color = self._get_or_create_datasource(data[2]) if not styles.has_key("color_mapper"): raise ValueError("Scalar 2D data requires a color_mapper.") colormap = styles.pop("color_mapper", None) if self.color_mapper is not None and self.color_mapper.range is not None: color_range = self.color_mapper.range else: color_range = DataRange1D() if isinstance(colormap, AbstractColormap): self.color_mapper = colormap if colormap.range is None: color_range.add(color) colormap.range = color_range elif callable(colormap): color_range.add(color) self.color_mapper = colormap(color_range) else: raise ValueError("Unexpected colormap %r in plot()." % colormap) if self.index_scale == "linear": imap = LinearMapper(range=self.index_range, stretch_data=self.index_mapper.stretch_data) else: imap = LogMapper(range=self.index_range, stretch_data=self.index_mapper.stretch_data) if self.value_scale == "linear": vmap = LinearMapper(range=self.value_range, stretch_data=self.value_mapper.stretch_data) else: vmap = LogMapper(range=self.value_range, stretch_data=self.value_mapper.stretch_data) cls = self.renderer_map["cmap_scatter"] plot = cls(index=index, index_mapper=imap, value=value, value_mapper=vmap, color_data=color, color_mapper=self.color_mapper, orientation=self.orientation, origin=origin, **styles) self.add(plot) self.plots[name] = [plot] else: raise ValueError("Unknown plot type: " + plot_type) return self.plots[name]