Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent invisible artist annotation #104

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
24 changes: 24 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,30 @@ As an example (This also demonstrates using the ``display='multiple'`` kwarg)::
:align: center
:target: https://github.com/joferkington/mpldatacursor/blob/master/examples/draggable_example.py

Magnetic Cursors
----------------
If ``magnetic=True`` is specified, the annotations will only appear on actual data points and not the interpolated lines connecting them. This works with Line Charts and other artists which has x and y attributes. For other artists, ``magnetic`` has no effect.

As an example (This also demonstrates using the ``display='multiple'`` and ``draggable='True'`` kwarg)::

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()

.. image:: https://raw.githubusercontent.com/siriuspal/mpldatacursor/images/draggable_magnetic_example.png
:align: center
:target: https://github.com/siriuspal/mpldatacursor/blob/master/examples/draggable_magnetic_example.py

Further Customization
---------------------
Additional keyword arguments to ``datacursor`` are passed on to ``annotate``.
Expand Down
19 changes: 19 additions & 0 deletions examples/draggable_magnetic_example.py
Original file line number Diff line number Diff line change
@@ -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()
32 changes: 28 additions & 4 deletions mpldatacursor/datacursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
"""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()):
Expand All @@ -717,10 +737,14 @@ def contains(artist, event):
for artist in self.artists:
fixed_event = event_axes_data(event, artist.axes)
inside, info = contains(artist, fixed_event)
if inside:
if inside and artist.get_visible():
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
Expand Down