def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, \ index_only = True): """ Maps a screen space point to an index into the plot's index array(s). """ screen_points = self._cached_screen_pts if len(screen_points) == 0: return None data_pt = self.map_data(screen_pt) if ((data_pt < self.mapper.range.low) or \ (data_pt > self.mapper.range.high)) and outside_returns_none: return None if self._cached_data_pts_sorted is None: self._cached_data_argsort = np.argsort(self._cached_data_pts) self._cached_data_pts_sorted = self._cached_data_pts[self._cached_data_argsort] data = self._cached_data_pts_sorted try: ndx = reverse_map_1d(data, data_pt, "ascending") except IndexError, e: if outside_returns_none: return None else: if data_pt < data[0]: return 0 else: return len(data) - 1
def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, index_only=False): """ Maps a screen space point to an index into the plot's index array(s). Implements the AbstractPlotRenderer interface. """ data_pt = self.map_data(screen_pt) if ((data_pt < self.index_mapper.range.low) or \ (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None try: ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError: return None x = index_data[ndx] y = value_data[ndx] result = self.map_screen(array([[x,y]])) if result is None: return None sx, sy = result[0] if index_only and ((screen_pt[0]-sx) < threshold): return ndx elif ((screen_pt[0]-sx)**2 + (screen_pt[1]-sy)**2 < threshold*threshold): return ndx else: return None
def reverse_map(self, pt, index=0, outside_returns_none=True): """Returns the index of *pt* in the data source. Parameters ---------- pt : scalar value value to find index ignored for data series with 1-D indices outside_returns_none : Boolean Whether the method returns None if *pt* is outside the range of the data source; if False, the method returns the value of the bound that *pt* is outside of. """ if self.sort_order == "none": raise NotImplementedError # index is ignored for dataseries with 1-dimensional indices minval, maxval = self._cached_bounds if (pt < minval): if outside_returns_none: return None else: return self._min_index elif (pt > maxval): if outside_returns_none: return None else: return self._max_index else: return reverse_map_1d(self._data, pt, self.sort_order)
def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, \ index_only=False): """ Maps a screen space point to an index into the plot's index array(s). Implements the AbstractPlotRenderer interface. """ data_pt = self.map_data(screen_pt) if ((data_pt < self.index_mapper.range.low) or \ (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None try: ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError, e: # if reverse_map raises this exception, it means that data_pt is # outside the range of values in index_data. if outside_returns_none: return None else: if data_pt < index_data[0]: return 0 else: return len(index_data) - 1
def hittest(self, screen_pt, threshold=7.0): """ Tests whether the given screen point is within *threshold* pixels of any data points on the line. If so, then it returns the (x,y) value of a data point near the screen point. If not, then it returns None. Note: This only checks data points and *not* the actual line segments connecting them. """ ndx = self.map_index(screen_pt, threshold) if ndx is not None: return (self.index.get_data()[ndx], self.value.get_data()[ndx]) else: data_x = self.map_data(screen_pt) xmin, xmax = self.index.get_bounds() if xmin <= data_x <= xmax: if self.orientation == "h": sy = screen_pt[1] else: sy = screen_pt[0] interp_val = self.interpolate(data_x) interp_y = self.value_mapper.map_screen(interp_val) if abs(sy - interp_y) <= threshold: return reverse_map_1d(self.index.get_data(), data_x, self.index.sort_order) return None
def map_index(self, screen_pt, threshold=0.0, outside_returns_none=True, \ index_only = False): """ Maps a screen space point to an index into the plot's index array(s). Overrides the BaseXYPlot implementation.. """ index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None if index_only and self.index.sort_order != "none": data_pt = self.map_data(screen_pt)[0] # The rest of this was copied out of BaseXYPlot. # We can't just used BaseXYPlot.map_index because # it expect map_data to return a value, not a pair. if ((data_pt < self.index_mapper.range.low) or \ (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None try: ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError, e: # if reverse_map raises this exception, it means that data_pt is # outside the range of values in index_data. if outside_returns_none: return None else: if data_pt < index_data[0]: return 0 else: return len(index_data) - 1 if threshold == 0.0: # Don't do any threshold testing return ndx x = index_data[ndx] y = value_data[ndx] if isnan(x) or isnan(y): return None sx, sy = self.map_screen([x, y]) if ((threshold == 0.0) or (screen_pt[0] - sx) < threshold): return ndx else: return None
def map_index(self, screen_pt, threshold=0.0, outside_returns_none=True, \ index_only = False): """ Maps a screen space point to an index into the plot's index array(s). Overrides the BaseXYPlot implementation.. """ index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None if index_only and self.index.sort_order != "none": data_pt = self.map_data(screen_pt)[0] # The rest of this was copied out of BaseXYPlot. # We can't just used BaseXYPlot.map_index because # it expect map_data to return a value, not a pair. if ((data_pt < self.index_mapper.range.low) or \ (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None try: ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError, e: # if reverse_map raises this exception, it means that data_pt is # outside the range of values in index_data. if outside_returns_none: return None else: if data_pt < index_data[0]: return 0 else: return len(index_data) - 1 if threshold == 0.0: # Don't do any threshold testing return ndx x = index_data[ndx] y = value_data[ndx] if isnan(x) or isnan(y): return None sx, sy = self.map_screen([x,y]) if ((threshold == 0.0) or (screen_pt[0]-sx) < threshold): return ndx else: return None
def reverse_map(self, pt, index=0, outside_returns_none=True): """Returns the index of *pt* in the data source. Overrides ArrayDataSource. Parameters ---------- pt : (x, y) value to find index : 0 or 1 Which of the axes of *pt* the *sort_order* refers to. outside_returns_none : Boolean Whether the method returns None if *pt* is outside the range of the data source; if False, the method returns the value of the bound that *pt* is outside of, in the *index* dimension. """ # reverse_map is of limited utility for a PointDataSeries and thus # we only perform reverse-mapping if the data is sorted along an axis. if self.sort_order == "none": # By default, only provide reverse_map if the value data is sorted # along one of its axes. raise NotImplementedError if index != 0 and index != 1: raise ValueError, "Index must be 0 or 1." # This basically reduces to a scalar data search along self.data[index]. lowerleft, upperright = self._cached_bounds min_val = lowerleft[index] max_val = upperright[index] val = pt[index] if (val < min_val): if outside_returns_none: return None else: return self._min_index elif (val > max_val): if outside_returns_none: return None else: return self._max_index else: return reverse_map_1d(self._data[:, index], val, self.sort_order)
def reverse_map(self, pt, index=0, outside_returns_none=True): """Returns the index of *pt* in the data source. Overrides ArrayDataSource. Parameters ---------- pt : (x, y) value to find index : 0 or 1 Which of the axes of *pt* the *sort_order* refers to. outside_returns_none : Boolean Whether the method returns None if *pt* is outside the range of the data source; if False, the method returns the value of the bound that *pt* is outside of, in the *index* dimension. """ # reverse_map is of limited utility for a PointDataSeries and thus # we only perform reverse-mapping if the data is sorted along an axis. if self.sort_order == "none": # By default, only provide reverse_map if the value data is sorted # along one of its axes. raise NotImplementedError if index != 0 and index != 1: raise ValueError, "Index must be 0 or 1." # This basically reduces to a scalar data search along self.data[index]. lowerleft, upperright= self._cached_bounds min_val = lowerleft[index] max_val = upperright[index] val = pt[index] if (val < min_val): if outside_returns_none: return None else: return self._min_index elif (val > max_val): if outside_returns_none: return None else: return self._max_index else: return reverse_map_1d(self._data[:,index], val, self.sort_order)
def interpolate(self, index_value): """ Returns the value of the plot at the given index value in screen space. Raises an IndexError when *index_value* exceeds the bounds of indexes on the value. """ if self.index is None or self.value is None: raise IndexError, "cannot index when data source index or value is None" index_data = self.index.get_data() value_data = self.value.get_data() ndx = reverse_map_1d(index_data, index_value, self.index.sort_order) # quick test to see if this value is already in the index array if index_value == index_data[ndx]: return value_data[ndx] # get x and y values to interpolate between if index_value < index_data[ndx]: x0 = index_data[ndx - 1] y0 = value_data[ndx - 1] x1 = index_data[ndx] y1 = value_data[ndx] else: x0 = index_data[ndx] y0 = value_data[ndx] x1 = index_data[ndx + 1] y1 = value_data[ndx + 1] if x1 != x0: slope = float(y1 - y0)/float(x1 - x0) dx = index_value - x0 yp = y0 + slope * dx else: yp = inf return yp
def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, index_only=False): """ Maps a screen space point to an index into the plot's index array(s). Implements the AbstractPlotRenderer interface. """ data_pt = self.map_data(screen_pt) if ((data_pt < self.index_mapper.range.low) or \ (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None try: ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError: return None x = index_data[ndx] y = value_data[ndx] result = self.map_screen(array([[x, y]])) if result is None: return None sx, sy = result[0] if index_only and ((screen_pt[0] - sx) < threshold): return ndx elif ((screen_pt[0] - sx)**2 + (screen_pt[1] - sy)**2 < threshold * threshold): return ndx else: return None
def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, index_only=False): """ Maps a screen space point to an index into the plot's index arrays. Implements the AbstractPlotRenderer interface. The *index_only* parameter is ignored because the index is intrinsically 2-D. """ if self.orientation == 'h': x_pt,y_pt = self.map_data([screen_pt])[0] else: x_pt,y_pt = self.map_data([(screen_pt[1],screen_pt[0])])[0] if ((x_pt < self.index_mapper.range.low[0]) or (x_pt > self.index_mapper.range.high[0]) or (y_pt < self.index_mapper.range.low[1]) or (y_pt > self.index_mapper.range.high[1])) and outside_returns_none: return None, None x_index_data, y_index_data = self.index.get_data() if x_index_data.get_size() == 0 or y_index_data.get_size() == 0: return None, None # attempt to map to the x index x_data = x_index_data.get_data() y_data = y_index_data.get_data() try: x_ndx = reverse_map_1d(x_data, x_pt, self.index.sort_order[0], floor_only=True) except IndexError, e: if outside_returns_none: return None, None # x index if x_pt < x_data[0]: x_ndx = 0 else: x_ndx = len(x_data) - 1
def interpolate(self, index_value): """ Returns the value of the plot at the given index value in screen space. Raises an IndexError when *index_value* exceeds the bounds of indexes on the value. """ if self.index is None or self.value is None: raise IndexError, "cannot index when data source index or value is None" index_data = self.index.get_data() value_data = self.value.get_data() ndx = reverse_map_1d(index_data, index_value, self.index.sort_order) # quick test to see if this value is already in the index array if index_value == index_data[ndx]: return value_data[ndx] # get x and y values to interpolate between if index_value < index_data[ndx]: x0 = index_data[ndx - 1] y0 = value_data[ndx - 1] x1 = index_data[ndx] y1 = value_data[ndx] else: x0 = index_data[ndx] y0 = value_data[ndx] x1 = index_data[ndx + 1] y1 = value_data[ndx + 1] if x1 != x0: slope = float(y1 - y0) / float(x1 - x0) dx = index_value - x0 yp = y0 + slope * dx else: yp = inf return yp
def hittest(self, screen_pt, threshold=7.0, return_distance=False): """ Tests whether the given screen point is within *threshold* pixels of any data points on the line. If so, then it returns the (x,y) value of a data point near the screen point. If not, then it returns None. """ # First, check screen_pt is directly on a point in the lineplot ndx = self.map_index(screen_pt, threshold) if ndx is not None: # screen_pt is one of the points in the lineplot data_pt = (self.index.get_data()[ndx], self.value.get_data()[ndx]) if return_distance: scrn_pt = self.map_screen(data_pt) dist = sqrt((screen_pt[0] - scrn_pt[0])**2 + (screen_pt[1] - scrn_pt[1])**2) return (data_pt[0], data_pt[1], dist) else: return data_pt else: # We now must check the lines themselves # Must check all lines within threshold along the major axis, # so determine the bounds of the region of interest in dataspace if self.orientation == "h": dmax = self.map_data((screen_pt[0] + threshold, screen_pt[1])) dmin = self.map_data((screen_pt[0] - threshold, screen_pt[1])) else: dmax = self.map_data((screen_pt[0], screen_pt[1] + threshold)) dmin = self.map_data((screen_pt[0], screen_pt[1] - threshold)) xmin, xmax = self.index.get_bounds() # Now compute the bounds of the region of interest as indexes if dmin < xmin: ndx1 = 0 elif dmin > xmax: ndx1 = len(self.value.get_data()) - 1 else: ndx1 = reverse_map_1d(self.index.get_data(), dmin, self.index.sort_order) if dmax < xmin: ndx2 = 0 elif dmax > xmax: ndx2 = len(self.value.get_data()) - 1 else: ndx2 = reverse_map_1d(self.index.get_data(), dmax, self.index.sort_order) start_ndx = max(0, min( ndx1 - 1, ndx2 - 1, )) end_ndx = min( len(self.value.get_data()) - 1, max(ndx1 + 1, ndx2 + 1)) # Compute the distances to all points in the range of interest start = array([ self.index.get_data()[start_ndx:end_ndx], self.value.get_data()[start_ndx:end_ndx] ]) end = array([ self.index.get_data()[start_ndx + 1:end_ndx + 1], self.value.get_data()[start_ndx + 1:end_ndx + 1] ]) # Convert to screen points s_start = transpose(self.map_screen(transpose(start))) s_end = transpose(self.map_screen(transpose(end))) # t gives the parameter of the closest point to screen_pt # on the line going from s_start to s_end t = _closest_point(screen_pt, s_start, s_end) # Restrict to points on the line segment s_start->s_end t = clip(t, 0, 1) # Gives the corresponding point on the line px, py = _t_to_point(t, s_start, s_end) # Calculate distances dist = sqrt((px - screen_pt[0])**2 + (py - screen_pt[1])**2) # Find the minimum n = argmin(dist) # And return if it is good if dist[n] <= threshold: best_pt = self.map_data((px[n], py[n]), all_values=True) if return_distance: return [best_pt[0], best_pt[1], dist[n]] else: return best_pt return None
def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, index_only=False): """ Maps a screen space point to an index into the plot's index array(s). Implements the AbstractPlotRenderer interface. Parameters ---------- screen_pt : Screen space point threshold : float Maximum distance from screen space point to plot data point. A value of 0.0 means no threshold (any distance will do). outside_returns_none : bool If True, a screen space point outside the data range returns None. Otherwise, it returns either 0 (outside the lower range) or the last index (outside the upper range) index_only : bool If True, the threshold is measured on the distance between the index values, otherwise as Euclidean distance between the (x,y) coordinates. """ data_pt = self.map_data(screen_pt) if ((data_pt < self.index_mapper.range.low) or (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None try: # find the closest point to data_pt in index_data ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError: # if reverse_map raises this exception, it means that data_pt is # outside the range of values in index_data. if outside_returns_none: return None else: if data_pt < index_data[0]: return 0 else: return len(index_data) - 1 if threshold == 0.0: # Don't do any threshold testing return ndx x = index_data[ndx] y = value_data[ndx] if isnan(x) or isnan(y): return None # transform x,y in a 1x2 array, which is the preferred format of # map_screen. this makes it robust against differences in # the map_screen methods of logmapper and linearmapper # when passed a scalar xy = array([[x,y]]) sx, sy = self.map_screen(xy).T if index_only and (threshold == 0.0 or screen_pt[0]-sx < threshold): return ndx elif ((screen_pt[0]-sx)**2 + (screen_pt[1]-sy)**2 < threshold*threshold): return ndx else: return None
def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, index_only=False): """ Maps a screen space point to an index into the plot's index array(s). Implements the AbstractPlotRenderer interface. Parameters ---------- screen_pt : Screen space point threshold : float Maximum distance from screen space point to plot data point. A value of 0.0 means no threshold (any distance will do). outside_returns_none : bool If True, a screen space point outside the data range returns None. Otherwise, it returns either 0 (outside the lower range) or the last index (outside the upper range) index_only : bool If True, the threshold is measured on the distance between the index values, otherwise as Euclidean distance between the (x,y) coordinates. """ data_pt = self.map_data(screen_pt) if ((data_pt < self.index_mapper.range.low) or (data_pt > self.index_mapper.range.high)) and outside_returns_none: return None index_data = self.index.get_data() value_data = self.value.get_data() if len(value_data) == 0 or len(index_data) == 0: return None try: # find the closest point to data_pt in index_data ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order) except IndexError: # if reverse_map raises this exception, it means that data_pt is # outside the range of values in index_data. if outside_returns_none: return None else: if data_pt < index_data[0]: return 0 else: return len(index_data) - 1 if threshold == 0.0: # Don't do any threshold testing return ndx x = index_data[ndx] y = value_data[ndx] if isnan(x) or isnan(y): return None # transform x,y in a 1x2 array, which is the preferred format of # map_screen. this makes it robust against differences in # the map_screen methods of logmapper and linearmapper # when passed a scalar xy = array([[x, y]]) sx, sy = self.map_screen(xy).T if index_only and (threshold == 0.0 or screen_pt[0] - sx < threshold): return ndx elif ((screen_pt[0] - sx)**2 + (screen_pt[1] - sy)**2 < threshold * threshold): return ndx else: return None
class Base2DPlot(AbstractPlotRenderer): """ Base class for 2-D plots. """ #------------------------------------------------------------------------ # Data-related traits #------------------------------------------------------------------------ # The data source to use for the index coordinate. index = Instance(GridDataSource) # The data source to use as value points. value = Instance(ImageData) # Screen mapper for 2-D structured (gridded) index data. index_mapper = Instance(GridMapper) # Convenience property for accessing the data range of the mapper. index_range = Property # Convenience property for accessing the plots labels. labels = Property # The direction that the first array returned by self.index.get_data() # maps to. # # * 'h': index maps to x-direction # * 'v': index maps to y-direction orientation = Enum("h", "v") # Overrides PlotComponent; 2-D plots draw on the 'image' layer, # underneath all decorations and annotations, and above only the background # fill color. draw_layer = "image" # Convenience property for accessing the x-direction mappers regardless # of orientation. This provides compatibility with a number of tools. x_mapper = Property # Convenience property for accessing the y-direction mappers regardless # of orientation. This provides compatibility with a number of tools. y_mapper = Property # Overall alpha value of the image. Ranges from 0.0 for transparent to 1.0 # for full intensity. alpha = Trait(1.0, Range(0.0, 1.0)) # Event fired when the index data changes. Subclasses can listen for this # event and take appropriate steps (except for requesting a redraw, which # is done in this class). index_data_changed = Event # Event fired when the index mapper changes. Subclasses can listen for this # event and take appropriate steps (except for requesting a redraw, which # is done in this class). index_mapper_changed = Event # Event fired when the value data changes. Subclasses can listen for this # event and take appropriate steps (except for requesting a redraw, which # is done in this class). value_data_changed = Event #------------------------------------------------------------------------ # Public methods #------------------------------------------------------------------------ def __init__(self, **kwargs): # Handling the setting/initialization of these traits manually because # they should be initialized in a certain order. kwargs_tmp = {"trait_change_notify": False} for trait_name in ("index", "value"): if trait_name in kwargs: kwargs_tmp[trait_name] = kwargs.pop(trait_name) self.set(**kwargs_tmp) super(Base2DPlot, self).__init__(**kwargs) if self.index is not None: self.index.on_trait_change(self._update_index_data, "data_changed") if self.index_mapper: self.index_mapper.on_trait_change(self._update_index_mapper, "updated") if self.value is not None: self.value.on_trait_change(self._update_value_data, "data_changed") # If we are not resizable, we will not get a bounds update upon layout, # so we have to manually update our mappers if self.resizable == "": self._update_mappers() return #------------------------------------------------------------------------ # AbstractPlotRenderer interface #------------------------------------------------------------------------ def map_screen(self, data_pts): """ Maps an array of data points into screen space and returns it as an array. Implements the AbstractPlotRenderer interface. """ # data_pts is Nx2 array if len(data_pts) == 0: return [] return asarray(self.index_mapper.map_screen(data_pts)) def map_data(self, screen_pts): """ Maps a screen space point into the "index" space of the plot. Implements the AbstractPlotRenderer interface. """ return self.index_mapper.map_data(screen_pts) def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, index_only=False): """ Maps a screen space point to an index into the plot's index arrays. Implements the AbstractPlotRenderer interface. The *index_only* parameter is ignored because the index is intrinsically 2-D. """ if self.orientation == 'h': x_pt,y_pt = self.map_data([screen_pt])[0] else: x_pt,y_pt = self.map_data([(screen_pt[1],screen_pt[0])])[0] if ((x_pt < self.index_mapper.range.low[0]) or (x_pt > self.index_mapper.range.high[0]) or (y_pt < self.index_mapper.range.low[1]) or (y_pt > self.index_mapper.range.high[1])) and outside_returns_none: return None, None x_index_data, y_index_data = self.index.get_data() if x_index_data.get_size() == 0 or y_index_data.get_size() == 0: return None, None # attempt to map to the x index x_data = x_index_data.get_data() y_data = y_index_data.get_data() try: x_ndx = reverse_map_1d(x_data, x_pt, self.index.sort_order[0], floor_only=True) except IndexError, e: if outside_returns_none: return None, None # x index if x_pt < x_data[0]: x_ndx = 0 else: x_ndx = len(x_data) - 1 try: y_ndx = reverse_map_1d(y_data, y_pt, self.index.sort_order[1], floor_only=True) except IndexError, e: if outside_returns_none: return None, None # y index if y_pt < y_data[0]: y_ndx = 0 else: y_ndx = len(y_data) - 1
def hittest(self, screen_pt, threshold=7.0, return_distance = False): """ Tests whether the given screen point is within *threshold* pixels of any data points on the line. If so, then it returns the (x,y) value of a data point near the screen point. If not, then it returns None. """ # First, check screen_pt is directly on a point in the lineplot ndx = self.map_index(screen_pt, threshold) if ndx is not None: # screen_pt is one of the points in the lineplot data_pt = (self.index.get_data()[ndx], self.value.get_data()[ndx]) if return_distance: scrn_pt = self.map_screen(data_pt) dist = sqrt((screen_pt[0] - scrn_pt[0])**2 + (screen_pt[1] - scrn_pt[1])**2) return (data_pt[0], data_pt[1], dist) else: return data_pt else: # We now must check the lines themselves # Must check all lines within threshold along the major axis, # so determine the bounds of the region of interest in dataspace if self.orientation == "h": dmax = self.map_data((screen_pt[0]+threshold, screen_pt[1])) dmin = self.map_data((screen_pt[0]-threshold, screen_pt[1])) else: dmax = self.map_data((screen_pt[0], screen_pt[1]+threshold)) dmin = self.map_data((screen_pt[0], screen_pt[1]-threshold)) xmin, xmax = self.index.get_bounds() # Now compute the bounds of the region of interest as indexes if dmin < xmin: ndx1 = 0 elif dmin > xmax: ndx1 = len(self.value.get_data())-1 else: ndx1 = reverse_map_1d(self.index.get_data(), dmin, self.index.sort_order) if dmax < xmin: ndx2 = 0 elif dmax > xmax: ndx2 = len(self.value.get_data())-1 else: ndx2 = reverse_map_1d(self.index.get_data(), dmax, self.index.sort_order) start_ndx = max(0, min(ndx1-1, ndx2-1,)) end_ndx = min(len(self.value.get_data())-1, max(ndx1+1, ndx2+1)) # Compute the distances to all points in the range of interest start = array([ self.index.get_data()[start_ndx:end_ndx], self.value.get_data()[start_ndx:end_ndx] ]) end = array([ self.index.get_data()[start_ndx+1:end_ndx+1], self.value.get_data()[start_ndx+1:end_ndx+1] ]) # Convert to screen points s_start = transpose(self.map_screen(transpose(start))) s_end = transpose(self.map_screen(transpose(end))) # t gives the parameter of the closest point to screen_pt # on the line going from s_start to s_end t = _closest_point(screen_pt, s_start, s_end) # Restrict to points on the line segment s_start->s_end t = clip(t, 0, 1) # Gives the corresponding point on the line px, py = _t_to_point(t, s_start, s_end) # Calculate distances dist = sqrt((px - screen_pt[0])**2 + (py - screen_pt[1])**2) # Find the minimum n = argmin(dist) # And return if it is good if dist[n] <= threshold: best_pt = self.map_data((px[n], py[n]), all_values=True) if return_distance: return [best_pt[0], best_pt[1], dist[n]] else: return best_pt return None