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

« back to all changes in this revision

Viewing changes to chaco/lineplot.py

  • Committer: Package Import Robot
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2014-06-01 17:04:08 UTC
  • mfrom: (7.2.5 sid)
  • Revision ID: package-import@ubuntu.com-20140601170408-m86xvdjd83a4qon0
Tags: 4.4.1-1ubuntu1
* Merge from Debian unstable. Remaining Ubuntu changes:
 - Let the binary-predeb target work on the usr/lib/python* directory
   as we don't have usr/share/pyshared anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
8
8
 
9
9
# Major library imports
10
10
from numpy import argsort, array, concatenate, inf, invert, isnan, \
11
 
                  take, transpose, zeros
 
11
                  take, transpose, zeros, sqrt, argmin, clip, column_stack
12
12
 
13
13
# Enthought library imports
14
14
from enable.api import black_color_trait, ColorTrait, LineStyle
15
 
from traits.api import Enum, Float, List, Str
 
15
from traits.api import Enum, Float, List, Str, Property, Tuple, cached_property
16
16
from traitsui.api import Item, View
17
17
 
18
18
# Local relative imports
32
32
    # The color of the line.
33
33
    color = black_color_trait
34
34
 
 
35
    # The RGBA tuple for rendering lines.  It is always a tuple of length 4.
 
36
    # It has the same RGB values as color_, and its alpha value is the alpha
 
37
    # value of self.color multiplied by self.alpha. 
 
38
    effective_color = Property(Tuple, depends_on=['color', 'alpha'])
 
39
 
35
40
    # The color to use to highlight the line when selected.
36
41
    selected_color = ColorTrait("lightyellow")
37
42
 
79
84
    _cached_screen_pts = List
80
85
 
81
86
 
82
 
    def hittest(self, screen_pt, threshold=7.0):
 
87
    def hittest(self, screen_pt, threshold=7.0, return_distance = False):
83
88
        """
84
89
        Tests whether the given screen point is within *threshold* pixels of
85
90
        any data points on the line.  If so, then it returns the (x,y) value of
86
91
        a data point near the screen point.  If not, then it returns None.
 
92
        """
87
93
 
88
 
        Note: This only checks data points and *not* the actual line segments
89
 
        connecting them.
90
 
        """
 
94
        # First, check screen_pt is directly on a point in the lineplot
91
95
        ndx = self.map_index(screen_pt, threshold)
92
96
        if ndx is not None:
93
 
            return (self.index.get_data()[ndx], self.value.get_data()[ndx])
 
97
            # screen_pt is one of the points in the lineplot
 
98
            data_pt = (self.index.get_data()[ndx], self.value.get_data()[ndx])
 
99
            if return_distance:
 
100
                scrn_pt = self.map_screen(data_pt)
 
101
                dist = sqrt((screen_pt[0] - scrn_pt[0])**2
 
102
                            + (screen_pt[1] - scrn_pt[1])**2)
 
103
                return (data_pt[0], data_pt[1], dist)
 
104
            else:
 
105
                return data_pt
94
106
        else:
95
 
            data_x = self.map_data(screen_pt)
 
107
            # We now must check the lines themselves
 
108
 
 
109
            # Must check all lines within threshold along the major axis,
 
110
            # so determine the bounds of the region of interest in dataspace
 
111
            if self.orientation == "h":
 
112
                dmax = self.map_data((screen_pt[0]+threshold, screen_pt[1]))
 
113
                dmin = self.map_data((screen_pt[0]-threshold, screen_pt[1]))
 
114
            else:
 
115
                dmax = self.map_data((screen_pt[0], screen_pt[1]+threshold))
 
116
                dmin = self.map_data((screen_pt[0], screen_pt[1]-threshold))
 
117
 
96
118
            xmin, xmax = self.index.get_bounds()
97
 
            if xmin <= data_x <= xmax:
98
 
                if self.orientation == "h":
99
 
                    sy = screen_pt[1]
 
119
 
 
120
            # Now compute the bounds of the region of interest as indexes
 
121
            if dmin < xmin:
 
122
                ndx1 = 0
 
123
            elif dmin > xmax:
 
124
                ndx1 = len(self.value.get_data())-1
 
125
            else:
 
126
                ndx1 = reverse_map_1d(self.index.get_data(), dmin,
 
127
                                      self.index.sort_order)
 
128
            if dmax < xmin:
 
129
                ndx2 = 0
 
130
            elif dmax > xmax:
 
131
                ndx2 = len(self.value.get_data())-1
 
132
            else:
 
133
                ndx2 = reverse_map_1d(self.index.get_data(), dmax,
 
134
                                      self.index.sort_order)
 
135
 
 
136
            start_ndx = max(0, min(ndx1-1, ndx2-1,))
 
137
            end_ndx = min(len(self.value.get_data())-1, max(ndx1+1, ndx2+1))
 
138
 
 
139
            # Compute the distances to all points in the range of interest
 
140
            start = array([ self.index.get_data()[start_ndx:end_ndx],
 
141
                            self.value.get_data()[start_ndx:end_ndx] ])
 
142
            end = array([ self.index.get_data()[start_ndx+1:end_ndx+1],
 
143
                            self.value.get_data()[start_ndx+1:end_ndx+1] ])
 
144
 
 
145
            # Convert to screen points
 
146
            s_start = transpose(self.map_screen(transpose(start)))
 
147
            s_end = transpose(self.map_screen(transpose(end)))
 
148
 
 
149
            # t gives the parameter of the closest point to screen_pt
 
150
            # on the line going from s_start to s_end
 
151
            t = _closest_point(screen_pt, s_start, s_end)
 
152
 
 
153
            # Restrict to points on the line segment s_start->s_end
 
154
            t = clip(t, 0, 1)
 
155
 
 
156
            # Gives the corresponding point on the line
 
157
            px, py = _t_to_point(t, s_start, s_end)
 
158
 
 
159
            # Calculate distances
 
160
            dist =  sqrt((px - screen_pt[0])**2 +
 
161
                         (py - screen_pt[1])**2)
 
162
 
 
163
            # Find the minimum
 
164
            n = argmin(dist)
 
165
            # And return if it is good
 
166
            if dist[n] <= threshold:
 
167
                best_pt = self.map_data((px[n], py[n]), all_values=True)
 
168
 
 
169
                if return_distance:
 
170
                    return [best_pt[0], best_pt[1], dist[n]]
100
171
                else:
101
 
                    sy = screen_pt[0]
102
 
 
103
 
                interp_val = self.interpolate(data_x)
104
 
                interp_y = self.value_mapper.map_screen(interp_val)
105
 
 
106
 
                if abs(sy - interp_y) <= threshold:
107
 
                    return reverse_map_1d(self.index.get_data(), data_x,
108
 
                                          self.index.sort_order)
 
172
                    return best_pt
 
173
 
109
174
            return None
110
175
 
111
176
    def interpolate(self, index_value):
248
313
                        if end != data_end:
249
314
                            end += 1
250
315
 
251
 
                        run_data = transpose(array((block_index[start:end],
252
 
                                                    block_value[start:end])))
 
316
                        run_data = ( block_index[start:end],
 
317
                                     block_value[start:end] )
 
318
                        run_data = column_stack(run_data)
 
319
 
253
320
                        points.append(run_data)
254
321
 
255
322
            self._cached_data_pts = points
310
377
                render(gc, selected_points, self.orientation)
311
378
 
312
379
            # Render using the normal style
313
 
            gc.set_stroke_color(self.color_)
 
380
            gc.set_stroke_color(self.effective_color)
314
381
            gc.set_line_width(self.line_width)
315
382
            gc.set_line_dash(self.line_style_)
316
383
            render(gc, points, self.orientation)
356
423
 
357
424
    def _render_icon(self, gc, x, y, width, height):
358
425
        with gc:
359
 
            gc.set_stroke_color(self.color_)
 
426
            gc.set_stroke_color(self.effective_color)
360
427
            gc.set_line_width(self.line_width)
361
428
            gc.set_line_dash(self.line_style_)
362
429
            gc.set_antialias(0)
384
451
        return
385
452
 
386
453
    def _alpha_changed(self):
387
 
        self.color_ = self.color_[0:3] + (self.alpha,)
388
454
        self.invalidate_draw()
389
455
        self.request_redraw()
390
456
        return
412
478
 
413
479
        return state
414
480
 
 
481
    @cached_property
 
482
    def _get_effective_color(self):
 
483
        alpha = self.color_[-1] if len(self.color_) == 4 else 1
 
484
        c = self.color_[:3] + (alpha * self.alpha,)
 
485
        return c
 
486
 
 
487
def _closest_point(target, p1, p2):
 
488
    '''Utility function for hittest:
 
489
    finds the point on the line between p1 and p2 to
 
490
    the target. Returns the 't' value of that point
 
491
    where the line is parametrized as
 
492
        t -> p1*(1-t) + p2*t
 
493
    Notably, if t=0 is p1, t=2 is p2 and anything outside
 
494
    that range is a point outisde p1, p2 on the line
 
495
    Note: can divide by zero, so user should check for that'''
 
496
    t = ((p1[0] - target[0])*(p1[0]-p2[0]) \
 
497
            + (p1[1] - target[1])*(p1[1]-p2[1]))\
 
498
        / ((p1[0] - p2[0])*(p1[0] - p2[0]) + (p1[1] - p2[1])*(p1[1] - p2[1]))
 
499
    return t
 
500
 
 
501
def _t_to_point(t, p1, p2):
 
502
    '''utility function for hittest for use with _closest_point
 
503
    returns the point corresponding to the parameter t
 
504
    on the line going between p1 and p2'''
 
505
    return ( p1[0]*(1-t) + p2[0]*t,
 
506
             p1[1]*(1-t) + p2[1]*t )
 
507
 
415
508
 
416
509
# EOF