Can I embed plotly graphs (offline) in my PyQt4 application? - python

I know that plotly renders into HTML and can be embedded in web-like environments. I wonder if one can do that inside an HTML window in a PyQt application? Specifically I'd like to know if that works offline, having no internet connection.
EDIT:
This is an excerpt how I finally embedded graphs using matplotlib:
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg \
import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg \
import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
class Contour(QtGui.QFrame):
def __init__(self, parent=None):
super(Contour, self).__init__(parent)
self.parent = parent
# a figure instance to plot on
self.figure = plt.figure(figsize=(20, 30))
r, g, b = 100./255., 100./255., 100./255.
self.figure.patch.set_facecolor(color=(r, g, b))
# this is the Canvas Widget that displays the `figure`
# it takes the `figure` instance as a parameter to __init__
self.canvas = FigureCanvas(self.figure)
# this is the Navigation widget
# it takes the Canvas widget and a parent
self.toolbar = NavigationToolbar(self.canvas, self)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
And then in another function:
# create an axis
ax1 = self.figure.add_subplot(211, frame_on=False)
ax2 = self.figure.add_subplot(212, frame_on=False)
# plot data
r, g, b = 39./255., 40./255., 34./255.
ax1.plot(x, y, ls='o', color=(r, g, b), linewidth=3)
ax1.plot(coo[0], coo[1], 'go', zorder=20) # leading edge
ax1.plot(xg, yg, 'mo', zorder=30) # leading edge
ax1.plot(xr, yr, 'yo', zorder=30) # curvature circle center
ax1.add_patch(circle)
ax1.set_title('Contour', fontsize=14)
ax1.set_xlim(-10.0, 110.0)
# ax1.set_ylim(-10.0, 14.0)
r, g, b = 249./255., 38./255., 114./255.
ax1.fill(x, y, color=(r, g, b))
ax1.set_aspect('equal')
ax2.plot(coo[0], gradient, 'go-', linewidth=3)
ax2.set_title('Gradient', fontsize=14)
ax2.set_xlim(-10.0, 110.0)

You can use QWebEngineView from the QWebEngineWidgets module (I used PyQt5 here but I guess it'll also work for PyQt4). You create html code using plotly.offline.plot and set this as the html text for your instance of QWebEngineView. If you specify output_type='div' it will give you directly a string. I do not know why, but in my case it only worked with include_plotlyjs='cdn', but in that case you need to be on the internet for it to work according to the plotly documentation. By doing it that way the plot stays interactive also in your PyQt application.
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QMainWindow
from plotly.graph_objects import Figure, Scatter
import plotly
import numpy as np
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
# some example data
x = np.arange(1000)
y = x**2
# create the plotly figure
fig = Figure(Scatter(x=x, y=y))
# we create html code of the figure
html = '<html><body>'
html += plotly.offline.plot(fig, output_type='div', include_plotlyjs='cdn')
html += '</body></html>'
# we create an instance of QWebEngineView and set the html code
plot_widget = QWebEngineView()
plot_widget.setHtml(html)
# set the QWebEngineView instance as main widget
self.setCentralWidget(plot_widget)
if __name__ == '__main__':
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()

I once tried using the:
import plotly.offline as plt
.
.
.
plt.plot(fig, filename=testName + '__plot.html')
and then tried to generate a plot.. this gave me an HTML file which then I also tried putting on a QWebView as its URL property [just to see if it renders].
Here is the image for your reference:

Plotly is primarily developed to make graphing in a browser easy. I don't think it can be embedded into a UI framework like PyQT or Tkinter. Plotly has a enterprise version which works in our company network without a internet connection.
PyQtgraph or MatPlotLib may be an option for you if you really need to embedd graphs in PyQT.
Here's sample code to export graphs as png's and then embed png image into your PyQT application.
import matplotlib.pyplot as plt
plt.plot([1,2,3,4,5], [10,20,30])
plt.savefig('graphs.png')
import os,sys
from PyQt4 import QtGui
pic.setPixmap(QtGui.QPixmap("graphs.png"))

Not sure if this totally meets your need put given you have
import plotly.plotly as py
you can save the image and then
py.image.save_as({'data': data}, 'your_image_filename.png')
pic.setPixmap(QtGui.QPixmap("your_image_filename.png"))
Warning, I haven't tried this

Related

How to detect pan and zoom action in matplotlib navibar?

I am using the matplotlib canvas and navbar by creating a custom MplWidget in python, as follows:
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class MplWidget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(vertical_layout)
self.canvas.toolbar = NavigationToolbar(self.canvas, self)
self.layout().addWidget(self.canvas.toolbar)
self.layout().addWidget(self.canvas)
self.canvas.axes.grid(b=True, which='both', axis='both')
self.canvas.figure.set_tight_layout(True)
I want to detect when the pan or the zoom tool is toggled. I found this: in matplotlib how do I catch that event "zoom tool" has been selected?
Following the solution there, I tried
self.canvas.toolbar.get_state()['_current_action']
or simply just self.canvas.toolbar.get_state(), but I get the error:
AttributeError: 'NavigationToolbar2QT' object has no attribute 'get_state'
It seems like a very basic function to see which action is in use currently, so I am sure there is a simple solution, but I can't seem to find it.
zoom toggled
I've found two solutions, but I don't know if they work with all kinds of backends (not an expert in those).
The first is axes-dependant:
ax.get_navigate_mode()
it returns 'PAN', 'ZOOM', or None.
(Link to documentation)
The second is figure-dependant:
fig.canvas.toolbar.mode
it returns "pan/zoom", "zoom rect" or "".
(Link to source code of the NavigationToolbar2 object)

plot doesn't update on button click but does when window resized

What's going on:
Program in python3/qt5/matplotlib. I am plotting three subplots and few plots in each. I wanted to update plots if user changes any data. That proved rather complicated hence the button 'Replot'. It calls a function that clears canvas and ... reloads the entire widget. It doesn't work as expected: when I press the button nothing happens, but when window gets resized after button clicked it does update the plot.
What would be a correct way to do it without having to put all the plots in that reload function?
I've managed to cut out a minimum code that does what I described - only it'd require the GUI file (created in qt5 designer), sensor log files and a settings file for configparser. (That indentation change on mplwidget is botched on purpose so that code displays as code. Didn't know how to make it display correctly otherwise)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys, os
from PyQt5.QtCore import Qt
from PyQt5 import QtCore, QtGui, QtWidgets, QtQuick
import matplotlib
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import dates
import numpy as np
import pandas as pd
import configparser
from RPGH_main_gui_signals_sorted import Ui_MainWindow
sys.path.append('..')
LOCAL_DIR = os.path.dirname(os.path.realpath(__file__)) + "/"
config_f = 'settings_config_parser.py'
cfg = configparser.ConfigParser()
cfg.read('settings_config_parser.py')
class DesignerMainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
super(DesignerMainWindow, self).__init__(parent)
self.setupUi(self)
self.replot_pushButton.clicked.connect(self.mpl_replot)
self.temperature_temp_max.setValue(cfg.getint('temperature', 'temp_max'))
self.temperature_temp_max.valueChanged.connect(self.write_temp_max)
self.mplwidget()
def mplwidget(self):
temp_max = cfg.getint('temperature', 'temp_max')
temp_log = 'sensor_logs/temperature_out.dat'
time_format = '%d,%m,%Y,%X'
temp_data = pd.read_csv(temp_log, header=None, delim_whitespace=True)
temp_data.columns = ['timestamp', 'temp_up', 'temp_down',
'temp_ground']
timestamp = pd.to_datetime(pd.Series(temp_data.timestamp),
format=time_format)
self.mpl.canvas.ax.plot(timestamp, temp_data.temp_up)
self.mpl.canvas.ax.fill_between(timestamp, temp_data.temp_up, temp_max,
where=temp_data.temp_up>=temp_max, edgecolor='red', facecolor='none',
hatch='/', interpolate=True)
def mpl_replot(self):
self.mpl.canvas.ax.clear()
self.mplwidget()
def write_temp_max(self):
send = self.temperature_temp_max.value()
message = str(send)
cfg.set('temperature', 'temp_max', message)
with open(config_f, 'w') as conffile:
cfg.write(conffile)
if __name__ == '__main__':
app = QtWidgets.QApplication([sys.argv])
gui = DesignerMainWindow()
gui.show()
sys.exit(app.exec_())

Embedding matplotlib figure in QtDesigner GUI

I am trying to muddle my way through embedding a matplotlib figure inside of a Qt GUI created using Qt Designer. I am already able to create the figure I want just through Python. I've created a basic GUI with widgets to allow me to select/load input files, and plot the data in those files in a matplotlib figure that is embedded in the GUI. I accomplish this by adding a blank widget to the GUI called plotwidget, and then calling the GraphInit class with this widget as input.
The problem I am currently facing is that while my plot shows up and updates as desired inside the GUI, it doesn't seem to pay attention to the size policy defined for it in either my Python code (where the FigureCanvas is created) or for the plotWidget widget in Qt Designer. I have been using this demo, among others, as a starting point for this project. However, all of these examples have generated the GUI entirely from within Python. I suspect I am doing something wrong in assigning the matplotlib figure to the widget, but I haven't been able to figure out what.
Code (import & plotting code removed, but the core where figure is added is there):
import sys
import os
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMessageBox, QFileDialog, QPushButton, QLabel, QRadioButton, QDoubleSpinBox, QSpinBox, QWidget, QSizePolicy, QMainWindow
import PyQt5.uic
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import json
from datetime import datetime
import numpy as np
class LogViewer(QApplication):
def __init__(self):
QApplication.__init__(self, sys.argv)
self.ui = UI()
#staticmethod
def main():
vwr = LogViewer()
vwr.run()
def run(self):
self.ui.win.show() # Show the UI
self.ui.run() # Execute the UI run script
self.exec_()
class Graph_init(FigureCanvas):
def __init__(self, parent=None):
fig = Figure()
self.axes = fig.add_subplot(111)
self.compute_initial_figure()
self.axes.grid()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class UI(object):
def __init__(self):
ui_path = os.path.dirname(os.path.realpath(__file__))
self.win = PyQt5.uic.loadUi(ui_path + '\\logview.ui')
self.filename = ui_path + '\\test.txt'
self.plotWin = Graph_init(self.win.plotWidget)
def run(self):
self.init_ui() # get initial values from the controllers
self.attach_ui_connections()
def init_ui(self):
w = self.win
w.txtLogFilename.setText(self.filename.split('/')[-1]) # just the file (no path)
def attach_ui_connections(self):
w = self.win
if __name__ == '__main__':
LogViewer.main()
GUI is available here. Any suggestions appreciated!
I checked your ui file and plotWidget has no layout at all. Try to give it one, I suggest a grid layout.
Then, after parenting the canvas
self.setParent(parent)
try adding it to the parent layout:
parent.layout().addWidget(self)

Multiple embedded matplotlib canvases in pyqt change size on mouse over

I'm trying to embed multiple matplotlib plots in a multi-column layout in a PyQt GUI. At first sight I succeed in setting up the layout as wanted but when moving the mouse over any of the canvasses they change size and 'flicker'. When pressing the zoom button on the toolbar this becomes more pronounced.
For each matplotlib canvas I have connected a toolbar. If I do not connect the toolbars the problem does not appear. I have tried arranging the toolbars and canvases several ways - with a QGridLayout or nested QVBoxLayouts and QHBoxLayouts. Either way the problem appears if there are plots along side each other. If I put all plots in a single column it does not.
I have tried this in Python 3.6 in Windows (Anaconda 5.0.1) with PyQt4 and Python 3.5.2 in Linux (KDE Neon 64 bit) and with both PyQt4 and PyQt5 (v. 5.7.1), matplotlib 1.5.1 but with the same result. I have also tried using add_axes instead of add_subplot. Can someone help me understand what is causing this or find some kind of workaround? I can not use matplotlib subplots.
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
figure1 = Figure()
figure2 = Figure()
figure3 = Figure()
figure4 = Figure()
canvas1 = FigureCanvas(figure1)
canvas2 = FigureCanvas(figure2)
canvas3 = FigureCanvas(figure3)
canvas4 = FigureCanvas(figure4)
ax1 = figure1.add_subplot(111)
ax2 = figure2.add_subplot(111)
ax3 = figure3.add_subplot(111)
ax4 = figure4.add_subplot(111)
toolbar1 = NavigationToolbar(canvas1, self)
toolbar2 = NavigationToolbar(canvas2, self)
toolbar3 = NavigationToolbar(canvas3, self)
toolbar4 = NavigationToolbar(canvas4, self)
mainLayout = QtWidgets.QGridLayout()
mainLayout.addWidget(toolbar1,0,0)
mainLayout.addWidget(toolbar2,0,1)
mainLayout.addWidget(toolbar3,2,0)
mainLayout.addWidget(toolbar4,2,1)
mainLayout.addWidget(canvas1,1,0)
mainLayout.addWidget(canvas2,1,1)
mainLayout.addWidget(canvas3,3,0)
mainLayout.addWidget(canvas4,3,1)
self.setLayout(mainLayout)
self.setWindowTitle("Flow Layout")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWin = Window()
mainWin.show()
sys.exit(app.exec_())
I had a similar issue and I solved it by:
toolbar1.setMinimumWidth(canvas1.width())
toolbar2.setMinimumWidth(canvas2.width())
toolbar3.setMinimumWidth(canvas3.width())
toolbar4.setMinimumWidth(canvas4.width())
The problem is when the toolbar becomes wider than the canvas.

Updating Graph generated using Matplotlib by button click

I am working on a GUI application, which generates graphs using Matplotlib package, for gui design i am using PyQt5.
In this application users loads the data from a line and then on pressing the generate button, a processed graph is generated, now the problem is that, on closing the graph, when user loads the new data, and press the generate button, graph is not displayed again.
Code
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
plt.subplots_adjust(hspace=0)
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 100, 100)
self.setWindowTitle('Generate Graph')
self.home()
def home(self):
btn = QPushButton('Generate', self)
btn.clicked.connect(self.generate_graph)
#btn.resize(100, 100)
#btn.move(100, 100)
self.show()
def generate_graph(self):
# In real application these points gets updated
x = [0,1,2,3,4,5,6,7,8,9]
y1 = [0,1,2,3,4,5,6,7,8,9]
y2 = [0,1,2,3,4,5,6,7,8,9]
ax1.plot(x,y1)
ax2.plot(x,y2)
plt.show()
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()
So i am posting the sample program which can show my problem, in this i created a button and generated two plots.
(Note: these are two subplots, i created two subplots because, i need to write ylabel on the adjacent axis, so it is a requirement i can't change and it must be like this)
I pressed the generate button, graph gets generated.
I closed the graph, and again pressed the generate button but its not re-generated.
Please suggest me what i can add to make this happen.
Is it possible to generate new graph every-time user presses the generate button, i think this will also solve the problem.
Please suggest and thanks in advance.
I had searched with this topic on this forum, and tried various thing like clearing the axis etc etc, but i think i am doing something wrong as i am new to all this.
You're mixing matplotlib.pyplot's show GUI with another PyQt GUI. The problem is that the figure to show in the matplotlib GUI is created only once. As soon as it's closed, it's lost.
The simple solution is to create it within the generate_graph function. Thereby a new figure is created and shown every time the button is pressed.
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
import matplotlib.pyplot as plt
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 100, 100)
self.setWindowTitle('Generate Graph')
self.home()
def home(self):
btn = QPushButton('Generate', self)
btn.clicked.connect(self.generate_graph)
self.show()
def generate_graph(self):
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
plt.subplots_adjust(hspace=0)
x = [0,1,2,3,4,5,6,7,8,9]
y1 = [0,1,2,3,4,5,6,7,8,9]
y2 = [0,1,2,3,4,5,6,7,8,9]
ax1.plot(x,y1)
ax2.plot(x,y2)
plt.show()
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()

Categories