How to dynamic plot with data from database instead of .txt? - python-3.x

I have a code that mimics a sensor, putting random values and timestamps on a txt file. From this file, being constantly updated, I can dynamic plot those values. Now my idea is to do the same thing, but getting data from a database. I'm already able to save whatever data comes for my server into the database. The thing now is the dynamic plot area.
My code for saving data into the table:
while True:
data = conn.recv(2048).decode('utf-8')
if not data:
break
#print('Servidor recebeu :', repr(data))
t = dt.datetime.now().strftime('%H:%M:%S')
c.execute('INSERT INTO edgedata VALUES (?,?)', (data, t))
con.commit()
My code for dynamic plotting from .txt file:
#!/usr/bin/env python
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
print("inside animate")
pullData = open("datatest.txt","r").read()
dataArray = pullData.split('\n')
xar = []
yar = []
for eachLine in dataArray:
if len(eachLine)>1:
x,y = eachLine.split(',')
xar.append(str(x))
yar.append(float(y))
ax1.clear()
ax1.plot(xar,yar)
plt.xlabel('Hora')
plt.xticks(rotation=45, ha='right')
plt.subplots_adjust(bottom=0.30)
plt.ylabel('Valor Dado')
plt.title('Pseudo-Sensor x Hora')
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
Any help will be appreciated. Thanks in advance!

This code did the job for me. Thanks
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
print("inside animate")
con = sqlite3.connect('edgedb')
c = con.cursor()
c.execute('SELECT data, timestamp FROM edgedata')
data = c.fetchall()
datas = []
dates = []
for row in data:
datas.append(row[1])
dates.append(float(row[0]))
ax1.clear()
ax1.plot_date(datas,dates,'-')
plt.xlabel('Hora')
plt.xticks(rotation=45, ha='right')
plt.subplots_adjust(bottom=0.30)
plt.ylabel('Valor Dado')
plt.title('Pseudo-Sensor x Hora')
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()

Related

Using matplotlib and PyQt5

I'm writing code to update a matplotlib graph in real time while embedded into a PyQt5 application. Separately, the two pieces of code (for the graph and the embedding of a still graph. Putting the two together causes a blank window with a graph to open. Upon closing the window it opens the correct application window, with the embedded graph. However, the graph is not updating, but with the line data of what SHOULD be on the graph at said time.
Removing plt.show() only causes a blank graph to appear in the embedded window. It appears that the code runs on the bank graph that opens first, and then updates it to the hidden graph in the application window.
Is there any...simple fix, or is this going to be a much longer process?
# This aint it chief
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from PyQt5.QtWidgets import QApplication, QWidget
import itertools as itrt
import matplotlib.animation as animation
class Canvas(FigureCanvas):
def __init__(self, parent):
fig, self.ax = plt.subplots()
super().__init__(fig)
self.setParent(parent)
# creates figure "ax" with a grid
self.ax.grid()
# creates time
t = []
# sets time as xdata
xdata = t
# ydata inputs
ydataps1 = []
ydataps2 = []
ydataps3 = []
ydataps4 = []
ydatads1 = []
# generates data for the graph
def datagen():
# creates timer on the x-axis
for cnt in itrt.count():
t = cnt / 10
# y value inputs
ps1y = np.log(np.pi * t)
ps2y = np.log(5 * np.pi * t)
ps3y = np.log(3 * np.pi * t)
ps4y = np.log(2 * np.pi * t)
ds1y = np.log(1.5 * np.pi * t)
# yields data to move it to run function
yield t, ps1y, ps2y, ps3y, ps4y, ds1y
# # creates lines with line-width two, at points t and y[]
lineps1, = self.ax.plot(t, [], lw=2)
lineps2, = self.ax.plot(t, [], lw=2)
lineps3, = self.ax.plot(t, [], lw=2)
lineps4, = self.ax.plot(t, [], lw=2)
lineds1, = self.ax.plot(t, [], lw=2)
# init function, clears line data and sets the line data to be t and y[]
def init():
# clear data
del xdata[:]
del ydataps1[:]
del ydataps2[:]
del ydataps3[:]
del ydataps4[:]
del ydatads1[:]
# set line data to cleared
lineps1.set_data(xdata, ydataps1)
lineps2.set_data(xdata, ydataps2)
lineps3.set_data(xdata, ydataps3)
lineps4.set_data(xdata, ydataps4)
lineds1.set_data(xdata, ydatads1)
# return updated values
return lineps1, lineps2, lineps3, lineps4, lineds1
# updates values for data
def run(data):
# time (t) and y = data
t, yps1, yps2, yps3, yps4, yds1 = data
# update x to be set to time
xdata.append(t)
# update ydatas to new y values
ydataps1.append(yps1)
ydataps2.append(yps2)
ydataps3.append(yps3)
ydataps4.append(yps4)
ydatads1.append(yds1)
# auto-scaling (kinda)
xmin, xmax = self.ax.get_xlim()
ymin, ymax = self.ax.get_ylim()
# compares all y data to ensure the graph scales on the highest value
ydata_list = [yps1, yps2, yps3, yps4, yds1]
max_value = max(ydata_list)
# y scale
if max_value >= ymax:
self.ax.set_ylim(ymin, 2*ymax)
self.ax.figure.canvas.draw()
# Time autoscale
if t >= xmax:
self.ax.set_xlim(xmin, 2*xmax)
self.ax.figure.canvas.draw()
# updates lines
lineps1.set_data(xdata, ydataps1)
lineps2.set_data(xdata, ydataps2)
lineps3.set_data(xdata, ydataps3)
lineps4.set_data(xdata, ydataps4)
lineds1.set_data(xdata, ydatads1)
# returns updated line values
return lineps1, lineps2, lineps3, lineps4, lineds1
# creates an animation function which runs all the functions in a loop
ani = animation.FuncAnimation(fig, run, datagen, interval=1, init_func=init)
# show graph, absolutely necessary
plt.show(block=True)
class AppDemo(QWidget) :
def __init__(self):
super().__init__()
self.resize(1600, 800)
chart = Canvas(self)
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()
sys.exit(app.exec_())

How to set Timestamp (pandas Datetime) to xlim of plot with FunctAnimation?

I want to limit my graph from left and set (the current time - 2 hours) to xlim. I tried to add to the "update" function this
self.ax.set_xlim(left=max(self.data.iloc[0, 0], self.data.iloc[-1, 0] - pd.Timedelta(hours=2)))
But this is doesn't work. Could anyone help me do this, please?
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from datetime import datetime
import pandas as pd
def to_pd(wt, wf):
p = pd.DataFrame({"Date": [wt], "Cost": [wf]})
p["Date"] = pd.to_datetime(p["Date"], format='%Y-%m-%d %H:%M:%S')
return p
fig = plt.figure(figsize=(18,8), dpi=90)
class Graph():
def __init__(self):
self.ax = fig.add_subplot()
self.start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.data = to_pd(self.start_time,0)
self.line, = self.ax.plot(self.data.Date,self.data.Cost)
def update(self,i):
self.current_time = (self.data.iloc[-1,0]+pd.Timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
self.data = self.data.append(to_pd(self.current_time,(-1)**i))
self.line.set_data(self.data.Date, self.data.Cost)
self.ax.relim()
self.ax.autoscale_view()
return self.line,
object = Graph()
def animate(i):
return object.update(i)
anim = FuncAnimation(fig, animate, frames=200,interval=100, blit=True)
plt.show()
Solved, but I think there is more convenient way. But, here what am done, just added it into the 'update' function, it's clear all old data:
if self.data.iloc[-1, 0] - pd.Timedelta(hours=2) >= self.data.iloc[0, 0]:
self.data = self.data[self.data.Date > self.data.iloc[0, 0]]

prevent camera resetting after plotting with Plotly-python

I am trying to plot some data for a 3d Quiver or Cone using dash and plotly and I want to update the Graph periodically through an interval Input!
So I managed to animate the graph but the problem is that the camera angle and zoom keep resetting after each update.
i have the following code:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from dash.dependencies import Output, Input
import pickle
#reading initial data
with open("shared.pkl", "rb") as f:
quivDic = pickle.load(f)
quiver_3d = go.Cone(x = quivDic["X"], y = quivDic["Y"], z = quivDic["Z"],
u = quivDic["U"], v = quivDic["V"], w = quivDic["W"],
colorscale = 'Blues', name = "testScatter")
data = [quiver_3d]
layout = dict(title ="Test Quiver", showlegend=False, aspectratio=dict(x=1, y=1, z=0.8),
camera_eye=dict(x=1.2, y=1.2, z=0.6))
fig = dict(data=data, layout=layout)
app = dash.Dash()
app.layout = html.Div([
html.Div(html.H4("TEST CONE")),
html.Div(dcc.Graph(id = "testCone", figure=fig)),
dcc.Interval(
id='graph-update',
interval=1000,
n_intervals = 0
),
])
#app.callback(Output('testCone', 'figure'),
[Input('graph-update', 'n_intervals')])
def refresh(n):
#reading new data
with open("shared.pkl", "rb") as f:
quivDic = pickle.load(f)
quiver_3d.x = quivDic["X"]
quiver_3d.y = quivDic["Y"]
quiver_3d.z = quivDic["Z"]
quiver_3d.u = quivDic["U"]
quiver_3d.v = quivDic["V"]
quiver_3d.w = quivDic["W"]
data = [quiver_3d]
#creating new figure
fig = dict(data=data)
return fig
app.run_server(debug=True)
Does anyone know how to avoid this problem?
Ideally I'd like to update the data without redrawing the whole frame, something like "set_data" from matplotlib. Otherwise is there a way to keep track of the latest camera angle and update the layout through the callback?
and Thanks ^^
Yes, you can use the uirevision attribute, as detailed here: https://community.plot.ly/t/preserving-ui-state-like-zoom-in-dcc-graph-with-uirevision/15793

Update plot in for loop in function

I'm trying to call a function like in the example below, and plot while running the code. The real values that I get as y-data are not really random numbers, but the point is that I would like it to get updated real-time. The plot in my example code below is just empty though and isn't getting updated.
import numpy as np
import matplotlib.pyplot as plt
import random as rnd
import time
initial_time = time.time()
def multiple_runs(number_of_runs):
x_data, y_data = [], []
fig, ax = plt.subplots()
sc = ax.scatter(x_data, y_data)
plt.draw()
for i in range(0, number_of_runs):
x_data.append(i+1)
y_data.append(rnd.randint(0,100))
sc.set_offsets(np.c_[x_data, y_data])
fig.canvas.draw_idle()
plt.pause(0.1)
print ('Total time after run number ' + str(i+1) + ': ' + str(time.time() - initial_time))
multiple_runs(100)
UPDATE:
Thanks #ImportanceOfBeingErnest , I got the code to work. However my problem right now is that the figure closes down as soon as it's finished, is there anyway to keep it open? I tried using plt.waitforbuttonpress() but I get a strange error from QTimer, not sure how or why. This is my working example code;
import numpy as np
import matplotlib.pyplot as plt
import random as rnd
import time
initial_time = time.time()
def multiple_runs(number_of_runs):
x_data, y_data = [], []
x_data2, y_data2 = [], []
fig, ax = plt.subplots(2, sharex = True)
sc = ax[0].scatter(x_data, y_data)
sc2 = ax[1].scatter(x_data2, y_data2)
ax[0].set(xlim=(0,100), ylim=(0,100))
ax[1].set(xlim=(0,100), ylim=(0,100))
plt.draw()
for i in range(0, number_of_runs):
x_data.append(i+1)
y_data.append(rnd.randint(0,100))
x_data2.append(i+1)
y_data2.append(rnd.randint(0,100))
sc.set_offsets(np.c_[x_data, y_data])
sc2.set_offsets(np.c_[x_data2, y_data2])
fig.canvas.draw_idle()
plt.pause(0.1)
print ('Total time after run number ' + str(i+1) + ': ' + str(time.time() - initial_time))
multiple_runs(100)
UPDATE2:
I tried using FuncAnimation, but getting the error TypeError: update() missing 2 required positional arguments: 'y' and 'y2'. I still need to use the for-loop because in my real code I'm using the previous values of y, to calculate the next values of y. This is my example code which is giving me the error;
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import random as rnd
import time
initial_time = time.time()
def multiple_runs(number_of_runs):
x_data, y_data = [], []
x_data2, y_data2 = [], []
fig, ax = plt.subplots(2, sharex = True)
sc = ax[0].scatter(x_data, y_data)
sc2 = ax[1].scatter(x_data2, y_data2)
ax[0].set(xlim=(0,100), ylim=(0,100))
ax[1].set(xlim=(0,100), ylim=(0,100))
def update(i, y, y2):
x_data.append(i+1)
y_data.append(y)
x_data2.append(i+1)
y_data2.append(y2)
sc.set_offsets(np.c_[x_data, y_data])
sc2.set_offsets(np.c_[x_data2, y_data2])
print ('Total time after run number ' + str(i+1) + ': ' + str(time.time() - initial_time))
for i in range(0, number_of_runs):
y = rnd.randint(0,100)
y2 = rnd.randint(0,100)
update(i,y,y2)
ani = FuncAnimation(fig, update, frames=number_of_runs, interval=100, repeat=False)
plt.show()
multiple_runs(100)
As commented, I would recommend to use FuncAnimation. This would look as follows in your case. Note that in order to close the window, one would need to press q or close it with the mouse.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import random as rnd
import time
initial_time = time.time()
def multiple_runs(number_of_runs):
x_data, y_data = [], []
x_data2, y_data2 = [], []
fig, ax = plt.subplots(2, sharex = True)
sc = ax[0].scatter(x_data, y_data)
sc2 = ax[1].scatter(x_data2, y_data2)
ax[0].set(xlim=(0,100), ylim=(0,100))
ax[1].set(xlim=(0,100), ylim=(0,100))
def get_ydata(i):
y = rnd.randint(0,100)
y2 = rnd.randint(0,100)
return y, y2
def update(i):
y, y2 = get_ydata(i)
x_data.append(i+1)
y_data.append(y)
x_data2.append(i+1)
y_data2.append(y2)
sc.set_offsets(np.c_[x_data, y_data])
sc2.set_offsets(np.c_[x_data2, y_data2])
ani = FuncAnimation(fig, update, frames=number_of_runs, interval=100, repeat=False)
plt.show()
multiple_runs(100)

Main thread not in main loop error in threading module

import time
import datetime as dt
import urllib.request
from bs4 import BeautifulSoup
import matplotlib.pyplot as plt
import matplotlib.animation as Animation
from matplotlib import style
import matplotlib
import csv
import threading
style.use('fivethirtyeight')
fig = plt.figure()
def usd_in_bitcoin():
try:
resp = urllib.request.urlopen("https://bitcoinwisdom.com/")
except Exception as e:
print(e)
text = resp.read()
soup = BeautifulSoup(text, 'html.parser')
intermediate = soup.find('tr', {"id": "o_btcusd"})
ans = intermediate.find('td', {'class': 'r'})
return ans.contents[0]
def write_to_file(interval):
while True:
value = str(usd_in_bitcoin())
unix_time = str(time.time())
print(unix_time, value)
with open('bitcoin_usd.csv', 'a+') as file:
file.write(unix_time)
file.write("," + str(value))
file.write('\n')
time.sleep(interval)
def animate(i):
with open('bitcoin_usd.csv') as csv_file:
readcsv = csv.reader(csv_file, delimiter=',')
xs = []
ys = []
for row in readcsv:
if len(row) > 1:
x, y = [float(s) for s in row]
xs.append(dt.datetime.fromtimestamp(x))
ys.append(y)
print(len(xs))
dates = matplotlib.dates.date2num(xs)
# print(dates)
fig.clear()
plt.plot_date(dates, ys)
def plotting():
ani = Animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
def main():
# plotting()
b = threading.Thread(name='making graph', target=plotting)
# a = threading.Thread(name='updating_csv', target=write_to_file, args=(5,))
# a.start()
b.start()
if __name__ == '__main__':
main()
In the above block of code, I am trying to plot the value of a bitcoin in usd by using scraping and then putting the value in a csv file.
Then I read the csv file to plot the graph.
Both plotting and scraping seem to work fine but if I do both of them simultaneously, I am getting an error saying main thread not in main loop. I searched a lot but was not able to solve this problem
The problem here is with the sequence of lines in main()
Try this:
def main():
a = threading.Thread(name='updating_csv', target=write_to_file, args=(5,))
a.start()
b = threading.Thread(name='making graph', target=plotting)
b.start()
plotting()

Resources