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()
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'd like to adapt my plotting code in order to show min/max bar as depicted in the figure below:
My code is:
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("white")
sns.set_style('darkgrid',{"axes.facecolor": ".92"}) # (1)
sns.set_context('notebook')
Delay = ['S1', 'S2', 'S3', 'S4']
Time = [87, 66, 90, 55]
df = pd.DataFrame({'Delay':Delay,'Time':Time})
print("Accuracy")
display(df) # in jupyter
fig, ax = plt.subplots(figsize = (8,6))
x = Delay
y = Time
plt.xlabel("Delay", size=14)
plt.ylim(-0.3, 100)
width = 0.1
for i, j in zip(x,y):
ax.bar(i,j, edgecolor = "black",
error_kw=dict(lw=1, capsize=1, capthick=1))
ax.set(ylabel = 'Accuracy')
from matplotlib import ticker
ax.yaxis.set_major_locator(ticker.MultipleLocator(10))
plt.savefig("Try.png", dpi=300, bbox_inches='tight')
The code produce this figure:
The min/max I want to add is for:
87 (60-90)
66 (40-70)
90 (80-93)
55 (23-60)
Thanks in advance for help.
This answer expands on the code from your previous question, by including examples for seaborn.barplot and ax.bar.
Also see Different ways of specifying error bars & matplotlib.pyplot.errorbar
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# set edgecolor param (this is a global setting, so only set it once)
plt.rcParams["patch.force_edgecolor"] = True
# setup the dataframe
Delay = ['S1', 'S2', 'S3', 'S4']
Time = [87, 66, 90, 55]
df = pd.DataFrame({'Delay':Delay,'Time':Time})
# create a dict for the errors
error = {87: {'max': 90,'min': 60}, 66: {'max': 70,'min': 40}, 90: {'max': 93,'min': 80}, 55: {'max': 60,'min': 23}}
seaborn.barplot
seaborn.barplot will add error bars automatically, as shown in the examples at the link. However, this is specific to using many data points. In this case, a value is being specified as the error, the error is not being determined from the data.
When error bars are added in this way, the capsize parameter can be specified, to add horizontal lines at the top and bottom of the error bar.
# plot the figure
fig, ax = plt.subplots(figsize=(8, 6))
sns.barplot(x='Delay', y='Time', data=df, ax=ax)
# add the lines for the errors
for p in ax.patches:
x = p.get_x() # get the bottom left x corner of the bar
w = p.get_width() # get width of bar
h = p.get_height() # get height of bar
min_y = error[h]['min'] # use h to get min from dict z
max_y = error[h]['max'] # use h to get max from dict z
plt.vlines(x+w/2, min_y, max_y, color='k') # draw a vertical line
As noted in the answer from gepcel, the yerr parameter can be used to explicitly provide errors to the API.
However, the format of your errors is not correct for the parameter. yerr expects the values to be in relation to the top of the bar
S1 is 87, with min or 60 and max of 90. Therefore, ymin is 27, (87-60), and ymax is 3, (90-87).
The seaborn.barplot capsize parameter doesn't seem to work with yerr, so you must set the matplotlib 'errorbar.capsize' rcParmas. See Matplotlib Errorbar Caps Missing
# set capsize param (this is a global setting, so only set it once)
plt.rcParams['errorbar.capsize'] = 10
# create dataframe as shown by gepcel
Delay = ['S1', 'S2', 'S3', 'S4']
Time = [87, 66, 90, 55]
_min = [60, 40, 80, 23]
_max = [90, 70, 93, 60]
df = pd.DataFrame({'Delay':Delay,'Time':Time, 'Min': _min, 'Max': _max})
# create ymin and ymax
df['ymin'] = df.Time - df.Min
df['ymax'] = df.Max - df.Time
# extract ymin and ymax into a (2, N) array as required by the yerr parameter
yerr = df[['ymin', 'ymax']].T.to_numpy()
# plot with error bars
fig, ax = plt.subplots(figsize=(8, 6))
sns.barplot(x='Delay', y='Time', data=df, yerr=yerr, ax=ax)
pandas.DataFrame.plot.bar
fig, ax = plt.subplots(figsize=(8, 6))
df.plot.bar(x='Delay', ax=ax)
for p in ax.patches:
x = p.get_x() # get the bottom left x corner of the bar
w = p.get_width() # get width of bar
h = p.get_height() # get height of bar
min_y = error[h]['min'] # use h to get min from dict z
max_y = error[h]['max'] # use h to get max from dict z
plt.vlines(x+w/2, min_y, max_y, color='k') # draw a vertical line
ax.bar
fig, ax = plt.subplots(figsize=(8, 6))
ax.bar(x='Delay', height='Time', data=df)
for p in ax.patches:
x = p.get_x() # get the bottom left x corner of the bar
w = p.get_width() # get width of bar
h = p.get_height() # get height of bar
min_y = error[h]['min'] # use h to get min from dict z
max_y = error[h]['max'] # use h to get max from dict z
plt.vlines(x+w/2, min_y, max_y, color='k') # draw a vertical line
You can use yerr arg of plt.bar directly. Using #Trenton McKinney's code for an example:
import pandas as pd
import matplotlib.pyplot as plt
# setup the dataframe
Delay = ['S1', 'S2', 'S3', 'S4']
Time = [87, 66, 90, 55]
_min = [60, 40, 80, 23]
_max = [90, 70, 93, 60]
df = pd.DataFrame({'Delay':Delay,'Time':Time, 'Min': _min, 'Max': _max})
df = (df.assign(yerr_min = df.Time-df.Min)
.assign(yerr_max=df.Max-df.Time))
plt.figure(figsize=(8, 6))
plt.bar(x='Delay', height='Time', yerr=df[['yerr_min', 'yerr_max']].T.values, capsize=10, data=df)
plt.show()
Here's a solution using yerr and numpy. It has less boilerplate code than #gepcel's.
import matplotlib.pyplot as plt
import numpy as np
Delay = ['S1', 'S2', 'S3', 'S4'] # Categories
Time = [87, 66, 90, 55]
_min = [60, 40, 80, 23]
_max = [90, 70, 93, 60]
plt.figure(figsize=(8, 6))
yerr = [np.subtract(Time, _min), np.subtract(_max, Time)]
plt.bar(Delay, Time, yerr=yerr, capsize=10)
plt.show()
Hey i have been trying to increase the fourth bar on my bar graph by three everytime the animattion_frame function runs, but no matter what i do it does not want to work.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
N = 5
menMeans = [20, 35, 30, 35, 27]
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars: can also be len(x) sequence
fig, ax1 = plt.subplots()
#plt.bar(ind, menMeans, width)
ax=plt.yticks(np.arange(0, 81, 10))
def animation_frame(ind,i,menMeans):
#x_data.append(i * 10)
menMeans[3]=menMeans[3]+3
ax=plt.clear()
ax=plt.bar(ind,menMeans,width)
return ax
animation = FuncAnimation(fig,fargs=(menMeans,ind), func=animation_frame, interval=100)
plt.show()
There are a lot of weird things going on with your code, but I tried to salvage most of it. Is this what you are trying to achieve?
N = 5
menMeans = [20, 35, 30, 35, 27]
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars: can also be len(x) sequence
fig, ax1 = plt.subplots()
ax1.set_yticks(np.arange(0, 81, 10))
def animation_frame(i, menMeans, ind):
menMeans[3] += 3
ax1.cla()
ret = ax1.bar(ind,menMeans,width)
return ret,
animation = FuncAnimation(fig,fargs=(menMeans, ind), func=animation_frame, interval=100)
plt.show()
import matplotlib.pyplot as plt
gridnumber = range(1,4)
b1 = plt.bar(gridnumber, [0.2, 0.3, 0.1], width=0.4,
label="Bar 1", align="center")
b2 = plt.bar(gridnumber, [0.3, 0.2, 0.2], color="red", width=0.4,
label="Bar 2", align="center")
plt.ylim([0,0.5])
plt.xlim([0,4])
plt.xticks(gridnumber)
plt.legend()
plt.show()
Currently b1 and b2 overlap each other. How do I plot them separately like so:
There is an example in the matplotlib site. Basically, you just shift the x values by width. Here is the relevant bit:
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig = plt.figure()
ax = fig.add_subplot(111)
rects1 = ax.bar(ind, menMeans, width, color='royalblue', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind+width, womenMeans, width, color='seagreen', yerr=womenStd)
# add some
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels( ('G1', 'G2', 'G3', 'G4', 'G5') )
ax.legend( (rects1[0], rects2[0]), ('Men', 'Women') )
plt.show()
Below answer will explain each and every line of code in the simplest manner possible:
# Numbers of pairs of bars you want
N = 3
# Data on X-axis
# Specify the values of blue bars (height)
blue_bar = (23, 25, 17)
# Specify the values of orange bars (height)
orange_bar = (19, 18, 14)
# Position of bars on x-axis
ind = np.arange(N)
# Figure size
plt.figure(figsize=(10,5))
# Width of a bar
width = 0.3
# Plotting
plt.bar(ind, blue_bar , width, label='Blue bar label')
plt.bar(ind + width, orange_bar, width, label='Orange bar label')
plt.xlabel('Here goes x-axis label')
plt.ylabel('Here goes y-axis label')
plt.title('Here goes title of the plot')
# xticks()
# First argument - A list of positions at which ticks should be placed
# Second argument - A list of labels to place at the given locations
plt.xticks(ind + width / 2, ('Xtick1', 'Xtick3', 'Xtick3'))
# Finding the best position for legends and putting it
plt.legend(loc='best')
plt.show()
Sometimes could be tricky to find the right bar width. I usually use this np.diff to find the right dimension.
import numpy as np
import matplotlib.pyplot as plt
#The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = [5.5,6,7,8.5,8.9]
#Calculate optimal width
width = np.min(np.diff(indices))/3
fig = plt.figure()
ax = fig.add_subplot(111)
# matplotlib 3.0 you have to use align
ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin',align='edge')
ax.bar(indices,menMeans,width,color='r',label='Ymax',align='edge')
ax.set_xlabel('Test histogram')
plt.show()
# matplotlib 2.0 (you could avoid using align)
# ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin')
# ax.bar(indices,menMeans,width,color='r',label='Ymax')
This is the result:
What if my indices on my x axis are nominal values like names:
#
import numpy as np
import matplotlib.pyplot as plt
# The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = range(len(womenMeans))
names = ['Asian','European','North Amercian','African','Austrailian','Martian']
# Calculate optimal width
width = np.min(np.diff(indices))/3.
fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(indices-width/2.,womenMeans,width,color='b',label='-Ymin')
ax.bar(indices+width/2.,menMeans,width,color='r',label='Ymax')
#tiks = ax.get_xticks().tolist()
ax.axes.set_xticklabels(names)
ax.set_xlabel('Test histogram')
plt.show()
Here are two examples of creating a side-by-side bar chart when you have more than two "categories" in a group.
Manual Method
Manually set the position and width of each bar.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = np.array([.01, .05, .10, .25])
# Coin values times *n* coins
# This controls how many bars we get in each group
values = [worth*i for i in range(1,6)]
n = len(values) # Number of bars to plot
w = .15 # With of each column
x = np.arange(0, len(coins)) # Center position of group on x axis
for i, value in enumerate(values):
position = x + (w*(1-n)/2) + i*w
plt.bar(position, value, width=w, label=f'{i+1}x')
plt.xticks(x, coins);
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.legend()
Pandas Method
If you put the data into a pandas DataFrame, pandas will do the hard stuff for you.
import pandas as pd
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = [0.01, 0.05, 0.10, 0.25]
df = pd.DataFrame(worth, columns=['1x'], index=coins)
df['2x'] = df['1x'] * 2
df['3x'] = df['1x'] * 3
df['4x'] = df['1x'] * 4
df['5x'] = df['1x'] * 5
from matplotlib import ticker
import matplotlib.pyplot as plt
df.plot(kind='bar')
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.gca().xaxis.set_tick_params(rotation=0)
Pandas creates a similar figure...