{"id":11922,"date":"2025-05-28T11:25:12","date_gmt":"2025-05-28T09:25:12","guid":{"rendered":"https:\/\/www.basyskom.de\/?page_id=11922"},"modified":"2025-05-28T15:24:24","modified_gmt":"2025-05-28T13:24:24","slug":"interactive-plots-with-pyside6","status":"publish","type":"post","link":"https:\/\/www.basyskom.de\/en\/interactive-plots-with-pyside6\/","title":{"rendered":"Interactive Plots with PySide6"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"11922\" class=\"elementor elementor-11922\" data-elementor-post-type=\"post\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0b0126a e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"0b0126a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-89c5c87 elementor-widget elementor-widget-heading\" data-id=\"89c5c87\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Introduction<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-7ecd357 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"7ecd357\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-028e87d elementor-widget elementor-widget-text-editor\" data-id=\"028e87d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>In the Python world Matplotlib has become the de-facto standard for drawing charts. While getting started is easy, as developers can create charts with only a few lines of code, Matplotlib can be a little quirky sometimes. In this article we would like to demonstrate that it is actually not so difficult to integrate Matplotlib charts in a Qt application written in PySide. It is even possible to customize the chart by tweaking the default behavior, exchange menu entries or add specific paint logic.<\/p><p>\u00a0<\/p><p>If you are more interested in a general overview of using Qt (and Qt Creator) together with Python or if you need some arguments to start, you may want to look also into the <a href=\"https:\/\/www.basyskom.de\/pyside6-qt-for-python\/\">blog post<\/a> written by my collegaue Kai Uwe Broulik. Now let&#8217;s start with an example in the next section.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-400e9a7 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"400e9a7\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-95b8eca elementor-widget elementor-widget-heading\" data-id=\"95b8eca\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">A first example<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-926436a e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"926436a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e4de289 elementor-widget elementor-widget-text-editor\" data-id=\"e4de289\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>To run the examples, just create a virtual environment and install the dependencies PySide6, Matplotlib and Pandas.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-55870ff e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"55870ff\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6534a52 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"6534a52\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-solid'>python -m venv env\nsource env\/bin\/activate\npip install pyside6 matplotlib pandas <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4e32a76 elementor-widget elementor-widget-text-editor\" data-id=\"4e32a76\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>basysKom is a company that is enthuasiastic about Open Source and Linux &#8211; so using a dataset to classify penguins seems to be a natural fit for this example: You can download it from <a href=\"https:\/\/github.com\/allisonhorst\/palmerpenguins\/blob\/main\/inst\/extdata\/penguins.csv\" target=\"_blank\" rel=\"noopener\">Github<\/a>. Each row in the dataset belongs to a penguin of one of the species adelie, gentoo\u00a0 (yeah, the eponymous penguin species of the Linux distribution) and chinstrap. The columns store measurements of bill length and thickness, flipper length and the body mass next to some other features like island habitat and year of observation. If you want to know more about the data, take a look at the <a href=\"https:\/\/allisonhorst.github.io\/palmerpenguins\/index.html\" target=\"_blank\" rel=\"noopener\">official page<\/a>.<\/p><p>\u00a0<\/p><p>Following is the complete code to load the dataset, remove rows with missing values and visualize the features with a scatter plot. The feature on the x- and y-axis can be selected by choosing the corresponding entry in the\u00a0<em>QComboBox<\/em>.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-4d3ea97 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"4d3ea97\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b9c5c59 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"b9c5c59\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-python'>import sys\n \nimport pandas as pd\n \nfrom PySide6.QtWidgets import (\n    QApplication, QMenu, QWidget, QVBoxLayout,\n    QSizePolicy, QComboBox, QLabel, QPushButton\n)\nfrom PySide6.QtGui import QAction, QPainter, QColor\nfrom PySide6.QtCore import Qt, QObject, QRectF\n \nimport matplotlib.pyplot as plt\nfrom matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar\nfrom matplotlib.backends.backend_qtagg import FigureCanvas\nfrom matplotlib.figure import Figure\n \nclass DataVisWidget(FigureCanvas):\n    def __init__(self, parent=None, width=300, height=300, dpi=100):\n        self.fig = Figure(figsize=(width \/ dpi, height \/ dpi))\n        super().__init__(self.fig)\n         \n        self.ax = self.fig.subplots()\n        if parent is not None:\n            self.setParent(parent)\n \n    def update_plot(self, df, column_name_x, column_name_y, column_name_label):\n        categories = df[column_name_label].astype(&quot;category&quot;)\n        codes = categories.cat.codes\n        labels = categories.cat.categories\n         \n        cmap = plt.get_cmap(&quot;Set2&quot;)\n        colormap = [cmap(code) for code in codes]\n \n        self.ax.clear()\n        self.ax.scatter(df[column_name_x], df[column_name_y], c=colormap)\n        self.ax.legend(handles=[\n           plt.Line2D([], [], marker=&#039;o&#039;, linestyle=&#039;&#039;, color=cmap(i), label=str(label))\n                      for i, label in enumerate(labels)\n        ])\n        self.ax.set_xlabel(column_name_x)\n        self.ax.set_ylabel(column_name_y)\n        self.request_plot_update()\n \n    def request_plot_update(self):\n        self.fig.canvas.draw()\n        self.update()\n \ndef main():\n    # load palmer penguin dataset\n    df = pd.read_csv(&quot;.\/data\/penguins.csv&quot;)\n    columns = {\n        &quot;bill_length_mm&quot;: &quot;Bill Length (mm)&quot;,\n        &quot;bill_depth_mm&quot;: &quot;Bill Depth (mm)&quot;,\n        &quot;flipper_length_mm&quot;: &quot;Flipper Length (mm)&quot;,\n        &quot;body_mass_g&quot;: &quot;Body Mass&quot;\n    }\n    target_column = &quot;species&quot;\n    subset = list(columns.keys()) + [target_column]\n    #remove rows with zero values\n    df.dropna(subset=subset, inplace=True)\n \n    app = QApplication(sys.argv)\n    app.setApplicationDisplayName(QObject.tr(&quot;Palmer Penguins&quot;))\n    main_widget = QWidget()\n    layout = QVBoxLayout(main_widget)\n    vis_widget = DataVisWidget(main_widget, width=800, height=600)\n    vis_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)\n \n    toolbar = NavigationToolbar(vis_widget, main_widget)\n \n    combobox_x = QComboBox(main_widget)\n    combobox_y = QComboBox(main_widget)\n    for feature, name in columns.items():\n        combobox_x.addItem(QObject.tr(name), feature)\n        combobox_y.addItem(QObject.tr(name), feature)\n \n    def update_plot():\n        vis_widget.update_plot(df, combobox_x.currentData(), combobox_y.currentData(), target_column)\n \n    combobox_x.currentIndexChanged.connect(update_plot)\n    combobox_y.currentIndexChanged.connect(update_plot)\n    combobox_y.setCurrentIndex(1)\n \n    button = QPushButton(QObject.tr(&quot;Quit&quot;), main_widget)\n    button.clicked.connect(app.quit)\n \n    layout.addWidget(toolbar)\n    layout.addWidget(QLabel(QObject.tr(&quot;X Axis:&quot;)))\n    layout.addWidget(combobox_x)\n    layout.addWidget(QLabel(QObject.tr(&quot;Y Axis:&quot;)))\n    layout.addWidget(combobox_y)\n    layout.addWidget(vis_widget)\n    layout.addWidget(button)\n \n    main_widget.show()\n    app.exec()\n \nif __name__ == &quot;__main__&quot;:\n    main() <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5855bab e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"5855bab\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e19d1c3 elementor-widget elementor-widget-text-editor\" data-id=\"e19d1c3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>After starting the application, you should see something similar to the screenshot:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-113df33 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"113df33\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-aa074ce elementor-widget elementor-widget-image\" data-id=\"aa074ce\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img fetchpriority=\"high\" decoding=\"async\" width=\"800\" height=\"821\" src=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL.png\" class=\"attachment-large size-large wp-image-11928\" alt=\"basysKom, HMI Dienstleistung, Qt, Cloud, Azure\" srcset=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL.png 834w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL-292x300.png 292w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL-768x788.png 768w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL-12x12.png 12w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL-560x575.png 560w\" sizes=\"(max-width: 800px) 100vw, 800px\">\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-df5a564 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"df5a564\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4e52a3c elementor-widget elementor-widget-text-editor\" data-id=\"4e52a3c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>So how does the integration wit Qt\/PySide6 work? Matplotlib offers different render backends of which the <em>QtAgg<\/em> backend is used by default. Extend the rendering canvas as follows: Import <em>FigureCanvas<\/em> from <em>matplotlib.backends.backend_qtagg<\/em> and let your class inherit from it, then create a <em>Figure<\/em> object in the constructor. When adding one or more subplots to the figure, store the returned axes object (or list of axes objects, if you are creating multiple subplots) as a class member. Use the axes object for configuring and rendering the plot as it is done in the update_plot() method. As a <em>FigureCanvas<\/em> based class inherits also from <em>QWidget<\/em>, it can be integrated into a Qt application like any other QWidget.<\/p><p>\u00a0<\/p><p>Adding Matplotlib&#8217;s <em>NavigationToolbar2QT<\/em> makes the plot interactive: In the snippet above, we just instantiate the toolbar, pass the <em>FigureCanvas<\/em> based instance as a parameter and add everything to our layout. That&#8217;s it. Now, the user is able to pan and zoom the plot and save it as a png image.<\/p><p>\u00a0<\/p><p>Unfortunately it is not straightforward to deviate from this standard appearance: The rubberband used to select the zoom region is <a href=\"https:\/\/github.com\/matplotlib\/matplotlib\/blob\/b4cb934bce90e7fc8fadc8dbeca70febd8f340d9\/lib\/matplotlib\/backends\/backend_qt.py#L526\" target=\"_blank\" rel=\"noopener\">hardcoded<\/a>, the toolbar is ready-made and doesn&#8217;t offer many options to tweak its behavior. So how it is possible to add a bit more customization on top?<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-f854ae4 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"f854ae4\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-17ffea9 elementor-widget elementor-widget-heading\" data-id=\"17ffea9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Customizing Matplotlibs<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-ffe9113 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"ffe9113\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-33fc90e elementor-widget elementor-widget-text-editor\" data-id=\"33fc90e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>As FigureCanvas is QWidget based, you can override all the well-known event handlers like <em>paintEvent()<\/em>, <em>contextMenuEvent()<\/em>, etc. &#8230; Additionally, Matplotlib offers its own callbacks for event handling.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-a167c80 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"a167c80\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d6ed79d elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"d6ed79d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-python'>self.fig.canvas.mpl_connect(&quot;button_press_event&quot;, self.on_press)\nself.fig.canvas.mpl_connect(&quot;motion_notify_event&quot;, self.on_notify)\nself.fig.canvas.mpl_connect(&quot;button_release_event&quot;, self.on_release) <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6a7cdab e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"6a7cdab\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-dde7adb elementor-widget elementor-widget-text-editor\" data-id=\"dde7adb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>So, what is the advantage to use these specialized Matplotlib event handlers over Qt&#8217;s own <em>mousePressEvent()<\/em>, <em>mouseReleaseEvent()<\/em>, etc.? Matplotlib passes an event parameter to the callback handlers that stores mouse coordinates in both widget space (mouse position inside the widget) and plot space (mouse position relative to the x- and y-axes, i.e. translated to the space of the plotted function) along with useful information such as whether the mouse is inside or outside the chart&#8217;s axes.<\/p><p>\u00a0<\/p><p>This knowledge makes it easy to implement our own rubberband logic. Here you can find the updated DataVisWidget (everything else stays the same):<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-ac9ec7a e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"ac9ec7a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-996acd5 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"996acd5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-python'>class DataVisWidget(FigureCanvas):\n    def __init__(self, parent=None, width=300, height=300, dpi=100):\n        self.fig = Figure(figsize=(width \/ dpi, height \/ dpi))\n        super().__init__(self.fig)\n         \n        self.ax = self.fig.subplots()\n        if parent is not None:\n            self.setParent(parent)\n \n        self.fig.canvas.mpl_connect(&quot;button_press_event&quot;, self.on_press)\n        self.fig.canvas.mpl_connect(&quot;motion_notify_event&quot;, self.on_notify)\n        self.fig.canvas.mpl_connect(&quot;button_release_event&quot;, self.on_release)\n        self.reset_plot_action = QAction(&quot;Reset Plot&quot;, self)\n        self.reset_plot_action.triggered.connect(self.reset_plot)\n        self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)\n        self.addAction(self.reset_plot_action)\n \n        self.rubberband_rect = None\n        self.data_rect = None\n \n        self.x_lim = None\n        self.y_lim = None\n \n    def request_plot_update(self):\n        self.fig.canvas.draw()\n        self.update()\n \n    def update_plot(self, df, column_name_x, column_name_y, column_name_label):\n        categories = df[column_name_label].astype(&quot;category&quot;)\n        codes = categories.cat.codes\n        labels = categories.cat.categories\n         \n        cmap = plt.get_cmap(&quot;Set2&quot;)\n        colormap = [cmap(code) for code in codes]\n \n        self.ax.clear()\n        self.ax.scatter(df[column_name_x], df[column_name_y], c=colormap)\n        self.ax.legend(handles=[\n           plt.Line2D([], [], marker=&#039;o&#039;, linestyle=&#039;&#039;, color=cmap(i), label=str(label))\n                      for i, label in enumerate(labels)\n        ])\n        self.ax.set_xlabel(column_name_x)\n        self.ax.set_ylabel(column_name_y)\n        self.x_lim = self.ax.get_xlim()\n        self.y_lim = self.ax.get_ylim()\n        self.request_plot_update()\n \n    def on_press(self, event):\n        if self.rubberband_rect is not None or self.data_rect is not None:\n            return\n        if event.button != 1: # left button\n            return\n        self.rubberband_rect = [event.x, event.y, event.x, event.y]\n        self.data_rect = [event.xdata, event.ydata, event.xdata, event.ydata]\n        self.update()\n \n    def on_notify(self, event):\n        if self.rubberband_rect is None or self.data_rect is None:\n            return\n        if event.inaxes:\n            self.rubberband_rect[2] = event.x\n            self.rubberband_rect[3] = event.y\n            self.data_rect[2] = event.xdata\n            self.data_rect[3] = event.ydata\n            self.update()\n \n    def on_release(self, event):\n        if self.data_rect is not None:          \n            x1, y1, x2, y2 = self.data_rect\n            if x1 != x2 and y1 != y2:\n                self.ax.set_xlim(sorted([x1, x2]))\n                self.ax.set_ylim(sorted([y1, y2]))\n        self.rubberband_rect = None\n        self.data_rect = None\n        self.fig.canvas.draw()\n        self.update()\n \n    def reset_plot(self):\n        if self.x_lim is None or self.y_lim is None:\n            return\n        self.ax.set_xlim(self.x_lim)\n        self.ax.set_ylim(self.y_lim)\n        self.request_plot_update()\n \n    def paintEvent(self, event):\n        super().paintEvent(event)\n        if self.rubberband_rect is not None:\n            p = QPainter(self)\n            p.setBrush(QColor(0, 0, 127, 64))\n            x1, y1, x2, y2 = self.rubberband_rect\n            box = QRectF()\n            box.setCoords(x1, self.height() - y1, x2, self.height() - y2)\n            p.drawRect(box) <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-230a494 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"230a494\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9ab468f elementor-widget elementor-widget-text-editor\" data-id=\"9ab468f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tIn the mouse event handlers, we store both, the mouse coordinates in widget and in plot space. We use the widget space mouse coordinates to draw a rubberband rectangle inside the paintEvent(). Make sure to call the paintEvent of the super class first to plot the chart. Now you can observe that the rubberband has indeed changed from being rendered with a dashed pattern to being colorized in a half-transparent blue.\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-bf5f414 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"bf5f414\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e6118af elementor-widget elementor-widget-image\" data-id=\"e6118af\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" width=\"800\" height=\"784\" src=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL_Customized.png\" class=\"attachment-large size-large wp-image-11927\" alt=\"basysKom, HMI Dienstleistung, Qt, Cloud, Azure\" srcset=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL_Customized.png 829w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL_Customized-300x294.png 300w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL_Customized-768x752.png 768w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL_Customized-12x12.png 12w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/05\/Palmer_Penguins_MPL_Customized-560x549.png 560w\" sizes=\"(max-width: 800px) 100vw, 800px\">\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-f2892ed e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"f2892ed\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-513d454 elementor-widget elementor-widget-text-editor\" data-id=\"513d454\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tWhen the user releases the mouse button, the limits of the x- and y-axes are set to the formerly stored plot space coordinates. Et voil\u00e0 &#8211; the plot now displays the section defined by the rubberband rectangle. We also included a context menu by setting the context menu policy to <em>ContextMenuPolicy.ActionsContextMenu<\/em> and adding the reset action. Now the user is able to reset the plot to the default limits, which we store when the plot is created.\n<h3 id=\"InteractivePlotswithPySide6-Otherplottinglibararies\"><\/h3>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-c31f0be e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"c31f0be\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-33b1696 elementor-widget elementor-widget-heading\" data-id=\"33b1696\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Other plotting libararies<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-433b164 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"433b164\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-aad1f5f elementor-widget elementor-widget-text-editor\" data-id=\"aad1f5f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tYou may have observed that the method update_plot() to draw the actual plot is rather complicated. The reason is that Matplotlib expects an array with indices or color values to colorize the scatter points\u00a0 &#8211; it cannot process an array with category labels that are stored as strings. That means, we have to convert the category labels to a color map and create the legend accordingly. Having the possibility to just throw the species column to the scatter method would make life much easier. The plotting library <a href=\"https:\/\/seaborn.pydata.org\/\" target=\"_blank\" rel=\"noopener\">Seaborn<\/a> offers exactly this. Seaborn is built on top of Matplotlib, making it just as seamless to integrate into Qt applications as Matplotlib itself. The updated method using Seaborn is much shorter:\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-037e84f e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"037e84f\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-bcb9d55 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"bcb9d55\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-python'># Don&#039;t forget to install Seaborn first: pip install seaborn\nimport seaborn as sns\n \n# The rest of the code remains the same\n# ...\n \n# The updated method using Seaborn\ndef update_plot(self, df, column_name_x, column_name_y, column_name_label):\n    self.ax.clear()\n    sns.scatterplot(data=df, x = column_name_x, y = column_name_y, hue=column_name_label, ax=self.ax)\n    self.x_lim = self.ax.get_xlim()\n    self.y_lim = self.ax.get_ylim()\n    self.request_plot_update()\n\n\n <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-19eef97 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"19eef97\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5fe98b7 elementor-widget elementor-widget-text-editor\" data-id=\"5fe98b7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Note the <em>ax<\/em> parameter that we have passed to Seaborn&#8217;s <em>scatterplot()<\/em> method: If <em>ax<\/em> is not None, Seaborn uses the given axis to draw into. This is important. Without the ax parameter, Seaborn tries to create a new axis and therefore a new chart.<\/p><p>\u00a0<\/p><p>Matplotlib and Seaborn are among the most popular, but certainly far from being the only plotting libraries in the Python world. There exists others like <a href=\"https:\/\/plotly.com\/python\/\" target=\"_blank\" rel=\"noopener\">Ploty<\/a> and <a href=\"https:\/\/bokeh.org\/\" target=\"_blank\" rel=\"noopener\">Bokeh<\/a>, that render interactive plots by default. But they output HTML and JavaScript, meaning that a renderer for web content like QWebEngine is needed to embed them into a PySide6 application.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3605214 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"3605214\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c798f2c elementor-widget elementor-widget-heading\" data-id=\"c798f2c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Conclusion<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-4d113c9 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"4d113c9\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a67380b elementor-widget elementor-widget-text-editor\" data-id=\"a67380b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tIn this article we have demonstrated that it is easy to integrate Matplotlib charts into PySide6 applications. It is even possible to tailor Matplotlib charts to your own styling guidelines and design specifications by customizing Matplotlib&#8217;s look and feel. If you feel that Matplotlib is too complex or limited, then Seaborn offers more sophisticated plot widgets and easier integration. We think that writing Qt user interfaces on top of complex data science applications is a really good fit.\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Nowadays it is getting more and more popular to write Qt applications in Python using a binding module like PySide6. One reason for this is probably Python&#8217;s rich data science ecosystem which makes it a breeze to load and visualize complex datasets. In this article we focus (although not exclusively) on the widespread plotting library Matplotlib: We demonstrate how you can embed it in PySide applications and how you can customize the default look and feel to your needs. We round off the article with an outlook into Python plotting libaries beyond Matplotlib and their significance for Qt.<\/p>","protected":false},"author":16,"featured_media":4011,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1,2,846,8],"tags":[805,804,15],"class_list":["post-11922","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein","category-blog","category-python","category-qt","tag-pyside","tag-python","tag-qt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/11922","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/comments?post=11922"}],"version-history":[{"count":38,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/11922\/revisions"}],"predecessor-version":[{"id":12008,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/11922\/revisions\/12008"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media\/4011"}],"wp:attachment":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media?parent=11922"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/categories?post=11922"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/tags?post=11922"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}