2
Defines the GridMapper class, which maps from a 2-D region in data space
3
into a structured (gridded) 1-D output space.
6
# Major library imports
7
from numpy import transpose
9
# Enthought library imports
10
from enthought.traits.api import Bool, DelegatesTo, Instance, Float, Property
12
# Local relative imports
13
from abstract_mapper import AbstractMapper
14
from base_1d_mapper import Base1DMapper
15
from data_range_2d import DataRange2D
16
from linear_mapper import LinearMapper
17
from log_mapper import LogMapper
20
class GridMapper(AbstractMapper):
22
Maps a 2-D data space to and from screen space by specifying a 2-tuple in
23
data space or by specifying a pair of screen coordinates.
25
The mapper concerns itself only with metric and not with orientation. So, to
26
"flip" a screen space orientation, swap the appropriate screen space
27
values for **x_low_pos**, **x_high_pos**, **y_low_pos**, and **y_high_pos**.
30
# The data-space bounds of the mapper.
31
range = Instance(DataRange2D)
33
# The screen space position of the lower bound of the horizontal axis.
34
x_low_pos = Float(0.0)
36
# The screen space position of the upper bound of the horizontal axis.
37
x_high_pos = Float(1.0)
39
# The screen space position of the lower bound of the vertical axis.
40
y_low_pos = Float(0.0)
42
# The screen space position of the upper bound of the vertical axis.
43
y_high_pos = Float(1.0)
45
# Convenience property for low and high positions in one structure.
46
# Must be a tuple (x_low_pos, x_high_pos, y_low_pos, y_high_pos).
47
screen_bounds = Property
49
# Should the mapper stretch the dataspace when its screen space bounds are
50
# modified (default), or should it preserve the screen-to-data ratio and
51
# resize the data bounds? If the latter, it will only try to preserve
52
# the ratio if both screen and data space extents are non-zero.
53
stretch_data_x = DelegatesTo("_xmapper", prefix="stretch_data")
54
stretch_data_y = DelegatesTo("_ymapper", prefix="stretch_data")
56
#------------------------------------------------------------------------
58
#------------------------------------------------------------------------
60
_updating_submappers = Bool(False)
62
_xmapper = Instance(Base1DMapper)
63
_ymapper = Instance(Base1DMapper)
66
#------------------------------------------------------------------------
68
#------------------------------------------------------------------------
70
def __init__(self, x_type="linear", y_type="linear", range=None, **kwargs):
71
# TODO: This is currently an implicit assumption, i.e. that the range
72
# will be passed in to the constructor. It would be impossible to
73
# create the xmapper and ymapper otherwise. However, this should be
74
# changed so that the mappers get created or modified in response to
75
# the .range attribute changing, instead of requiring the range to
76
# be passed in at construction time.
79
if "_xmapper" not in kwargs:
80
if x_type == "linear":
81
self._xmapper = LinearMapper(range=self.range.x_range)
83
self._xmapper = LogMapper(range=self.range.x_range)
85
raise ValueError("Invalid x axis type: %s" % x_type)
87
self._xmapper = kwargs.pop("_xmapper")
89
if "_ymapper" not in kwargs:
90
if y_type == "linear":
91
self._ymapper = LinearMapper(range=self.range.y_range)
93
self._ymapper = LogMapper(range=self.range.y_range)
95
raise ValueError("Invalid y axis type: %s" % y_type)
97
self._ymapper = kwargs.pop("_ymapper")
99
# Now that the mappers are created, we can go to the normal HasTraits
100
# constructor, which might set values that depend on us having a valid
102
super(GridMapper, self).__init__(**kwargs)
105
def map_screen(self, data_pts):
106
""" map_screen(data_pts) -> screen_array
108
Maps values from data space into screen space.
110
xs, ys = transpose(data_pts)
111
screen_xs = self._xmapper.map_screen(xs)
112
screen_ys = self._ymapper.map_screen(ys)
113
return zip(screen_xs,screen_ys)
115
def map_data(self, screen_pts):
116
""" map_data(screen_pts) -> data_vals
118
Maps values from screen space into data space.
120
screen_xs, screen_ys = transpose(screen_pts)
121
xs = self._xmapper.map_data(screen_xs)
122
ys = self._ymapper.map_data(screen_ys)
125
def map_data_array(self, screen_pts):
126
return self.map_data(screen_pts)
129
#------------------------------------------------------------------------
131
#------------------------------------------------------------------------
133
def _update_bounds(self):
134
self._updating_submappers = True
135
self._xmapper.screen_bounds = (self.x_low_pos, self.x_high_pos)
136
self._ymapper.screen_bounds = (self.y_low_pos, self.y_high_pos)
137
self._updating_submappers = False
140
def _update_range(self):
144
#------------------------------------------------------------------------
146
#------------------------------------------------------------------------
148
def _range_changed(self, old, new):
150
old.on_trait_change(self._update_range, "updated", remove=True)
152
new.on_trait_change(self._update_range, "updated")
153
if self._xmapper is not None:
154
self._xmapper.range = new.x_range
155
if self._ymapper is not None:
156
self._ymapper.range = new.y_range
159
def _x_low_pos_changed(self):
160
self._xmapper.low_pos = self.x_low_pos
162
def _x_high_pos_changed(self):
163
self._xmapper.high_pos = self.x_high_pos
165
def _y_low_pos_changed(self):
166
self._ymapper.low_pos = self.y_low_pos
168
def _y_high_pos_changed(self):
169
self._ymapper.high_pos = self.y_high_pos
171
def _set_screen_bounds(self, new_bounds):
172
# TODO: figure out a way to not need to do this check:
173
if self.screen_bounds == new_bounds:
175
self.set(x_low_pos = new_bounds[0], trait_change_notify=False)
176
self.set(x_high_pos = new_bounds[1], trait_change_notify=False)
177
self.set(y_low_pos = new_bounds[2], trait_change_notify=False)
178
self.set(y_high_pos = new_bounds[3], trait_change_notify=False)
179
self._update_bounds( )
181
def _get_screen_bounds(self):
182
return (self.x_low_pos, self.x_high_pos,
183
self.y_low_pos, self.y_high_pos)
185
def _updated_fired_for__xmapper(self):
186
if not self._updating_submappers:
189
def _updated_fired_for__ymapper(self):
190
if not self._updating_submappers: