I am just learning to use Kivy and want to embed some plots from Matplotlib and potentially OpenGL graphics in an app. I was looking at this particular tutorial on how to use kivy-garden to display a Matplotlib plot
However, I was hoping someone might be able to point me to an example of importing a plot into Kivy without using the matplotlib kivy-garden widgets. I want to be a little plotting backend agnostic, and hence I wanted to learn to import plots directly into the Kivy widgets. Matplotlib will export an image from plt.show() so I imagine the corresponding Kivy widget needs to have a property that can receive images? Plotly exports something different, so I was hoping to understand how to directly import these different plots into Kivy.
If anyone knows some good examples of directly plotting into Kivy, it would be appreciated.
You can load an Image. Here is an example hacked out of some code that I use.
kivy file:
#:kivy 2.0.0
<MatPlot>:
Image:
size_hint_x: 12
source: root.matplotlib_image2.source
Image:
size_hint_x: 5
source: root.matplotlib_image1.source
Python:
my method on_start() probably needs to be called from some other code. I think the on_start is only intrinsic with App and not other widgets. I have used this with matplotlib which as you point out can export image files.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pathlib import Path
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
# requires kivy element <MatPlot> from kv_mat_plot.kv
class MatPlot(BoxLayout):
matplotlib_image1 = Image()
matplotlib_image2 = Image()
def __init__(self,
graph_directory: Path,
**kwargs,
):
super().__init__(**kwargs)
self.graph_directory = graph_directory
def load_image1(self, filename: str):
self.matplotlib_image1.source = str(Path(self.graph_directory, filename))
self.matplotlib_image1.reload()
def load_image2(self, filename: str):
self.matplotlib_image2.source = str(Path(self.graph_directory, filename))
self.matplotlib_image2.reload()
def reload_image2(self):
self.matplotlib_image2.reload()
print(f"imag2={self.matplotlib_image2.source}")
def reload_image1(self):
self.matplotlib_image1.reload()
print(f"image1={self.matplotlib_image1.source}")
def reload_images(self):
self.reload_image2()
self.reload_image1()
def on_start(self):
print(f"{self.name} on_start {self}")
self.matplotlib_image2.source = str(Path(self.graph_directory, "one_image.png"))
self.matplotlib_image1.source = str(Path(self.graph_directory, "second_image.png"))
Related
am trying to add elevation effect to MDBottomNavigation in kivymd but coldn't get it to work can someone help me ?like in this imageenter image description here
i tried importing from kivy lik this but ddin't work
from kivymd.uix.bottomnavigation import MDBottomNavigation
from kivymd.uix.behaviors import CommonElevationBehavio
class MDBottomNavigation1(MDBottomNavigation,CommonElevationBehavior ):
pass
I'm new to programming and trying to use Kivy to create a simple game.
I am following a tutorial, and I'm using VSCode, but I can't understand why the code I write in the .kvfile doesn't is used by the main.pyfile.
I have two files in the directory.
main.py
from kivy.app import App
from kivy.uix.widget import Widget
class MainWidget(Widget):
pass
class TheLabApp(App):
pass
TheLabApp().run()
thelab.kv
MainWidget:
<MainWidget>:
Button:
text: 'Hello'
size: 400, 200
I have installed a Kivy extension, and of course the Kivy module. But when I run the code the only thing that appears is a black screen, without the button.
What is happening?
I found the solution. It is required to save the .kv file before running the code, it is just such a sily thing, but if it isn't saved it is read like if it was empty, or never changed.
Consider the following python3 PyQt code to display an interactive matplotlib graph with toolbar
import sys, sip
import numpy as np
from PyQt5 import QtGui, QtWidgets
from PyQt5.Qt import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
app = QApplication(sys.argv)
top = QWidget()
fig = plt.figure()
ax = fig.gca()
x = np.linspace(0,5,100)
ax.plot(x,np.sin(x))
canvas = FigureCanvas(fig)
toolbar = NavigationToolbar(canvas, top)
def pick(event):
if (event.xdata is None) or (event.ydata is None): return
ax.plot([0,event.xdata],[0,event.ydata])
canvas.draw()
canvas.mpl_connect('button_press_event', pick)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(toolbar)
layout.addWidget(canvas)
top.setLayout(layout)
top.show()
app.exec_()
Now I'd like to achieve the same by using PyQt with QML instead. I have some experience with creating QML GUIs in C++ and I really like the fact that the layout code is nicely separated from the core logic of the code.
I have found several examples on how to show plots in PyQt and on how to use Python with QML, but nothing that combines the two.
To start off, my python and QML snippets look as follows:
Python:
import sys, sip
import numpy as np
from PyQt5 import QtGui, QtWidgets
from PyQt5.Qt import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load(QUrl('layout.qml'))
root = engine.rootObjects()[0]
root.show()
sys.exit(app.exec_())
Layout:
import QtQuick 2.7
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 400
height: 400
Canvas {
// canvas may not be the right choice here
id: mycanvas
anchors.fill: parent
}
}
But I am quite lost on how to continue.
More concretely, the question would be: Is there a way to display an interactive matplotlib plot in QML (by interactive I mean not just a figure that has been saved as an image, ideally with the standard toolbar for zoom etc.)
Can anyone help? Or is the combination of QML and plots just simply discouraged (this question suggests python and QML should work together quite well)?
I don't have a full solution, but if you're OK with just displaying charts and the fact that you'll have to provide any interactive controls by yourself, then there's a reasonably simple way to do that.
First of all, you will need to convert your matplotlib chart into a QImage. Fortunately doing so is surprisingly easy. The canonical backend (renderer) for matplotlib is *Agg`, and it allows you to render your Figure into a memory. Just make a suitable Canvas object for you Figure, then call .draw(). The QImage constructor will take generated data directly as inputs.
canvas = FigureCanvasAgg(figure)
canvas.draw()
img = QtGui.QImage(canvas.buffer_rgba(), *canvas.get_width_height(), QtGui.QImage.Format_RGBA8888).copy()
The Qt way to provide that image into QML is to use QQuickImageProvider. It will get "image name" as input from QML and should provide a suitable image as output. This allows you to serve all matplotlib charts in your app with just one Image provider. When I was working on a small visualization app for internal use, I ended up with a code like this:
import PyQt5.QtCore as QtCore
import PyQt5.QtGui as QtGui
import PyQt5.QtQuick as QtQuick
import PyQt5.QtQml as QtQml
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg
class MatplotlibImageProvider(QtQuick.QQuickImageProvider):
figures = dict()
def __init__(self):
QtQuick.QQuickImageProvider.__init__(self, QtQml.QQmlImageProviderBase.Image)
def addFigure(self, name, **kwargs):
figure = Figure(**kwargs)
self.figures[name] = figure
return figure
def getFigure(self, name):
return self.figures.get(name, None)
def requestImage(self, p_str, size):
figure = self.getFigure(p_str)
if figure is None:
return QtQuick.QQuickImageProvider.requestImage(self, p_str, size)
canvas = FigureCanvasAgg(figure)
canvas.draw()
w, h = canvas.get_width_height()
img = QtGui.QImage(canvas.buffer_rgba(), w, h, QtGui.QImage.Format_RGBA8888).copy()
return img, img.size()
Whenever I need to draw a plot in python code, I just create Figure using this addFigure to give it a name and let the Qt to know about it. Once you got Figure, rest of matplotlib drawing happens exactly as usual. Make axes and plot.
self.imageProvider = MatplotlibImageProvider()
figure = self.imageProvider.addFigure("eventStatisticsPlot", figsize=(10,10))
ax = figure.add_subplot(111)
ax.plot(x,y)
Then in QML code I can simply refer matplotlib image by name ("eventStatisticsPlot")
Image {
source: "image://perflog/eventStatisticsPlot"
}
Note that URL is prefixed by "image://" to tell QML that we need to get image from QQuickImageProvider and includes name ("perflog") of a particular provider to use. For this stuff to work we need to register our provider during QML initialization with a call to addImageProvider. For example,
engine = QtQml.QQmlApplicationEngine()
engine.addImageProvider('perflog', qt_model.imageProvider)
engine.load(QtCore.QUrl("PerflogViewer.qml"))
At this point you should be able to see static graphs shown, but they will not be updated properly because Image component in QML assumes that image that we provide does not change. I found no good solution for it, but an ugly workaround is fairly simple. I added a signal called eventStatisticsPlotChanged to my helper class that exposes Python app data to QML and .emit() it whenever the relevant plot is changed. E.g. here's a chunk of code where I get data from QML on a time interval selected by user.
#QtCore.pyqtSlot(float, float)
def selectTimeRange(self, min_time, max_time):
self.selectedTimeRange = (min_time, max_time)
_, ax, _ = self.eventStatisticsPlotElements
ax.set_xlim(*self.selectedTimeRange)
self.eventStatisticsPlotChanged.emit()
See that .emit() in the end? In QML this event forces image to reload URL like this:
Image {
source: "image://perflog/eventStatisticsPlot"
cache: false
function reload() { var t = source; source = ""; source = t; }
}
Connections {
target: myDataSourceObjectExposedFromPython
onEventStatisticsPlotChanged: eventStatisticsPlot.reload()
}
So whenever user moves a control, following happens:
QML sends updated time interval to my data source via selectTimeRange() call
My code calls .set_xlim on appopriate matplotlib object and emit() a signal to notify QML that chart changed
QML queries my imageProvider for updated chart image
My code renders matplotlib chart into new QImage with Agg and passes it to Qt
QML shows that image to user
It might sound a bit complicated, but its actually easy to design and use.
Here's an example of how all this looks in our small visualization app. That's pure Python + QML, with pandas used to organize data and matplotlib to show it. Scroll-like element on bottom of the screen essentially redraws chart on every event and it happens so fast that it feels real-time.
I also tried to use SVG as a way to feed vector image into QML. It's also possible and it also works. Matplotlib offers SVG backend (matplotlib.backends.backend_svg) and Image QML component support inline SVG data as a Source. The SVG data is text so it can be easily passed around between python and QML. You can update (source) field with new data and image will redraw itself automatically, you can rely on data binding. It could've worked quite well, but sadly SVG support in Qt 4 and 5 is poor. Clipping is not supported (charts will go out of the axes); resizing Image does not re-render SVG but resizes pixel image of it; changing SVG causes image to blink; performance is poor. Maybe this will change one day later, but for now stick to agg backend.
I really love design of both matlpotlib and Qt. It's smart and it meshes well without too much effort or boilerplate code.
It's in a fairly basic state, but https://github.com/jmitrevs/matplotlib_backend_qtquick provides a workable model to start from.
To quickly summarize the gist of the example provided with it:
The library provides the types FigureCanvasQtQuickAgg and NavigationToolbar2QtQuick.
The FigureCanvasQtQuickAgg type is registered with QML:
QtQml.qmlRegisterType(FigureCanvasQtQuickAgg, "Backend", 1, 0, "FigureCanvas")
This allows you to use it from within QML:
FigureCanvas {
id: mplView
objectName : "figure"
dpi_ratio: Screen.devicePixelRatio
anchors.fill: parent
}
The objectName property allows the canvas instance to be found from within the Python code.
The toolbar is made out of QML buttons,
In the demo, they provide a DisplayBridge Python class that is linked to the canvas and is responsible for the actual plotting and for forwarding events from the various toolbar buttons
Internally, the FigureCanvasQtQuickAgg backend is a QQuickPaintedItem. In its paint function it copies data from the renderer attribute of the matplotlib FigureCanvasAgg base class into a QImage and the QImage is then painted. This is a fairly similar design to how the QWidget version of matplotlib works.
I'm new with kivy, but I have decent experience with Python and Tkinter. I'm trying to control a carousel in kivy programmatically. Essentially, I have an external python program which I want to use to automatically switch images in the carousel. To make an example, I have some code in another file:
import time
while True:
time.sleep(1)
#do something to control the carousel
and then I have my kivy app:
import kivy
from kivy.app import App
from kivy.uix.carousel import Carousel
from kivy.uix.image import AsyncImage
class CarouselApp(App):
self.srcs = ["/a/bunch.png", "/of/paths.jpg", "/to/images.png."]
def build(self):
self.carousel = Carousel(direction="right")
for i in range(0, len(self.srcs)):
src = self.srcs[i]
image = AsyncImage(source=src, allow_stretch=True)
self.carousel.add_widget(image)
return self.carousel
if __name__ == "__main__":
CarouselApp().run()
I would like to be able to control which slide is displayed in the carousel using the top code, but I'm not sure how I would go about doing that, since I can't execute anything after App.run()
I have investigated kivy's Clock module, but I'm not sure that would work for me since I want to switch slides when certain conditions are satisfied rather than on a time basis. The time example I gave is simply an example of my line of thinking.
Any help would be greatly appreciated!
I strongly suggest using a Kivy file to handle this situation. Second, AsyncImage is used when you want to use an image that will be downloaded from the Internet, and as I can see you have the images you are going to use are locally stored, so use Image instead.
I think you should implement the method on_start in the class which is extending App (CarouselApp in this case) and schedule a function using Clock as,
def on_start(self):
Clock.schedule_interval(self.my_callback, 1)
Then in the same class (CarouselApp) you should define my_callback:
def my_callback(self, nap):
if condition_is_satisfied: # this is supposed to be your condition
# control carousel
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
class Imglayout(FloatLayout):
def __init__(self,**args):
super(Imglayout,self).__init__(**args)
with self.canvas.before:
Color(0,0,0,0)
self.rect=Rectangle(size=self.size,pos=self.pos)
self.bind(size=self.updates,pos=self.updates)
def updates(self,instance,value):
self.rect.size=instance.size
self.rect.pos=instance.pos
class MainTApp(App):
im=Image(source='img1.jpg')
def build(self):
root = BoxLayout(orientation='vertical')
c = Imglayout()
root.add_widget(c)
self.im.keep_ratio= False
self.im.allow_stretch = True
cat=Button(text="Categories",size_hint=(1,.07))
cat.bind(on_press=self.callback)
c.add_widget(self.im)
root.add_widget(cat);
return root
def callback(self,value):
self.im=Image(source='img2.jpg')
if __name__ == '__main__':
MainTApp().run()
What i am trying to do here, is change the image first loaded during object creation, which is shown when the app starts, and then change it when the button cat is pressed. I am trying it to do this way but it isnt happening. I would eventually want it to change with a swipe gesture.(with a little bit of swipe animation like it happens in the phone
what i am trying to build is a slideshow, which will change image in t seconds, unless swiped, and then when a new image comes the timer resets.
When the category button is pressed the image will not be there and a list of categories to select from. and when an item from the list is touched the images from that list will be displayed on the screen.
And at the end when everything has been done i would want to make it such that it automatically detects categories(based on directories in a specified location.) and then all the images will be available to it.(that is not telling it explicitly how many images and what images.)
But, i am not able to do the first thing, so i would really like some help on that. And maybe a few pointers on how to achieve the other things as well.
def callback(self,value):
self.im=Image(source='img2.jpg')
Your problem is in the definition of the callback. You create a new image with the new source, but you don't do anything with it, like adding it to your widget tree.
What you really want to do is modify the source of the existing image:
def callback(self, instance, value):
self.im.source = 'img2.jpg'
This will immediately replace the source of the existing image, which will immediately update in your gui. Note that I also added an instance parameter, which will be passed to the callback so you need to catch it or you'll crash with the wrong number of arguments.
Also, I'd define your image inside build rather than as a class level variable. I'm not sure if the current way will actually cause you problems, but it could in some circumstances.