1
""" Defines the base class for various types of zoom tools.
4
from numpy import allclose, inf
6
# Enthought library imports
7
from enthought.traits.api import Enum, Float, HasTraits
9
class BaseZoomTool(HasTraits):
10
""" Defines traits and methods to actually perform the logic of zooming
14
# If the tool only applies to a particular axis, this attribute is used to
15
# determine which mapper and range to use.
16
axis = Enum("index", "value")
18
# The maximum ratio between the original data space bounds and the zoomed-in
19
# data space bounds. If None, then there is no limit (not advisable!).
20
max_zoom_in_factor = Float(1e5, allow_none=True)
22
# The maximum ratio between the zoomed-out data space bounds and the original
23
# bounds. If None, then there is no limit.
24
max_zoom_out_factor = Float(1e5, allow_none=True)
26
def _zoom_limit_reached(self, orig_low, orig_high, new_low, new_high):
27
""" Returns True if the new low and high exceed the maximum zoom
30
orig_bounds = orig_high - orig_low
32
if orig_bounds == inf:
33
# There isn't really a good way to handle the case when the
34
# original bounds were infinite, since any finite zoom
35
# range will certainly exceed whatever zoom factor is set.
36
# In this case, we just allow unbounded levels of zoom.
39
new_bounds = new_high - new_low
40
if allclose(orig_bounds, 0.0):
42
if allclose(new_bounds, 0.0):
44
if (new_bounds / orig_bounds) > self.max_zoom_out_factor or \
45
(orig_bounds / new_bounds) > self.max_zoom_in_factor:
49
#------------------------------------------------------------------------
50
# Utility methods for computing axes, coordinates, etc.
51
#------------------------------------------------------------------------
53
def _get_mapper(self):
54
""" Returns the mapper for the component associated with this tool.
56
The zoom tool really only cares about this, so subclasses can easily
57
customize SimpleZoom to work with all sorts of components just by
58
overriding this method.
60
if self.component is not None:
61
return getattr(self.component, self.axis + "_mapper")
66
def _get_axis_coord(self, event, axis="index"):
67
""" Returns the coordinate of the event along the axis of interest
68
to the tool (or along the orthogonal axis, if axis="value").
70
event_pos = (event.x, event.y)
72
return event_pos[ self._determine_axis() ]
74
return event_pos[ 1 - self._determine_axis() ]
76
def _determine_axis(self):
77
""" Determines whether the index of the coordinate along the axis of
78
interest is the first or second element of an (x,y) coordinate tuple.
80
if self.axis == "index":
81
if self.component.orientation == "h":
85
else: # self.axis == "value"
86
if self.component.orientation == "h":
91
def _map_coordinate_box(self, start, end):
92
""" Given start and end points in screen space, returns corresponding
93
low and high points in data space.
97
for axis_index, mapper in [(0, self.component.x_mapper), \
98
(1, self.component.y_mapper)]:
99
low_val = mapper.map_data(start[axis_index])
100
high_val = mapper.map_data(end[axis_index])
102
if low_val > high_val:
103
low_val, high_val = high_val, low_val
104
low[axis_index] = low_val
105
high[axis_index] = high_val