Pregunta

I want to create a real time graph in kivy. How can i achieve that? I m new to kivy. Please help me.

¿Fue útil?

Solución

define your plot

e.g.

plot = MeshLinePlot(color=next(colors))

define graph

e.g.

graph = Graph(
    xlabel='Iteration',
    ylabel='Value',
    x_ticks_minor=1,
    x_ticks_major=5,
    y_ticks_major=1,
    y_grid_label=True,
    x_grid_label=True,
    padding=5,
    xlog=False,
    ylog=False,
    x_grid=True,
    y_grid=True,
    ymin=0,
    ymax=11,
    **graph_theme)

update graph and update x axis:

e.g.

    def update_xaxis(self,*args):
        global graph
        global cnt
        graph.xmin = cnt - 50
        graph.xmax = cnt

    def update_points(self, *args):
        global i
        global MYLIST
        global cnt

        #self.plot.points = [(i,i)]
        self.plot.points = [z for z in MYLIST]

call a Clock

e.g.

        Clock.schedule_interval(self.update_points, 1/60.)
        Clock.schedule_interval(self.update_xaxis, 1/60.)

and add the widget:

        b.add_widget(graph)

I hope I have not forgotten anything. It gives you running graph with kivy Garden.

Otros consejos

There is a graph widget in the kivy garden. You can read about using garden widgets in kivy's documentation.

I'm also trying to do a real time graph in Kivy.

Youscope

I started with Youscope. A demo of youscope you can see in the following youtube video https://www.youtube.com/watch?v=-1E0DpQ_cFo

And the source code is here: https://code.google.com/p/felipesanches/source/browse/trunk/youscope-emu/youscope-emu.py

It's written with Pygame and uses a wave audio file as input source, but you could also use other sources (e.g. serial data or a calculated curve).

The problem with Youscope is that I'm not able to build an APK for Android from it. I've tried to install the python subset for android but I get always error messages at building. (Not figured out what's wrong.)

So I decided to port the Youscope code to Kivy because with Buildozer I can make Android APKs. (Not tested to build the graphing app yet, but that should work.)

Youscope with Kivy

The drawing seems to run in kivy as fast as the original code but at the moment I'm stuck at redrawing the curve. But I think drawing should be faster maybe calculating the points is taking too long. I think I should check a WAV-file as input and if it's faster.

Clock.schedule_intervall (Kivy) vs. game loop (Pygame)

The source for Kivy is pretty simillar to the pygame code but in Kivy is no game loop with a while loop. In Kivy you're using callbacks with Clock.schedule_intervall(callback, time_in_sec) (see http://kivy.org/docs/api-kivy.clock.html) for updating/drawing the screen.

Use framebuffer for drawing

For drawing you need to use a framebuffer that is added to the canvas. see http://kivy.org/docs/api-kivy.graphics.fbo.html

The curve is drawn from left to right point by point. With redrawing I mean that I draw the first curve (I'm using a calculated sine wave) on the framebuffer and after I reached the right edge of the screen, I start to draw from the left again with the new curve.

And now there's still the previously drawn curve that needs to be cleared. You could redraw the whole screen here but that's probably slower than removing the old line point by point.

The difficulty here is to restore the background color that's underneath the old curve. It looks like I'm getting the color of the wrong pixel, but I'm not sure what's wrong.

Get_pixel_color() for refreshing the screen

With Framebuffer.get_pixel_color(wx,wy) (Kivy 1.8.0 required) you can get the color of a pixel in rgba and that's not working properly. Maybe it's an updating issue but I'm not sure.

Clearing with a black pixel (with-out get_pixel_color) is working but that removes the background grid.

Here's the code I wrote, needing a trend curve.

class TrendCurve(BoxLayout):
def __init__(self, **kwargs):
    super(TrendCurve, self).__init__(**kwargs)
    #self size and position
    self.size = (1000, 500)

    self.pos = (60,1)#((Window.width / 2) - ((self.size[0] / 2) - 80) , (Window.height / 2) - (self.size[1] / 2))
    self.text = ""

    self.number_labels = {}
    #This is the point where the trend starts
    self.point_zero = (self.pos[0] + 10, self.pos[1] + 10)
    self.point_zero_x =  self.pos[0] + 10
    self.point_zero_y =  self.pos[1] + 10

    #Points for drawing the line around the rectangle
    #"border line"
    self.x1 = self.pos[0] - 50
    self.y1 = self.pos[1]
    self.x2 = self.pos[0] - 50
    self.y2 = self.pos[1] + self.size[1]
    self.x3 = self.pos[0] + self.size[0]
    self.y3 = self.y2
    self.x4 = self.x3
    self.y4 = self.pos[1]
    self.x5 = self.pos[0] - 50
    self.y5 = self.y4
    self.box_points = [self.x1, self.y1, self.x2, self.y2, self.x3, self.y3, self.x4, self.y4, self.x5, self.y5]

    #Trend line
    self.trend_points = []
    #Trend starts at point zero
    self.trend_points = [self.point_zero_x, self.point_zero_y]
    #Variable for setting resolution of points and numbers
    self.resolution = 10
    #Lines for x and y on the trend.
    self.xline_points = [self.pos[0] + 10, self.pos[1] + 10, self.pos[0] + 10, (self.pos[1] + self.size[1] - 10)]
    self.yline_points = [self.pos[0] + 10, self.pos[1] + 10, (self.pos[0] + self.size[0] - 10), self.pos[1] + 10]

    self.pointlinesx = {}
    self.pointlinesy = {}
    self.r = 0
    self.g = 1
    self.b = 0


    #This is the resolution for how far forward we go for each update that comes.
    self.x_update = 1

    #This is to be rendered before
    with self.canvas.before:
        Color(0.4, 0.4, 0.4, 1)
        self.rectangle = Rectangle(size=self.size, pos=self.pos)
        self.left_addon_rectangle = Rectangle(size=(50, self.size[1]), pos=(self.pos[0] - 50, self.pos[1]))

    #This is the main canvas
    with self.canvas:
        Color(0.2, 0.2, 0.2)
        self.box = Line(points=self.box_points, width=1)
        Color(1, 1, 1)
        self.xline = Line(points=self.xline_points)
        self.yline = Line(points=self.yline_points)


        #These are the small lines for value_y, changing color as it goes upwards
        #red gets more powerful and green gets less powerful
        for i in range(0, self.size[1] - self.resolution, self.resolution):

            if self.r < 1:
                self.r += 0.03
            if self.g > 0:
                self.g -= 0.04

            Color(self.r,self.g, 0)

            if i >= 20:
                self.pointlinesx[i] = Line(points=(self.point_zero_x - 3, self.point_zero_y + i, self.point_zero_x + 3, self.point_zero_y + i), width=0.8)

                self.number_labels[i] = Label(size=(50, 20),font_size= 8, pos=(self.point_zero_x - 40, (self.point_zero_y + i) - 10), text=str(0 + i))

            self.top_label = Label(text=self.text, size=(100, 50), pos=(self.center[0] - 50, self.center[1] + (self.size[1] / 2) - 50))
            self.ms_label = Label(text="ms", size=(100,50), font_size= 11, pos=(self.point_zero_x - 90, self.point_zero_y + (self.size[1] / 2) - 25))
        #These are the small lines for value_x, only white colored.
        Color(1,1,1)
        for i in range(0, self.size[0], 20):
            if i >= 20:
                self.pointlinesy[i] = Line(points=(self.point_zero_x + i, self.point_zero_y - 3, self.point_zero_x + i, self.point_zero_y + 3), width=0.8)

    #This is to be rendered after
    with self.canvas.after:
        Color(0.3,0.6,1)
        self.trend = Line(points=self.trend_points, width=0.8)


def add_points_test(self, dt):
    new_num = randint(50, 200)
    self.add_point(new_num)

def update(self):
    self.trend.points = self.trend_points

def add_point(self, y):
    try:
        y = int(y)
    except ValueError:
        pass

    if type(y) == int:
        #The x is updated x pixels forth at a time
        x = self.trend_points[len(self.trend_points) - 2] + self.x_update
        self.trend_points.append(x)

        #y must be between max and min
        if y < 500 > 0:
            self.trend_points.append(self.point_zero_y + y)

        if y > 500:
            self.trend_points.append(500)

        if y < 0:
            self.trend_points.append(0)

        if x > (self.rectangle.size[0] - 10):

            new_point_list = []
            count = 0

            for i in self.trend_points:
                if (count % 2) != 1:
                    i -= self.x_update

                new_point_list.append(i)
                count += 1

            del (new_point_list[0])
            del (new_point_list[1])
            new_point_list[0] = self.point_zero_x + 20

            self.trend_points = new_point_list

    self.update()

This is my workaround solution for the same. The code is not very clean but this will give you a gist of how to handle a real-time graphing in Kivy using Matplotlib.

app.py

from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.uix.floatlayout import FloatLayout
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import random
import threading
import time
from kivy.clock import Clock

x = [1,2,3,4,5]
y = [5,12,6,24,29]

def data():
    data.a = None
    data.fig = None

data()

plt.plot(x,y)
plt.ylabel("Y axis")
plt.xlabel("X axis")

def gen_rand_int(intput):
        return random.randint(20,50)

data.fig = Figure(figsize=(5,4), dpi=100)
data.a = data.fig.add_subplot(111)
data.a.plot([1,2,3,4,5])
run_thread = True

def animate():
        while run_thread:
            data.a.clear()
            n_list = list(map(gen_rand_int, [0]*5))
            data.a.plot(n_list)
            time.sleep(0.5)
            print("animate")

calcThread = threading.Thread(target=animate)
calcThread.start()

class View(FloatLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.box = self.ids.box
        canvas = FigureCanvasKivyAgg(data.fig)
        self.box.add_widget(canvas)
        Clock.schedule_interval(self.timer, 1)

    
    def timer(self, dt):
        canvas = FigureCanvasKivyAgg(data.fig)
        self.box.clear_widgets()
        self.box.add_widget(canvas)
        print("timer")

    def save_it(self):
        print("button clicked")


class MainApp(MDApp):
    def build(self):
        self.theme_cls.theme_style = "Dark"
        self.theme_cls.primary_palette = "BlueGray"
        Builder.load_file('view.kv')
        return View()
    
    
try:
    MainApp().run()
except:
    run_thread = False
    print("Keyboard interrupt")

view.kv

<View>
    BoxLayout:
        id: box
        size_hint_y: 0.8
        pos_hint: {"top": 1}
        
    BoxLayout:
        size_hint_y: .2
        TextInput:
            id: namer
            multiline: False
        
        Button:
            text: "Save It!!"
            on_release: root.save_it()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top