Matplotlib animation: draw lines in different colours - python

I have the following code right now, to show growth of a curve:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
def move_curve(i, line, x, y, z):
# Add points rather than changing start and end points.
line.set_data(x[:i+1], y[:i+1])
line.set_3d_properties(z[:i+1])
fig = plt.figure()
ax = fig.gca(projection='3d')
x = [1, 3, 8, 11, 17]
y = [7, 2, -5, 3, 5]
z = [5, 7, 9, 13, 18]
i = 0
line = ax.plot([x[i], x[i+1]], [y[i],y[i+1]], [z[i],z[i+1]])[0]
ax.set_xlim3d([1, 17])
ax.set_ylim3d([-5, 7])
ax.set_zlim3d([5, 18])
line_ani = animation.FuncAnimation(fig, move_curve, 5, fargs=(line, x, y, z))
plt.show()
I want to show the different lines in different colours. Also, I want to update the length of the axis as the curve grows.
How to do that? I am new to python so I might be missing something simple. Thanks for the help!

Here is how #MrT's answer would look like using FuncAnimation. The advantage is that you do not need to care about autoscaling; that is done automatically on the fly.
import matplotlib.pyplot as plt
import matplotlib.animation as anim
import mpl_toolkits.mplot3d.axes3d as p3
fig = plt.figure()
ax = fig.gca(projection='3d')
x = [1, 3, 8, 11, 17]
y = [7, 2, -5, 3, 5]
z = [5, 7, 9, 13, 18]
#colour map
colors = ["green", "blue", "red", "orange"]
def init():
ax.clear()
def update(i):
newsegm, = ax.plot([x[i], x[i + 1]], [y[i], y[i + 1]], [z[i], z[i + 1]], colors[i])
ani = anim.FuncAnimation(fig, update, init_func=init,
frames = range(len(x)-1), interval = 300, repeat=True)
plt.show()

You can use ArtistAnimation and attribute an individual colour to each line segment:
import matplotlib.pyplot as plt
import matplotlib.animation as anim
import mpl_toolkits.mplot3d.axes3d as p3
fig = plt.figure()
ax = fig.gca(projection='3d')
x = [1, 3, 8, 11, 17]
y = [7, 2, -5, 3, 5]
z = [5, 7, 9, 13, 18]
#colour map
cmap = ["green", "blue", "red", "orange"]
#set up list of images for animation with empty list
lines=[[]]
for i in range(len(x) - 1):
#create next segment with new color
newsegm, = ax.plot([x[i], x[i + 1]], [y[i], y[i + 1]], [z[i], z[i + 1]], cmap[i])
#append new segment to previous list
lines.append(lines[-1] + [newsegm])
#animate list of line segments
ani = anim.ArtistAnimation(fig, lines, interval = 300)
plt.show()
Output:

Related

Animating a line plot in python with mathplotlib

So I'm trying to animate a line that gradually goes through plot points and I can't seem to figure out a way to do so. I've tried using FuncAnimation with no success and similar questions on Stackoverflow haven't helped me. Any suggestions?
Here's my code:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import math
import csv
import sys
import numpy
towns=[['Orleans', '1.750115', '47.980822'],
['Bordeaux', '-0.644905', '44.896839'],
['Bayonne', '-1.380989', '43.470961'],
['Toulouse', '1.376579', '43.662010'],
['Marseille', '5.337151', '43.327276'],
['Nice', '7.265252', '43.745404'],
['Nantes', '-1.650154', '47.385427'],
['Rennes', '-1.430427', '48.197310'],
['Paris', '2.414787', '48.953260'],
['Lille', '3.090447', '50.612962'],
['Dijon', '5.013054', '47.370547'],
['Valence', '4.793327', '44.990153'],
['Aurillac', '2.447746', '44.966838'],
['Clermont-Ferrand', '3.002556', '45.846117'],
['Reims', '4.134148', '49.323421'],
['Strasbourg', '7.506950', '48.580332'],
['Limoges', '1.233757', '45.865246'],
['Troyes', '4.047255', '48.370925'],
['Le Havre', '0.103163', '49.532415'],
['Cherbourg', '-1.495348', '49.667704'],
['Brest', '-4.494615', '48.447500'],
['Niort', '-0.457140', '46.373545']]
order=[0,
8,
17,
14,
9,
18,
19,
7,
6,
21,
1,
2,
3,
12,
13,
16,
11,
4,
5,
10,
15,
20,
0]
fig=plt.figure()
fig.patch.set_facecolor('#ffffff')
fig.patch.set_alpha(0.7)
ax = plt.axes()
ax.set_facecolor('grey')
plt.axis('off')
for i in range(len(order)):
plt.plot(float(towns[order[i]][1]), float(towns[order[i]][2]), 'o', color='black')
try:
line = plt.Line2D((float(towns[order[i]][1]), float(towns[order[i+1]][1])), (float(towns[order[i]][2]), float(towns[order[i+1]][2])), lw=2)
plt.gca().add_line(line)
except IndexError:
pass
towns[order[i]][1] are x values
towns[order[i]][2] are y values
How can I make it look like this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
x_data = []
y_data = []
fig, ax = plt.subplots()
ax.set_xlim(0, 105)
ax.set_ylim(0, 12)
line, = ax.plot(0, 0)
def animation_frame(i):
x_data.append(i * 10)
y_data.append(i)
line.set_xdata(x_data)
line.set_ydata(y_data)
return line,
animation = FuncAnimation(fig, func=animation_frame, frames=np.arange(0, 10, 0.1), interval=10)
plt.show()
Thank you
There are a few ways to do this, but one option is to expand the data using list comprehension and passed it to a function to show it in a controlled manner. With all the data ready (t, x, y, l) use the animate function to place all cities in the graph with ax.plot(x, y,...) and the cities names with ax.annotate. Then, pass to the Matplotlib function FuncAnimation a function (update in the code below) to call at each frame update to refresh the line segment being displayed.
def animate(t, x, y, l):
fig, ax = plt.subplots()
# fit the labels inside the plot
ax.set_xlim(min(x)-1, max(x)+3)
ax.set_ylim(min(y)-1, max(y)+1)
# place all cities on the graph
cities, = ax.plot(x, y, marker = "o", color = "black", ls = "", markersize = 5)
line_1, = ax.plot([], [], marker = "", color = "blue", ls ="-", lw = 2)
LABEL_OFFSET = 1.003 # text label offset from marker
for i in t:
ax.annotate(f'{l[i]}', xy=(x[i]*LABEL_OFFSET,y[i]*LABEL_OFFSET))
def update(i):
line_1.set_data(x[:i], y[:i])
return line_1
anim = FuncAnimation(fig, update, frames=len(t)+1, interval=300, repeat=True)
return anim
t = range(len(order))
x = [float(towns[order[i]][1]) for i in t]
y = [float(towns[order[i]][2]) for i in t]
l = [towns[order[i]][0] for i in t]
cities_travel = animate(t, x, y, l)
cities_travel.save("cities_travel.gif", writer = PillowWriter(fps = 2))

How to pass an object to a separate function - Python

I’m aiming to animate a scatter plot using the df below. I’m trying to pass the plot and groups function to the animate function. I’m trying to return the values from each function are pass them to subsequent functions but I’m getting aNameError as these values aren't being registered.
The script works if I remove the plot and groups functions and pass objects to animate from the global workspace but then I have to write these out all the time. Rather than house in separate functions.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
from matplotlib import animation
df1 = pd.DataFrame({
'Time' : [1,1,1,2,2,2,3,3,3],
'GroupA_X' : [3, 4, 5, 2, 5, 6, 1, 6, 7],
'GroupA_Y' : [2, 4, 5, 2, 5, 5, 2, 6, 5],
'GroupB_X' : [2, 5, 3, 2, 4, 2, 2, 3, 1],
'GroupB_Y' : [2, 4, 3, 3, 3, 4, 4, 2, 5],
})
def plot():
fig, ax = plt.subplots()
ax.grid(False)
xy = 0,0
Oval = mpl.patches.Ellipse(xy, 160, 130, lw = 2, edgecolor = 'black', color = 'blue', alpha = 0.2)
ax.add_patch(Oval)
return fig, ax
def groups():
plot()
Group_A = df1[['Time','GroupA_X','GroupA_Y']]
Group_B = df1[['Time','GroupB_X','GroupB_Y']]
GA_X = np.array(Group_A.groupby(['Time'])['GroupA_X'].apply(list))
GA_Y = np.array(Group_A.groupby(['Time'])['GroupA_Y'].apply(list))
GB_X = np.array(Group_B.groupby(['Time'])['GroupB_X'].apply(list))
GB_Y = np.array(Group_B.groupby(['Time'])['GroupB_Y'].apply(list))
GA = ax.scatter(GA_X[0], GA_Y[0], c = ['blue'], marker = 'o', s = 10, edgecolor = 'black')
GB = ax.scatter(GB_X[0], GB_Y[0], c = ['brown'], marker = 'o', s = 10, edgecolor = 'black')
return GA, GB
def animate(i) :
plot()
groups()
GA.set_offsets(np.c_[GA_X[0+i], GA_Y[0+i]])
GB.set_offsets(np.c_[GB_X[0+i], GB_Y[0+i]])
plot()
groups()
ani = animation.FuncAnimation(fig, animate, np.arange(0,3), interval = 1000, blit = False)
Error Output:
GA = ax.scatter(GA_X[0], GA_Y[0], c = ['blue'], marker = 'o', s = 10, edgecolor = 'black')
NameError: name 'ax' is not defined
animation draws/repeats on the global figure, so you need to create subplots in global scope. If you define subplots inside plot function, every call of plot will create a new subplots
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
from matplotlib import animation
df1 = pd.DataFrame({
'Time' : [1,1,1,2,2,2,3,3,3],
'GroupA_X' : [3, 4, 5, 2, 5, 16, 21, 36, 47],
'GroupA_Y' : [2, 4, 5, 2, 5, 15, 22, 36, 45],
'GroupB_X' : [2, 5, 3, 2, 4, 12, 22, 33, 41],
'GroupB_Y' : [2, 4, 3, 3, 3, 14, 24, 32, 45],
})
fig, ax = plt.subplots()
def plot():
# fig, ax = plt.subplots() #declared in global scope
ax.grid(False)
xy = 0,0
Oval = mpl.patches.Ellipse(xy, 160, 130, lw = 2, edgecolor = 'black', color = 'blue', alpha = 0.2)
ax.add_patch(Oval)
# return fig, ax #no need return since `fig, ax` are in global scope
def groups():
# plot() #no need since this function use nothing from `plot`
Group_A = df1[['Time','GroupA_X','GroupA_Y']]
Group_B = df1[['Time','GroupB_X','GroupB_Y']]
GA_X = np.array(Group_A.groupby(['Time'])['GroupA_X'].apply(list))
GA_Y = np.array(Group_A.groupby(['Time'])['GroupA_Y'].apply(list))
GB_X = np.array(Group_B.groupby(['Time'])['GroupB_X'].apply(list))
GB_Y = np.array(Group_B.groupby(['Time'])['GroupB_Y'].apply(list))
GA = ax.scatter(GA_X[0], GA_Y[0], c = ['blue'], marker = 'o', s = 10, edgecolor = 'black')
GB = ax.scatter(GB_X[0], GB_Y[0], c = ['brown'], marker = 'o', s = 10, edgecolor = 'black')
return GA, GB, GA_X, GA_Y, GB_X, GB_Y
def animate(i) :
# plot()
GA, GB, GA_X, GA_Y, GB_X, GB_Y = groups()
GA.set_offsets(np.c_[GA_X[0+i], GA_Y[0+i]])
GB.set_offsets(np.c_[GB_X[0+i], GB_Y[0+i]])
plot()
# groups()
ani = animation.FuncAnimation(fig, animate, np.arange(0,3), interval = 1000, blit = False)
Note: I changed values in df1 to make the values changing between each animation clearer. Codes fixing above works. I tested it. However, I don't know whether it is efficient. I basically just fix your codes to make it run.
You said animation runs fine if you declare everything in global scope. Therefore, I assume your system already had ffmpeg installed and you codes is able to call/find ffmpeg.exe to display the repreating/looping animation
Have you tried modifying the function to pass the returned ax to a variable inside groups function as below:
def groups():
_,ax = plot()
As the error states the name ax is not defined in groups function. It has to be defined by capturing the value returned from plot()

How to create a scatter plot with color specified for each point individually?

Using matplotlib I create a scatter plot animation that shows a new point after each second and shows all old points partly transparent. Each point is defined by x and y, but also by a category s. I want the color of the points to be tied to its category. Ideally that means that the array s contains values 1, 2 and 3, and the colors belonging to those values are defined seperately. However, I can not get this to work.
What I do get to work is to specify the edgecolors of each point individually in s, the code for this is shown below.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as plti
import matplotlib.animation
s = [[1,0,0],[0,1,0],[0,0,1]];
x = [525,480,260];
y = [215,180,180];
img = plti.imread('myimage.png')
fig, ax = plt.subplots()
plt.imshow(img)
plt.axis('off')
x_vals = []
y_vals = []
intensity = []
iterations = len(x)
colors = []
t_vals = np.linspace(0,iterations-1,iterations,dtype=int)
scatter = ax.scatter(x_vals, y_vals, s=100, c=colors, vmin=0, vmax=1)
def init():
pass
def update(t):
global x, y, x_vals, y_vals, intensity
x_vals.extend([x[t]])
y_vals.extend([y[t]])
scatter.set_offsets(np.c_[x_vals,y_vals])
intensity = np.concatenate((np.array(intensity), np.ones(1)))
if len(intensity) > 1:
intensity[-2] = 0.5
scatter.set_array(intensity)
colors.extend([s[t]])
scatter.set_color(colors)
return ani
ani = matplotlib.animation.FuncAnimation(fig, update, frames=t_vals, interval=1000, repeat=False, init_func=init)
plt.show()
Simply changing c=colors to facecolor=colors does not work. Also I have tried to use colormaps but I cannot get it to work using that either.
The resulting animation from the code above looks as below.
However, the animation should look like this..
So my question is; does someone know how to tie the facecolor of each point to the category that that point belongs to?
The normal way to plot plots with points in different colors in matplotlib is to pass a list of colors as a parameter.
E.g.:
import matplotlib.pyplot
matplotlib.pyplot.scatter([1,2,3],[4,5,6],color=['red','green','blue'])
But if for some reason you wanted to do it with just one call, you can make a big list of colors, with a list comprehension and a bit of flooring division:
import matplotlib
import numpy as np
X = [1,2,3,4]
Ys = np.array([[4,8,12,16],
[1,4,9,16],
[17, 10, 13, 18],
[9, 10, 18, 11],
[4, 15, 17, 6],
[7, 10, 8, 7],
[9, 0, 10, 11],
[14, 1, 15, 5],
[8, 15, 9, 14],
[20, 7, 1, 5]])
nCols = len(X)
nRows = Ys.shape[0]
colors = matplotlib.cm.rainbow(np.linspace(0, 1, len(Ys)))
cs = [colors[i//len(X)] for i in range(len(Ys)*len(X))] #could be done with numpy's repmat
Xs=X*nRows #use list multiplication for repetition
matplotlib.pyplot.scatter(Xs,Ys.flatten(),color=cs)
The problem occurred because the line scatter.set_array(intensity) was called before scatter.set_color(colors). So instead of defining the intensity by a seperate variable, it is instead integrated into the colors directly. The following code produces the desired result.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as plti
import matplotlib.animation
s = [1,2,3];
x = [525,480,260];
y = [215,180,180];
img = plti.imread('myimage.png')
fig, ax = plt.subplots()
plt.imshow(img)
plt.axis('off')
x_vals = []
y_vals = []
iterations = len(x)
colors = []
t_vals = np.linspace(0,iterations-1,iterations,dtype=int)
scatter = ax.scatter(x_vals, y_vals, s=100, color=colors, vmin=0, vmax=1)
def init():
pass
def update(t):
global x, y, x_vals, y_vals
x_vals.extend([x[t]])
y_vals.extend([y[t]])
scatter.set_offsets(np.c_[x_vals,y_vals])
if t > 0:
if s[t-1] == 1:
colors[t-1] = [1,0,0,0.5];
elif s[t-1] == 2:
colors[t-1] = [0,1,0,0.5];
else:
colors[t-1] = [0,0,1,0.5];
if s[t] == 1:
colors.extend([[1,0,0,1]])
elif s[t] == 2:
colors.extend([[0,1,0,1]])
else:
colors.extend([[0,0,1,1]])
scatter.set_color(colors);
return ani
ani = matplotlib.animation.FuncAnimation(fig, update, frames=t_vals, init_func=init, interval=1000, repeat=False)
plt.show()

Got more ylabels than specified by set_yticklabel() [duplicate]

Here is a simple plot:
1) How to disable the ticks?
2) How to reduce their number?
Here is a sample code:
from pylab import *
import numpy as np
x = [5e-05, 5e-06, 5e-07, 5e-08, 5e-09, 5e-10]
y = [-13, 14, 100, 120, 105, 93]
def myfunc(x,p):
sl,yt,yb,ec=p
y = yb + (yt-yb)/(1+np.power(10, sl*(np.log10(x)-np.log10(ec))))
return y
xp = np.power(10, np.linspace(np.log10(min(x)/10), np.log10(max(x)*10), 100))
pxp=myfunc(xp, [1,100,0,1e-6])
subplot(111,axisbg="#dfdfdf")
plt.plot(x, y, '.', xp, pxp, 'g-', linewidth=1)
plt.xscale('log')
plt.grid(True,ls="-", linewidth=0.4, color="#ffffff", alpha=0.5)
plt.draw()
plt.show()
Which produces:
plt.minorticks_off()
Turns em off!
To change the number of them/position them, you can use the subsx parameter. like this:
plt.xscale('log', subsx=[2, 3, 4, 5, 6, 7, 8, 9])
From the docs:
subsx/subsy: Where to place the subticks between each major tick.
Should be a sequence of integers. For example, in a log10 scale: [2,
3, 4, 5, 6, 7, 8, 9]
will place 8 logarithmically spaced minor ticks between each major
tick.
Calling plt.minorticks_off() will apply this to the current axis. (The function is actually a wrapper to gca().minorticks_off().)
You can also apply this to an individual axis in the same way:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.minorticks_off()
from pylab import *
import numpy as np
x = [5e-05, 5e-06, 5e-07, 5e-08, 5e-09, 5e-10]
y = [-13, 14, 100, 120, 105, 93]
def myfunc(x,p):
sl,yt,yb,ec=p
y = yb + (yt-yb)/(1+np.power(10, sl*(np.log10(x)-np.log10(ec))))
return y
xp = np.power(10, np.linspace(np.log10(min(x)/10), np.log10(max(x)*10), 100))
pxp=myfunc(xp, [1,100,0,1e-6])
ax=subplot(111,axisbg="#dfdfdf")
plt.plot(x, y, '.', xp, pxp, 'g-', linewidth=1)
plt.xscale('log')
plt.grid(True,ls="-", linewidth=0.4, color="#ffffff", alpha=0.5)
plt.minorticks_off() # turns off minor ticks
plt.draw()
plt.show()

How to plot text below xticks (using x-coords) in matplotlib when x-axis are dates?

I'm trying to plot arbitrary text below the x tick marks in a matplotlib figure (see example figure below). I'm using dates as the x-axis and, for instance, I want to display counts of some variable associated with each date.
In the example below I use the relative positions from 0-1 within the figure for the x-coordinate of where the text should be positioned. However I've just guessed these relative values (using trial and error) and so I would like to know how would one plot text below the x-ticks using the actual positions of the x-data instead of using these 0-1 relative scaling that's the default in figtext?
import numpy as np
import matplotlib.pyplot as plt
import datetime
x = [datetime.datetime(2010, 12, 1, 0, 0),
datetime.datetime(2011, 1, 1, 0, 0),
datetime.datetime(2011, 5, 1, 1, 0)]
y = [4, 9, 2]
fig, ax = plt.subplots()
ax.bar(x, y, width = 20)
xticks = ax.get_xticks()
xtick_rel_position = np.linspace(0.13, 0.81, len(xticks)) # <- these are just guessed
counts = np.random.randint(0, 25, len(xticks))
for i, xpos in enumerate(xtick_rel_position):
plt.figtext(xpos, 0.028, "Below tick\nlabel "+str(i),
size = 6, ha = 'center')
plt.figtext(xpos, 0.005, "Count: "+str(counts[i]),
size = 6, ha = 'center')
# For better aesthetics
ax.yaxis.set_visible(False)
plt.show()
I've tried including transform = ax.transAxes with the actual x coordinates from ax.get_xticks() but this doesn't change anything.
Use ax.text for text positions relative to axis positions rather than fig positions:
import matplotlib.pyplot as plt
import datetime
import numpy as np
x = [datetime.datetime(2010, 12, 1, 0, 0),
datetime.datetime(2011, 1, 1, 0, 0),
datetime.datetime(2011, 5, 1, 1, 0)]
y = [4, 9, 2]
fig, ax = plt.subplots()
ax.bar(x, y, width = 20, align='center')
counts = np.random.randint(0, 25, len(ax.get_xticks()))
for i, xpos in enumerate(ax.get_xticks()):
ax.text(xpos,-1, "Below tick\nlabel "+str(i),
size = 6, ha = 'center')
ax.text(xpos, -1.25, "Count: "+str(counts[i]),
size = 6, ha = 'center')
You can use the date2num function to do the conversion, and a DataFormatter to display the dates correctly. Finally get_xticks is used to get the locations of the ticks for the text to be accurately displayed underneath:
import numpy as np
import matplotlib.pyplot as plt
import datetime
import matplotlib
x = [datetime.datetime(2010, 12, 1, 0, 0),
datetime.datetime(2011, 1, 1, 0, 0),
datetime.datetime(2011, 5, 1, 1, 0)]
y = [4, 9, 2]
hfmt = matplotlib.dates.DateFormatter('%b %Y')
months = matplotlib.dates.MonthLocator(range(1, 13), bymonthday=1, interval=1)
xs = matplotlib.dates.date2num(x)
fig, ax = plt.subplots()
ax.bar(xs, y, width=20)
ax.xaxis.set_major_locator(months)
ax.xaxis.set_major_formatter(hfmt)
ax.yaxis.set_visible(False)
for i, x in enumerate(ax.get_xticks()):
plt.text(x, -1.3, "Below tick\nlabel {}\nCount x".format(i), size=7, ha='center')
fig.subplots_adjust(bottom=0.15) # Add space at bottom
plt.show()
This would display:

Categories