From 6890b12a8fef925078e467e20cdac94bfdbea834 Mon Sep 17 00:00:00 2001 From: siriuspal Date: Fri, 12 Feb 2021 09:20:39 +0530 Subject: [PATCH] Added magnetic data cursor --- examples/draggable_magnetic_example.py | 19 ++++++++++++++++ mpldatacursor/datacursor.py | 30 +++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 examples/draggable_magnetic_example.py diff --git a/examples/draggable_magnetic_example.py b/examples/draggable_magnetic_example.py new file mode 100644 index 0000000..3be807f --- /dev/null +++ b/examples/draggable_magnetic_example.py @@ -0,0 +1,19 @@ +""" +This example demonstrates draggable annotation boxes, using the +``display="multiple"`` option and ``magnetic="True"`` option. +Magnetic option will cause pointer to stick to nearest datapoint +instead of anywhere on the line. +""" +import matplotlib.pyplot as plt +import numpy as np +from mpldatacursor import datacursor + +data = np.outer(range(10), range(1, 5)) + +fig, ax = plt.subplots() +ax.set_title('Try clicking in between data points') +ax.plot(data, 'o-') + +datacursor(display='multiple', draggable=True, magnetic=True) + +plt.show() diff --git a/mpldatacursor/datacursor.py b/mpldatacursor/datacursor.py index 88bbaa5..5eb1c91 100644 --- a/mpldatacursor/datacursor.py +++ b/mpldatacursor/datacursor.py @@ -58,7 +58,7 @@ class DataCursor(object): def __init__(self, artists, tolerance=5, formatter=None, point_labels=None, display='one-per-axes', draggable=False, hover=False, props_override=None, keybindings=True, date_format='%x %X', - display_button=1, hide_button=3, keep_inside=True, + display_button=1, hide_button=3, keep_inside=True, magnetic=False, **kwargs): """Create the data cursor and connect it to the relevant figure. @@ -125,6 +125,11 @@ def __init__(self, artists, tolerance=5, formatter=None, point_labels=None, the figure. This option has no effect on draggable datacursors. Defaults to True. Note: Currently disabled on OSX and NbAgg/notebook backends. + magnetic: boolean, optional + Magnetic will attach the cursor only to the data points. + Default is cursor can be added to interpolated lines. + If exact data point is not clicked, nearby data point will be selected. + Works with artists that have x, y attributes. Other plots will ignore Magnetic. **kwargs : additional keyword arguments, optional Additional keyword arguments are passed on to annotate. """ @@ -171,6 +176,7 @@ def filter_artists(artists): self.display = 'single' self.draggable = False + self.magnetic = magnetic self.keep_inside = keep_inside self.tolerance = tolerance self.point_labels = point_labels @@ -704,6 +710,20 @@ def contains(artist, event): else: return False, {} + def magnetic_datapoint_adjustment(event, artist): + """Updates the event coordinates to one of the closest data points""" + + try: + # Get closest data point of x-axis + x = min(artist._x, key=lambda x: abs(x-event.xdata)) + # Identify index of x value in _x and then get y value from _y + y = artist._y[list(artist._x).index(x)] + # If artist do not have x and y attributes, example Image, PathCollection + except AttributeError: + pass + else: + event.xdata, event.ydata = x, y + # If we're on top of an annotation box, hide it if right-clicked or # do nothing if we're in draggable mode for anno in list(self.annotations.values()): @@ -719,8 +739,12 @@ def contains(artist, event): inside, info = contains(artist, fixed_event) if inside: fig = artist.figure - new_event = PickEvent('pick_event', fig.canvas, fixed_event, - artist, **info) + + # If magnetic is True, update event to closest data points + if self.magnetic: + magnetic_datapoint_adjustment(fixed_event, artist) + + new_event = PickEvent('pick_event', fig.canvas, fixed_event, artist, **info) self(new_event) # Only fire a single pick event for one mouseevent. Otherwise