I'm trying to optimize the plotting in matplotlib in real time. I've tried to implement through plt.plot
and plt.figure
. In the case of plt.plot
, the graph was updated due to return FigureCanvasKivyAgg(plt.gcf())
, this method copes disgustingly, after 100 points, the fps drops from 30 to 10. With plt.figure
(using plt.canvas.draw
), the situation somewhat better, the initial fps is about 70...80, it drops to 15 after about 500...600 points.
import matplotlib
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from garden_matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivy.uix.label import Label
import matplotlib.animation as animation
#-------------------------------------------------------------------------#
n = 0
x = 0
y = 0
s=0
lst = [[x],
[y]]
plt_limit_x=200
plt_limit_y=1000
fig=plt.figure()
#--------------------------------MENUSCREEN------------------_-------------#
class MenuScreen(Screen):
pass
box = ObjectProperty(None)
#------------------------------------PLOT----------------------------------#
class MyFigure(FigureCanvasKivyAgg): #Plot
def __init__(self, **kwargs):
global fig
global x
global y
y=x+x
x+=1
lst[0].append(x)
lst[1].append(y)
self.ax=fig.add_subplot(111)
self.line = self.ax.plot(lst[0], lst[1])
super(MyFigure, self).__init__(fig, **kwargs)
fig.canvas.draw()
def start_updating(self):
Clock.schedule_interval(self.Update, 1/30)
def stop_updating(self):
Clock.unschedule(self.Update)
def Update(self, *args):
global fig, x, y, lst, n
self.line.clear()
n=n+1
y=x+x
x+=1
lst[0].append(x)
lst[1].append(y)
self.line = self.ax.plot(lst[0], lst[1], color='black')
return fig.canvas.draw()
#------------------------------------MAINAPP----------------------------------#
class MainApp(MDApp):
def build(self):
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
return sm
def on_start(self):
self.fps_monitor_start()
#---------------------------------END-------------------------------------#
if __name__=='__main__':
app = MainApp()
app.run()
I decided to try to implement matplotlib.animation, following the example I wrote plt.show() derivation and everything works fine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x=0
y=0
data = ([x],[y])
def update_line(*args, **kwargs):
global x
global y
x=x+1
y=y+2
data[0].append(x)
data[1].append(y)
l.set_data(data)
#print (l)
return l,
fig1 = plt.figure()
l, = plt.plot([], [], 'r-')
plt.xlim(0, 10000)
plt.ylim(0, 10000)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, 10000, fargs=(data, l),
interval=1, blit=True)
plt.show()
But when I try to translate it into kivy, the graph does not want to be updated, there is an assumption that the problem is in the init method, but I don’t know how to implement the program without it.
import matplotlib
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from garden_matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivy.uix.label import Label
import matplotlib.animation as animation
#-------------------------------------------------------------------------#
n = 0
x = 0
y = 0
s=0
lst = [[x],
[y]]
fig=plt.figure()
data = ([x],[y])
l, = plt.plot([], [], 'r-')
#--------------------------------MENUSCREEN------------------_-------------#
class MenuScreen(Screen):
pass
box = ObjectProperty(None)
#------------------------------------PLOT----------------------------------#
class MyFigure(FigureCanvasKivyAgg):
def __init__(self, **kwargs):
global l
plt.xlim(0, 10000)
plt.ylim(0, 10000)
plt.xlabel('x')
plt.title('test')
super(MyFigure, self).__init__(fig, **kwargs)
def start_updating(self):
Clock.schedule_interval(self.Update, 1/30)
def stop_updating(self):
Clock.unschedule(self.Update)
def Update(self, *args, **kwargs):
global x, y, l #data
x=x+1
y=y+2
data[0].append(x)
data[1].append(y)
#print (data)
l.set_data(data)
print (l)
return l,
line_ani = animation.FuncAnimation(fig,
Update,
10000,
fargs=(data, l),
interval=1,
blit=True)
fig.canvas.draw()
#------------------------------------MAINAPP----------------------------------#
class MainApp(MDApp):
def build(self):
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
return sm
def on_start(self):
self.fps_monitor_start()
#---------------------------------END-------------------------------------#
if __name__=='__main__':
app = MainApp()
app.run()
Please help optimize the program. It might be possible to do this with fig.canvas.draw(). But in the ideal case, one would like to use matplotlib.animation.
UPD. I completely forgot, here is the main.kv file code
<Manager>:
transition: SlideTransition()
MenuScreen:
name: 'MenuScreen'
<MenuScreen>:
box: box
BoxLayout:
orientation: 'horizontal'
GridLayout:
cols: 1
size_hint: .2, 1
Button:
text: 'Start measurement'
on_press: myfig.start_updating()
Button:
text: 'Stop measurement'
on_press: myfig.stop_updating()
Button:
text: 'Quit'
on_press: app.root_window.close ()
BoxLayout:
size_hint: .8, 1
id:box
MyFigure:
id:myfig
UPD. Found a similar issue using Tkinter Embedding a matplotlib animation into a tkinter frame, after converting the code to add the init_plot function,
import matplotlib
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from garden_matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivy.uix.label import Label
import matplotlib.animation as animation
#-------------------------------------------------------------------------#
n = 0
x = 0
y = 0
s=0
lst = [[x],
[y]]
fig=plt.figure()
data = ([x],[y])
l, = plt.plot([], [], 'r-')
#--------------------------------MENUSCREEN------------------_-------------#
class MenuScreen(Screen):
pass
box = ObjectProperty(None)
#------------------------------------PLOT----------------------------------#
class MyFigure(FigureCanvasKivyAgg):
def __init__(self, **kwargs):
super(MyFigure, self).__init__(fig, **kwargs)
self.init_plot()
def start_updating(self):
Clock.schedule_interval(self.Update, 1/30)
def stop_updating(self):
Clock.unschedule(self.Update)
#fig.canvas.draw()
def Update(self, *args, **kwargs):
global x, y, l #data
x=x+1
y=y+2
data[0].append(x)
data[1].append(y)
#print (data)
l.set_data(data)
print (l)
return l,
def init_plot(self):
global fig
global l
plt.xlim(0, 10000)
plt.ylim(0, 10000)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig,
self.Update,
10000,
fargs=(data, l),
interval=1,
blit=True)
#------------------------------------MAINAPP----------------------------------#
class MainApp(MDApp):
def build(self):
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
return sm
def on_start(self):
self.fps_monitor_start()
#---------------------------------END-------------------------------------#
if __name__=='__main__':
app = MainApp()
app.run()
I got the following error:
File "C:\Python\Stackoverflow question\13.05.2023\garden_matplotlib\backend_kivy.py", line 1078, in _timer_set_interval
if self._timer is not None:
AttributeError: 'TimerKivy' object has no attribute '_timer'. Did you mean: '_on_timer'?
If I put line_ani
in the build
function I get a similar error.
I tried to use matplotlib.animation in simple Kivy project but got the same error too
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from kivy.clock import Clock
import matplotlib.animation as animation
n = 0
x = 0
y = 0
s=0
lst = [[x],
[y]]
fig=plt.figure()
data = ([x],[y])
l, = plt.plot([], [], 'r-')
canvas = FigureCanvasKivyAgg(fig)
class MainApp(App):
def init_plot(self):
global fig, l
plt.xlim(0, 10000)
plt.ylim(0, 10000)
plt.xlabel('x')
plt.title('test')
def on_press_button(self, instance):
Clock.schedule_interval(self.Update, 1/30)
def Update(self, *args, **kwargs):
global x, y, l #data
x=x+1
y=y+2
data[0].append(x)
data[1].append(y)
#print (data)
l.set_data(data)
print (l)
return l,
def build(self):
main_layout = BoxLayout(orientation='horizontal')
grid_layout=GridLayout(cols=1,
row_force_default=True,
row_default_height=100,
size_hint=(.2,1)
)
main_layout.add_widget(grid_layout)
grid_layout.add_widget(Label(text='Hello from Kivy'))
button1=grid_layout.add_widget(Button(text='Hello 1', on_press=self.on_press_button))
global canvas
main_layout.add_widget(canvas)
print ('build called')
return main_layout
line_ani = animation.FuncAnimation(fig,
Update,
10000,
fargs=(data, l),
interval=1,
blit=True)
if __name__ == '__main__':
app = MainApp()
app.run()
The problem is solved, the point was that with each update, new graphs were built. I figured it out using the print (self.ax.get_lines())
. Actually here is the solution. Instead of building a new plot in a loop (i mean schedule_interval
) with self.line = self.ax.plot(lst[0], lst[1], color='black')
, we set in __init__
,self.line = self.ax.plot (lst[0], lst[1], color='black')
. Note that a comma is placed after line
. This is necessary to return a list of Line2D
objects. After that, we draw the graph in a loop as in the first option: fig.canvas.draw()
. As a result, a stable 60 fps was obtained.
The problem with the bug below is still open. For me it looks like there is no way to work in Kivy
with matplotlib.animation
.
File "C:\Python\Stackoverflow question\13.05.2023\garden_matplotlib\backend_kivy.py", line 1078, in _timer_set_interval
if self._timer is not None:
AttributeError: 'TimerKivy' object has no attribute '_timer'. Did you mean: '_on_timer'?
UPD.The draw method fig.canvas.draw_idle()
works faster in my case, stabe 70 FPS.
UPD. What do we know in the end?
clear()
, cla()
or clf()
methods in a loop, because they are extremely slow.plt.plot
with FigureCanvasKivyAgg(plt.gcf())
is slow.FigureCanvasKivyAgg(plt.fig())
.draw()
method, it returns the entire graph each time, and each time a new line in each cycle.self.ax=fig.add_subplot()
and self.line,= self.ax.plot()
with a comma in the __init__
function. To update the graph, we add data in each cycle using self.line.set_data([],[])
.fig.canvas.draw_idle()
, which is somewhat faster (about 10-20%) than fig.canvas.draw()
.