diff --git a/CHANGES.txt b/CHANGES.txt index 76991fe2e..fb6abb18e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,18 @@ Chaco CHANGELOG =============== +Release 4.7.2 +------------- + +Fixes + +* FIX: Ensure contiguous inputs to points_in_polygon (#409 & #410) +* FIX: Handle multiple plots in LegendHighlighter (#403) +* FIX: alias six.moves as sm (#401) +* FIX: Respect visibility in the LegendHighlighter (#402) +* BUG: Fix use of itertools.chain() (#394) + + Release 4.7.1 ------------- diff --git a/chaco/errorbar_plot.py b/chaco/errorbar_plot.py index 261729fae..e50c7c59e 100644 --- a/chaco/errorbar_plot.py +++ b/chaco/errorbar_plot.py @@ -2,7 +2,7 @@ from __future__ import with_statement import six -import six.moves +import six.moves as sm # Major library imports from numpy import column_stack, compress, invert, isnan, transpose @@ -159,4 +159,3 @@ def _render_bar_endcap(self, gc, start, end, low, high, axis): def _render_icon(self, gc, x, y, width, height): pass - diff --git a/chaco/plot.py b/chaco/plot.py index 5b17199f1..cb4a690b6 100644 --- a/chaco/plot.py +++ b/chaco/plot.py @@ -1276,7 +1276,7 @@ def _handle_range_changed(self, name, old, new): if new is not None: new.add(datasource) range_name = name + "_range" - for renderer in itertools.chain(six.itervalues(self.plots)): + for renderer in itertools.chain(*six.itervalues(self.plots)): if hasattr(renderer, range_name): setattr(renderer, range_name, new) diff --git a/chaco/polygon_plot.py b/chaco/polygon_plot.py index 4234bf667..aec89af41 100644 --- a/chaco/polygon_plot.py +++ b/chaco/polygon_plot.py @@ -134,7 +134,7 @@ def hittest(self, screen_pt, threshold=7.0, return_distance=False): data_pt = self.map_data(screen_pt, all_values=True) index = self.index.get_data() value = self.value.get_data() - poly = np.vstack((index,value)).T + poly = np.column_stack((index, value)) if points_in_polygon([data_pt], poly)[0] == 1: return True else: diff --git a/chaco/tests/errorbarplot_test_case.py b/chaco/tests/errorbarplot_test_case.py new file mode 100644 index 000000000..70a7a0bc5 --- /dev/null +++ b/chaco/tests/errorbarplot_test_case.py @@ -0,0 +1,36 @@ +import unittest + +import numpy as np +from numpy import alltrue + +# Chaco imports +from chaco.api import ( + ArrayDataSource, DataRange1D, ErrorBarPlot, LinearMapper, + PlotGraphicsContext +) + + +class DrawErrorBarPlotCase(unittest.TestCase): + def test_errorbarplot(self): + """ Coverage test to check basic case works """ + size = (50, 50) + x = np.array([1, 2]) + y = np.array([5, 10]) + errors = np.array([1, 2]) + low = ArrayDataSource(y - errors) + high = ArrayDataSource(y + errors) + errorbar_plot = ErrorBarPlot( + index=ArrayDataSource(x), + values=ArrayDataSource(y), + index_mapper=LinearMapper(range=DataRange1D(low=0, high=3)), + value_mapper=LinearMapper(range=DataRange1D(low=0, high=15)), + value_low=low, + value_high=high, + color='blue', + line_width=3.0, + ) + errorbar_plot.outer_bounds = list(size) + gc = PlotGraphicsContext(size) + gc.render_component(errorbar_plot) + actual = gc.bmp_array[:, :, :] + self.assertFalse(alltrue(actual == 255)) diff --git a/chaco/tests/plot_test_case.py b/chaco/tests/plot_test_case.py index 3234dcf20..ceb4207a0 100644 --- a/chaco/tests/plot_test_case.py +++ b/chaco/tests/plot_test_case.py @@ -3,7 +3,7 @@ from numpy import arange # Chaco imports -from chaco.api import ArrayPlotData, Plot +from chaco.api import ArrayPlotData, Plot, DataRange1D class PlotTestCase(unittest.TestCase): @@ -18,5 +18,18 @@ def test_plot_from_unsupported_array_shape(self): data.update_data(x=arr, y=arr) self.assertRaises(ValueError, plot.plot, ("x", "y")) + def test_range_change(self): + arr = arange(10) + data = ArrayPlotData(x=arr, y=arr) + plot = Plot(data) + renderer = plot.plot(('x', 'y'))[0] + new_range = DataRange1D() + old_range = plot.index_range + self.assertIsNot(old_range, new_range) + self.assertIs(renderer.index_range, old_range) + plot.index_range = new_range + self.assertIs(plot.index_range, new_range) + self.assertIs(renderer.index_range, new_range) + if __name__ == "__main__": unittest.main() diff --git a/chaco/tools/lasso_selection.py b/chaco/tools/lasso_selection.py index f3c7bc57c..053eb863d 100644 --- a/chaco/tools/lasso_selection.py +++ b/chaco/tools/lasso_selection.py @@ -2,7 +2,7 @@ """ # Major library imports import numpy -from numpy import array, empty, sometrue, transpose, vstack, zeros +from numpy import array, column_stack, empty, sometrue, vstack, zeros # Enthought library imports from traits.api import Any, Array, Enum, Event, Bool, Instance, \ @@ -308,8 +308,8 @@ def _map_data(self, point): def _get_data(self): """ Returns the datapoints in the plot, as an Nx2 array of (x,y). """ - return transpose(array((self.plot.index.get_data(), self.plot.value.get_data()))) - + return column_stack((self.plot.index.get_data(), + self.plot.value.get_data())) #------------------------------------------------------------------------ # Property getter/setters diff --git a/chaco/tools/legend_highlighter.py b/chaco/tools/legend_highlighter.py index 0d2bf5469..5ad9c3579 100644 --- a/chaco/tools/legend_highlighter.py +++ b/chaco/tools/legend_highlighter.py @@ -1,12 +1,19 @@ -import operator - -import six -import six.moves as sm +from itertools import chain # ETS imports from chaco.tools.api import LegendTool from traits.api import List, Float +concat = chain.from_iterable + + +def _ensure_list(obj): + """ NOTE: The Legend stores plots in a dictionary with either single + renderers as values, or lists of renderers. + This function helps us assume we're always working with lists + """ + return obj if isinstance(obj, list) else [obj] + def get_hit_plots(legend, event): if legend is None or not legend.is_in(event.x, event.y): @@ -27,7 +34,7 @@ def get_hit_plots(legend, event): ndx = legend._cached_labels.index(label) label_name = legend._cached_label_names[ndx] renderers = legend.plots[label_name] - return renderers + return _ensure_list(renderers) except (ValueError, KeyError): return [] @@ -50,39 +57,39 @@ class LegendHighlighter(LegendTool): _selected_renderers = List def normal_left_down(self, event): - if not self.component.is_in(event.x, event.y): + if (not self.component.visible or + not self.component.is_in(event.x, event.y)): return plots = get_hit_plots(self.component, event) - - if len(plots) > 0: - plot = plots[0] - - if event.shift_down: - # User in multi-select mode by using [shift] key. + if event.shift_down: + # User in multi-select mode by using [shift] key. + for plot in plots: if plot in self._selected_renderers: self._selected_renderers.remove(plot) else: self._selected_renderers.append(plot) - - else: - # User in single-select mode. - add_plot = plot not in self._selected_renderers - self._selected_renderers = [] - if add_plot: - self._selected_renderers.append(plot) - - if self._selected_renderers: - self._set_states(self.component.plots) - else: - self._reset_selects(self.component.plots) - plot.request_redraw() + elif plots: + # User in single-select mode. + add_plot = any(plot not in self._selected_renderers + for plot in plots) + self._selected_renderers = [] + if add_plot: + self._selected_renderers.extend(plots) + + if self._selected_renderers: + self._set_states(self.component.plots) + else: + self._reset_selects(self.component.plots) + + if plots: + plots[0].request_redraw() event.handled = True def _reset_selects(self, plots): """ Set all renderers to their default values. """ - for plot in sm.reduce(operator.add, plots.values()): + for plot in concat(_ensure_list(p) for p in plots.values()): if not hasattr(plot, '_orig_alpha'): plot._orig_alpha = plot.alpha plot._orig_line_width = plot.line_width @@ -92,7 +99,7 @@ def _reset_selects(self, plots): def _set_states(self, plots): """ Decorates a plot to indicate it is selected """ - for plot in sm.reduce(operator.add, plots.values()): + for plot in concat(_ensure_list(p) for p in plots.values()): if not hasattr(plot, '_orig_alpha'): # FIXME: These attributes should be put into the class def. plot._orig_alpha = plot.alpha diff --git a/examples/demo/multiaxis.py b/examples/demo/multiaxis.py index 451a95fbc..f6a4a3c26 100644 --- a/examples/demo/multiaxis.py +++ b/examples/demo/multiaxis.py @@ -25,8 +25,8 @@ from chaco.api import create_line_plot, add_default_axes, \ add_default_grids, OverlayPlotContainer, \ PlotLabel, Legend, PlotAxis -from chaco.tools.api import PanTool, LegendTool, TraitsTool, \ - BroadcasterTool +from chaco.tools.api import (PanTool, LegendTool, LegendHighlighter, + TraitsTool, BroadcasterTool) #=============================================================================== # # Create the Chaco plot. @@ -76,6 +76,7 @@ def _create_plot_component(): legend = Legend(component=container, padding=10, align="ur") legend.tools.append(LegendTool(legend, drag_button="right")) + legend.tools.append(LegendHighlighter(legend)) container.overlays.append(legend) # Set the list of plots on the legend diff --git a/setup.py b/setup.py index 66a1bf24a..29fd20dcf 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ MAJOR = 4 MINOR = 7 -MICRO = 2 +MICRO = 3 IS_RELEASED = False