# Copyright Eric Urban 2023 # available under the following license: # https://creativecommons.org/licenses/by/4.0/legalcode # # This example shows how to render a plot using matplotlib on a Qt GUI. # # the following dependencies are required from PyPi # matplotlib==3.7.1 # PyQt6==6.4.2 # PyQt6-Qt6==6.4.2 # PyQt6-sip==13.4.1 from PyQt6.QtWidgets import QApplication, QLabel, QWidget, QMainWindow, QVBoxLayout from PyQt6.QtGui import QColor, QPainter, QImage from PyQt6.QtCore import QMutexLocker, QMutex, QRect from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib.figure import Figure def create_example_plot(width_px:int, height_px:int, xdata, ydata): dpi = 100 # use a fixed DPI width_inches = width_px / dpi height_inches = height_px / dpi fig = Figure(figsize=(width_inches, height_inches), dpi=dpi,layout='compressed') canvas = FigureCanvasAgg(fig) ax = fig.add_subplot() ax.set(ylim=(0.0, 110.0)) ax.set_ylabel('Y Value') ax.set(xlim=(0.0, 1.0)) ax.set_xlabel('X Value') ax.plot(xdata, ydata, color='green') canvas.draw() w, h = canvas.get_width_height(physical=True) print("rendering is %d X %d out of %d X %d" % (w, h, width_px, height_px,)) # get a view into the raw data & make a deep copy rawdata = canvas.buffer_rgba() return w, h, bytearray(rawdata) class PlotRenderArea(QWidget): def __init__(self, width: int, height: int, plot_fn): super().__init__() self.l = QMutex() # used to protect access to self.plot_data self.image = None self.last_dim = None self.setMinimumSize(width, height) self.plot_fn = plot_fn self.plot_data = None def set_plot_data(self, **kwargs): with QMutexLocker(self.l): self.plot_data = kwargs # store the data self.last_dim = None # clear the most recent render self.image = None self.update() # signal Qt to redraw this now def paintEvent(self, paintEvent) -> None: # this is called whenever Qt is redrawing this UI element sz = self.size() with QMutexLocker(self.l): needs_render = True if self.last_dim is not None: last_width, last_height = self.last_dim if sz.width() == last_width and sz.height() == last_height: needs_render = False painter = QPainter(self) # fill with black painter.fillRect(QRect(0, 0, sz.width(), sz.height()), QColor(0, 0, 0)) # if there is data and it needs to be rendered, do it if self.plot_data is not None and needs_render: w, h, rgb_data = self.plot_fn(sz.width(), sz.height(), **self.plot_data) self.image = QImage(rgb_data, w, h, QImage.Format.Format_RGBA8888) # paint the image if it exists if self.image is not None: draw_w = min(sz.width(), self.image.width()) draw_h = min(sz.height(), self.image.height()) print("rendering %d X %d" % (draw_w, draw_h)) painter.drawImage(0, 0, self.image, 0, 0, draw_w, draw_h) # store the dimensions self.last_dim = (sz.width(), sz.height(),) app = QApplication([]) main_window = QMainWindow() main_window.setWindowTitle('Matplotlib PyQt6 example application') main_widget = QWidget() layout = QVBoxLayout() label = QLabel('Matplotlib PyQt6 example application') label.setMaximumHeight(60) layout.addWidget(label) render_area = PlotRenderArea(400, 300, create_example_plot) layout.addWidget(render_area) main_widget.setLayout(layout) main_window.setCentralWidget(main_widget) main_window.resize(1024, 768) xdata = [x/10.0 for x in range(10)] ydata = [x**2.0 for x in range(10)] # set the data for the renderer render_area.set_plot_data(xdata=xdata,ydata=ydata) main_window.show() app.exec()