ipywidget.Button induces persistence of output - ipywidgets

I am running the following code in a jupyter notebook. It produces some plots based on the selection in the SelectMultiple widget. When I use the plot method directly inst.plot(), the output works, only one figure is displayed. But when I use the button to create new plots the output is not cleared, instead of just one plot multiple figures are displayed. Any idea why that happens and how to fix it? GitHub Issue
%pylab inline
import pandas as pd
import numpy as np
import ipywidgets as widgets
from ipywidgets import SelectMultiple, Select, Layout, Button, VBox
from ipywidgets import IntProgress as Progress
from IPython.display import display, clear_output
class TheClass():
def __init__(self):
self._xmax = 0
self._xmin = 10
self.selector_steps = SelectMultiple(options=[5, 10, 100, 1000], value=(10, 100))
self.output = widgets.Output()
self.n_steps = 10
self.button_plot = Button(description='Plot')
self.button_plot.on_click(self.plot)
#property
def xmax(self):
return self._xmax
#xmax.setter
def xmax(self, value):
self._xmax = value
def x(self):
return np.linspace(self._xmin, self._xmax, self.n_steps)
def y(self):
return np.sin(self.x())
def plot(self, button=None):
with self.output:
clear_output()
self._create_plots()
show()
display(self.output)
##lru_cache(maxsize=32)
def _create_plots(self):
plots = []
figure()
for n_steps in self.selector_steps.value:
self.n_steps = n_steps
plt.plot(self.x(), self.y())
inst = TheClass()
VBox([inst.selector_steps, inst.button_plot])

Related

PyQt5 pyqtgraph (adding/removing) curves in a single plot

I'm new to pyqt5 and am just looking for some direction.
In a single pyqtgraphics PlotItem Graph I would like to add/remove configurable PlotCurveItems.
I started with QWidgetList items but it seems that will not provide me the ability to add the same ListItem->functions with multiple configurations.
As a next step I'm looking at using Parameter Trees but am not sure if I am just making things more complicated.
Ultimately I would like to use the PlotItem.addItem() to run a configurable function/method and view a list of Items I have added that I can remove or reconfigure.
Thanks in advance.
You might find what you are looking for here:
import pyqtgraph.examples
pyqtgraph.examples.run()
basically you create your plot as follow:
from PyQt5.QtGui import*
from PyQt5.QtCore import*
import pyqtgraph as pg
import numpy as np
import sys
class MyWidget(QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.win = pg.GraphicsWindow()
self.p = []
self.c = []
for i in range(3):
self.p.append(self.win.addPlot(row=i, col=0))
for j in range(2):
self.c.append(self.p[-1].plot(np.random.rand(100), pen=3*i+j))
self.update()
self.del_curve()
self.add_curve()
def update(self): # update a curve
self.c[3].setData(np.random.rand(100)*10)
def del_curve(self): # remove a curve
self.c[5].clear()
def add_curve(self): # add a curve
self.c.append(self.p[2].plot(np.random.rand(100)))
def startWindow():
app = QApplication(sys.argv)
mw = MyWidget()
app.exec_()
if __name__ == '__main__':
startWindow()

pyqt5: how to zoom in a figure on both x axes and y axes together?

I want to plot a figure by pyqt5 as below codes, and I want to see all the data in one figure and zoom in to see some detals; I hope when ´╝ęzoom in a part of this figure, x axes and y axes fit the detail auto together;
import sys
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import matplotlib.finance as mpf
class Window(QtWidgets.QDialog):
def __init__(self,Data,parent=None):
super().__init__(parent)
self.candleData=Data[0]
self.plots=len(Data)
if self.plots>1:
self.lineData=Data[1]
self.figure = plt.figure(figsize=(30,18))
self.axes = self.figure.add_subplot(111)
self.axes.hold(True)
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
self.toolbar.hide()
self.button2 = QtWidgets.QPushButton('Zoom')
self.button2.clicked.connect(self.zoom)
self.button3 = QtWidgets.QPushButton('Pan')
self.button3.clicked.connect(self.pan)
self.button4 = QtWidgets.QPushButton('Home')
self.button4.clicked.connect(self.home)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
btnlayout = QtWidgets.QHBoxLayout()
btnlayout.addWidget(self.button2)
btnlayout.addWidget(self.button3)
btnlayout.addWidget(self.button4)
qw = QtWidgets.QWidget(self)
qw.setLayout(btnlayout)
layout.addWidget(qw)
self.setLayout(layout)
def home(self):
self.toolbar.home()
def zoom(self):
self.toolbar.zoom()
def pan(self):
self.toolbar.pan()
def plot(self):
[obj.insert(0,i) for i,obj in enumerate(self.candleData)]
mpf.candlestick_ohlc(self.axes,self.candleData,width=0.8,colorup='r',colordown='g')
self.axes.grid()
print(self.plots)
if self.plots>1:
for i in range(len(self.lineData)):
self.axes.plot(self.lineData[i][0],self.lineData[i][1],color=self.lineData[i][2])
self.canvas.draw()
Now I plot a figure and zoom in for some small part as blow:
Screenshot:
but I should use "pan" to see all of it; shall I see all of it auto when I zoom in?

pyqtgraph custom tick labels potential bug

I'm using PyQtGraph and embedding a graph into a Qt program. I've added axisItems to the PlotWidget's constructor. It seems to do something a bit weird; I get some off line with 0___1 in the top right-hand corner. Does anyone know what is causing this and/or how to get rid of it?
I've attached a picture showing this and my code to generate what's in the picture.
If I don't add the AxisItem to the constructor (hence not getting my custom tick labels), the issue doesn't occur.
import sys
import numpy as np
from PySide import QtCore, QtGui, QtUiTools, QtXml
from ui import *
import pyqtgraph as pg
class PlotWidgetBarGraph(pg.PlotWidget):
def __init__(self, **opts):
# Add custom tick strings.
xDict = {1.:'1', 2.:'2', 3.:'3', 4.:'4',
5.:'5', 6.:'6', 7.:'7', 8.:'8',
9.:'1/2', 10.:'1/3', 11.:'1/4',
12.:'2/3', 13.:'2/4', 14.:'3/4',
15.:'1/2/3', 16.:'2/3/4',
17:'1/2/3/4'}
xAxis = pg.AxisItem(orientation='bottom')
xAxis.setTicks([xDict.items(), []])
super().__init__(axisItems={'bottom': xAxis})
# Create and add bar graph.
bg = pg.BarGraphItem(**opts)
self.addItem(bg)
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
splitter = QtGui.QSplitter(QtCore.Qt.Vertical)
x = np.arange(20)
y = np.abs(np.sin(x))+0.1
plot1 = PlotWidgetBarGraph(x=x, height=y, width=0.8, brush='b')
plot2 = PlotWidgetBarGraph(x=x, height=y, width=0.8, brush='b')
splitter.addWidget(plot1)
splitter.addWidget(plot2)
splitter.setStretchFactor(0, 1)
splitter.setStretchFactor(1, 2)
self.verticalLayout.addWidget(splitter)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Unable to implement mpl_connect

I'm working on a GUI that basically hold multiple widgets that each contain a figure as well as a few buttons/whatever. One of the figures is supposed to be interactive, calling a function whenever the user clicks on any part of the plot. Yet I can't get the function to fire using mpl_connect, even after playing with focus and whatnot. I'm somewhat new to PySide/Qt, so I don't exactly understand why my code is behaving like this (I've been searching for days for a solution, but haven't found anything about it).
I used Qt Designer to create the layout for the GUI. I'm using Spyder from Anaconda 2.2.0 (32-bit), Python 2.7, and PySide to develop the GUI. If it's any help, I come from more of a MATLAB background where I developed a full version of the GUI I'm trying to make in Python.
Below is the relevant code (scroll down a bit to see where the problem is):
from PySide import QtCore, QtGui
from PySide.QtCore import *
from PySide.QtGui import *
import numpy as np
import matplotlib
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
from PySide.QtGui import QPalette, QCursor
import matplotlib.colors as colors
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1316, 765)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.widget = QtGui.QWidget(self.centralwidget)
self.widget.setGeometry(QtCore.QRect(75, 40, 375, 490))
self.widget.setObjectName("widget")
color = self.centralwidget.palette().color(QPalette.Window)
self.leftPlot = MatplotlibWidget(None,'','','',False,color)
self.setupPlot(self.widget,self.leftPlot)
self.leftPlot.figure.tight_layout()
self.leftImage = self.leftPlot.axes.imshow(self.defaultSlide, cmap = mymap)
Snippet of interest:
self.leftPlot.figure.canvas.setFocusPolicy(QtCore.Qt.StrongFocus)
self.leftPlot.figure.canvas.setFocus()
cid = self.leftPlot.figure.canvas.mpl_connect('button_release_event', self.getCoordinates) # doesn't get called
plt.show()
def getCoordinates(self, event):
print 'dasdsadadsa'
print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)
The rest:
class MatplotlibWidget(FigureCanvas):
def __init__(self, parent=None,xlabel='x',ylabel='y',title='Title',showTicks=False,color=None):
super(MatplotlibWidget, self).__init__(Figure())
self.setParent(parent)
if color != None:
self.figure = Figure(facecolor=(color.red()/256.0,color.green()/256.0,color.blue()/256.0),frameon=0)
else:
self.figure = Figure(frameon=0)
self.canvas = FigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
self.axes.set_xlabel(xlabel)
self.axes.set_ylabel(ylabel)
self.axes.set_title(title)
self.axes.get_xaxis().set_visible(showTicks)
self.axes.get_yaxis().set_visible(showTicks)
class ControlMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ControlMainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
plt.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mySW = ControlMainWindow()
mySW.show()
sys.exit(app.exec_())
I'm aware the code is messy, but any input is greatly appreciated.
Update (2015-09-04) : I've updated the MWE I provided as part of my original answer to use instead the approach that is suggested in the matplotlib documentation to embed a mpl figure in an application. This approach does not use the pyplot interface (as in my original answer) and use the Object Oriented API of mpl instead. Also, since all the mpl artists (figure, axes, etc.) know each other, there is no need to explicitly create new class variables. This allows a structure of code that is, IMO, easier to read and to maintain.
The problem comes from the fact that you are not connecting correctly your event to self.leftPlot (FigureCanvasQTAgg), but to self.leftPlot.figure.canvas (FigureCanvasQTAgg.figure.FigureCanvasQTAgg) instead. You are creating a canvas within a canvas in the MatplotlibWidget class (which is already a subclass of FigureCanvasQTAgg). You only need to create one mpl canvas, pass a figure to it, and then connect the event to it directly.
I've put together a MWE to demonstrate how this can be done using the Object Oriented API of Matplotlib as suggested in the documentation:
from PySide import QtGui
import numpy as np
import sys
import matplotlib as mpl
mpl.use('Qt4Agg')
mpl.rcParams['backend.qt4']='PySide'
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
class ControlMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ControlMainWindow, self).__init__(parent)
self.setupUi()
def setupUi(self):
figure = mpl.figure.Figure(figsize=(5, 5))
leftPlot = MatplotlibWidget(figure)
self.setCentralWidget(leftPlot)
class MatplotlibWidget(FigureCanvasQTAgg):
def __init__(self, fig):
super(MatplotlibWidget, self).__init__(fig)
#-- set up an axe artist --
ax = fig.add_axes([0.1, 0.1, 0.85, 0.85])
ax.plot(np.arange(15), np.arange(15))
self.draw()
#---- setup event ----
self.mpl_connect('button_press_event', self.onclick)
def onclick(self, event):
x, y = event.x, event.y
print(x, y)
if x != None and y != None:
ax = self.figure.axes[0]
ax.plot(event.xdata, event.ydata, 'ro')
self.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mySW = ControlMainWindow()
mySW.show()
sys.exit(app.exec_())
The code above results in:

Draw a vertical line with Matplotlib

I would like to draw a vertical line with Matpotlib. I find this method : axvline but it doesn't work.
Here's my code :
import sys
import matplotlib
matplotlib.use('Qt4Agg')
from ui_courbe import *
from PyQt4 import QtGui
from matplotlib import pyplot as plt
class Window(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.setupUi(self)
self.boutonDessiner.clicked.connect(self.generatePlot)
def generatePlot(self):
# generate the plot
ax = self.graphicsView.canvas.fig.add_subplot(111)
ax.plot([1,3,5,7],[2,5,1,-2])
plt.axvline(x=4)
self.graphicsView.canvas.draw()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
I can see my plot but no vertical line. Why?
Best regards,
Philippe
Your example is not self contained, but I think you need to replace:
plt.axvline(x=4)
with:
ax.axvline(x=4)
You are adding the line to an axis that you are not displaying. Using plt. is the pyplot interface which you probably want to avoid for a GUI. So all your plotting has to go on an axis like ax.

Resources