Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plotnine and pyqt5 #298

Open
breisfeld opened this issue Aug 11, 2019 · 9 comments
Open

plotnine and pyqt5 #298

breisfeld opened this issue Aug 11, 2019 · 9 comments

Comments

@breisfeld
Copy link

@breisfeld breisfeld commented Aug 11, 2019

Hello,

plotnine is a wonderful package. I am trying to figure out how to incorporate plots into a pyqt5 application. For matplotlib, there are a number of examples, but with plotnine, I don't know where to start. Any guidance is greatly appreciated.

Thank you.

@has2k1
Copy link
Owner

@has2k1 has2k1 commented Aug 12, 2019

I have not tried it. Can you post a link to a pyqt5 example application for matplotlib.

@breisfeld
Copy link
Author

@breisfeld breisfeld commented Aug 12, 2019

Here is an example using pyqt4, but the example will work with pyqt5 if the imports are changed appropriately:

https://stackoverflow.com/a/12465861

The imports should look something like the following:

from PyQt5.QtWidgets import QPushButton, QDialog, QVBoxLayout, QLabel, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

Thanks!

@breisfeld
Copy link
Author

@breisfeld breisfeld commented Sep 1, 2019

I haven't looked at the implementation, but it might be enough if an API exposed the matplotlib axes or figure.

Right now if I try one of the examples on the website, which culminates in something like the following:

>> myplot = (ggplot(df)
 + geom_col(aes('x', 'y', fill='cat'))
 + geom_point(aes('x', y='yfit', color='cat'))
 + geom_path(aes('x', y='yfit', color='cat'))
)

and then try to access some of the properties of myplot, I don't get access to the underlying matplotlib objects:

>> print(type(myplot.figure), type(myplot.axs))
<class 'NoneType'> <class 'NoneType'>

Perhaps I am misunderstanding the existing API.

@has2k1
Copy link
Owner

@has2k1 has2k1 commented Sep 1, 2019

The way to get the objects is by calling the draw method.

>> myplot = (ggplot(df)
 + geom_col(aes('x', 'y', fill='cat'))
 + geom_point(aes('x', y='yfit', color='cat'))
 + geom_path(aes('x', y='yfit', color='cat'))
)

fig = myplot.draw()
axs = fig.get_axes()
@breisfeld
Copy link
Author

@breisfeld breisfeld commented Sep 1, 2019

Awesome! Thanks.

@has2k1
Copy link
Owner

@has2k1 has2k1 commented Sep 1, 2019

@breisfeld, please post a solution if you have one. I want to make sure this works for the next release.

@breisfeld
Copy link
Author

@breisfeld breisfeld commented Sep 1, 2019

The solution I am using may not be at all useful to others because of the way my application is structured. In any case, here is a little about what I am doing at the moment.

My present application is a PyQT5 wrapper that creates inputs for, runs, and analyzes outputs from a computational engine written in C. I am using plotnine for the analysis phase.

As of now, I am pickling figures from matplotlib (and can now do the same with plotnine) during the analysis phase. These then get stored in an sqlite database. The GUI accesses the database, unpickles the figures, and displays them.

The relevant code for the display (which I have as a QDialog) is something like

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT
from matplotlib.figure import Figure

class PlotsDialog(QDialog):
    def __init__(self, title, simplot, parent=None, *args):
        super().__init__(parent, *args)
        self._title = title
        self._plot = simplot
        self.setup_ui()

    def setup_ui(self):

        self.figure = Figure()
        self.canvas = FigureCanvas(self._plot)
        self.toolbar = NavigationToolbar2QT(self.canvas, self)

        # set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        self.setLayout(layout)
        self.setWindowTitle(f"Simulation results: {self._title}")

        buttons = QDialogButtonBox(QDialogButtonBox.Ok)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)

        self.show()

Here, the simplot argument is the unpickled figure object.

This approach allows panning and zooming of static plots, which is adequate for now. If matplotlib figure pickles contain the underlying plotted data, something much more interactive could be implemented.

@has2k1
Copy link
Owner

@has2k1 has2k1 commented Sep 2, 2019

Once the ui is setup, is it possible to create new figures e.g by replacing the "canvas" widget with another canvas widget for another figure?

@breisfeld
Copy link
Author

@breisfeld breisfeld commented Sep 2, 2019

Here is a stand-alone application that demonstrates switching figures in a canvas:

"""
Example adapted for plotnine from
https://stackoverflow.com/a/12465861/11918518
"""

import sys
import random

from plotnine import ggplot, geom_point, aes, geom_line
import pandas as pd
import numpy as np

from PyQt5.QtWidgets import QApplication, QPushButton, QDialog, QVBoxLayout

import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure


class Window(QDialog):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        self.figure = Figure()
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)

        self.button = QPushButton("Plot")
        self.button.clicked.connect(self.plot)

        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)
        self.setLayout(layout)

    def plot(self):
        # generate some 'data' to plot
        data = np.empty([100, 4])
        data[:, 0] = x = np.linspace(0, 2 * np.pi, 100)
        data[:, 1] = np.random.rand(1, 100)
        data[:, 2] = np.sin(x)
        data[:, 3] = np.cos(x) * np.sin(x)
        df = pd.DataFrame(data, columns=["x", "y1", "y2", "y3"])

        # change the dependent variable and color each time this method is called
        y = random.choice(["y1", "y2", "y3"])
        color = random.choice(["blue", "red", "green"])

        # specify the plot and get the figure object
        ff = ggplot(df, aes("x", y)) + geom_point(color=color) + geom_line()
        fig = ff.draw()

        # update to the new figure
        self.canvas.figure = fig

        # refresh canvas
        self.canvas.draw()

        # close the figure so that we don't create too many figure instances
        plt.close(fig)


if __name__ == "__main__":
    app = QApplication(sys.argv)

    main = Window()
    main.show()

    sys.exit(app.exec_())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.