# python – Getting coordinates of the closest data point on matplotlib plot-ThrowExceptions

Exception or error:

I am using `matplotlib` with `NavigationToolbar2QT`. The toolbar is showing the position of the cursor. But I would like that the cursor snaps to the nearest data point (when close enough) or simply show the coordinate of nearest data point. Can that be somehow arranged?

How to solve:

If you are working with large sets of points, I advice you to use `CKDtrees`:

``````import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial

points = np.column_stack([np.random.rand(50), np.random.rand(50)])
fig, ax = plt.subplots()
coll = ax.scatter(points[:,0], points[:,1])
ckdtree = scipy.spatial.cKDTree(points)
``````

I refactored `kpie's` answer here little bit. Once `ckdtree` is created, you can identify closest points instantly and various kind of information about them with a little effort:

``````def closest_point_distance(ckdtree, x, y):
#returns distance to closest point
return ckdtree.query([x, y])[0]

def closest_point_id(ckdtree, x, y):
#returns index of closest point
return ckdtree.query([x, y])[1]

def closest_point_coords(ckdtree, x, y):
# returns coordinates of closest point
return ckdtree.data[closest_point_id(ckdtree, x, y)]
# ckdtree.data is the same as points
``````

Interactive display of cursor position.
If you want coordinates of the closest point to be displayed on Navigation Toolbar:

``````def val_shower(ckdtree):
#formatter of coordinates displayed on Navigation Bar
return lambda x, y: '[x = {}, y = {}]'.format(*closest_point_coords(ckdtree, x, y))

plt.gca().format_coord = val_shower(ckdtree)
plt.show()
``````

Using events.
If you want another kind of interactivity, you can use events:

``````def onclick(event):
if event.inaxes is not None:
print(closest_point_coords(ckdtree, event.xdata, event.ydata))

fig.canvas.mpl_connect('motion_notify_event', onclick)
plt.show()
``````

The following code will print the coordinates of the dot closest to the mouse when you click.

``````import matplotlib.pyplot as plt
import numpy as np
np.random.seed(19680801)
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
fig,ax = plt.subplots()
plt.scatter(x, y)
points = list(zip(x,y))
def distance(a,b):
return(sum([(k[0]-k[1])**2 for k in zip(a,b)])**0.5)
def onclick(event):
dists = [distance([event.xdata, event.ydata],k) for k in points]
print(points[dists.index(min(dists))])
fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
``````

You could subclass `NavigationToolbar2QT` and override the `mouse_move` handler. The `xdata` and `ydata` attributes contain the current mouse position in plot coordinates. You can snap that to the closest data point before passing the event to the base class `mouse_move` handler.

Full example, with highlighting of the closest point in the plot as a bonus:

``````import sys

import numpy as np

from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.figure import Figure

class Snapper:
"""Snaps to data points"""

def __init__(self, data, callback):
self.data = data
self.callback = callback

def snap(self, x, y):
pos = np.array([x, y])
distances = np.linalg.norm(self.data - pos, axis=1)
dataidx = np.argmin(distances)
datapos = self.data[dataidx,:]
self.callback(datapos[0], datapos[1])
return datapos

def __init__(self, canvas, parent, coordinates=True):
super().__init__(canvas, parent, coordinates)
self.snapper = None

def set_snapper(self, snapper):
self.snapper = snapper

def mouse_move(self, event):
if self.snapper and event.xdata and event.ydata:
event.xdata, event.ydata = self.snapper.snap(event.xdata, event.ydata)
super().mouse_move(event)

class Highlighter:
def __init__(self, ax):
self.ax = ax
self.marker = None
self.markerpos = None

def draw(self, x, y):
"""draws a marker at plot position (x,y)"""
if (x, y) != self.markerpos:
if self.marker:
self.marker.remove()
del self.marker
self.marker = self.ax.scatter(x, y, color='yellow')
self.markerpos = (x, y)
self.ax.figure.canvas.draw()

class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
canvas = FigureCanvas(Figure(figsize=(5,3)))

data = np.random.randn(100, 2)
ax = canvas.figure.subplots()
ax.scatter(data[:,0], data[:,1])

self.highlighter = Highlighter(ax)
snapper = Snapper(data, self.highlighter.draw)
toolbar.set_snapper(snapper)

if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
``````