Select your language

Matplotlib offers great means of visualizing graps. Integrating the Matplotlib graphs in Kivy if fairly simple with the integrated backend. The article shows how to set up a simple, reuseable widget with two plots that share a common x-axis and wihich can be modified through a kv file.

Prerequisites

If you don't have Kivy or Matplotlib installed, follow these instructions:

Install Kiv

Install Kivy Garden Matplotlib

The kv file

The widget is embedded in a BoxLayout with the name MyRootLayout. The bound properties define the look of your widget.

soucceX: List of x-data

sourceY: List of y-data

units: units of y-axis

titles: titles of each plot

ax_colors: colors of each line

formatter: date formatter

The on_touch_down event is going to fire the update of the plot once you click on the screen after starting the app.

<MyRootLayout>:
    on_touch_down: self.ids.plt1.update_plot()
    orientation: 'vertical'
    TwoPlotsSharedXWidget:
        id: plt1
        size_hint: 1, .85
        pos_hint: {'top': .85, 'left':1}
        sourceX: ['2020-1','2020-12', '2020-30', '2020-52']
        sourceY: [[1,2,3,4], [1,3,6,10]]
        units : ['','']
        titles : ['individual','cumulative']
        ax_colors : ['r','b']
        formatter : '%d-%b'

<TwoPlotsSharedXWidget>:

The Python File

The class TwoPlotsSharedXWidget is derived from the  FigureCanvasKivyAgg. This class requires that you hand over a valid plot during initialization. You need to know that this figure cannot be exchanged. You need to update the figure to update the plot.

Both plots will share a common x-axis to save space on small screens. You only need to take care of some Matplotlib bugs. One is that both plots x-axis need to be modified the same way. That's why the loop modifies them identically. 

# encoding: utf-8

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty, StringProperty, ListProperty
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
import matplotlib.dates as dt
from datetime import datetime
from time import mktime, strptime
import os

class TwoPlotsSharedXWidget(FigureCanvasKivyAgg):
    '''Displays two datetimeplots with shared x-axis.
        Attributes:
        The attributes are bound by name to propertis in the kv file. Updating them will automatically update the displayed data in the visualisation
            sourceX (ListProperty):
                list of datetime values for the timeseries
            sourceY (ListProperty):
                list holding two lists of values corresponding with timestamp
            units (ObjectProperty, str):
                list of string. Holding the units of the y-axis.
                Initially set to --.
            titles (ObjectProperty, str):
                list of string. Holding the titles for the plots.
                Initially set to --.
            ax_colors (ObjectProperty, str):
                List, setting the color of the plot.
                Initially set to green.
                Other parameters to change to different colors can be found in the matplotib documentation https://matplotlib.org/2.1.1/api/_as_gen/matplotlib.pyplot.plot.html
            notification  (StringProperty, str):
                Error string. Shows exceptions, like no data available.
                Initially set to --.
    '''

    plt.style.use('dark_background')

    sourceX = ListProperty([])
    sourceY = ListProperty([])
    units = ObjectProperty(['--','--'])
    titles = ObjectProperty(['--','--'])
    ax_colors = ObjectProperty(['g','g'])
    notification = StringProperty('')
    formatter = StringProperty('')
    
    def __init__(self, **kwargs):
        '''__init__ takes the figure the backend is going to work with'''
        super(TwoPlotsSharedXWidget, self).__init__(plt.figure(), **kwargs)
        
    def update_plot(self, *args, **kwargs):
        '''
        reads the latest data, updates the figure and plots it.
        
        Args:
            *args (): not used. For further development.
            **kwargs (): not used. For further development.
        Returns:
            Nothing.
        '''

        #Clear the figure
        myfigure = self.figure
        myfigure.clf()

        axes = myfigure.subplots(2,1, sharex=True)

        #Add the data to the axes and modify their axis
        for n in range(len(axes)):
            axes[n].plot_date(self.sourceX, self.sourceY[n], self.ax_colors[n], xdate=True)             
            axes[n].set_ylabel(self.units[n])
            axes[n].set_title(self.titles[n])
            plt.ylim(min(self.sourceY[n])-2,max(self.sourceY[n])+2)
            axes[n].xaxis.set_major_locator(dt.MonthLocator(bymonth=range(1,13))) #show major ticks with a step width of 1 month
            axes[n].xaxis.set_major_formatter(dt.DateFormatter(self.formatter))

        #the axis labels for the first subplot are made invisible
        plt.setp(axes[0].get_xticklabels(which='both'), visible=False)

        #draw the figure
        myfigure.canvas.draw_idle()

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

class TwoPlotsSharedXWidgetApp(App):
    def build(self):
        return MyRootLayout()

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

Das Ergebnis

Fertig ist der Plot. Temperatur und Feuchtigkeit mit den entsprechenden Einheiten und Überschriften über den Plots.

 

Sonstiges

Das Beispiel hier wurde zum Plotten von Daten über mehrere Tage hinweg optimiert. Daher wird beim Erzeugen der Achsen die Funktion

axes[n].plot_date()  

verwendet. Die ist speziell dafür gemacht. Wer andere Daten darstellen möchte, sollte sich über die Funktion plot schlau machen.

Wichtig zu wissen ist auch, dass der Weg nicht der einzige ist, um Plots anzuzeigen, bzw. zu modifizieren. Eine weitere Möglichkeit ist, das TwoPlotWidget vom Typ BoxLayout zu machen. Anschließend fügt man bei jedem Update ein neues FigureKanvasKivyAgg als Widget hinzu. Bei dieser Variante kann man jedes mal eine neue Figure erstellen und ist etwas freier in den Funktionen, die man verwendet. Beispiele dafür findet man im DocString des Backend.

Da ich in meiner App generell einen schwarzen Hintergrund habe, verwende ich das Stylesheet dark_background. Es lassen sich aber hier natürlich alle andern vordefinierten Stylesheets oder ein selbst gemachtes verwenden. Um das Standard Stylesheet zu verwenden einfach die Zeile auskommentieren oder 'default' eintragen.

plt.style.use('default')

Quellcode auf GitHub

https://github.com/HaikoKrais/TwoPlotsWidget