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))
Related
As the title says, I am trying to put my matplotlib animation into a GUI however, im not too sure where to start. I am very much new to python, especially using it to make GUIs. Right now this is what I have for my animation:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
points = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
fig, ax = plt.subplots()
xfixdata, yfixdata = 15,10
xdata, ydata = 5, None
ln, = plt.plot([], [], 'ro-', animated=True)
plt.plot([xfixdata], [yfixdata], 'bo', ms=10)
def init():
ax.set_xlim(0, 20)
ax.set_ylim(0, 20)
return ln,
def update(frame):
ydata = points[frame]
ln.set_data([xfixdata,xdata], [yfixdata,ydata])
return ln,
ani = FuncAnimation(fig, update, interval=80, frames=range(len(points)),
init_func=init, blit=True)
plt.show()
Right now, I've been attempting to transfer this code into a canvas using pysimpleGUI however, I am not making any progress. Is there any chance that one of you could somewhat walk me through the process of converting this? Thank you very much.
Here's one demo code for you about the matplotlib animation in PySimpleGUI Graph element, of course, you can use Canvas element
import math
from matplotlib import use as use_agg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import PySimpleGUI as sg
def pack_figure(graph, figure):
canvas = FigureCanvasTkAgg(figure, graph.Widget)
plot_widget = canvas.get_tk_widget()
plot_widget.pack(side='top', fill='both', expand=1)
return plot_widget
def plot_figure(index, theta):
fig = plt.figure(index) # Active an existing figure
ax = plt.gca() # Get the current axes
x = [degree for degree in range(1080)]
y = [math.sin((degree+theta)/180*math.pi) for degree in range(1080)]
ax.cla() # Clear the current axes
ax.set_title(f"Sensor Data {index}")
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_xscale('log')
ax.grid()
plt.plot(x, y) # Plot y versus x as lines and/or markers
fig.canvas.draw() # Rendor figure into canvas
# Use Tkinter Agg
use_agg('TkAgg')
layout = [[sg.Graph((640, 480), (0, 0), (640, 480), key='Graph1'), sg.Graph((640, 480), (0, 0), (640, 480), key='Graph2')]]
window = sg.Window('Matplotlib', layout, finalize=True)
# Initial
graph1 = window['Graph1']
graph2 = window['Graph2']
plt.ioff() # Turn the interactive mode off
fig1 = plt.figure(1) # Create a new figure
ax1 = plt.subplot(111) # Add a subplot to the current figure.
fig2 = plt.figure(2) # Create a new figure
ax2 = plt.subplot(111) # Add a subplot to the current figure.
pack_figure(graph1, fig1) # Pack figure under graph
pack_figure(graph2, fig2)
theta1 = 0 # theta for fig1
theta2 = 90 # theta for fig2
plot_figure(1, theta1)
plot_figure(2, theta2)
while True:
event, values = window.read(timeout=10)
if event == sg.WINDOW_CLOSED:
break
elif event == sg.TIMEOUT_EVENT:
theta1 = (theta1 + 40) % 360
plot_figure(1, theta1)
theta2 = (theta2 + 40) % 260
plot_figure(2, theta2)
window.close()
I have a running times dataset that I have broken down into six months (Jan - Jun). I want to plot an animation of a scatter plot showing distance on the x-axis and time on the y-axis.
Without any animations I have:
plt.figure(figsize = (8,8))
plt.scatter(data = strava_df, x = 'Distance', y = 'Elapsed Time', c = col_list, alpha = 0.7)
plt.xlabel('Distance (km)')
plt.ylabel('Elapsed Time (min)')
plt.title('Running Distance vs. Time')
plt.show()
Which gives me:
What I'd like is an animation that plots the data for the first month, then after a delay the second month, and so on.
from matplotlib.animation import FuncAnimation
fig = plt.figure(figsize=(10,10))
ax = plt.axes(xlim=(2,15), ylim=(10, 80))
x = []
y = []
scat = plt.scatter(x, y)
def animate(i):
for m in range(0,6):
x.append(strava_df.loc[strava_df['Month'] == m,strava_df['Distance']])
y.append(strava_df.loc[strava_df['Month'] == m,strava_df['Elapsed Time']])
FuncAnimation(fig, animate, frames=12, interval=6, repeat=False)
plt.show()
This is what I've come up with, but it isn't working. Any advice?
The animate function should update the matplotlib object created by a call to scat = ax.scatter(...) and also return that object as a tuple. The positions can be updated calling scat.set_offsets() with an nx2 array of xy values. The color can be updated with scat.set_color() with a list or array of colors.
Supposing col_list is a list of color names or rgb-values, the code could look like:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pandas as pd
import numpy as np
strava_df = pd.DataFrame({'Month': np.random.randint(0, 6, 120),
'Distance': np.random.uniform(2, 13, 120),
'Color': np.random.choice(['blue', 'red', 'orange', 'cyan'], 120)
})
strava_df['Elapsed Time'] = strava_df['Distance'] * 5 + np.random.uniform(0, 5, 120)
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(xlim=(2, 15), ylim=(10, 80))
scat = ax.scatter([], [], s=20)
def animate(i):
x = np.array([])
y = np.array([])
c = np.array([])
for m in range(0, i + 1):
x = np.concatenate([x, strava_df.loc[strava_df['Month'] == m, 'Distance']])
y = np.concatenate([y, strava_df.loc[strava_df['Month'] == m, 'Elapsed Time']])
c = np.concatenate([c, strava_df.loc[strava_df['Month'] == m, 'Color']])
scat.set_offsets(np.array([x, y]).T)
scat.set_color(c)
return scat,
anim = FuncAnimation(fig, animate, frames=12, interval=6, repeat=False)
plt.show()
I'm trying to plot a big amount of curves in a stackplot with matplotlib, using python.
To read the graph, I need to show legends, but if I show it with the legend method, my graph is unreadable (because of the number of legends, and their size).
I have found that mplcursors could help me to do that with a popup in the graph itself. It works with "simple" plots, but not with a stackplot.
Here is the warning message with stackplots:
/usr/lib/python3.7/site-packages/mplcursors/_pick_info.py:141: UserWarning: Pick support for PolyCollection is missing.
warnings.warn(f"Pick support for {type(artist).__name__} is missing.")
And here is the code related to this error (it's only a proof of concept):
import matplotlib.pyplot as plt
import mplcursors
import numpy as np
data = np.outer(range(10), range(1, 5))
timestamp = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
tmp = list()
tmp.append(data[:, 0])
tmp.append(data[:, 1])
tmp.append(data[:, 2])
tmp.append(data[:, 3])
print(data)
print(tmp)
fig, ax = plt.subplots()
ax.stackplot(timestamp, tmp, labels=('curve1', 'line2', 'curvefever', 'whatever'))
ax.legend()
mplcursors.cursor()
cursor = mplcursors.cursor(hover=True)
#cursor.connect("add")
def on_add(sel):
print(sel)
label = sel.artist.get_label()
sel.annotation.set(text=label)
plt.show()
Do you have an idea of how to fix that, or do you know another way to do something like that ?
It is not clear why mplcursors doesn't accept a stackplot. But you can replicate the behavior with more primitive matplotlib functionality:
import matplotlib.pyplot as plt
import numpy as np
def update_annot(label, x, y):
annot.xy = (x, y)
annot.set_text(label)
def on_hover(event):
visible = annot.get_visible()
is_outside_of_stackplot = True
if event.inaxes == ax:
for coll, label in zip(stckplt, labels):
contained, _ = coll.contains(event)
if contained:
update_annot(label, event.x, event.y)
annot.set_visible(True)
is_outside_of_stackplot = False
if is_outside_of_stackplot and visible:
annot.set_visible(False)
fig.canvas.draw_idle()
data = np.random.randint(1, 5, size=(4, 40))
fig, ax = plt.subplots()
labels = ('curve1', 'line2', 'curvefever', 'whatever')
stckplt = ax.stackplot(range(data.shape[1]), data, labels=labels)
ax.autoscale(enable=True, axis='x', tight=True)
# ax.legend()
annot = ax.annotate("", xy=(0, 0), xycoords="figure pixels",
xytext=(20, 20), textcoords="offset points",
bbox=dict(boxstyle="round", fc="yellow", alpha=0.6),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
plt.connect('motion_notify_event', on_hover)
plt.show()
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()
I have a list of x and a list of y values. I'd like to construct a scatterplot in Matplotlib and divide the dots into five categories based on their x and y coordinates, like in the image below:
angles = [0, 18, 36, 54, 72, 90]
colors = ['r','g','b','c']
x = [....]
y = [....]
All of the points in the divided category will be the same color. It would also be great to have a legend for the categories. I am new to Matplotlib and Python, does anyone know how I can approach this?
Here's a working example which will give you a little idea to get started:
from matplotlib import pyplot as plt
from matplotlib.lines import Line2D
import math
import random
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
w, h = 7, 5
a = [[random.random() * w, random.random() * h] for i in range(100)]
plt.plot(*zip(*a), marker='o', color='r', ls='')
for deg in [18, 36, 54]:
r = 10
line = Line2D([0, r * math.cos(math.radians(deg))],
[0, r * math.sin(math.radians(deg))],
linewidth=1, linestyle="-", color="green")
ax.add_line(line)
ax.set_xlim(0, w)
ax.set_ylim(0, h)
plt.legend()
plt.show()