L'intégration de Matplotlib (MPL) dans une interface graphique (GUI) offre une flexibilité et une interactivité considérablement améliorées pour les utilisateurs. Alors que mpl seul permet de créer des graphiques statiques, l'incorporation dans une GUI permet aux utilisateurs d'interagir directement avec les données visuelles par le biais d'outils de zoom, de panning, de sélection et d'autres options personnalisables. Cela rend l'analyse des données plus intuitive et efficace, car les utilisateurs peuvent explorer les tendances et les détails spécifiques sans avoir à générer de nombreux graphiques statiques pour chaque vue.
En outre, une interface utilisateur bien conçue peut simplifier la manipulation de vastes ensembles de données et rendre les visualisations plus accessibles, même à ceux qui ne sont pas familiers avec la programmation.
On parle de backend pour identifier la solution technique utilisée pour la mise en oeuvre d'interfaces graphiques. Matplotlib offre une grande variété de backends et notamment :
Backend Qt (Qt5Agg) : Qt est un framework de développement d'applications multiplateforme largement utilisé, qui permet de créer des interfaces graphiques, des applications de bureau, des applications mobiles et même des interfaces pour des systèmes embarqués. Il est réputé pour sa flexibilité, sa performance et son ensemble complet de widgets et d'outils de développement. MPL est compatible avec les deux binding Qt existants : PySide et PyQt.
Backend Tkinter (TkAgg) : Tk est une bibliothèque de création d'interfaces graphiques (GUI) qui est utilisée dans divers langages de programmation (initialement développée pour TCL), et qui est particulièrement connue pour sa simplicité et sa légèreté. Tkinter est l'interface Python (on dit aussi le binding) pour Tk, et est souvent considérée comme la bibliothèque de GUI standard pour Python. Moins riche que Qt, Tk permet néanmoins de développer des interfaces graphiques complexes et fonctionnelles.
Backend GTK (GTK3Agg) : GTK, acronyme de GIMP Toolkit, est une bibliothèque de création d'interfaces graphiques (GUI) multiplateforme largement utilisée, en particulier dans les environnements de bureau Linux tels que GNOME. GTK est apprécié pour sa flexibilité, son look moderne et sa capacité à créer des interfaces hautement personnalisables. Bien entendu, GTK et utilisable en Python.
Il existe encore d'autres backends...
Nous allons développer une application graphique en Python qui permet aux utilisateurs de visualiser des courbes de fonctions sinus et cosinus. La fenêtre de l'application contient deux boutons, "Sinus" et "Cosinus", placés côte à côte en haut, ainsi qu'une zone de tracé en dessous. En cliquant sur l'un des boutons, la courbe correspondante de la fonction sinus ou cosinus est tracée sur la zone de dessin. De plus, lorsque l'utilisateur clique sur la zone de tracé, un marqueur est ajouté à l'endroit cliqué et des informations sur l'événement de clic sont affichées dans la console. L'application utilise la bibliothèque Tkinter pour l'interface graphique et Matplotlib pour les graphiques.
Voici le visuel de l'application à développer.
Et voici maintenant le code Python/Tkinter :
import sys
import numpy as np
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class MyWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Curve Tracer V1.0")
# On définit le Frame qui va contenir nos boutons.
button_frame = tk.Frame(self)
button_frame.pack(side=tk.TOP, fill=tk.X)
# On place les deux boutons dans le frame.
self.__btn_sinus = tk.Button(button_frame, text="Sinus", command=self.btn_sinus_clicked)
self.__btn_sinus.grid(row=0, column=0, sticky="nsew")
self.__btn_cosinus = tk.Button(button_frame, text="Cosinus", command=self.btn_cosinus_clicked)
self.__btn_cosinus.grid(row=0, column=1, sticky="nsew")
# On configure les boutons pour se partager la place disponible.
button_frame.grid_columnconfigure(0, weight=1)
button_frame.grid_columnconfigure(1, weight=1)
# On instancie le Canvas MPL.
self.__fig = Figure(figsize=(4, 3))
self.__canvas = FigureCanvasTkAgg(self.__fig, master=self)
self.__canvas.get_tk_widget().pack(fill=tk.BOTH, expand=1)
self.__canvas.mpl_connect("button_press_event", self.canvas_clicked)
self.__plt = self.__fig.add_subplot(111)
self.btn_sinus_clicked()
def canvas_clicked(self, event):
self.__plt.scatter(event.xdata, event.ydata, marker="+", s=100)
self.__canvas.draw()
print(dir(event))
def btn_sinus_clicked(self):
self.__plt.clear()
x = np.linspace(-10, 10, 1000)
y = np.sin(x)
self.__plt.plot(x, y, "r")
self.__canvas.draw()
def btn_cosinus_clicked(self):
self.__plt.clear()
x = np.linspace(-10, 10, 1000)
y = np.cos(x)
self.__plt.plot(x, y, "b--")
self.__canvas.draw()
if __name__ == "__main__":
app = MyWindow()
app.mainloop()
Comme vous le constater, le look Tk n'est pas forcément le plus sympa.
Heureusement, le projet CustomTkinter permet de relooker un peu les choses. Deux thèmes graphiques peuvent utilisés : Light et Dark.
Et voici les modifications à apporter au code pour utiliser l'extension CustomTkinter.
Note : pensez à l'installer (!pip install CustomTkinter).
import sys
import numpy as np
import customtkinter as ctk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class MyWindow(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Curve Tracer V1.0")
# On définit le Frame qui va contenir nos boutons.
button_frame = ctk.CTkFrame(self)
button_frame.pack(side='top', fill='x')
# On place les deux boutons dans le frame.
self.__btn_sinus = ctk.CTkButton(button_frame, text="Sinus", command=self.btn_sinus_clicked)
self.__btn_sinus.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
self.__btn_cosinus = ctk.CTkButton(button_frame, text="Cosinus", command=self.btn_cosinus_clicked)
self.__btn_cosinus.grid(row=0, column=1, sticky="nsew", padx=10, pady=10)
# On configure les boutons pour se partager la place disponible.
button_frame.grid_columnconfigure(0, weight=1)
button_frame.grid_columnconfigure(1, weight=1)
# On instancie le Canvas MPL.
self.__fig = Figure(figsize=(4, 3))
self.__canvas = FigureCanvasTkAgg(self.__fig, master=self)
self.__canvas.get_tk_widget().pack(fill='both', expand=1)
self.__canvas.mpl_connect("button_press_event", self.canvas_clicked)
self.__plt = self.__fig.add_subplot(111)
self.btn_sinus_clicked()
def canvas_clicked(self, event):
self.__plt.scatter(event.xdata, event.ydata, marker="+", s=100)
self.__canvas.draw()
print(dir(event))
def btn_sinus_clicked(self):
self.__plt.clear()
x = np.linspace(-10, 10, 1000)
y = np.sin(x)
self.__plt.plot(x, y, "r")
self.__canvas.draw()
def btn_cosinus_clicked(self):
self.__plt.clear()
x = np.linspace(-10, 10, 1000)
y = np.cos(x)
self.__plt.plot(x, y, "b--")
self.__canvas.draw()
if __name__ == "__main__":
app = MyWindow()
app.mainloop()
PyQt et PySide sont deux bindings populaires qui permettent aux développeurs Python d'intégrer la bibliothèque Qt dans leurs applications.
Les deux bindings sont très similaires en termes de fonctionnalités, et le choix entre eux dépend souvent de considérations de licence et de préférences personnelles. Personnellement, j'utilise plutôt PySide (en version 6) pour sa licence LGPL.
L'exemple de code suivant est similaire à l'exemple pour Tkinter vu précédemment.
import sys
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QApplication
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvas
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Curve Tracer V1.0")
mainWidget = QWidget()
self.setCentralWidget(mainWidget)
vbox = QVBoxLayout(mainWidget)
# vbox = QVBoxLayout()
# mainWidget.setLayout(vbox)
self.__btnSinus = QPushButton("Sinus")
self.__btnSinus.clicked.connect(self.btnSinusClicked)
self.__btnCosinus = QPushButton("Cosinus")
self.__btnCosinus.clicked.connect(self.btnCosinusClicked)
hbox = QHBoxLayout()
hbox.addWidget( self.__btnSinus)
hbox.addWidget( self.__btnCosinus)
vbox.addLayout(hbox)
self.__canvas = FigureCanvas(Figure(figsize=(4, 3)))
self.__canvas.mpl_connect("button_press_event", self.canvasClicked)
vbox.addWidget(self.__canvas)
self.__plt = self.__canvas.figure.subplots()
self.btnSinusClicked()
def canvasClicked(self, event):
self.__plt.scatter(event.xdata, event.ydata, marker="+", s=100)
self.__canvas.draw()
print(dir(event))
def btnSinusClicked(self):
self.__plt.clear()
x = np.linspace(-10, 10, 1000)
y = np.sin(x)
self.__plt.plot(x, y, "r")
self.__canvas.draw()
def btnCosinusClicked(self):
self.__plt.clear()
x = np.linspace(-10, 10, 1000)
y = np.cos(x)
self.__plt.plot(x, y, "b--")
self.__canvas.draw()
if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
sys.exit(app.exec())
Pour ceux qui sont intéressés par l'utilisation de Qt pour y intégrer un composant MatPlotLib, je vous renvoie vers mon tuto Qt : vous y trouverez quelques exemples complémentaires.