from matplotlib.widgets import MultiCursor
import scipy.spatial as spatial
import numpy as np
def fmt(x, y):
return 'x: {x:0.2f}\ny: {y:0.2f}'.format(x=x, y=y)
class FollowDotCursor(object):
def __init__(self, canvas, ax, df, tolerance=5, formatter=fmt, offsets=(20, 20)):
self.offsets = offsets
self.df = df
self._points = np.column_stack((df['Time'], df['A']))
self.scale = df['Time'].ptp()
self.scale = df['A'].ptp() / self.scale if self.scale else 1
self.tree = spatial.cKDTree(self.scaled(self._points))
self.formatter = formatter
self.tolerance = tolerance
self.ax = ax
self.fig = ax.figure
self.ax.xaxis.set_label_position('top')
self.dot = ax.scatter(
[df['Time'].min()], [df['A'].min()], s=130, color='green', alpha=0.7)
self.annotation = self.setup_annotation()
self.cid = self.fig.canvas.mpl_connect('motion_notify_event', self)
axs = list()
axs.append(ax)
self.cursor = MultiCursor(canvas, axs, lw=1, ls='--')
self.up_points = None
def __call__(self, event):
ax = self.ax
inv = ax.transData.inverted()
x, y = inv.transform([(event.x, event.y)]).ravel()
annotation = self.annotation
x, y = self.snap(x, y)
annotation.xy = x, y
annotation.set_text(self.formatter(x, y))
self.dot.set_offsets((x, y))
event.canvas.draw()
def scaled(self, points):
if len(points) == 2:
time = points[0]
xy = self.df[self.df["Time"] <= time]
dy = self.df[self.df["Time"] > time]
if xy.empty or dy.empty:
return self.up_points
d_min = xy['Time'].max()
d_max = dy['Time'].min()
pi = (d_min + d_max) / 2.0
if time <= pi:
aa = self.df["A"][self.df["Time"] == d_min]
points = (d_min, aa)
else:
aa = self.df["A"][self.df["Time"] == d_max]
points = (d_max, aa)
self.up_points = points
return points
points = np.asarray(points)
return points * (self.scale, 1)
def setup_annotation(self):
"""Draw and hide the annotation box."""
annotation = self.ax.annotate(
'', xy=(0, 0), ha='right',
xytext=self.offsets, textcoords='offset points',
va='bottom',
bbox=dict(
boxstyle='round,pad=0.5', fc='yellow', alpha=0.75),
arrowprops=dict(
arrowstyle='->', connectionstyle='arc3,rad=0'))
return annotation
def snap(self, x, y):
"""Return the value in self.tree closest to x, y."""
dist, idx = self.tree.query(self.scaled((x, y)), k=1, p=1)
try:
return self._points[idx]
except IndexError:
# IndexError: index out of bounds
return self._points[0]
评论1
最新资源