I want to draw an animated chart using python3.5.4's matplotlib package, the examples of matplotlib official website works well on my local Python environment.
But those codes I wrote can not show me any chart, I can not figure out what's problem in those codes, so I come here to look for some help. Here are my codes.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class Plot(object):
def __init__(self, update_func, frames):
self.x_data, self.y_data = [], []
self.update_func = update_func
self.frames = frames
self.t = 0
def draw(self):
fig = plt.figure()
self.ax = plt.axes()
self.line, = self.ax.plot([1, 2, 3, 4], [1, 2, 3, 4], lw=2)
# Without invoke the FuncAnimation can display the chart.
self.ani_ref = FuncAnimation(fig, self._update, frames=self.frames, blit=True,
interval=20, init_func=self._animation_init)
plt.show()
def _animation_init(self):
self.line.set_data(self.x_data, self.y_data)
return self.line
def _update(self, i):
# modified the data from outside update function
self.x_data, self.y_data = self.update_func(self.x_data, self.y_data)
x_min, x_max = self.ax.get_xlim()
y_min, y_max = self.ax.get_ylim()
if np.max(self.x_data) >= x_max:
x_max = np.max(self.x_data) + 10
if np.min(self.x_data) <= x_min:
x_min = np.min(self.x_data) - 10
if np.max(self.y_data) >= y_max:
y_max = np.max(self.y_data) + 10
if np.min(self.y_data) <= y_min:
y_min = np.min(self.y_data) - 10
self.ax.set_xlim(x_min, x_max)
self.ax.set_ylim(y_min, y_max)
self.ax.figure.canvas.draw()
self.line.set_data(self.x_data, self.y_data)
return self.line
if __name__ == "__main__":
def update(x_data, y_data):
x, y = x_data[-1], np.sin(2 * np.pi * (x_data[-1] + 0.1))
x_data.append(x)
y_data.append(y)
return x_data, y_data
p = Plot(update_func=update, frames=100)
p.draw()
Related
I'm trying to make a small animation of particles moving left or right but the figure just closes immediately after running. I tried to read some tips here in the forum but nothing helped.
Can't understand why the animation does not work for me.
I looked at some examples and I do not find the mistake I made.
Could you help me?
import numpy as np
import random
from scipy.spatial.distance import pdist, squareform
import matplotlib.pyplot as plt
import scipy.integrate as integrate
import matplotlib.animation as animation
class ParticleBox:
"""
init_state is a 3D array of x,y coordinates + probability to go left or right
bounds is the size of the box: [xmin, xmax, ymin, ymax]
"""
def __init__(self,
init_state, bounds = [-9, 9, -2, 2],
size = 0.04, step_size=0.05):
self.init_state = np.asarray(init_state, dtype=float)
self.size = size
self.state = self.init_state.copy()
self.time_elapsed = 0
self.bounds = bounds
self.ycorbeg=range(len(self.state))
def step(self, dts):
pr=0.4
pl=0.4
ps=0.2
self.time_elapsed+=dts
for i in range(len(self.state)):
print c
c=random.random()
if c<pr:
print c
self.state[i,2]=ze
elif (pr<=c and c<pr+pl):
self.state[i,2]=-step_size
else:
self.state[i,0]=self.state[i,0]
self.state[:, 0] += dt * self.state[:, 2]
np.random.seed(0)
beginx=np.random.rand(1000)-0.5
lenbegin=len(beginx)
def beginy(lenbegin,maxy=250):
i=0
beginy=np.zeros(lenbegin)
while i<lenbegin:
for j in range(int(-maxy/2),int(maxy/2)):
beginy[i]=j/50.0
i+=1
if len(beginx)==len(beginy):
ze=np.zeros(len(beginy))
print zip(beginx,beginy,ze)[0]
return zip(beginx,beginy,ze)
else:
raise ValueError
init_state=beginy(lenbegin)
box = ParticleBox(init_state)
dts = 1. / 30
#------------------------------------------------------------
# set up figure and animation
fig = plt.figure()
fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False,
xlim=(-3.2, 3.2), ylim=(-2.4, 2.4))
# particles holds the locations of the particles
particles, = ax.plot([], [], 'bo', ms=6)
# rect is the box edge
rect = plt.Rectangle(box.bounds[::2],
box.bounds[1] - box.bounds[0],
box.bounds[3] - box.bounds[2],
ec='none', lw=2, fc='none')
ax.add_patch(rect)
def init():
"""initialize animation"""
global box, rect
particles.set_data([], [])
rect.set_edgecolor('none')
return particles, rect
def animate(i):
"""perform animation step"""
global box, rect, dts, fig
box.step(dts)
ms = int(fig.dpi * 2 * box.size * fig.get_figwidth()
/ np.diff(ax.get_xbound())[0])
# update pieces of the animation
rect.set_edgecolor('k')
particles.set_data(box.state[:, 0], box.state[:, 1])
particles.set_markersize(ms)
return particles, rect
anim = animation.FuncAnimation(fig, animate, frames=600,
interval=10, blit=True, init_func=init)
plt.show()
I want to animate a random rectangle in matplotlib. I found out that when I set blit=True, the animation always keeps the first rectangle calculated. If I set blit=False, the animation will keep update the rectangle and the old rectangle disappears. How to remove the old rectangle if I want to set blit=True?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
import random
class box_sim_env(object):
def __init__(self):
self.env_fig = plt.figure()
self.ax = self.env_fig.add_subplot(111, aspect='equal')
self.box_pose = np.array([0.48, 0.5, 0])
self.create_box(self.box_pose)
self.ax.add_patch(self.box_patch)
def update_box(self, pose):
self.box_verts = self.get_rect_verts(np.array([pose[0], pose[1]]), 0.6, 0.3,
angle=pose[2])
self.box_patch.set_xy(self.box_verts)
def create_box(self, pose):
self.box_verts = self.get_rect_verts(np.array([pose[0], pose[1]]), 0.6, 0.3,
angle=pose[2])
self.box_patch = plt.Polygon(self.box_verts, facecolor='cyan', edgecolor='blue')
def get_rect_verts(self, center, length, width, angle):
rotation_mtx = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
half_length = length / 2.0
half_width = width / 2.0
verts = np.array([[-half_length, half_width],
[half_length, half_width],
[half_length, -half_width],
[-half_length, -half_width]])
verts_rot = np.dot(rotation_mtx, verts.T)
verts_trans = verts_rot.T + center.reshape((1,2))
return verts_trans.reshape((4,2))
def animate(self, i):
pose = np.zeros(3)
pose[0] = random.uniform(-3,3)
pose[1] = random.uniform(-3,3)
pose[2] = random.uniform(-np.pi, np.pi)
self.update_box(pose)
return [self.box_patch,]
def plt_show(self):
self.anim = animation.FuncAnimation(self.env_fig, self.animate,
init_func=None,
frames=1000,
interval=1,
# repeat= False,
blit=True)
plt.xlim([-4,4])
plt.ylim([-4,4])
plt.show()
if __name__ == '__main__':
print '============'
box_sim = box_sim_env()
box_sim.plt_show()
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'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()
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()