Search code examples
pythonmatplotlibkivy

Kivy Graphs from matplotlib not plotting the right size


I created a Kivy app that is supposed to output the following: Row 1 (Spans across 3 columns): A graph of candlegraph Row 2 (3 columns): 1 graph in each column

The graphs update itself once every few seconds. I have only implemented code for the graph for row 1 and row 2 column 1. The code is shown below:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.properties import ListProperty, NumericProperty
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt


class TestScreen(Widget):
    TestNum = NumericProperty(0)
    TestArray = ListProperty()


class Graph_Item(FigureCanvasKivyAgg):
    def __init__(self, **kwargs):
        super().__init__(plt.gcf(), **kwargs)


class AppLayoutTest(App):
    def build(self):
        # update graph every 10 seconds        
        Clock.schedule_interval(lambda dt: self.mainExecutable(), 10)
        return TestScreen()

    def PlotCandlegraph(self, Data):
        plt.clf()
        for x in range(len(Data['Close'])):
            thickHeight = abs(Data['Close'][x] - Data['Open'][x])
            thinHeight = abs(Data['High'][x] - Data['Low'][x])
            thinBottom = Data['Low'][x]
            if Data['Close'][x] > Data['Open'][x]:
                thickBottom = Data['Open'][x]
                candleColor = 'green'
            else:
                thickBottom = Data['Close'][x]
                candleColor = 'red'

            plt.bar(x=Data.index[x], height=thickHeight, width=0.005, bottom=thickBottom, color=candleColor)
            plt.bar(x=Data.index[x], height=thinHeight, width=0.002, bottom=thinBottom, color=candleColor)

        plt.grid(True)
        plt.title("Candlegraph")
        graph = self.root.ids.Candlegraph
        graph.figure = plt.gcf()
        graph.draw()

    def mainExecutable(self):
        # input from somewhere else
        self.PlotCandlegraph(Data=RawData)
        self.TestPlot()

    def TestPlot(self):
        self.root.TestNum += 1
        self.root.TestArray.append(self.root.TestNum)

        plt.clf()
        plt.plot(self.root.TestArray, color='r')
        plt.grid(True)
        plt.title("Test Graph")
        graph = self.root.ids.LossGraph
        graph.figure = plt.gcf()
        graph.draw()

if __name__ == '__main__':
    AppLayoutTest().run()

The kv file is shown here:

<TestScreen>
    GridLayout:
        rows: 2
        size: root.width, root.height

        Graph_Item:
            id: Candlegraph

        GridLayout:
            cols: 3
            Graph_Item:
                id: LossGraph

            Label:
                text: "RewardGraph"

            Label:
                text: "Information"

This is the result on the first cycle of the code: 1

Am I using the libary wrong? Is there a way to keep the graph at a certain size that spans across however many columns?

When I remove the plot from the second row by replacing the graph with a label instead, the following is shown and does not get squished however many cycle it runs: 2

I also tried defining the size of the plots using the matplotlib library's plt.figsize() and still did not work.

Can someone tell me how to get the graph to span across 3 columns on the first row please?


Solution

  • The problem is that methods like plt.clf(), plt.bar(), plt.grid(), and plt.title() all refer to the current figure. In your app, you have two Graph_Item instances, but only one figure. The figure reacts to the size of the Graph_Item, but since there is only one figure, the sizing is confused. You can fix this by creating a new figure for each Graph_Item instance:

    class Graph_Item(FigureCanvasKivyAgg):
        def __init__(self, **kwargs):
            super().__init__(plt.figure(), **kwargs)
    

    Then, in your PlotCandlegraph() method, you can use the saved plt_num to activate the correct figure:

    def PlotCandlegraph(self, Data):
        plt_num = self.root.ids.Candlegraph.figure.number
        plt.figure(num=plt_num)
        plt.clf()
    

    The plt.figure() method activates the figure with the specified num (or creates a new figure). See the documentation