Get data from Python plot with matplotlib then save to array - python

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.

Related

Display y coordinate values with interactive cursor crosshairs in Python (matplotlib) [duplicate]

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

Shading over plot with funcanimation

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)

Possible to make labels appear when hovering over a point in matplotlib in stem plot?

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.

Get data from plot with matplotlib

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

Animating 3d scatterplot in matplotlib

I'm trying to get a 3d animation of a scatterplot in matplotlib, based off the 2d scatterplot animation posted here and the 3d line plot posted here.
The problems arise from set_data and set_offsets not working in 3D, so you're supposed to use set_3d_properties to tack on the z information. Playing around with that it usually chokes, but with the code posted below it runs. However, the transparency increases enough that the points just fade away after a few frames. What am I doing wrong here? I want the points to jump around within the bounds of the box for a while. Even adjusting the step size to something very small doesn't slow down the transparency.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
FLOOR = -10
CEILING = 10
class AnimatedScatter(object):
def __init__(self, numpoints=5):
self.numpoints = numpoints
self.stream = self.data_stream()
self.angle = 0
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111,projection = '3d')
self.ani = animation.FuncAnimation(self.fig, self.update, interval=100,
init_func=self.setup_plot, blit=True)
def change_angle(self):
self.angle = (self.angle + 1)%360
def setup_plot(self):
x, y, z = next(self.stream)
c = ['b', 'r', 'g', 'y', 'm']
self.scat = self.ax.scatter(x, y, z,c=c, s=200, animated=True)
self.ax.set_xlim3d(FLOOR, CEILING)
self.ax.set_ylim3d(FLOOR, CEILING)
self.ax.set_zlim3d(FLOOR, CEILING)
return self.scat,
def data_stream(self):
data = np.zeros((3, self.numpoints))
xyz = data[:3, :]
while True:
xyz += 2 * (np.random.random((3, self.numpoints)) - 0.5)
yield data
def update(self, i):
data = next(self.stream)
data = np.transpose(data)
self.scat.set_offsets(data[:,:2])
#self.scat.set_3d_properties(data)
self.scat.set_3d_properties(data[:,2:],'z')
self.change_angle()
self.ax.view_init(30,self.angle)
plt.draw()
return self.scat,
def show(self):
plt.show()
if __name__ == '__main__':
a = AnimatedScatter()
a.show()
Found the solution finally, here is how to update points w/o touching colors:
from mpl_toolkits.mplot3d.art3d import juggle_axes
scat._offsets3d = juggle_axes(xs, ys, zs, 'z')
this is internally done by set_3d_properties along with re-initializing colors
I've found this, and more generic, solution:
You shold add np.ma.ravel( x_data ) ... before inserting your data in the collection.
But the scatter plot don't seems to be intended for animations; it's too slow.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
FLOOR = -10
CEILING = 10
class AnimatedScatter(object):
def __init__(self, numpoints=5):
self.numpoints = numpoints
self.stream = self.data_stream()
self.angle = 0
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111,projection = '3d')
self.ani = animation.FuncAnimation(self.fig, self.update, interval=100,
init_func=self.setup_plot, blit=True)
def change_angle(self):
self.angle = (self.angle + 1)%360
def setup_plot(self):
X = next(self.stream)
c = ['b', 'r', 'g', 'y', 'm']
self.scat = self.ax.scatter(X[:,0], X[:,1], X[:,2] , c=c, s=200, animated=True)
self.ax.set_xlim3d(FLOOR, CEILING)
self.ax.set_ylim3d(FLOOR, CEILING)
self.ax.set_zlim3d(FLOOR, CEILING)
return self.scat,
def data_stream(self):
data = np.zeros(( self.numpoints , 3 ))
xyz = data[:,:3]
while True:
xyz += 2 * (np.random.random(( self.numpoints,3)) - 0.5)
yield data
def update(self, i):
data = next(self.stream)
data = np.transpose(data)
self.scat._offsets3d = ( np.ma.ravel(data[:,0]) , np.ma.ravel(data[:,0]) , np.ma.ravel(data[:,0]) )
self.change_angle()
self.ax.view_init(30,self.angle)
plt.draw()
return self.scat,
def show(self):
plt.show()
if __name__ == '__main__':
a = AnimatedScatter()
a.show()

Categories

Resources