Matplotlib besitzt eine integrierte
Unterstützung für PyQt6. Du kannst Matplotlib-Diagramme direkt als Widgets in eine PyQt6-Benutzeroberfläche
einbetten. Dafür stellt die Bibliothek spezielle Backend-Klassen bereit. "FigureCanvasQTAgg" Diese Klasse
fungiert als reguläres Qt-Widget (QWidget). Sie rendert das Matplotlib-Diagramm und kann überall in Ihr
PyQt6-Layout eingefügt werden.
Diese Quelle
habe ich angezapft!
Diese Webseite stellt für den Quellcode Download- und Kopierfunktionen zur Verfügung. Das geht nur mit JavaScript.
Wenn du mich für einen unsicheren Kantonisten hältst, verlasse diese Webseite. Alle Informationen und Angebote
sind ohne Gewähr!
#!/usr/bin/env python3
import sys
import math
import os
import numpy as np
from PyQt6.QtWidgets import (
QApplication,
QWidget,
QLabel,
QLineEdit,
QPushButton,
QVBoxLayout,
QHBoxLayout,
QFrame,
)
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import Qt
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
# Verzeichnis dieser Datei, damit das Icon unabhängig vom Startort gefunden wird
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ICON_PATH = os.path.join(BASE_DIR, "icon.png")
class QuadraticSolver(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Quadratische Gleichung")
# --- Icon ---
if os.path.exists(ICON_PATH):
self.setWindowIcon(QIcon(ICON_PATH))
# --- Feste Startgröße ---
self.resize(520, 560)
# --- Größenbeschränkungen ---
# Mindestgröße: darunter würden Eingabefelder, Button und Plot zu eng werden
self.setMinimumSize(460, 500)
# Maximalgröße: großzügiger als in Phase 2, da der Plot von mehr Fläche profitiert,
# aber weiterhin begrenzt, damit die Formularelemente nicht unproportioniert wirken
self.setMaximumSize(900, 850)
# --- Eingabefelder für a, b, c ---
self.input_a = QLineEdit()
self.input_a.setPlaceholderText("a")
self.input_a.setObjectName("coeffInput")
self.input_b = QLineEdit()
self.input_b.setPlaceholderText("b")
self.input_b.setObjectName("coeffInput")
self.input_c = QLineEdit()
self.input_c.setPlaceholderText("c")
self.input_c.setObjectName("coeffInput")
# Formel-Labels bekommen einen eigenen Objektnamen,
# damit sie sich optisch von der Ergebnis-Anzeige unterscheiden
formula_label_1 = QLabel("x² +")
formula_label_1.setObjectName("formulaLabel")
formula_label_2 = QLabel("x +")
formula_label_2.setObjectName("formulaLabel")
formula_label_3 = QLabel("= 0")
formula_label_3.setObjectName("formulaLabel")
# Zeile mit den drei Eingabefeldern nebeneinander,
# dazwischen kleine Labels zur Lesbarkeit der Formel
input_row = QHBoxLayout()
input_row.addWidget(self.input_a)
input_row.addWidget(formula_label_1)
input_row.addWidget(self.input_b)
input_row.addWidget(formula_label_2)
input_row.addWidget(self.input_c)
input_row.addWidget(formula_label_3)
# Rahmen um die Eingabezeile, damit die Formel optisch als
# zusammengehörige Einheit erkennbar ist
input_frame = QFrame()
input_frame.setObjectName("inputFrame")
input_frame.setLayout(input_row)
# --- Button ---
self.button_calc = QPushButton("Berechnen")
self.button_calc.setObjectName("calcButton")
self.button_calc.clicked.connect(self.calculate)
self.button_calc.setCursor(Qt.CursorShape.PointingHandCursor)
# --- Ergebnis-Anzeige ---
self.result_label = QLabel("")
self.result_label.setObjectName("resultLabel")
self.result_label.setWordWrap(True)
# --- Matplotlib-Plotbereich ---
# figsize/dpi sind Startwerte; Qt skaliert das Canvas-Widget innerhalb
# des Layouts ohnehin flexibel mit
self.figure = Figure(figsize=(4.5, 3.2), dpi=100)
self.canvas = FigureCanvas(self.figure)
self.canvas.setMinimumHeight(260)
self.ax = self.figure.add_subplot(111)
self.draw_empty_plot()
# --- Gesamtlayout (senkrecht gestapelt) ---
main_layout = QVBoxLayout()
main_layout.addWidget(input_frame)
main_layout.addWidget(self.button_calc)
main_layout.addWidget(self.result_label)
main_layout.addWidget(self.canvas)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(14)
self.setLayout(main_layout)
self.apply_styles()
# Fenster mittig auf dem Bildschirm positionieren
self.center_on_screen()
def apply_styles(self):
"""Definiert das Aussehen aller Widgets über ein zentrales QSS-Stylesheet."""
self.setStyleSheet("""
/* Fensterhintergrund */
QWidget {
background-color: #f4f6fb;
font-family: "Segoe UI", Arial, sans-serif;
font-size: 14px;
}
/* Rahmen um die Eingabezeile */
QFrame#inputFrame {
background-color: #ffffff;
border: 1px solid #d0d7e6;
border-radius: 10px;
padding: 10px;
}
/* Eingabefelder a, b, c */
QLineEdit#coeffInput {
background-color: #ffffff;
border: 2px solid #c7cede;
border-radius: 6px;
padding: 6px 8px;
min-width: 50px;
max-width: 70px;
font-weight: 600;
color: #1f2937;
}
QLineEdit#coeffInput:hover {
border-color: #93a3c4;
}
QLineEdit#coeffInput:focus {
border-color: #2962bd;
background-color: #eef3fc;
}
/* Formel-Labels (x² +, x +, = 0) */
QLabel#formulaLabel {
color: #3a4257;
font-weight: 600;
background: transparent;
}
/* Berechnen-Button */
QPushButton#calcButton {
background-color: #2962bd;
color: #ffffff;
border: none;
border-radius: 8px;
padding: 10px 16px;
font-weight: 600;
}
QPushButton#calcButton:hover {
background-color: #2650a0;
}
QPushButton#calcButton:pressed {
background-color: #1e3f80;
}
/* Ergebnis-Anzeige */
QLabel#resultLabel {
background: transparent;
color: #1f2937;
font-size: 14px;
padding-top: 4px;
}
""")
def draw_empty_plot(self):
"""Zeigt einen leeren, aber gestylten Plot, solange noch nicht gerechnet wurde."""
self.ax.clear()
self.style_axes()
self.ax.text(
0.5, 0.5, "Noch keine Berechnung",
transform=self.ax.transAxes,
ha="center", va="center",
fontsize=10, color="#9aa5b8",
)
self.canvas.draw()
def style_axes(self):
"""Zentrale Optik-Einstellungen für die Achsen, damit der Plot
zum restlichen Fenster-Design passt."""
self.figure.patch.set_facecolor("#f4f6fb")
self.ax.set_facecolor("#ffffff")
for spine in self.ax.spines.values():
spine.set_color("#c7cede")
self.ax.tick_params(labelsize=8, colors="#3a4257")
self.ax.grid(True, linestyle="--", alpha=0.4)
self.ax.axhline(0, color="#9aa5b8", linewidth=1)
self.ax.axvline(0, color="#9aa5b8", linewidth=1)
def update_plot(self, a, b, c, real_roots, vertex):
"""Zeichnet die Parabel inklusive Nullstellen und Scheitelpunkt."""
self.ax.clear()
vx, vy = vertex
punkte_von_interesse = list(real_roots) + [vx]
x_min = min(punkte_von_interesse)
x_max = max(punkte_von_interesse)
# Rand links/rechts, damit die Kurve nicht am Bildrand klebt;
# bei nur einem Punkt (z.B. Scheitel = einzige Nullstelle) trotzdem sinnvoller Bereich
spanne = max(x_max - x_min, 2.0)
rand = spanne * 0.6 + 1.0
xs = np.linspace(x_min - rand, x_max + rand, 400)
ys = a * xs**2 + b * xs + c
self.ax.plot(xs, ys, color="#2962bd", linewidth=2, label="f(x)")
if real_roots:
self.ax.scatter(
real_roots, [0] * len(real_roots),
color="#e14b4b", zorder=5, s=45, label="Nullstellen"
)
for rx in real_roots:
self.ax.annotate(
f"{rx:.3g}", (rx, 0),
textcoords="offset points", xytext=(0, 10),
ha="center", fontsize=8, color="#e14b4b",
)
self.ax.scatter([vx], [vy], color="#f5a623", zorder=5, s=45, label="Scheitelpunkt")
self.ax.annotate(
f"S({vx:.3g}, {vy:.3g})", (vx, vy),
textcoords="offset points", xytext=(8, -12),
fontsize=8, color="#c9860a",
)
self.style_axes()
self.ax.set_title("Graph der Parabel", fontsize=10, color="#3a4257")
self.ax.legend(loc="upper center", fontsize=7, framealpha=0.9)
self.figure.tight_layout()
self.canvas.draw()
def center_on_screen(self):
"""Positioniert das Fenster mittig auf dem primären Bildschirm."""
screen = QApplication.primaryScreen()
if screen is None:
return
screen_geometry = screen.availableGeometry()
window_geometry = self.frameGeometry()
center_point = screen_geometry.center()
window_geometry.moveCenter(center_point)
# move() erwartet die obere linke Ecke, daher aus window_geometry entnehmen
self.move(window_geometry.topLeft())
def calculate(self):
"""Liest a, b, c aus, prüft die Eingabe, berechnet Nullstellen/Scheitelpunkt
und aktualisiert sowohl den Ergebnistext als auch den Plot."""
try:
a = float(self.input_a.text().replace(",", "."))
b = float(self.input_b.text().replace(",", "."))
c = float(self.input_c.text().replace(",", "."))
except ValueError:
self.result_label.setText("Bitte gültige Zahlen für a, b und c eingeben.")
self.draw_empty_plot()
return
if a == 0:
self.result_label.setText("a darf nicht 0 sein (sonst keine quadratische Gleichung).")
self.draw_empty_plot()
return
# Scheitelpunkt: x = -b/2a, y = f(x) an dieser Stelle
vx = -b / (2 * a)
vy = a * vx**2 + b * vx + c
diskriminante = b**2 - 4 * a * c
if diskriminante > 0:
x1 = (-b + math.sqrt(diskriminante)) / (2 * a)
x2 = (-b - math.sqrt(diskriminante)) / (2 * a)
self.result_label.setText(f"Zwei Lösungen: x1 = {x1:.4g}, x2 = {x2:.4g}")
self.update_plot(a, b, c, real_roots=[x1, x2], vertex=(vx, vy))
elif diskriminante == 0:
x = -b / (2 * a)
self.result_label.setText(f"Eine Lösung: x = {x:.4g}")
self.update_plot(a, b, c, real_roots=[x], vertex=(vx, vy))
else:
realteil = -b / (2 * a)
imagteil = math.sqrt(-diskriminante) / (2 * a)
self.result_label.setText(
f"Keine reelle Lösung: x1 = {realteil:.4g} + {imagteil:.4g}i, "
f"x2 = {realteil:.4g} - {imagteil:.4g}i"
)
# Keine reellen Nullstellen -> leere Liste, Kurve und Scheitelpunkt
# werden trotzdem angezeigt, damit man sieht, warum es keine Lösung gibt
self.update_plot(a, b, c, real_roots=[], vertex=(vx, vy))
def main():
app = QApplication(sys.argv)
window = QuadraticSolver()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
Vorbereitung:
Fenstergrößen anpassen, da nun ein Plot-Bereich Platz braucht.
Nun die Matplotlib-Canvas erstellen und ins Layout einfügen.
Die Plot-Methoden hinzufügen (draw_empty_plot, style_axes, update_plot) direkt nach apply_styles.
Die calculate()-Funktion, [Zeile 271 / 312], erweitern, sodass sie nach
der Lösung den Plot aktualisiert (und bei Fehlern/a=0 den leeren Plot zeigt).
Jetzt Header-Kommentar aktualisieren und mit dem ersten Testlauf alles prüfen.
Matplotlib-Einbettung in Qt:
Matplotlib bringt einen eigenen Qt-Backend mit: FigureCanvasQTAgg ist ein QWidget, das eine Matplotlib-Figure
rendert. Dadurch kann ich es genau wie jedes andere Qt-Widget behandeln – self.canvas wird einfach per
main_layout.addWidget(self.canvas) ins Layout gehängt und verhält sich wie Button oder Label auch.
Figure vs. Axes:
Figure ist die gesamte "Zeichenfläche" (inkl. Hintergrund).
self.ax (eine Axes-Instanz via add_subplot(111)), [Zeile 103], ist das eigentliche
Koordinatensystem, in das geplottet wird. Ich erstelle beide einmal im Konstruktor und rufe bei jeder
Berechnung self.ax.clear() auf, statt jedes Mal eine neue Figure zu bauen – das ist deutlich performanter und vermeidet
Speicherlecks bei wiederholten Klicks.
Trennung der Optik-Logik (style_axes):
Da ich die Achsen bei jedem Update mit clear() zurücksetze, gehen auch alle Styling-Einstellungen (Gitter, Rahmenfarben,
Nulllinien) verloren. Daher habe ich das in eine eigene Methode style_axes() ausgelagert, die nach jedem clear() erneut
aufgerufen wird – sowohl im leeren Zustand (draw_empty_plot), [Zeile 190, 104,280,285],
als auch nach einer Berechnung (update_plot), [Zeile 214,298,302,312]. So bleibt der Look
konsistent mit dem restlichen Fenster (gleiche Blau-/Grautöne wie im QSS-Stylesheet).
Dynamischer x-Wertebereich:
Statt eines festen Plot-Bereichs (z. B. immer -10 bis 10) berechne ich die Spanne aus den tatsächlich interessanten
Punkten – Nullstellen und Scheitelpunkt: x_min/x_max aus diesen Punkten plus ein proportionaler Randfaktor. Das sorgt
dafür, dass die Parabel bei kleinen wie bei großen Koeffizienten immer gut sichtbar und zentriert im Bild liegt, statt
z. B. bei c=1000000 nur eine winzige Kurve am Bildrand zu zeigen.
Drei Sonderfälle im Plot:
Zwei reelle Nullstellen: beide werden als rote Punkte markiert, mit Achsenbeschriftung. Eine Lösung (Diskriminante = 0):
Nullstelle und Scheitelpunkt fallen zusammen – der Plot zeigt trotzdem beide Marker sauber übereinander. Keine reelle
Lösung: Ich übergebe eine leere real_roots-Liste. Die Parabel und der Scheitelpunkt werden trotzdem gezeichnet – so
sieht man visuell sofort, warum es keine Lösung gibt (die Parabel schneidet die x-Achse nicht).
Leerer Ausgangszustand:
draw_empty_plot() zeigt beim Programmstart (und bei Fehleingaben) einen dezenten
Platzhaltertext statt eines nackten weißen Rechtecks – das wirkt weniger wie ein Darstellungsfehler.
PyQt6 Bauplan Fenster Phase 4. Ohne Gewähr © 2026 Hans Busche