diff --git a/chaco/api.py b/chaco/api.py index fa590c4ed..4ed38f99e 100644 --- a/chaco/api.py +++ b/chaco/api.py @@ -54,6 +54,7 @@ - :class:`~.DiscreteColorMapper` - :class:`~.TransformColorMapper` - :class:`~.BandedMapper` +- :class:`~.PolarMapper` Visual Components ----------------- @@ -108,6 +109,7 @@ - :class:`~.TextPlot1D` - :class:`~.SegmentPlot` - :class:`~.TextPlot` +- :class:`~.PolarLineRenderer` Plot Factories -------------- @@ -290,6 +292,7 @@ from .discrete_color_mapper import DiscreteColorMapper from .transform_color_mapper import TransformColorMapper from .horizon_plot import BandedMapper +from .polar_mapper import PolarMapper # Visual components from .abstract_plot_renderer import AbstractPlotRenderer @@ -351,6 +354,7 @@ from .text_plot_1d import TextPlot1D from .segment_plot import SegmentPlot from .text_plot import TextPlot +from .polar_line_renderer import PolarLineRenderer # Plot factories from .plot_factory import ( diff --git a/chaco/polar_mapper.py b/chaco/polar_mapper.py index afb0a531c..b7506cb4d 100644 --- a/chaco/polar_mapper.py +++ b/chaco/polar_mapper.py @@ -4,7 +4,7 @@ """ # Major library imports -from numpy import array +from numpy import array, float64, full_like, ndarray # Enthought library imports from traits.api import Bool, Float @@ -12,11 +12,11 @@ # Local relative imports from .abstract_mapper import AbstractMapper -############################################################### -# same as linear mapper at the moment... to be modified later # -############################################################### + class PolarMapper(AbstractMapper): """ + Same as linear mapper at the moment... to be modified later + Maps a 1-D data space to and from screen space by specifying a range in data space and a corresponding fixed line in screen space. @@ -29,8 +29,11 @@ class PolarMapper(AbstractMapper): # Private traits # ------------------------------------------------------------------------ - _scale = Float(1.0) # number of screen space units per data space unit + # Number of screen space units per data space unit. + _scale = Float(1.0) + # Is the range of the screen space empty? _null_screen_range = Bool(False) + # Is the range of the data space empty? _null_data_range = Bool(False) # ------------------------------------------------------------------------ @@ -40,27 +43,38 @@ class PolarMapper(AbstractMapper): def map_screen(self, data_array): """map_screen(data_array) -> screen_array - Converts radius and theta values from *data_array* - to x and y values and then maps - values from data space into screen space. + Overrides AbstractMapper. Maps values from data space into screen space. """ self._compute_scale() if self._null_data_range: - return array([self.low_pos] * len(data_array)) + if isinstance(data_array, (tuple, list, ndarray)): + return full_like(data_array, self.low_pos, dtype=float64) + else: + return array([self.low_pos]) else: return (data_array - self.range.low) * self._scale + self.low_pos def map_data(self, screen_val): """map_data(screen_val) -> data_val - Maps values from screen space into data space. + Overrides AbstractMapper. Maps values from screen space into data space. """ self._compute_scale() if self._null_screen_range: return array([self.range.low]) + elif self._null_data_range: + return array([self.range.low]) else: return (screen_val - self.low_pos) / self._scale + self.range.low + def map_data_array(self, screen_vals): + """map_data_array(screen_vals) -> data_vals + + Overrides AbstractMapper. Maps an array of values from screen space + into data space. + """ + return self.map_data(screen_vals) + # ------------------------------------------------------------------------ # Private methods # ------------------------------------------------------------------------ @@ -73,9 +87,9 @@ def _compute_scale(self): self._cache_valid = False return - d = self.range + r = self.range screen_range = self.high_pos - self.low_pos - data_range = self._pol_to_rect(d.high) - self._pol_to_rect(d.low) + data_range = r.high - r.low if screen_range == 0.0: self._null_screen_range = True else: @@ -84,6 +98,10 @@ def _compute_scale(self): self._null_data_range = True else: self._scale = screen_range / data_range - self._null_data_range = False + # The screen_range might be small enough that dividing by the + # data_range causes it to go to 0. Explicitly call bool because + # _scale might also be a numpy scalar and yield another numpy scalar + # that the Bool trait rejects. + self._null_data_range = bool(self._scale == 0.0) self._cache_valid = True