0

I am trying to embed a matplotlib figure in my QT application, using which I want to show different plots upon different user actions.

I have created a custom figure canvas, in which I initialize a figure and a subplot. The code is as followed:

from matplotlib.backends.backend_qt5 import FigureCanvasQT, FigureManagerQT
from PyQt5 import QtWidgets
import matplotlib.pyplot as plt
import numpy as np

class CustomFigureCanvas(FigureCanvasQT):
    def __init__(self, parent=None, cmap_name="coolwarm"):
        fig = plt.Figure()
        self.color_map = plt.get_cmap(cmap_name)
        self.axes = fig.add_subplot(111)
        super().__init__(fig)
        self.setParent(parent)
        self.setBaseSize(300, 300)
        self.setMaximumSize(400, 400)
        self.setMinimumSize(250, 250)
        self.setSizePolicy(
            QtWidgets.QSizePolicy.MinimumExpanding,
            QtWidgets.QSizePolicy.MinimumExpanding,
        )

    def set_clf_2d(self, clf_2d):
        self.clf = clf_2d

    def plot_new_datapoints(self, x2D):
        self.add_datapoint(x2D)

    @staticmethod
    def _make_meshgrid(x, y, h=0.02):
        x_min, x_max = x.min() - 1, x.max() + 1
        y_min, y_max = y.min() - 1, y.max() + 1
        XX, YY = np.meshgrid(
            np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)
        )
        return XX, YY

    def _plot_contours(self, xx, yy, **params):
        """Plot the decision boundaries for a classifier.

        Parameters
        ----------
        ax: matplotlib axes object
        clf: a classifier
        xx: meshgrid ndarray
        yy: meshgrid ndarray
        params: dictionary of params to pass to contourf, optional
        """
        Z = self.clf.predict(np.c_[xx.ravel(), yy.ravel()])
        Z = Z.reshape(xx.shape)
        self.axes.contourf(xx, yy, Z, **params)

    def plot_data(self, x2D, y):
        """plots the given array and the decision function bounday.

        Arguments:
            x2D {np.array} -- [2d array]
            y {np.array} -- [1d array]
        """

        x0, x1 = x2D[:, 0], x2D[:, 1]
        xx, yy = CustomFigureCanvas._make_meshgrid(x0, x1)
        labels = ["Cognitive", "Not Cognitive"]
        colors = ["r", "b"]
        self.axes.clear()
        self._plot_contours(xx, yy, cmap=self.color_map, alpha=0.8)
        target_ids = [0, 1]
        for i, c, label in zip(target_ids, colors, labels):
            print(i, label)
            self.axes.scatter(
                x0[y == i, 0],
                x1[y == i, 1],
                color=c,
                label=label,
                marker="o",
                s=(15, 15),
            )

        self.axes.set_xlim(xx.min(), xx.max())
        self.axes.set_ylim(yy.min(), yy.max())
        self.axes.set_title("2D Representation using PCA")
        self.axes.legend(fontsize=8)
        self.axes.plot()

    def add_datapoint(self, x2d):
        """Adds a new datapoint to the plot

        Arguments:
            x2d {a 2d single point, [x,y]} -- [np.array with shape (1,2)]
            axes {plt.axes} -- [description]

        """
        print(x2d, type(x2d))
        self.axes.scatter(
            x2d[:, 0],
            x2d[:, 1],
            color="k",
            label="Current Text",
            marker="o",
            s=(15, 15),
        )
        self.axes.legend(fontsize=8)

My problem is that whatever I do, my FigureCanvas will always be filled with black, and won't be changed on any circumstances.

By using the CustomFigureCanvas you can reproduce my problem.

My problem. The black widget is my figure canvas.

Farhood ET
  • 1,432
  • 15
  • 32
  • 1
    Can you make a smaller [reprex]? I can't test this code (it's not complete) and I think you may need to change the way you configure matplotlib (which isn't shown in this code). – strubbly May 28 '19 at 15:33
  • @strubbly I have updated my post and put some classes that I had forgotten to put before. This is my whole UI package and is detached from my models and controller packages(which are many). I only use Matplotlib in my UI classes. – Farhood ET May 28 '19 at 15:46
  • Remove everything that isn't needed for the problem you are asking about. See [mcve]. – ImportanceOfBeingErnest May 28 '19 at 16:27

1 Answers1

1

So I was just trying to mimic what was written in this post, and I tried using FigureCanvasQTAgg instead of FigureCanvasQT and suddenly everything started to work. I don't know why this exists, and the pyqt documentation is very unclear about these kinds of differences.

Farhood ET
  • 1,432
  • 15
  • 32
  • 2
    Maybe [this makes things clearer](https://matplotlib.org/faq/usage_faq.html#what-is-a-backend). Basically, to draw something using matplotlib you need both a renderer (the thing that does the drawing) and a canvas (the place where things are drawn on). `FigureCanvasQT` only provides a canvas; to use it you would still need a renderer. `FigureCancasQtAgg` is based on `FigureCanvasQT` but includes an AGG rendering engine as well. – Heike May 28 '19 at 17:10