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
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
18
18
# Local relative imports
79
84
_cached_screen_pts = List
82
def hittest(self, screen_pt, threshold=7.0):
87
def hittest(self, screen_pt, threshold=7.0, return_distance = False):
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.
88
Note: This only checks data points and *not* the actual line segments
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])
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)
95
data_x = self.map_data(screen_pt)
107
# We now must check the lines themselves
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]))
115
dmax = self.map_data((screen_pt[0], screen_pt[1]+threshold))
116
dmin = self.map_data((screen_pt[0], screen_pt[1]-threshold))
96
118
xmin, xmax = self.index.get_bounds()
97
if xmin <= data_x <= xmax:
98
if self.orientation == "h":
120
# Now compute the bounds of the region of interest as indexes
124
ndx1 = len(self.value.get_data())-1
126
ndx1 = reverse_map_1d(self.index.get_data(), dmin,
127
self.index.sort_order)
131
ndx2 = len(self.value.get_data())-1
133
ndx2 = reverse_map_1d(self.index.get_data(), dmax,
134
self.index.sort_order)
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))
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] ])
145
# Convert to screen points
146
s_start = transpose(self.map_screen(transpose(start)))
147
s_end = transpose(self.map_screen(transpose(end)))
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)
153
# Restrict to points on the line segment s_start->s_end
156
# Gives the corresponding point on the line
157
px, py = _t_to_point(t, s_start, s_end)
159
# Calculate distances
160
dist = sqrt((px - screen_pt[0])**2 +
161
(py - screen_pt[1])**2)
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)
170
return [best_pt[0], best_pt[1], dist[n]]
103
interp_val = self.interpolate(data_x)
104
interp_y = self.value_mapper.map_screen(interp_val)
106
if abs(sy - interp_y) <= threshold:
107
return reverse_map_1d(self.index.get_data(), data_x,
108
self.index.sort_order)
111
176
def interpolate(self, index_value):
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,)
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
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]))
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 )