~ubuntu-branches/ubuntu/utopic/python-chaco/utopic

« back to all changes in this revision

Viewing changes to enthought/chaco/tools/cursor_tool.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-07-08 20:38:02 UTC
  • mfrom: (7.2.3 sid)
  • Revision ID: james.westby@ubuntu.com-20110708203802-5t32e0ldv441yh90
Tags: 4.0.0-1
* New upstream release
* debian/control:
  - Depend on python-traitsui (Closes: #633604)
  - Bump Standards-Version to 3.9.2
* Update debian/watch file
* Remove debian/patches/* -- no longer needed

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""
2
 
Defines some chaco tools to provide draggable cursor functionality
3
 
 
4
 
For XY-plots, the cursor tool requires the index_sort flag to be set
5
 
to either 'ascending' or 'descending'.
6
 
 
7
 
TODO:
8
 
 
9
 
- add some visual feedback to the user when a cursor is "grabbed" 
10
 
    (e.g. highlight the cursor line)
11
 
 
12
 
- update cursor position to the "selections" metadata on the owning 
13
 
    plot component
14
 
    
15
 
"""
16
 
from __future__ import with_statement
17
 
 
18
 
# Major library imports
19
 
import numpy
20
 
 
21
 
# Enthought library imports
22
 
from enthought.enable.tools.drag_tool import DragTool
23
 
from enthought.traits.api import Int, Property, cached_property, Float,\
24
 
                                Bool, Instance, Tuple, Disallow
25
 
 
26
 
# Chaco imports
27
 
from enthought.chaco.scatter_markers import CircleMarker
28
 
from enthought.chaco.base_xy_plot import BaseXYPlot
29
 
from enthought.chaco.base_2d_plot import Base2DPlot
30
 
from line_inspector import LineInspector
31
 
 
32
 
 
33
 
def CursorTool(component, *args, **kwds):
34
 
    """
35
 
    Factory function returning either a CursorTool1D or CursorTool2D instance
36
 
    depending on whether the provided plot component is an XY-plot or a 2D plot.
37
 
    """
38
 
    if isinstance(component, BaseXYPlot):
39
 
        return CursorTool1D(component, *args, **kwds)
40
 
    elif isinstance(component, Base2DPlot):
41
 
        return CursorTool2D(component, *args, **kwds)
42
 
    else:
43
 
        raise NotImplementedError("cursors available for BaseXYPlot and Base2DPlot only")
44
 
 
45
 
 
46
 
class BaseCursorTool(LineInspector, DragTool):
47
 
    """
48
 
    Abstract base class for CursorTool objects
49
 
    """
50
 
    
51
 
    #if true, draw a small circle at the cursor/line intersection
52
 
    show_marker = Bool(True)
53
 
    
54
 
    #the radius of the marker in pixels
55
 
    marker_size = Float(3.0)
56
 
    
57
 
    #the marker object. this should probably be private
58
 
    marker = Instance(CircleMarker, ())
59
 
    
60
 
    #pick threshold, in screen units (pixels)
61
 
    threshold = Float(5.0)
62
 
    
63
 
    #The current index-value of the cursor. Over-ridden in subclasses
64
 
    current_index = Disallow
65
 
    
66
 
    #The current position of the cursor in data units
67
 
    current_position = Property(depends_on=['current_index'])
68
 
    
69
 
    #Stuff from line_inspector which is not required
70
 
    axis = Disallow
71
 
    inspect_mode = Disallow
72
 
    is_interactive = Disallow
73
 
    is_listener = Disallow
74
 
    write_metadata = Disallow
75
 
    metadata_name = Disallow
76
 
 
77
 
    def _draw_marker(self, gc, sx, sy):
78
 
        """
79
 
        Ruthlessly hijacked from the scatterplot.py class. This design is silly; the
80
 
        choice of rendering path should be encapsulated within the GC.
81
 
        """
82
 
        if sx < self.component.x or sx > self.component.x2 or \
83
 
            sy < self.component.y or sy > self.component.y2:
84
 
            return
85
 
        
86
 
        marker = self.marker
87
 
        marker_size = self.marker_size
88
 
        points = [(sx,sy)]
89
 
 
90
 
        with gc:
91
 
            gc.set_fill_color(self.color_)
92
 
        
93
 
            gc.begin_path()
94
 
        
95
 
            # This is the fastest method - use one of the kiva built-in markers
96
 
            if hasattr(gc, "draw_marker_at_points") \
97
 
                and (gc.draw_marker_at_points(points,
98
 
                                              marker_size,
99
 
                                              marker.kiva_marker) != 0):
100
 
                    pass
101
 
        
102
 
            # The second fastest method - draw the path into a compiled path, then
103
 
            # draw the compiled path at each point
104
 
            elif hasattr(gc, 'draw_path_at_points'):
105
 
                #if debug:
106
 
                #    import pdb; pdb.set_trace()
107
 
                path = gc.get_empty_path()
108
 
                marker.add_to_path(path, marker_size)
109
 
                mode = marker.draw_mode
110
 
                if not marker.antialias:
111
 
                    gc.set_antialias(False)
112
 
                gc.draw_path_at_points(points, path, mode)
113
 
        
114
 
            # Neither of the fast functions worked, so use the brute-force, manual way
115
 
            else:
116
 
                if not marker.antialias:
117
 
                    gc.set_antialias(False)
118
 
                for sx,sy in points:
119
 
                    with gc:
120
 
                        gc.translate_ctm(sx, sy)
121
 
                        # Kiva GCs have a path-drawing interface
122
 
                        marker.add_to_path(gc, marker_size)
123
 
                        gc.draw_path(marker.draw_mode)    
124
 
        return
125
 
    
126
 
    def normal_mouse_move(self, event):
127
 
        """ Handles the mouse being moved. 
128
 
        """
129
 
        return
130
 
    
131
 
    def normal_mouse_leave(self, event):
132
 
        """ Handles the mouse leaving the plot.
133
 
        """
134
 
        return
135
 
        
136
 
 
137
 
class CursorTool1D(BaseCursorTool):
138
 
    """
139
 
    This tools provides a draggable cursor bound to a XY plot component instance.
140
 
    
141
 
    Note, be sure to select an drag_button which does not conflict with other tools 
142
 
    (e.g. the PanTool).
143
 
    
144
 
    """
145
 
    
146
 
    #The current index-value of the cursor
147
 
    current_index = Int(0)
148
 
    
149
 
    
150
 
    #if true, draws a line parallel to the index-axis 
151
 
    #through the cursor intersection point 
152
 
    show_value_line = Bool(True)
153
 
    
154
 
    def _current_index_changed(self):
155
 
        self.component.request_redraw()
156
 
    
157
 
    @cached_property
158
 
    def _get_current_position(self):
159
 
        plot = self.component
160
 
        ndx = self.current_index
161
 
        x = plot.index.get_data()[ndx]
162
 
        y = plot.value.get_data()[ndx]
163
 
        return x,y
164
 
    
165
 
    def _set_current_position(self, traitname, args):
166
 
        plot = self.component
167
 
        ndx = plot.index.reverse_map(args[0])
168
 
        if ndx is not None:
169
 
            self.current_index = ndx
170
 
    
171
 
    def draw(self, gc, view_bounds=None):
172
 
        """ Draws this tool on a graphics context.  
173
 
        
174
 
        Overrides LineInspector, BaseTool.
175
 
        """
176
 
        # We draw at different points depending on whether or not we are 
177
 
        # interactive.  If both listener and interactive are true, then the 
178
 
        # selection metadata on the plot component takes precendence.
179
 
        plot = self.component
180
 
        if plot is None:
181
 
            return
182
 
        
183
 
        sx, sy = plot.map_screen(self.current_position)
184
 
        orientation = plot.orientation
185
 
        
186
 
        if orientation == "h" and sx is not None:
187
 
            self._draw_vertical_line(gc, sx)
188
 
        elif sy is not None:
189
 
            self._draw_horizontal_line(gc, sy)
190
 
        
191
 
        if self.show_marker:
192
 
            self._draw_marker(gc, sx, sy)
193
 
            
194
 
        if self.show_value_line:
195
 
            if orientation == "h" and sy is not None:
196
 
                self._draw_horizontal_line(gc, sy)
197
 
            elif sx is not None:
198
 
                self._draw_vertical_line(gc, sx)
199
 
    
200
 
    def is_draggable(self, x, y):
201
 
        plot = self.component
202
 
        if plot is not None:
203
 
            orientation = plot.orientation
204
 
            sx, sy = plot.map_screen(self.current_position)
205
 
            if orientation=='h' and numpy.abs(sx-x) <= self.threshold:
206
 
                return True
207
 
            elif orientation=='v' and numpy.abs(sy-y) <= self.threshold:
208
 
                return True
209
 
        return False
210
 
    
211
 
    def dragging(self, event):
212
 
        x,y = event.x, event.y
213
 
        plot = self.component
214
 
        ndx = plot.map_index((x, y), threshold=0.0, index_only=True)
215
 
        if ndx is None:
216
 
            return
217
 
        self.current_index = ndx
218
 
        plot.request_redraw()
219
 
        
220
 
        
221
 
class CursorTool2D(BaseCursorTool):
222
 
    _dragV = Bool(False)
223
 
    _dragH = Bool(False)
224
 
    
225
 
    current_index = Tuple(0,0)
226
 
    
227
 
    def _current_index_changed(self):
228
 
        self.component.request_redraw()
229
 
    
230
 
    @cached_property
231
 
    def _get_current_position(self):
232
 
        plot = self.component
233
 
        ndx, ndy = self.current_index
234
 
        xdata, ydata = plot.index.get_data()
235
 
        x = xdata.get_data()[ndx]
236
 
        y = ydata.get_data()[ndy]
237
 
        return x,y
238
 
    
239
 
    def _set_current_position(self, traitname, args):
240
 
        plot = self.component
241
 
        xds, yds = plot.index.get_data()
242
 
        ndx = xds.reverse_map(args[0])
243
 
        ndy = yds.reverse_map(args[1])
244
 
        if ndx is not None and ndy is not None:
245
 
            self.current_index = ndx, ndy
246
 
    
247
 
    def is_draggable(self, x, y):
248
 
        plot = self.component
249
 
        if plot is not None:
250
 
            orientation = plot.orientation
251
 
            sx, sy = plot.map_screen([self.current_position])[0]
252
 
            self._dragV = self._dragH = False
253
 
            if orientation=='h':
254
 
                if numpy.abs(sx-x) <= self.threshold:
255
 
                    self._dragH = True
256
 
                if numpy.abs(sy-y) <= self.threshold:
257
 
                    self._dragV = True
258
 
            else:
259
 
                if numpy.abs(sx-x) <= self.threshold:
260
 
                    self._dragV = True
261
 
                if numpy.abs(sy-y) <= self.threshold:
262
 
                    self._dragH = True
263
 
            return self._dragV or self._dragH
264
 
        return False
265
 
     
266
 
    def draw(self, gc, view_bounds=None):
267
 
        """ Draws this tool on a graphics context.  
268
 
        
269
 
        Overrides LineInspector, BaseTool.
270
 
        """
271
 
        # We draw at different points depending on whether or not we are 
272
 
        # interactive.  If both listener and interactive are true, then the 
273
 
        # selection metadata on the plot component takes precendence.
274
 
        plot = self.component
275
 
        if plot is None:
276
 
            return
277
 
        sx, sy = plot.map_screen([self.current_position])[0]
278
 
        orientation = plot.orientation
279
 
        
280
 
        if orientation == "h":
281
 
            if sx is not None:
282
 
                self._draw_vertical_line(gc, sx)
283
 
            if sy is not None:
284
 
                self._draw_horizontal_line(gc, sy)
285
 
        else:
286
 
            if sx is not None:
287
 
                self._draw_horizontal_line(gc, sx)
288
 
            if sy is not None:
289
 
                self._draw_vertical_line(gc, sy)
290
 
        
291
 
        if self.show_marker and sx is not None and sy is not None:
292
 
            self._draw_marker(gc, sx, sy)
293
 
                
294
 
    def dragging(self, event):
295
 
        x,y = event.x, event.y
296
 
        plot = self.component
297
 
        ndx = plot.map_index((x, y), threshold=0.0, index_only=True)
298
 
        if ndx is None:
299
 
            return
300
 
        newx, newy = self.current_index
301
 
        if self._dragH and ndx[0] is not None:
302
 
            newx = ndx[0]
303
 
        if self._dragV and ndx[1] is not None:
304
 
            newy = ndx[1]
305
 
        self.current_index = newx, newy
306
 
        plot.request_redraw()