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()
Related
basically I am trying to have a sine wave be displayed by matplotlib and then when a certain x value is reached (block_start_pos) for the animation speed to change (slow down in this case). I understand that FuncAnimation repeatedly calls update_plot based on the given parameters but I was wondering if there was a way to change the interval mid animation. My code (mostly taken from a youtube video) is shown below. Thanks!
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import tkinter as tk
x = np.arange(0, 10*np.pi, 0.01)
index_of_refraction = 10
index_of_refraction_lst = [1, 200, 3, 4, 5]
medium = 20*index_of_refraction
w = 1
y = np.cos(w*x)
fig = plt.figure()
ax = plt.subplot(1, 1, 1)
data_skip = 50
block_start_pos = 6*np.pi
def init_func():
ax.clear()
plt.xlabel('pi')
plt.ylabel('sin(pi)')
plt.xlim((x[0], x[-1]))
plt.ylim((-1, 1))
def update_plot(i):
ax.plot(x[i:i+data_skip], y[i:i+data_skip], color='k')
ax.scatter(x[i], y[i], marker='o', color='r')
return medium_test(i)
def medium_test(i):
if x[i] > block_start_pos:
index_of_refraction = index_of_refraction_lst[1]
medium = 20*index_of_refraction
medium = 20*index_of_refraction
anim = FuncAnimation(fig,
update_plot,
frames=np.arange(0, len(x), data_skip),
init_func=init_func,
interval=medium)
plt.show()
# anim.save('sine.mp4', dpi=150, fps = 30, writer='ffmpeg')```
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))
I am trying to create a graph with a secondary x-axis however I want the label and the ticks of the secondary x-axis to lie under the first. I have currently only found methods to move it to the bottom and not to an exact position. I have attached an image of what I am trying to achieve.
y = [3, 5, 2, 8, 7]
x = [[10, 11, 12, 13, 14], [36, 39.6, 43.2, 46.8, 50.4]]
labels = ['m/s', 'km/hr']
fig,ax = plt.subplots()
ax.plot(x[0], y)
ax.set_xlabel("Velocity m/s")
ax.set_ylabel("Time /mins")
ax2=ax.twiny()
ax2.plot(x[1], y)
ax2.set_xlabel("Velocity km/hr")
plt.show()
Answer
Firstly you have to include the required libraries:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
then you could generate the first axis with
ax = host_subplot(111, axes_class = AA.Axes, figure = fig)
then generate the secondary axis by
ax2=ax.twiny()
At this point you need to make some space for the secondary axis, therefore you should raise the bottom of the plot area with
plt.subplots_adjust(bottom = 0.2)
and finally position the secondary axis under the first one by
offset = -40
new_fixed_axis = ax2.get_grid_helper().new_fixed_axis
ax2.axis['bottom'] = new_fixed_axis(loc = 'bottom',
axes = ax2,
offset = (0, offset))
ax2.axis['bottom'].toggle(all = True)
Whole code
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
y = [3, 5, 2, 8, 7]
x = [[10, 11, 12, 13, 14], [36, 39.6, 43.2, 46.8, 50.4]]
labels = ['m/s', 'km/hr']
fig = plt.figure()
# generate the first axis
ax = host_subplot(111, axes_class = AA.Axes, figure = fig)
ax.plot(x[0], y)
ax.set_xlabel("Velocity m/s")
ax.set_ylabel("Time /mins")
ax2=ax.twiny()
# make space for the secondary axis
plt.subplots_adjust(bottom = 0.2)
# set position ax2 axis
offset = -40
new_fixed_axis = ax2.get_grid_helper().new_fixed_axis
ax2.axis['bottom'] = new_fixed_axis(loc = 'bottom',
axes = ax2,
offset = (0, offset))
ax2.axis['bottom'].toggle(all = True)
ax2.plot(x[1], y)
ax2.set_xlabel("Velocity km/hr")
plt.show()
Result
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()
I'm trying to create a radar chart using Python / Matplotlib where measured data can be "played back" using matplotlib's built in animation module. I want the data points to move along their respective axes as the data set is traversed. I have problems reading the data and updating the chart, nor am I able to find an example of this.
I have attached a piece of code that should give you an idea of what I am trying to achieve:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from math import pi
class SubplotAnimation(animation.TimedAnimation):
def __init__(self, data):
self.data = data
fig = plt.figure()
ax = fig.add_subplot(111, projection='polar')
# Figure definition
cat = ['A', 'B', 'C', 'D', 'E']
values = [10, 10, 10, 10, 10]
N = len(cat)
x_as = [n / float(N) * 2 * pi for n in range(N)]
# Because our chart will be circular we need to append a copy of
# the first value of each list at the end of each list with data
values += values[:1]
x_as += x_as[:1]
plt.rc('axes', linewidth=0.5, edgecolor='#888888') # Set color of axes
# Create polar plot
ax = plt.subplot(111, projection='polar')
# Set clockwise rotation. That is:
ax.set_theta_offset(pi / 2)
ax.set_theta_direction(-1)
# Set position of y-labels
ax.set_rlabel_position(0)
# Set color and linestyle of grid
ax.xaxis.grid(True, color="#888888", linestyle='solid', linewidth=0.5)
ax.yaxis.grid(True, color="#888888", linestyle='solid', linewidth=0.5)
# Set number of radial axes and remove labels
plt.xticks(x_as[:-1], [])
# Set yticks
plt.yticks([20, 40, 60, 80, 100], ["20", "40", "60", "80", "100"])
# Set axes limits
plt.ylim(0, 100)
# Draw ytick labels to make sure they fit properly
for i in range(N):
angle_rad = i / float(N) * 2 * pi
if angle_rad == 0:
ha, distance_ax = "center", 10
elif 0 < angle_rad < pi:
ha, distance_ax = "left", 1
elif angle_rad == pi:
ha, distance_ax = "center", 1
else:
ha, distance_ax = "right", 1
ax.text(angle_rad, 100 + distance_ax, cat[i], size=10,
horizontalalignment=ha, verticalalignment="center")
animation.TimedAnimation.__init__(self, fig, interval=25, blit=True)
def new_frame_seq(self):
return iter(range(len(self.data)))
def _draw_frame(self, framedata):
ax.plot(ax, framedata)
testdata = [[10, 20, 30, 40, 50],
[10, 20, 30, 40, 50],
[40, 50, 60, 70, 80],
[40, 50, 60, 70, 80],
[50, 60, 70, 80, 90]]
ani = SubplotAnimation(testdata)
plt.show()
Any tips on how to make this work will be greatly appreciated!
It's not clear what the aim of subclassing TimedAnimation would be. It makes things much too complicated.
Here is a simple example of an animated radar plot using FuncAnimation.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig = plt.figure(figsize=(4,4))
ax = fig.add_subplot(111, projection='polar')
ax.set_ylim(0,100)
data = np.random.rand(50)*6+2
theta = np.linspace(0,2.*np.pi, num=50)
l, = ax.plot([],[])
def update(i):
global data
data += (np.random.rand(50)+np.cos(i*2.*np.pi/50.))*2
data[-1] = data[0]
l.set_data(theta, data )
return l,
ani = animation.FuncAnimation(fig, update, frames=50, interval=200, blit=True)
plt.show()