Trying to create a program that reads serial data and updates multiple figures (1 line and 2 bar charts for now but could potentially be more).
Using 3 separate calls to FuncAnimation() right now, but proving to be really slow which is not good as I still need the option of adding more animated figures in the future.
So how can I make it a single FuncAnimation (or maybe something similar) that updates all three (potentially more) figures? Alternatively, what can I do to speed it up a bit?
#figure for current
amps = plt.figure(1)
ax1 = plt.subplot(xlim = (0,100), ylim = (0,500))
line, = ax1.plot([],[])
ax1.set_ylabel('Current (A)')
#figure for voltage
volts = plt.figure(2)
ax2 = plt.subplot()
rects1 = ax2.bar(ind1, voltV, width1)
ax2.grid(True)
ax2.set_ylim([0,6])
ax2.set_xlabel('Cell Number')
ax2.set_ylabel('Voltage (V)')
ax2.set_title('Real Time Voltage Data')
ax2.set_xticks(ind1)
#figure for temperature
temp = plt.figure(3)
ax3 = plt.subplot()
rects2 = ax3.bar(ind2, tempC, width2)
ax3.grid(True)
ax3.set_ylim([0,101])
ax3.set_xlabel('Sensor Number')
ax3.set_ylabel('temperature (C)')
ax3.set_title('Real Time Temperature Data')
ax3.set_xticks(ind2)
def updateAmps(frameNum):
try:
#error check for bad serial data
serialString = serialData.readline()
serialLine = [float(val) for val in serialString.split()]
print (serialLine)
if (len(serialLine) == 5):
voltV[int(serialLine[1])] = serialLine[2]
tempC[int(serialLine[3])] = serialLine[4]
currentA.append(serialLine[0])
if (len(currentA)>100):
currentA.popleft()
line.set_data(range(100), currentA)
except ValueError as e:
#graphs not updated for bad serial data
print (e)
return line,
#function to update real-time voltage data
def updateVolts(frameNum):
for rects, h in zip(rects1,voltV):
rects.set_height(h)
return rects1
#function to update real-time temperature data
def updateTemp(frameNum):
for rects, h in zip(rects2,tempC):
rects.set_height(h)
return rects2
Call to funcAnimation:
anim1 = animation.FuncAnimation(amps, updateAmps,
interval = 20, blit = True)
anim2 = animation.FuncAnimation(volts, updateVolts, interval = 25, blit = True)
anim3 = animation.FuncAnimation(temp, updateTemp, interval = 30, blit = True)
Either you need to create one or you can use: matplotlib.pyplot.gcf(). Which stands for “Get current figure” This is the func part that goes inside the FuncAnimation as a parameter. If you’d like to have a graph that animates you will need to define a function that updates the data that goes in the plot and also the function that creates the chart.
Animations make even more sense when we are working with projects (stock markets, ECG Anomaly Detection, Internet Traffic Forecasting) that depicts the time series data. The matplotlib.animation.FuncAnimation class is used to make animation calls recursively.
Call to funcAnimation: anim1 = animation.FuncAnimation(amps, updateAmps, interval = 20, blit = True) anim2 = animation.FuncAnimation(volts, updateVolts, interval = 25, blit = True) anim3 = animation.FuncAnimation(temp, updateTemp, interval = 30, blit = True) pythonperformancematplotlib
The matplotlib.animation.FuncAnimation class is used to make animation calls recursively. You must store the created Animation in a variable that lives as long as the animation should run.
Echoing @ImportanceOfBeingErnest's comment, the obvious solution would be to use 3 subplots and only one FuncAnimation()
call. You simply have to make sure your callback function returns a list of ALL artists to be updated at each iteration.
One drawback is that the update will happen a the same interval in all 3 subplots (contrary to the different timings you had in your example). You could potentially work around that by using global variables that count how many time the function has been called and only do some of the plots every so often for example.
#figure
fig = plt.figure(1)
# subplot for current
ax1 = fig.add_subplot(131, xlim = (0,100), ylim = (0,500))
line, = ax1.plot([],[])
ax1.set_ylabel('Current (A)')
#subplot for voltage
ax2 = fig.add_subplot(132)
rects1 = ax2.bar(ind1, voltV, width1)
ax2.grid(True)
ax2.set_ylim([0,6])
ax2.set_xlabel('Cell Number')
ax2.set_ylabel('Voltage (V)')
ax2.set_title('Real Time Voltage Data')
ax2.set_xticks(ind1)
#subplot for temperature
ax3 = fig.add_subplot(133)
rects2 = ax3.bar(ind2, tempC, width2)
ax3.grid(True)
ax3.set_ylim([0,101])
ax3.set_xlabel('Sensor Number')
ax3.set_ylabel('temperature (C)')
ax3.set_title('Real Time Temperature Data')
ax3.set_xticks(ind2)
def updateAmps(frameNum):
try:
#error check for bad serial data
serialString = serialData.readline()
serialLine = [float(val) for val in serialString.split()]
print (serialLine)
if (len(serialLine) == 5):
voltV[int(serialLine[1])] = serialLine[2]
tempC[int(serialLine[3])] = serialLine[4]
currentA.append(serialLine[0])
if (len(currentA)>100):
currentA.popleft()
line.set_data(range(100), currentA)
except ValueError as e:
#graphs not updated for bad serial data
print (e)
return line,
#function to update real-time voltage data
def updateVolts(frameNum):
for rects, h in zip(rects1,voltV):
rects.set_height(h)
return rects1
#function to update real-time temperature data
def updateTemp(frameNum):
for rects, h in zip(rects2,tempC):
rects.set_height(h)
return rects2
def updateALL(frameNum):
a = updateAmps(frameNum)
b = updateVolts(frameNum)
c = updateTemp(frameNum)
return a+b+c
animALL = animation.FuncAnimation(fig, updateALL,
interval = 20, blit = True)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With