Search code examples
pythonmatplotlibuser-interfacekivy

Using Kivy and Matplotlib graph is not being plotted


I asked a question about this specific code a few days ago, however, some changes have been made since then. I am trying to write a mobile application where when the user presses a button, they can view various real time graphs. Currently, I am just working on a simple line plot. The issue is whenever I press the plot button the screen transitions correctly and the canvas is shown, but nothing is actually plotted on the canvas. It is just empty. Below is my python code and .kv file.

button_log = []



class HomeScreen(Screen):
    def button_log(self,button):

        button_text = button.text
        global button_log
        button_log.append(button_text)
        return button_text, button_log


class LinePlotScreen(Screen):
    def on_pre_enter(self):
        graph_widget = self.ids.graph_widget
        graph_widget.start_update()



class WindowManager(ScreenManager):
    pass

class GraphWidget1(BoxLayout):
    def __init__(self,**kwargs):
        super(GraphWidget1,self).__init__(**kwargs)

        self.fig , self.ax = plt.subplots()
        self.line, = self.ax.plot([],[])
        canvas = FigureCanvasKivyAgg(self.fig)
        self.add_widget(canvas)

        self.x_data = []
        self.y_data = []

        self.is_running = False

    def start_update(self):
        if not self.is_running:
            self.is_running = True
            #self.thread = threading.Thread(target=self.update_graph)
            #self.thread.daemon = True
            #self.thread.start()
            #Clock.schedule_interval(self.update_graph, 0.1)
            Clock.schedule_once(self.update_graph)

    def stop_update(self):
        if self.is_running():
            self.is_running = False

    def update_graph(self, dt):
        x = np.linspace(0,10,100)
        y = np.sin(x)

        self.x_data.append(x)
        self.y_data.append(y)
        #self.ax.cla()

        self.line.set_data(self.x_data,self.y_data)
        self.ax.relim()
        self.ax.autoscale_view(True,True,True)
        self.canvas.draw


        if self.is_running:
            Clock.schedule_once(self.update_graph, 0.1)


kv = Builder.load_file('Simplified.kv')
class TestApp(App):

    def build(self):

        self.stop_value = 0
        self.data_int = None
        self.left_shoe = []
        self.right_shoe = []

        return kv

    def on_button_press(self):
        global button_log
        if button_log[-1] == 'Start Data Collection':
            UDP_IP = "192.168.1.100"
            UDP_PORT = 3333
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.bind((socket.gethostname(), UDP_PORT))

            def collect_data(dt):
                nonlocal sock
                nonlocal self
                global button_log

                data, addr = sock.recvfrom(1024)
                self.data_int = fmt_str(data)
                if self.data_int[0] == 1:
                    self.left_shoe.append(self.data_int)
                elif self.data_int[0] == 2:
                    self.right_shoe.append(self.data_int)
                if self.stop_value == 1:
                    Clock.unschedule(collect_data)

            Clock.schedule_interval(collect_data, 0.1)

        elif button_log[-1] == 'Stop Data Collection':
            self.stop_value = 1
        elif button_log[-1] == 'Plot Line Charts':

            self.root.current = 'lineplot'
            line_plot_screen = self.root.get_screen('lineplot')
            graph_widget = line_plot_screen.ids.graph_widget

Simplified.kv

WindowManager:
    HomeScreen:
    LinePlotScreen:

<HomeScreen>:
    name: 'home'
    BoxLayout:
        orientation: 'vertical'
        size: root.width,root.height

        Label:
            text: 'Home'
            font_size: 32

        Button:
            id: Start
            text: 'Start Data Collection'
            font_size: 32
            pos_hint: {'center_x':0.5}
            size_hint: (0.5,1)
            valign: 'center'
            on_press: root.button_log(self)
            on_release: app.on_button_press()

        Button:
            id: Stop
            text: 'Stop Data Collection'
            font_size: 32
            pos_hint: {'center_x':0.5}
            size_hint: (0.5,1)
            valign: 'center'
            on_press: root.button_log(self)
            on_release: app.on_button_press()

        Button:
            id: plot_line
            text: 'Plot Line Charts'
            font_size: 32
            pos_hint: {'center_x':0.5}
            size_hint: (0.5,1)
            valign: 'center'
            on_press:
                root.button_log(self)
                app.on_button_press()

<LinePlotScreen>:
    name: 'lineplot'
    BoxLayout:
        id: box
        orientation: 'vertical'
        size: root.width,root.height

        Label:
            text: 'Line Graphs'
            font_size: 32

        GraphWidget1:
            id: graph_widget

I was having issues before trying to just transition the screen and have the figure displayed. Now, I am dealing with the issue of transitioning the screen successfully but the figure not plotting. I think the issue might be with the 'self.canvas.draw' line but I'm not totally sure. I tried adding parenthesis at the end of that line to make it a function call, but that just caused the window to crash when the plot button was pressed. I tried looking at other StackOverflow questions and couldn't really find one that fit my situation. Any help would be greatly appreciated, thanks.


Solution

  • You need to call the plot() method of pyplot in order for the new data to be plotted, and you must set the figure attribute of the FigureCanvasKivyAgg to the current updated plot.

    One way to do that, is to start by saving a reference to the FigureCanvasKivyAgg:

    class GraphWidget1(BoxLayout):
        def __init__(self,**kwargs):
            super(GraphWidget1,self).__init__(**kwargs)
    
            self.fig , self.ax = plt.subplots()
            self.line, = self.ax.plot([],[])
            self.plot = FigureCanvasKivyAgg(self.fig)
            self.add_widget(self.plot)
    
            self.x_data = []
            self.y_data = []
    
            self.is_running = False
    

    Then, in the update_graph():

    def update_graph(self, dt):
        plt.cla()
        x = np.linspace(0, 10, 100)
        y = np.sin(x)
        plt.plot(x, y)
        plt.grid(True)
        self.plot.figure = plt.gcf()
        self.plot.draw()