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 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')