Python Kivy real time chart - python

I am learning to use Kivy. My objective is to create an app to display a sound wave chart of a running sound.
Unfortunately, I cannot get the chart to update in real-time. I get "NameError: name 'graph' is not defined", and I don't really know how to fix it.
Code below:
from math import sin
from kivy.garden.graph import Graph, MeshLinePlot
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
class MyApp(App):
plot = MeshLinePlot(color=[1, 0, 0, 1])
graph = Graph(xlabel='X', ylabel='Y', x_ticks_minor=5,
x_ticks_major=25, y_ticks_major=1,
y_grid_label=False, x_grid_label=False, padding=5,
x_grid=False, y_grid=False, xmin=-0, xmax=100, ymin=-1, ymax=1,)
def build(self):
box = BoxLayout()
Clock.schedule_interval(self.update_points, 1/60.)
Clock.schedule_interval(self.update_xaxis, 1/60.)
box.add_widget(graph)
return box
def update_xaxis(self,*args):
global graph
graph.xmin = 0
graph.xmax = 100
def update_points(self, *args):
#self.plot.points = [(i,i)]
self.plot.points = [(x, sin(x / 10.)) for x in range(0, 101)]
MyApp().run()

Your references to plot and graph (except where they are created) should all be self.plot and self.graph. Also, you don't want the global graph line in your update_xaxis() method. After you make that correction, you next question should be a separate post.

Related

How to create a gauge using Kivy?

I want to make an internet speed test application for Android with Python.
I have done the back-end side but I have a hard time with the front-end.
After research, I decided to use the Kivy framework, however, I need guidance on how to create a gauge like this.
There are two ways to create a gauge. The first one is by using code with mathematics and the second one is by using graphic files for cadran and needle. Below, you will find the implementation of the second way, with the help of the original code of the gauge widget of Kivy Garden project, in which you will be able to understand how the gauge works more easily.
import kivy
kivy.require('1.6.0')
from kivy.app import App
from kivy.clock import Clock
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
from kivy.properties import BoundedNumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
from os.path import join, dirname, abspath
class Gauge(Widget):
'''
Gauge class
'''
unit = NumericProperty(1.8)
value = BoundedNumericProperty(0, min=0, max=100, errorvalue=0)
path = dirname(abspath(__file__))
file_gauge = StringProperty(join(path, "cadran.png"))
file_needle = StringProperty(join(path, "needle.png"))
size_gauge = BoundedNumericProperty(128, min=128, max=256, errorvalue=128)
size_text = NumericProperty(10)
def __init__(self, **kwargs):
super(Gauge, self).__init__(**kwargs)
self._gauge = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_gauge = Image(
source=self.file_gauge,
size=(self.size_gauge, self.size_gauge)
)
self._needle = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_needle = Image(
source=self.file_needle,
size=(self.size_gauge, self.size_gauge)
)
self._glab = Label(font_size=self.size_text, markup=True)
self._progress = ProgressBar(max=100, height=20, value=self.value)
self._gauge.add_widget(_img_gauge)
self._needle.add_widget(_img_needle)
self.add_widget(self._gauge)
self.add_widget(self._needle)
self.add_widget(self._glab)
self.add_widget(self._progress)
self.bind(pos=self._update)
self.bind(size=self._update)
self.bind(value=self._turn)
def _update(self, *args):
'''
Update gauge and needle positions after sizing or positioning.
'''
self._gauge.pos = self.pos
self._needle.pos = (self.x, self.y)
self._needle.center = self._gauge.center
self._glab.center_x = self._gauge.center_x
self._glab.center_y = self._gauge.center_y + (self.size_gauge / 4)
self._progress.x = self._gauge.x
self._progress.y = self._gauge.y + (self.size_gauge / 4)
self._progress.width = self.size_gauge
def _turn(self, *args):
'''
Turn needle, 1 degree = 1 unit, 0 degree point start on 50 value.
'''
self._needle.center_x = self._gauge.center_x
self._needle.center_y = self._gauge.center_y
self._needle.rotation = (50 * self.unit) - (self.value * self.unit)
self._glab.text = "[b]{0:.0f}[/b]".format(self.value)
self._progress.value = self.value
if __name__ == '__main__':
from kivy.uix.slider import Slider
class GaugeApp(App):
increasing = NumericProperty(1)
begin = NumericProperty(50)
step = NumericProperty(1)
def build(self):
box = BoxLayout(orientation='horizontal', padding=5)
self.gauge = Gauge(value=50, size_gauge=256, size_text=25)
self.slider = Slider(orientation='vertical')
stepper = Slider(min=1, max=25)
stepper.bind(
value=lambda instance, value: setattr(self, 'step', value)
)
box.add_widget(self.gauge)
box.add_widget(stepper)
box.add_widget(self.slider)
Clock.schedule_interval(lambda *t: self.gauge_increment(), 0.03)
return box
def gauge_increment(self):
begin = self.begin
begin += self.step * self.increasing
if 0 < begin < 100:
self.gauge.value = self.slider.value = begin
else:
self.increasing *= -1
self.begin = begin
GaugeApp().run()
Of course, if you don't want to use the default cadran and needle, you will have to design your own, using a vector graphics editor.

How Can I Animate a Label in Kivy?

Ok, My question goes this way How Can I Animate a Label in Kivy, My apologies if this question is annoyingly way too easy :)
Here is the Label in which I want animation. Can I have edits of the code in the comments?
Actually, I am obsessed with this script, racking my brains off to know the way to animate this thingy!! Pls Help!..
import kivy
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
lu = Label(text = "This is a label! Pls help me with Animation!")
return lu
if __name__ == '__main__':
MyApp().run()
If you want to update text on Label every few seconds then you can use Clock and Clock.schedule_interval(function_name, seconds) to execute function_name(dt) every seconds and in this function you should change text in Label
Minimal example which displays current time.
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
import datetime
def update_label(dt):
new_text = datetime.datetime.now().strftime('%H:%M:%S')
label.text = new_text
#print(new_text)
label = None # create global variable to access the same `label` in two different functions
class MyApp(App):
def build(self):
global label # inform function to assign `Label` to global variable
label = Label(text="???")
Clock.schedule_interval(update_label, 1)
return label
#Clock.schedule_interval(update_label, 1)
if __name__ == '__main__':
MyApp().run()
EDIT:
Another example which scroll text
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
import datetime
label = None
text = 'Hello World of Python!'
text_length = len(text)
index = 0
temp_text = text + ' ' + text
def update_label(dt):
global index
label.text = temp_text[index:index+15]
index += 1
if index >= text_length:
index = 0
class MyApp(App):
def build(self):
global label
label = Label(text="???")
Clock.schedule_interval(update_label, 0.20)
return label
if __name__ == '__main__':
MyApp().run()
EDIT:
For numerical values you can use Animation.
Here is blinking text.
It changes color to black (in 0.2 second) and next it change back to white (in 0.2 second). And it repeats it.
from kivy.app import App
from kivy.uix.label import Label
from kivy.animation import Animation
class MyApp(App):
def build(self):
label = Label(text='Hello World of Python!')
anim = Animation(color=(0, 0, 0, 1), duration=.2) + Animation(color=(1, 1, 1, 1), duration=.2)
anim.repeat = True
anim.start(label)
return label
if __name__ == '__main__':
MyApp().run()

Kivy and Python: How to make asymmetrical graphic of math functions at the same canvas?

all right? i hope so, I will be very grateful if you try to solve this problem that I'm facing about programming.
I'm trying to make a quadratic function plotter, however I can only display one side of the function.
If you uncomment that part of the code you will see that kivy only displays the other side, but the first side of the graphic is still hidden.
What I want to do and if you can help me here is to display both sides of the quadratic function in the same graph and code.
My code is bellow:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.graphics import Rectangle, Color
Builder.load_string(
'''
<QuadraticApp>:
my_size: [root.size[0]*4, root.size[1]*0.5]
canvas:
Color:
rgba: 1, 1, 1, 0
Rectangle:
pos: (root.pos[0], root.pos[1])
size:(500, 500)
'''
)
class QuadraticApp(Widget):
my_size = ListProperty([0, 0])
def __init__(self):
super(QuadraticApp, self).__init__()
new_y = self.my_size
new_x = self.my_size
#'''
for x in range(100):
with self.canvas:
Color(0.1, 0.75, 0.1, 1, mode='rgba') # GREEN
rectx = Rectangle(pos=(new_x), size=(10, 10))
new_x[0] += (x*0.25-50)/2
new_x[1] += eval('(x)**2')
#'''
# Comment the for loop above to hide the another showing graphic
# And Uncoment this code bellow to show the another part of graphic
'''
for y in range(100):
with self.canvas:
Color(0.75, 0.1, 0.1, 1, mode='rgba') # RED
recty = Rectangle(pos=(new_y), size=(10, 10))
new_y[0] += -(y*0.25-50)/2
new_y[1] += eval('(y)**2')
'''
class MathApp(App):
def build(self):
return QuadraticApp()
if __name__ == '__main__':
MathApp().run()
This image bellow show the graphic at the left simetric side
https://i.stack.imgur.com/i1WTy.jpg
This image bellow show the graphic at the right simetric side
https://i.stack.imgur.com/HVhAz.jpg
Now i can say thank you for u support.
The problem is that the code:
new_y = self.my_size
new_x = self.my_size
is just creating references to self.my_size, so when you change new_x, or new_y, or self.my_size, you are changing all three. At the start of your second loop, new_y starts out with the values from new_x from the end of the first loop. Try changing those two lines to:
new_y = self.my_size.copy()
new_x = self.my_size.copy()

Python Kivy Plots with MeshLinePlot confusion

I am trying to implement real time plots for measurements to a Kivy application, but I do not understand the inner workings of the kivy garden library.
My goal that I want to achieve: I want to have multiple plots inside a ScrollView such that I can add multiple real time plots programmatically from a dictionary and scroll through them if they occupy more space than one screen height. The main problem I have is that the Graph does not behave like an ordinary Widget but rather behaves like a canvas. I already tried to implement this with matplotlib as backend_kivyagg, but I failed to have fixed size for every subplot that I created.
There are multiple things that I do not understand why they happen like they happen.
from math import sin, cos
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.garden.graph import Graph, MeshLinePlot
class Plot(Widget):
def __init__(self):
super(Plot, self).__init__()
self.graph = Graph(xlabel="x", ylabel="y", x_ticks_minor=5, x_ticks_major=25, y_ticks_major=1,
y_grid_label=True, x_grid_label=True, x_grid=True, y_grid=True,
xmin=-0, xmax=100, ymin=-1, ymax=1, draw_border=False)
# graph.size = (1200, 400)
# self.graph.pos = self.center
self.plot = MeshLinePlot(color=[1, 1, 1, 1])
self.plot.points = [(x, sin(x / 10.)) for x in range(0, 101)]
self.plot2 = MeshLinePlot(color=[1, 0, 0, 1])
self.plot2.points = [(x, cos(x / 10.)) for x in range(0, 101)]
self.add_widget(self.graph)
self.graph.add_plot(self.plot)
self.graph.add_plot(self.plot2)
class GraphLayoutApp(App):
def build(self):
scroll_view = ScrollView()
grid_layout = GridLayout(cols=1, row_force_default=True, padding=20, spacing=20)
graph = Plot()
graph2 = Plot()
label = Label(text="Hello World!")
label2 = Label(text="Hello World!")
grid_layout.add_widget(label)
grid_layout.add_widget(graph)
grid_layout.add_widget(label2)
grid_layout.add_widget(graph2)
scroll_view.add_widget(grid_layout)
return scroll_view
if __name__ == '__main__':
GraphLayoutApp().run()
Questions:
Inside the class GraphLayoutApp I create two objects graph and graph2 but if I add them to the GridLayout() only one does appear? How
can I add multiple graphs?
Also Inside the build method of the class GraphLayoutApp I create two lables. I wanted to first display the first label then the graph
and then the second label label2. But it seems to me that the graph
is always displayed in the lower left corner. I guess this has to do
with the canvas on which it is drawn, but I cannot solve it.
Here is a modified version with some things fixed:
The issue here is that you made your Plot inherit from Widget, which really the most basic widget in the framework, and as it's not a layout, doesn't manage anything in the children you add to it, so the Graph widget you added to it was left at the default size/position (respectively [100, 100] and [0, 0]), and so they stacked on each others, i used RelativeLayout, which sets children by default to its own size and position, so the Graph follows the widget. Another solution is to simply make Plot inherit from Graph, since it's the only child, and to use "self.add_plot" instead of "self.graph.add_plot", and to override the Graph parameters, the best solution is probably to create a KV rule for the Plot class. But the first solution was the minimal change from your code.
The general principle you missed is that in kivy, widgets are by default not constrained to any position/size, unless their parents are layouts that specifically manage that (like GridLayout did for your labels).
I also made the GridLayout automatically size itself to the minimum_size (determined by the hardcoded size i put in all widgets), so you actually have something to scroll on.
This code is also very python-driven, in kivy, you usually want to do more things using KV, rather than using add_widget for static things.
from math import sin, cos
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.uix.relativelayout import RelativeLayout
from kivy_garden.graph import Graph, MeshLinePlot
class Plot(RelativeLayout):
def __init__(self, **kwargs):
super(Plot, self).__init__(**kwargs)
self.graph = Graph(xlabel="x", ylabel="y", x_ticks_minor=5, x_ticks_major=25, y_ticks_major=1,
y_grid_label=True, x_grid_label=True, x_grid=True, y_grid=True,
xmin=-0, xmax=100, ymin=-1, ymax=1, draw_border=False)
# graph.size = (1200, 400)
# self.graph.pos = self.center
self.plot = MeshLinePlot(color=[1, 1, 1, 1])
self.plot.points = [(x, sin(x / 10.)) for x in range(0, 101)]
self.plot2 = MeshLinePlot(color=[1, 0, 0, 1])
self.plot2.points = [(x, cos(x / 10.)) for x in range(0, 101)]
self.add_widget(self.graph)
self.graph.add_plot(self.plot)
self.graph.add_plot(self.plot2)
class GraphLayoutApp(App):
def build(self):
scroll_view = ScrollView()
grid_layout = GridLayout(cols=1, padding=20, spacing=20, size_hint_y=None)
grid_layout.bind(minimum_size=grid_layout.setter('size'))
graph = Plot(size_hint_y=None, height=500)
graph2 = Plot(size_hint_y=None, height=500)
label = Label(text="Hello World!", size_hint_y=None)
label2 = Label(text="Hello World!", size_hint_y=None)
grid_layout.add_widget(label)
grid_layout.add_widget(graph)
grid_layout.add_widget(label2)
grid_layout.add_widget(graph2)
scroll_view.add_widget(grid_layout)
# return grid_layout
return scroll_view
if __name__ == '__main__':
GraphLayoutApp().run()

Animate canvas Line on Kivy

I have an array with x and y positions. I want to show these points linking a line successively for each point, then, creating an animation. It is just like a path tracking whose trail is the line. I'm using python-kivy to try to display it.
I couldn't find any help on google.
There's a button that triggers this animation.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics.vertex_instructions import Line
from kivy.graphics.context_instructions import Color
from time import sleep
x_moves = [250, 250.4305, 249.8804, 246.0923, 239.7496, 233.8188, 225.7797, 215.8385, 205.8413, 196.6497, 189.7026, 181.2445, 174.9816, 171.9882, 166.1171, 161.6505, 159.9929, 161.1338, 164.853, 168.2874, 170.768, 178.6918, 184.5233, 190.0262, 195.607, 202.0255, 210.5954, 216.1031, 219.6285, 224.9134, 230.2314, 237.7017, 243.7408, 250.5839, 256.2949]
y_moves = [250, 240.0093, 230.0244, 220.7697, 213.0386, 204.9872, 199.0396, 197.9567, 197.7209, 201.6598, 208.8527, 214.1875, 221.9834, 231.5249, 239.62, 248.567, 258.4287, 268.3634, 277.6461, 287.0379, 296.7253, 302.8256, 310.9492, 319.299, 327.5969, 335.2652, 340.4185, 348.7651, 358.1231, 366.6125, 375.0812, 381.7291, 389.6996, 396.9915, 405.2003]
class my_App(App):
def build(self):
self.widget = Widget()
self.widget.on_touch_down = self.touch
with self.widget.canvas:
Color(0, 1, 0, 1) #just initial config
Line(points = [0,0,500,0,500,500,0,500], close = True) #just initial config
return self.widget
def touch(self, touch): #executes the animation
pts = []
for i in range(len(x_moves)):
pts.append(x_moves[i])
pts.append(y_moves[i])
self.widget.canvas.add(Line(points = pts))
sleep(0.1)
if __name__ == '__main__':
obj = my_App()
obj.run()
This is my code that doesn't work. But that's the idea.
You're blocking the UI from updating because your function doesn't return and you use sleep(). For obvious reasons, Kivy can't do anything while your code is running. If you want to wait before running some more code, you can use Kivy's Clock for this. Then Kivy will be able to update the screen.
But you should probably just look at Kivy's Animation, which is built to do this much better.

Categories

Resources