I'm using matplotlib in python to build a scatter plot.
suppose I have the following 2 data lists.
X=[1,2,3,4,5]
Y=[6,7,8,9,10]
then I use X as the X-axis value and Y as the Y-axis value to make a scatter plot. So I will have a picture with 5 scattering points on it, right?
Now the question: is it possible to build connection for these 5 points with the actual data. For example, when I click on one of these 5 points, it can tell me what original data I have used to make this point?
thanks in advance
Using a slightly modified version of Joe Kington's DataCursor:
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import matplotlib.cbook as cbook
import numpy as np
def fmt(x, y):
return 'x: {x:0.2f}\ny: {y:0.2f}'.format(x = x, y = y)
class DataCursor(object):
# https://stackoverflow.com/a/4674445/190597
"""A simple data cursor widget that displays the x,y location of a
matplotlib artist when it is selected."""
def __init__(self, artists, x = [], y = [], tolerance = 5, offsets = (-20, 20),
formatter = fmt, display_all = False):
"""Create the data cursor and connect it to the relevant figure.
"artists" is the matplotlib artist or sequence of artists that will be
selected.
"tolerance" is the radius (in points) that the mouse click must be
within to select the artist.
"offsets" is a tuple of (x,y) offsets in points from the selected
point to the displayed annotation box
"formatter" is a callback function which takes 2 numeric arguments and
returns a string
"display_all" controls whether more than one annotation box will
be shown if there are multiple axes. Only one will be shown
per-axis, regardless.
"""
self._points = np.column_stack((x,y))
self.formatter = formatter
self.offsets = offsets
self.display_all = display_all
if not cbook.iterable(artists):
artists = [artists]
self.artists = artists
self.axes = tuple(set(art.axes for art in self.artists))
self.figures = tuple(set(ax.figure for ax in self.axes))
self.annotations = {}
for ax in self.axes:
self.annotations[ax] = self.annotate(ax)
for artist in self.artists:
artist.set_picker(tolerance)
for fig in self.figures:
fig.canvas.mpl_connect('pick_event', self)
def annotate(self, ax):
"""Draws and hides the annotation box for the given axis "ax"."""
annotation = ax.annotate(self.formatter, xy = (0, 0), ha = 'right',
xytext = self.offsets, textcoords = 'offset points', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')
)
annotation.set_visible(False)
return annotation
def snap(self, x, y):
"""Return the value in self._points closest to (x, y).
"""
idx = np.nanargmin(((self._points - (x,y))**2).sum(axis = -1))
return self._points[idx]
def __call__(self, event):
"""Intended to be called through "mpl_connect"."""
# Rather than trying to interpolate, just display the clicked coords
# This will only be called if it's within "tolerance", anyway.
x, y = event.mouseevent.xdata, event.mouseevent.ydata
annotation = self.annotations[event.artist.axes]
if x is not None:
if not self.display_all:
# Hide any other annotation boxes...
for ann in self.annotations.values():
ann.set_visible(False)
# Update the annotation in the current axis..
x, y = self.snap(x, y)
annotation.xy = x, y
annotation.set_text(self.formatter(x, y))
annotation.set_visible(True)
event.canvas.draw()
x=[1,2,3,4,5]
y=[6,7,8,9,10]
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
scat = ax.scatter(x, y)
DataCursor(scat, x, y)
plt.show()
yields
You can click on any of the points and the balloon will show the underlying data values.
My slight modification to the DataCursor was to add the snap method, which ensures that the data point displayed came from the original data set, rather than the location where the mouse actually clicked.
If you have scipy installed, you might prefer this version of the Cursor, which makes the balloon follow the mouse (without clicking):
import datetime as DT
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import numpy as np
import scipy.spatial as spatial
def fmt(x, y, is_date):
if is_date:
x = mdates.num2date(x).strftime("%Y-%m-%d")
return 'x: {x}\ny: {y}'.format(x=x, y=y)
else:
return 'x: {x:0.2f}\ny: {y:0.2f}'.format(x=x, y=y)
class FollowDotCursor(object):
"""Display the x,y location of the nearest data point."""
def __init__(self, ax, x, y, tolerance=5, formatter=fmt, offsets=(-20, 20)):
try:
x = np.asarray(x, dtype='float')
self.is_date = False
except (TypeError, ValueError):
x = np.asarray(mdates.date2num(x), dtype='float')
self.is_date = True
y = np.asarray(y, dtype='float')
self._points = np.column_stack((x, y))
self.offsets = offsets
self.scale = x.ptp()
self.scale = y.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(
[x.min()], [y.min()], s=130, color='green', alpha=0.7)
self.annotation = self.setup_annotation()
plt.connect('motion_notify_event', self)
def scaled(self, points):
points = np.asarray(points)
return points * (self.scale, 1)
def __call__(self, event):
ax = self.ax
# event.inaxes is always the current axis. If you use twinx, ax could be
# a different axis.
if event.inaxes == ax:
x, y = event.xdata, event.ydata
elif event.inaxes is None:
return
else:
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.is_date))
self.dot.set_offsets((x, y))
bbox = ax.viewLim
event.canvas.draw()
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]
x = [DT.date.today()+DT.timedelta(days=i) for i in [10,20,30,40,50]]
y = [6,7,8,9,10]
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.scatter(x, y)
cursor = FollowDotCursor(ax, x, y)
fig.autofmt_xdate()
plt.show()
Related
I'm doing a Python program to fit Bezier curves to the eye contour (for example one curve that is adapted to the bottom line of the brow and the other to the lid's contour ). In order to achieve this, I'm trying to put the image of the eye as the background of a plot, and then interactive draw a line with the mousse that fits the best. My problem is that I need an arc and not a circle as I have done but I don't know how to solve this.
My initial image and the circle I have to fit but I only want an arc. The line fits the brow after using my mouse (the pink part is what I want). It's important to say I have to convert this arc or line into a Bézier curve, I have a program that does this function but It will be better If I could directly draw a Bezier line in the image but I don't know-how so the main problem is to have an arc or line but nos a circle because I need an open curve. I have to use the Bezier lines because I have to calculate some medical parameters with them, so I need their coordinates.
import numpy as np
import matplotlib
import shapely
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from matplotlib.lines import Line2D
from matplotlib.artist import Artist
from matplotlib.bezier import find_control_points
class PolygonInteractor(object):
"""
A polygon editor.
https://matplotlib.org/gallery/event_handling/poly_editor.html
Key-bindings
't' toggle vertex markers on and off. When vertex markers are on,
you can move them, delete them
'd' delete the vertex under point
'i' insert a vertex at point. You must be within epsilon of the
line connecting two existing vertices
"""
showverts = True
epsilon = 5 # max pixel distance to count as a vertex hit
def __init__(self, ax, poly, visible=False):
if poly.figure is None:
raise RuntimeError('You must first add the polygon to a figure '
'or canvas before defining the interactor')
self.ax = ax
canvas = poly.figure.canvas
self.poly = poly
self.poly.set_visible(visible)
x, y = zip(*self.poly.xy)
self.line = Line2D(x, y, ls="",
marker='*', linewidth=1, markerfacecolor='green',
animated=True)
self.ax.add_line(self.line)
self.cid = self.poly.add_callback(self.poly_changed)
self._ind = None # the active vert
canvas.mpl_connect('draw_event', self.draw_callback)
canvas.mpl_connect('button_press_event', self.button_press_callback)
canvas.mpl_connect('key_press_event', self.key_press_callback)
canvas.mpl_connect('button_release_event', self.button_release_callback)
canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)
self.canvas = canvas
x,y = self.interpolate()
self.line2 = Line2D(x, y, animated=True)
self.ax.add_line(self.line2)
def interpolate(self):
x, y = self.poly.xy[:].T
i = np.arange(len(x))
interp_i = np.linspace(0, i.max(), 100 * i.max())
xi = interp1d(i, x, kind='cubic')(interp_i)
yi = interp1d(i, y, kind='cubic')(interp_i)
return xi,yi
def draw_callback(self, event):
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.draw_artist(self.poly)
self.ax.draw_artist(self.line)
self.ax.draw_artist(self.line2)
# do not need to blit here, this will fire before the screen is
# updated
def poly_changed(self, poly):
'this method is called whenever the polygon object is called'
# only copy the artist props to the line (except visibility)
vis = self.line.get_visible()
Artist.update_from(self.line, poly)
self.line.set_visible(vis) # don't use the poly visibility state
def get_ind_under_point(self, event):
'get the index of the vertex under point if within epsilon tolerance'
# display coords
xy = np.asarray(self.poly.xy)
xyt = self.poly.get_transform().transform(xy)
xt, yt = xyt[:, 0], xyt[:, 1]
d = np.hypot(xt - event.x, yt - event.y)
indseq, = np.nonzero(d == d.min())
ind = indseq[0]
if d[ind] >= self.epsilon:
ind = None
return ind
def button_press_callback(self, event):
'whenever a mouse button is pressed'
if not self.showverts:
return
if event.inaxes is None:
return
if event.button != 1:
return
self._ind = self.get_ind_under_point(event)
def button_release_callback(self, event):
'whenever a mouse button is released'
if not self.showverts:
return
if event.button != 1:
return
self._ind = None
def key_press_callback(self, event):
'whenever a key is pressed'
if not event.inaxes:
return
if event.key == 't':
self.showverts = not self.showverts
self.line.set_visible(self.showverts)
if not self.showverts:
self._ind = None
elif event.key == 'd':
ind = self.get_ind_under_point(event)
if ind is not None:
self.poly.xy = np.delete(self.poly.xy,
ind, axis=0)
self.line.set_data(zip(*self.poly.xy))
elif event.key == 'i':
xys = self.poly.get_transform().transform(self.poly.xy)
p = event.x, event.y # display coords
for i in range(len(xys) - 1):
s0 = xys[i]
s1 = xys[i + 1]
d = dist_point_to_segment(p, s0, s1)
if d <= self.epsilon:
self.poly.xy = np.insert(
self.poly.xy, i+1,
[event.xdata, event.ydata],
axis=0)
self.line.set_data(zip(*self.poly.xy))
break
if self.line.stale:
self.canvas.draw_idle()
def motion_notify_callback(self, event):
'on mouse movement'
if not self.showverts:
return
if self._ind is None:
return
if event.inaxes is None:
return
if event.button != 1:
return
x, y = event.xdata, event.ydata
self.poly.xy[self._ind] = x, y
if self._ind == 0:
self.poly.xy[-1] = x, y
elif self._ind == len(self.poly.xy) - 1:
self.poly.xy[0] = x, y
self.line.set_data(zip(*self.poly.xy))
x,y = self.interpolate()
self.line2.set_data(x,y)
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.poly)
self.ax.draw_artist(self.line)
self.ax.draw_artist(self.line2)
self.canvas.blit(self.ax.bbox)
if __name__ == '__main__':
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
theta = np.arange(0, 2*np.pi, 0.1)
r = 1.5
xs = r*np.cos(theta)
ys = r*np.sin(theta)
xs = (921, 951, 993, 1000)
ys = (1181, 1230, 1243, 257)
poly = Polygon(list(zip(xs, ys)), animated=True)
#poly = LineString([(0, 0), (1, 1)])
img = plt.imread("/Users/raquel/Desktop/TFG/IMÁGENES/Unknown.jpeg")
fig, ax = plt.subplots()
img = ax.imshow(img, extent=[0, 1300, 0, 1300])
ax.add_patch(poly)
p = PolygonInteractor(ax, poly, visible=False)
ax.set_title('Click and drag a point to move it')
x = ax.set_xlim((0, 1300))
y = ax.set_ylim((0, 1300))
plt.show()
I have a data curve displayed in a matplotlib figure. I would like to attach a text box to the mouse cursor. That is, as the mouse cursor is moved around in the figure, the text box is moved with it. Also, I would like to be able to update the text in the text box as the cursor with attached text box is moved.
I started with the matplotlib example at https://matplotlib.org/gallery/misc/cursor_demo_sgskip.html but was unsuccessful in modification of it for my purpose. I also looked at some 3rd party packages (e.g. mpldatacursor and mplcursors); but, these did not seem appropriate for my application.
Here is some code that I was experimenting with that should illustrate what I am trying to accomplish.
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class Cursor(object):
"""
Purpose: Define a cursor whose interesection will track points along a curve
"""
def __init__(self, ax):
self.ax = ax
self.lx = ax.axhline(color='k',linewidth=0.25) # the horiz line
self.ly = ax.axvline(color='k',linewidth=0.25) # the vert line
# Text location in axes coords
self.txt = ax.text(0.7, 0.9, '', transform=ax.transAxes)
def mouse_move(self, event):
'''
Purpose: respond to movement of the mouse
'''
if not event.inaxes: return
x, y = event.xdata, event.ydata
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.ax.text(x, y, 'test', fontsize=8, bbox=props)
#self.ax.text(x,y,'')
#self.text(x, y, 'test', fontsize=8, bbox=props)
#ax.text(x, y, 'test', fontsize=8, bbox=props)
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.text(x,y,'')
plt.draw()
class SnaptoCursor(object):
"""
Like Cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.x = x
self.y = y
# Text location in axes coords
self.txt = ax.text(0.7, 0.9, '', transform=ax.transAxes)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.ax.text(x, y, 'test', fontsize=8, bbox=props)
#self.text(x, y, 'test', fontsize=8, bbox=props)
#ax.text(x, y, 'test', fontsize=8, bbox=props)
#self.ax.text(remove)
# Find closest pt on data curve to (x,y) of cross-air intersection
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
# place a text box in upper left in axes coords
#self.ax.text(x, y, 'test', transform=ax.transAxes, fontsize=8,
# verticalalignment='top', bbox=props)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
print('x=%1.2f, y=%1.2f' % (x, y))
plt.draw()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14,7.5))
fig.canvas.set_window_title('TS with tracking cursor')
# Need the following to set position the plot
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((20,20)) # pixels offset from top left corner of display
# cursor = Cursor(ax)
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
The text box does "stick" with the mouse cursor but it is not erased when the cursor is moved --- this is what needs to be solved. One small step in the right direction
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class SnaptoCursor(object):
"""
Like Cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.tx = ax.text(0.0,0.0,'test') # the text to follow cursor
self.x = x
self.y = y
# Text location in axes coords
self.txt = ax.text(0.7, 0.9, '', transform=ax.transAxes)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
self.tx.set_position((x,y))
# Find closest pt on data curve to (x,y) of cross-air intersection
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
print('x=%1.2f, y=%1.2f' % (x, y))
plt.draw()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14,7.5))
fig.canvas.set_window_title('TS with tracking cursor')
# Need the following to set position the plot
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((20,20)) # pixels offset from top left corner of display
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
This code will move the text with the cursor and erase previous text. However, I am still unable to change the text as the cursor is moved! Any suggestions would be appreciated :-)
You were creating a new Text object everytime you moved the mouse. You need to create the object during __init__ and then simply update its position/text when the mouse is moved:
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class Cursor(object):
"""
Purpose: Define a cursor whose interesection will track points along a curve
"""
def __init__(self, ax):
self.ax = ax
self.lx = ax.axhline(color='k',linewidth=0.25) # the horiz line
self.ly = ax.axvline(color='k',linewidth=0.25) # the vert line
# Text location in data coords
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.txt = self.ax.text(0, 0, '', fontsize=8, bbox=props)
def mouse_move(self, event):
'''
Purpose: respond to movement of the mouse
'''
if not event.inaxes: return
x, y = event.xdata, event.ydata
self.txt.set_position((x,y))
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
plt.draw()
class SnaptoCursor(object):
"""
Like Cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.x = x
self.y = y
# Text location in data coords
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.txt = self.ax.text(0, 0, '', fontsize=8, bbox=props)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
self.txt.set_position((x,y))
# Find closest pt on data curve to (x,y) of cross-air intersection
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
# place a text box in upper left in axes coords
#self.ax.text(x, y, 'test', transform=ax.transAxes, fontsize=8,
# verticalalignment='top', bbox=props)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
print('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw_idle()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14,7.5))
fig.canvas.set_window_title('TS with tracking cursor')
# cursor = Cursor(ax)
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
Here is a slightly edited version of Diziet Asahithat's code that answers my question:
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class SnaptoCursor(object):
"""
Like normal cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.x = x
self.y = y
# Text location in data coords (this is required!)
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.txt = self.ax.text(0, 0, '', fontsize=8, bbox=props)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs along with the text box
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
# Update the position of the text in the box attached to the cursor
self.txt.set_position((x+0.02,y)) # place the text in the box
# Place the center of the cross-hairs
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
# Place text in the text box
self.txt.set_text('Test\n x=%1.2f, y=%1.2f' % (x, y))
#print('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw_idle()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14.5,7.2))
ax.set_ylim(-1,+1)
fig.canvas.set_window_title('TS with tracking cursor')
# Need the following to set position the plot
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((20,20)) # pixels offset from top left corner of display
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
Thanks very much for your efforts DA :-)
I'm trying to shade over a map to show "explored regions" of the dot as it moves around using FuncAnimation. This is the code I have so far:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.animation as animation
import numpy as np
import random
import scipy.stats as stats
map_x = 100
map_y = 100
fig = plt.figure(0)
plt.figure(0)
ax1 = plt.subplot2grid((2,3), (0,0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid((2,3), (0,2), colspan=1)
ax1.set_xlim([0, map_x])
ax1.set_ylim([0, map_y])
ax2.set_xlim([0, map_x])
ax2.set_ylim([0, map_y])
agent = plt.Circle((50, 1), 2, fc='r')
agent2 = plt.Circle((50, 1), 2, fc='r')
agents = [agent, agent2]
ax1.add_patch(agent)
ax2.add_patch(agent2)
def animate(i):
x, y = agent.center
x = x+.1
y = y+.1
agent.center = (x, y)
agent2.center = (x, y)
return agent,
def fillMap(x, y):
circle=plt.Circle((x,y), 4, fc='b')
ax2.add_patch(circle)
def animate2(i):
x, y = agent2.center
x = x+.1
y = y+.1
agent2.center = (x, y)
fillMap(x, y)
return agent2,
anim = animation.FuncAnimation(fig, animate, frames=200, interval=20, blit=True)
anim2 = animation.FuncAnimation(fig, animate2, frames=200, interval=20, blit=True)
plt.show()
However, it only goes into fillMap once, and only draws the blue filled in circle once, instead of everywhere where the red dot goes in the smaller subplot.
If you want the circle you added to persist on the screen you probably shouldn't use blitting. Not using blitting makes the animation slower, but it may be enough to draw a new blue circle every 20th step or so.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.animation as animation
import numpy as np
import scipy.stats as stats
map_x = 100
map_y = 100
fig = plt.figure(0)
plt.figure(0)
ax1 = plt.subplot2grid((2,3), (0,0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid((2,3), (0,2), colspan=1)
ax1.set_xlim([0, map_x])
ax1.set_ylim([0, map_y])
ax2.set_xlim([0, map_x])
ax2.set_ylim([0, map_y])
agent = plt.Circle((50, 1), 2, fc='r')
agent2 = plt.Circle((50, 1), 2, fc='r')
agents = [agent, agent2]
ax1.add_patch(agent)
ax2.add_patch(agent2)
def fillMap(x, y):
circle=plt.Circle((x,y), 4, fc='b')
ax2.add_patch(circle)
def animate2(i):
x, y = agent.center
x = x+.1
y = y+.1
agent.center = (x, y)
x, y = agent2.center
x = x+.1
y = y+.1
agent2.center = (x, y)
if i%20==0:
circle = fillMap(x, y)
anim2 = animation.FuncAnimation(fig, animate2, frames=200, interval=20, blit=False)
plt.show()
In case you want to use blitting, consider using a line to mark the region where the circle has been.
line, =ax2.plot([],[], lw=3, color="b")
xl = []; yl=[]
def fillMap(x, y):
xl.append(x); yl.append(y)
line.set_data(xl,yl)
return line
def animate2(i):
x, y = agent.center
x = x+.1
y = y+.1
agent.center = (x, y)
x, y = agent2.center
x = x+.1
y = y+.1
agent2.center = (x, y)
if i%20==0:
fillMap(x, y)
return agent, agent2, line
anim2 = animation.FuncAnimation(fig, animate2, frames=200, interval=20, blit=True)
The first block of code in this answer allows the user to generate a matplotlib figure and by clicking on the graph, it is possible to display the x- and y- coordinates of the graph after each click. How can I save these coordinates to 5 decimal places, say, into a numpy array (X for the x-coordinates and Y for the y-coordinates)? I'm not really sure how to start this (and it's probably trivial), but here is the code:
import numpy as np
import matplotlib.pyplot as plt
X = []
Y = []
class DataCursor(object):
text_template = 'x: %0.2f\ny: %0.2f'
x, y = 0.0, 0.0
xoffset, yoffset = -20, 20
text_template = 'x: %0.2f\ny: %0.2f'
def __init__(self, ax):
self.ax = ax
self.annotation = ax.annotate(self.text_template,
xy=(self.x, self.y), xytext=(self.xoffset, self.yoffset),
textcoords='offset points', ha='right', va='bottom',
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')
)
self.annotation.set_visible(False)
def __call__(self, event):
self.event = event
self.x, self.y = event.mouseevent.xdata, event.mouseevent.ydata
if self.x is not None:
self.annotation.xy = self.x, self.y
self.annotation.set_text(self.text_template % (self.x, self.y))
self.annotation.set_visible(True)
event.canvas.draw()
fig = plt.figure()
line, = plt.plot(range(10), 'ro-')
fig.canvas.mpl_connect('pick_event', DataCursor(plt.gca()))
line.set_picker(5) # Tolerance in points
plt.show()
It sounds like you want plt.ginput().
As a quick example:
fig, ax = plt.subplots()
ax.plot(range(10), 'ro-')
points = plt.ginput(n=4)
print points
np.savetxt('yourfilename', points)
plt.show()
I think you can do this by using list members in DataCursor:
def __init__(self, ax):
...
self.mouseX = []
self.mouseY = []
In your call, you would then store the X and Y for each event into these members:
def __call__(self, event):
...
self.mouseX.append(self.x)
self.mouseY.append(self.y)
You would then pass this to mpl_connect like this:
DC = DataCursor(plt.gca())
fig.canvas.mpl_connect('pick_event', DC)
...
print DC.mouseX, DC.mouseY
I have illustrated the principle here, but I don't see why this couldn't be applied to numpy arrays as well.
I am new to matplotlib and I am looking to label stems in a stem plot with x,y co-od when mouse hovers over that point. When I searched everything was meant for scatter plot (Possible to make labels appear when hovering over a point in matplotlib? present code is like this:
def plot_matching(mzs,ints,matching,scan_num):
fig=p1.gcf()
fig.canvas.set_window_title('MS/MS Viewer')
rel_ints=relative_intensity(ints)
p1.xlim(min(mzs)-100,max(mzs)+100)
p1.ylim(min(rel_ints),max(rel_ints)+5)
p1.title('Scan Number:'+scan_num)
p1.xlabel('m/z')
p1.ylabel('Relative intensity')
mzs_rel=zip(mzs,rel_ints)
for x,y in mzs_rel:
x1=[]
y1=[]
x1.append(x)
y1.append(y)
markerline, stemlines, baseline=p1.stem(x1,y1)
p1.setp(markerline, 'Marker', '')
for m in matching:
if x==m[1] and y>3.0:
p1.setp(stemlines, linewidth=2, color='r')
p1.text(x,y,m[0],fontsize=12)
break
else:
p1.setp(stemlines,linewidth=2, color='g')
return p1
Will the scatter plot link for stem plot too?
To make a hovering label, you need to hook up a function to handle motion_notify_events:
plt.connect('motion_notify_event', some_function)
Below is some code showing one way to do it. The hovering label behavior is produced by
cursor = FollowDotCursor(ax, x, y)
where ax is the axis, x and y are lists of coordinates. Since you supply x and y, it does not matter if you are making a line plot or a stem plot or whatever. The labels appear when the mouse is moved near any point (xi, yi).
The code below uses scipy.spatial.cKDTree to locate the nearest point. Here is an older version of this code which does not require scipy.
import matplotlib.pyplot as plt
import scipy.spatial as spatial
import numpy as np
pi = np.pi
cos = np.cos
def fmt(x, y):
return 'x: {x:0.2f}\ny: {y:0.2f}'.format(x=x, y=y)
class FollowDotCursor(object):
"""Display the x,y location of the nearest data point.
https://stackoverflow.com/a/4674445/190597 (Joe Kington)
https://stackoverflow.com/a/13306887/190597 (unutbu)
https://stackoverflow.com/a/15454427/190597 (unutbu)
"""
def __init__(self, ax, x, y, tolerance=5, formatter=fmt, offsets=(-20, 20)):
try:
x = np.asarray(x, dtype='float')
except (TypeError, ValueError):
x = np.asarray(mdates.date2num(x), dtype='float')
y = np.asarray(y, dtype='float')
mask = ~(np.isnan(x) | np.isnan(y))
x = x[mask]
y = y[mask]
self._points = np.column_stack((x, y))
self.offsets = offsets
y = y[np.abs(y-y.mean()) <= 3*y.std()]
self.scale = x.ptp()
self.scale = y.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(
[x.min()], [y.min()], s=130, color='green', alpha=0.7)
self.annotation = self.setup_annotation()
plt.connect('motion_notify_event', self)
def scaled(self, points):
points = np.asarray(points)
return points * (self.scale, 1)
def __call__(self, event):
ax = self.ax
# event.inaxes is always the current axis. If you use twinx, ax could be
# a different axis.
if event.inaxes == ax:
x, y = event.xdata, event.ydata
elif event.inaxes is None:
return
else:
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(np.column_stack((x, y)))
bbox = self.annotation.get_window_extent()
self.fig.canvas.blit(bbox)
self.fig.canvas.draw_idle()
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]
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*pi, 10)
y = cos(x)
markerline, stemlines, baseline = ax.stem(x, y, '-.')
plt.setp(markerline, 'markerfacecolor', 'b')
plt.setp(baseline, 'color','r', 'linewidth', 2)
cursor = FollowDotCursor(ax, x, y, tolerance=20)
plt.show()
There is a great plugin: https://github.com/anntzer/mplcursors
very simple to install. No neet to implement on your own.