I'm using a Raspberry Pi to plot live data from serial, but eventually run out of memory. I'm not sure if/how I can close the figure, but still have a live data display.
Would it be possible to create and close a new figure with every animate?
My code at the moment:
import serial
import matplotlib
# Force matplotlib to not use any Xwindows backend.
matplotlib.use('TkAgg') #comment out for debugging
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import gc
# Create figure for plotting
fig = plt.figure()
xs = []
ysAC = []
ysDC = []
ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
ser.flush()
# This function is called periodically from FuncAnimation
def animate(i, xs, ysAC, ysDC):
values = getValues()
wAC = values[1]
wDC = values[2]
# Add x and y to lists
xs.append(i)
ysAC.append(wAC)
ysDC.append(wDC)
# Limit x and y lists to 10 items
xs = ['T-9','T-8','T-7','T-6','T-5','T-4','T-3','T-2','T-1','Now']
ysDC = ysDC[-10:]
ysAC = ysAC[-10:]
# Draw x and y lists
axRT1.clear()
if len(ysDC) == 10:
lineAC, = axRT1.plot(xs, ysAC, 'b:', label='Mains', linewidth = 4)
lineDC, = axRT1.plot(xs, ysDC, 'g--', label='Solar', linewidth = 4)
gc.collect()
#fig.clf()
#plt.close()
def getValues():
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8').rstrip()
return list(line.split(","))
# Set up plot to call animate() function periodically
ani = animation.FuncAnimation(fig, animate, fargs=(xs, ysAC, ysDC), interval=1000, blit=False)
plt.get_current_fig_manager().full_screen_toggle()
plt.ioff()
plt.show()
plt.draw()
The crude way of clearing the plots marked below fixed it for me:
import time
import serial
import datetime as dt
import matplotlib
# Force matplotlib to not use any Xwindows backend.
matplotlib.use('TkAgg') #comment out for debugging
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
import matplotlib.animation as animation
from decimal import Decimal
import pandas as pd
import numpy as np
import os.path as path
import re
import gc
import os
count = 0
# Create figure for plotting
fig = plt.figure()
fig.patch.set_facecolor('whitesmoke')
hFont = {'fontname':'sans-serif', 'weight':'bold', 'size':'12'}
xs = ['T-9','T-8','T-7','T-6','T-5','T-4','T-3','T-2','T-1','Now']
ysTemp = []
ysAC = []
ysDC = []
axRT1 = fig.add_subplot(2, 2, 1)
axRT2 = axRT1.twinx() # instantiate a second axes that shares the same x-axis
#Draw x and y lists
axRT1.clear()
axRT2.clear()
axRT1.set_ylim([0, 4])
axRT2.set_ylim([10, 70])
axRT1.set_ylabel('Power Consumption kW', **hFont)
axRT2.set_ylabel('Temperature C', **hFont)
axRT1.set_xlabel('Seconds', **hFont)
axRT1.set_title('Power Consumption and Temperature - Real Time', **hFont)
lineTemp, = axRT2.plot([], [], 'r', label='Temp', linewidth = 4)
lineAC, = axRT1.plot([], [], 'b:', label='Mains', linewidth = 4)
lineDC, = axRT1.plot([], [], 'g--', label='Solar', linewidth = 4)
fig.legend([lineAC, lineDC,lineTemp], ['Mains', 'Solar', 'Temp'], fontsize=20)
ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
ser.flush()
# This function is called periodically from FuncAnimation
def animate(i, xs, ysTemp, ysAC, ysDC):
values = getValues()
if values != 0:
temp_c = Decimal(re.search(r'\d+',values[3]).group())
if temp_c < 0: temp_c = 0
wAC = round(Decimal(re.search(r'\d+', values[1]).group())/1000, 2)
if wAC < 0.35: wAC = 0
aDC = float(re.search(r'\d+', values[2]).group()) #remove characters
vDC = float(re.search(r'\d+', values[4][:5]).group()) #remove characters
wDC = aDC * vDC
wDC = round(abs(Decimal(wDC))/1000, 2)
# Add x and y to lists
ysTemp.append(temp_c)
ysAC.append(wAC)
ysDC.append(wDC)
# Limit x and y lists to 10 items
ysTemp = ysTemp[-10:]
ysDC = ysDC[-10:]
ysAC = ysAC[-10:]
if len(ysTemp) == 10:
axRT2.lines = [] #This crude way of clearing the plots worked
axRT1.lines =[] #This crude way of clearing the plots worked
lineTemp, = axRT2.plot(xs, ysTemp, 'r', label='Temp', linewidth = 4)
lineAC, = axRT1.plot(xs, ysAC, 'b:', label='Mains', linewidth = 4)
lineDC, = axRT1.plot(xs, ysDC, 'g--', label='Solar', linewidth = 4)
def getValues():
measureList = 0
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8').rstrip()
print(line)
if line.count(',') == 4:
measureList = list(line.split(","))
return measureList
# Set up plot to call animate() function periodically
ani = animation.FuncAnimation(fig, animate, fargs=(xs, ysTemp, ysAC, ysDC), interval=1000, blit=False)
plt.get_current_fig_manager().full_screen_toggle()
plt.ioff()
plt.show()
plt.draw()
Related
I'm very early in making a simple graph to plot the temperature and humidity of a given space however, the graph plots both lines but they don't intersect instead they do something like in the image below:
here's the code:
import matplotlib.pyplot as plt
from itertools import count
from matplotlib.animation import FuncAnimation
import serial
import matplotlib as mpl
import numpy as np
x = []
line1 = []
line2 = []
serialPort = serial.Serial(port = "COM3", baudrate=115200,
bytesize=8, timeout=1, stopbits=serial.STOPBITS_ONE)
serialString = ""
index = count()
def animate(i):
if serialPort.in_waiting > 0:
serialString = serialPort.readline()
string = serialString.decode('Ascii')
line1.append(string.split(' ')[0])
line2.append(string.split(' ')[1])
x.append(next(index))
print(len(line1))
if len(line1) >= 1000:
line1.pop(0)
line2.pop(0)
x.pop(0)
plt.cla()
plt.plot(x, line1, label='Temperature')
plt.plot(x, line2, label='Humidity')
ani = FuncAnimation(plt.gcf(), animate, interval=400)
plt.tight_layout()
plt.show()
I try to create a live graph with matplotlib animation. below code report error:
UnboundLocalError: local variable 'lines' referenced before assignment
lines is defined above!
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
queue = []
qSize = 20
N = 3
lines = []
xidx = list(range(0, qSize))
def init():
for i in range(qSize):
queue.append([0]*N)
for i in range(N):
xdata = []
ydata = []
line, = ax.plot(xdata, ydata,label=str(i))
lines.append([line,xdata,ydata])
ax.legend(loc=2)
plt.xticks(rotation=45, ha='right')
#plt.subplots_adjust(bottom=0.30)
plt.title('Demo')
plt.ylabel('x')
plt.ylabel('Value')
return
def tick():
arr = []
for i in range(N):
d = random.randint(i*10,i*10+5)
arr.append(d)
queue.append(arr)
lines = lines[-qSize:]
return
def animate(i):
tick()
df = pd.DataFrame(data)
ax.set_xlim(0,qSize)
ax.set_ylim(df.min().min(),df.max().max())
for i,colname in enumerate(df.columns):
line,xdata,ydata = lines[i]
xdata = xidx
ydata = df[colname]
line.set_xdata(xdata)
line.set_ydata(ydata)
plt.draw()
return
init()
ani = animation.FuncAnimation(fig, animate, fargs=(), interval=1000)
plt.show()
I'm new to Python and I'm trying to combine two scripts together. The first script reads a value from a sensor and writes it to a .csv file.
#!/usr/bin/python
import csv
import spidev
import time
#Define Variables
x_value = 0
pad_value = 0
delay = 0.1
pad_channel = 0
fieldnames = ["x_value", "pad_value"]
#Create SPI
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz=1000000
def readadc(adcnum):
# read SPI data from the MCP3008, 8 channels in total
if adcnum > 7 or adcnum < 0:
return -1
r = spi.xfer2([1, 8 + adcnum << 4, 0])
data = ((r[1] & 3) << 8) + r[2]
return data
#Write headers
with open('data.csv', 'w') as csv_file:
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
csv_writer.writeheader()
#Write values
while True:
with open('data.csv', 'a') as csv_file:
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
info = {
"x_value": x_value,
"pad_value": pad_value
}
csv_writer.writerow(info)
#Update values
x_value += 1
pad_value = readadc(pad_channel)
print(x_value, pad_value)
time.sleep(delay)
The second script reads the .csv file and plots the data to a graph using matplotlib.
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
plt.style.use('fivethirtyeight')
def animate(i):
data = pd.read_csv('data.csv')
x = data['x_value'].tail(600)
y = data['pad_value'].tail(600)
plt.cla()
plt.plot(x, y)
ani = FuncAnimation(plt.gcf(), animate, interval=100)
plt.tight_layout()
plt.show()
I can run both scripts separately and they work, but I want to combine them into a single script.
I've tried to merge them, but when it gets to plt.show() it shows the graph but doesn't continue. I tried plt.show(block=False), which continues, but doesn't show the graph.
#!/usr/bin/python
import csv
import spidev
import time
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
#Define Variables
x_value = 0
pad_value = 0
delay = 0.1
pad_channel = 0
fieldnames = ["x_value", "pad_value"]
#Create SPI
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz=1000000
def readadc(adcnum):
# read SPI data from the MCP3008, 8 channels in total
if adcnum > 7 or adcnum < 0:
return -1
r = spi.xfer2([1, 8 + adcnum << 4, 0])
data = ((r[1] & 3) << 8) + r[2]
return data
#Animate graph
def animate(i):
data = pd.read_csv('data.csv')
x = data['x_value']
y = data['pad_value']
plt.cla()
plt.plot(x, y)
plt.style.use('fivethirtyeight')
plt.tight_layout()
plt.show(block=False)
#Write headers to CSV file
with open('data.csv', 'w') as csv_file:
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
csv_writer.writeheader()
#Append values to CSV file
while True:
with open('data.csv', 'a') as csv_file:
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
info = {
"x_value": x_value,
"pad_value": pad_value
}
csv_writer.writerow(info)
#Update values
x_value += 1
pad_value = readadc(pad_channel)
print(x_value, pad_value)
time.sleep(delay)
plt.style.use('fivethirtyeight')
ani = FuncAnimation(plt.gcf(), animate, interval=100)
plt.tight_layout()
plt.show(block=False)
Is there a simple way to combine these two?
You can interactively change data on the canvas by setting the plot to interactive mode like this:
import matplotlib.pyplot as plt
import numpy as np
import time
phase = 0
delay = 0.1
t = 0
x = np.linspace(t, t+2, 200)
data = 0.5*np.sin(x)+.5
#Setting interactive mode on plt
plt.ion()
#create new figure
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_ylim((-1.1, 1.1))
line1, = ax.plot(x, data, 'b-')
while True:
#I generate some random data, you will use your data (from the csv)
x = np.linspace(t, t+2, 200)
data = np.sin(x)
line1.set_ydata(data)
line1.set_xdata(x)
#Set the xlim to contain new data
ax.set_xlim((x[0], x[-1]))
#This shows newly set xdata and ydata on the plot
fig.canvas.draw()
#You need to use plt.pause otherwise the plot won't show up
plt.pause(0.001)
time.sleep(delay)
#Just another variable I used to generate new data
t += 1
See this link for the whole tutorial.
I have code that produces a live graph, updating every few seconds. It all functions EXACTLY as I want other than a single issue, the x axis keeps adding new values but never removing old ones
in the example code below, because I limit the dataframe to 6 columns, I expect to never see more than 6 measurements represented on my x-axis. Instead, the graph continues to update and eventually the poiunts are too close together.
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation
import pandas as pd
from datetime import datetime
import threading
import random
import time
measurements = ['abc','bcd','afr','reg','wow']
counter = 0
figure = pyplot.figure()
measurement_frame = pd.DataFrame(index = measurements)
def get_live(counter2, col_num):
measurement_frame.iat[counter2,col_num] = random.randint(50,80)
def add_to_dataframe():
global measurement_frame
#timey = datetime.now().strftime('%H:%M:%S')
timey = datetime.now().time()
if measurement_frame.shape[1] == 6:
measurement_frame.drop(measurement_frame.columns[0], axis = 1, inplace = True)
measurement_frame[timey] = measurements
col_num = measurement_frame.shape[1]-1
print(col_num)
counter2 = 0
for item in measurements:
t = threading.Thread(target=get_live, args=(counter2, col_num,))
t.start()
counter2 = counter2 +1
t.join()
print(measurement_frame.columns[0])
time.sleep(1)
def update(frame):
add_to_dataframe()
x_data = measurement_frame.columns
print(x_data[0])
y1_data = measurement_frame.loc[measurement_frame.index[0]]
y2_data = measurement_frame.loc[measurement_frame.index[1]]
y3_data = measurement_frame.loc[measurement_frame.index[2]]
y4_data = measurement_frame.loc[measurement_frame.index[3]]
y5_data = measurement_frame.loc[measurement_frame.index[4]]
line, = pyplot.plot_date(x_data, y1_data, '-', color = 'b')
line2, = pyplot.plot_date(x_data, y2_data, '-', color = 'g')
line3, = pyplot.plot_date(x_data, y3_data, '-', color = 'r')
line4, = pyplot.plot_date(x_data, y4_data, '-', color = 'm')
line5, = pyplot.plot_date(x_data, y5_data, '-', color = 'y')
line.set_data(x_data, y1_data)
line2.set_data(x_data, y2_data)
line3.set_data(x_data, y3_data)
line4.set_data(x_data, y4_data)
line5.set_data(x_data, y5_data)
figure.gca().set_xlim(x_data[0])
figure.gca().autoscale()
print(figure.gca().get_xlim())
return line, line2, line3, line4, line5,
animation = FuncAnimation(figure, update, interval=1000)
pyplot.show()
What I need is that after the dataframe reaches maximum size, the far left measurements are removed, so as to not exceed a set number of measurements on the screen at once. Note that the dataframe already drops unneeded columns before adding a new one when it reaches a certain size, but my graph does not reflect that
using autoscale tries to keep old data in view. If you drop autoscale and use
figure.gca().set_xlim(left =x_data[0], right = datetime.now().time())
it works as intended
the full code is now
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation
import pandas as pd
from datetime import datetime
import threading
import random
import time
measurements = ['abc','bcd','afr','reg','wow']
counter = 0
figure = pyplot.figure()
measurement_frame = pd.DataFrame(index = measurements)
def get_live(counter2, col_num):
measurement_frame.iat[counter2,col_num] = random.randint(50,80)
def add_to_dataframe():
global measurement_frame
#timey = datetime.now().strftime('%H:%M:%S')
timey = datetime.now().time()
if measurement_frame.shape[1] == 6:
measurement_frame.drop(measurement_frame.columns[0], axis = 1, inplace = True)
measurement_frame[timey] = measurements
col_num = measurement_frame.shape[1]-1
print(col_num)
counter2 = 0
for item in measurements:
t = threading.Thread(target=get_live, args=(counter2, col_num,))
t.start()
counter2 = counter2 +1
t.join()
print(measurement_frame.columns[0])
time.sleep(1)
def update(frame):
add_to_dataframe()
x_data = measurement_frame.columns
print(x_data[0])
y1_data = measurement_frame.loc[measurement_frame.index[0]]
y2_data = measurement_frame.loc[measurement_frame.index[1]]
y3_data = measurement_frame.loc[measurement_frame.index[2]]
y4_data = measurement_frame.loc[measurement_frame.index[3]]
y5_data = measurement_frame.loc[measurement_frame.index[4]]
line, = pyplot.plot_date(x_data, y1_data, '-', color = 'b')
line2, = pyplot.plot_date(x_data, y2_data, '-', color = 'g')
line3, = pyplot.plot_date(x_data, y3_data, '-', color = 'r')
line4, = pyplot.plot_date(x_data, y4_data, '-', color = 'm')
line5, = pyplot.plot_date(x_data, y5_data, '-', color = 'y')
line.set_data(x_data, y1_data)
line2.set_data(x_data, y2_data)
line3.set_data(x_data, y3_data)
line4.set_data(x_data, y4_data)
line5.set_data(x_data, y5_data)
figure.gca().set_xlim(left =x_data[0], right = datetime.now().time())
print(figure.gca().get_xlim())
return line, line2, line3, line4, line5,
animation = FuncAnimation(figure, update, interval=1000)
pyplot.show()
I'm sorry if my title is very unclear. I am working on an animation that represents a histogram of all the numbers as it crawls through a file.
I'm trying to get a unique ID, but they're all returning the same id. Then I thought about setting a unique ID, but I'm not entirely sure how to do that. I was thinking if I could just isolate the result in the sequence to be its own bar, I could clear it and then update it.
It's really hard to imagine what's going on so here's an image:
Here is the code:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
import numpy as np
import random
from collections import defaultdict
from collections import deque
import re
color = f"#{random.randrange(0x1000000):06x}"
color_border = f"#{random.randrange(0x1000000):06x}"
range = [0,1,2,3,4,5,6,7,8,9]
digit_finder = re.compile(r'(\d+)')
fig = plt.figure()
ax = fig.add_subplot(111, projection= '3d')
ax.set_xticks(range)
#def text_crawler(file, place, bptrial):
def animate(i):
global z_line
global color
global color_border
_last = _f.readlines(20)
for line in _last:
_line = line.decode("utf-8")
final = digit_finder.findall(_line)
#if not final: continue
print(final[1])
z_line.append(int(final[1]))
for z in z_line:
squared_num = createdict(str(z))
xs = list(squared_num.keys())
ys = list(squared_num.values())
#ax.clear()
ax.bar(xs, ys, zs=z ,zdir='y', color = color, alpha=0.8,
linewidth=3, edgecolor = color_border)
ax.set_yticks(z_line)
ax.set_xticks(range)
print(Axes3D.bar)
def createdict(it):
x = defaultdict(int)
for k in it:
x[k] += 1
return x
count = 10
#final = list(map(lambda x: createdict(x), nums))
with open('graph_index.txt', 'rb') as index_reader:
i_first = index_reader.readline()
if not i_first:
print('This is where you return index = 0')
index_reader.seek(-2, 2)
while index_reader.read(1) != b"\n":
index_reader.seek(-2,1)
i_last = index_reader.readline().decode("utf-8")
index = digit_finder.findall(i_last)
if not index:
print('This is also index = 0...just in case.')
else:
print(int(index[0]), 'This should be a number, in bytes, where you will continue from.')
z_line = deque([],maxlen=7)
with open('numbers.txt', 'rb') as _f:
first = _f.readline(0)
_f.seek(int(index[0]), 0)
while _f.read(1) != b"\n":
_f.seek(-2,1)
ani = animation.FuncAnimation(fig, animate, interval=10000)
print(ani)
plt.show()```