Problem description:
I'm building an interface for my lab, I intergrarted matplotlib with pyqt5 widget, there is a real time video display widget working on multi-thread and queue. I managed to show single shot with cross-section plot by adding divider. However, when I remove the cross-section plots, and redraw the figure_idle, the video frame can never moved back to its initial position with its initial size. I adjust the figure with navigator_tool_bar (top, bottom...), However it seems that there are blank areas left after removing the cross-section plots. Do you have any idea?
The initial figure:
Display cross-sections:
Clear cross-section and redraw:
Video widget code:
class VideoViewer(FigureCanvas):
def __init__(self, parent=None, width=4, height=4, dpi=70):
self.figwidth = width
self.figheight = height
self.fig = Figure(figsize=(self.figwidth, self.figheight), dpi=dpi)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.axes = self.fig.add_subplot()
self.fig.subplots_adjust(top=0.975,bottom=0.048,left=0.029,right=0.983,hspace=0.2,wspace=0.2)
Cross section code:
#create cross-sections
self.divider = make_axes_locatable(self.axes)
self.top_ax = self.divider.append_axes("top", 1.05, pad=0.2,sharex=self.axes)
self.right_ax = self.divider.append_axes("right", 1.05,pad=0.2,sharey=self.axes)
#create lines
self.v_line = self.axes.axvline(xvlinepos, color='r')
self.h_line = self.axes.axhline(yhlinepos, color='g')
self.v_cross, = self.right_ax.plot(Norm_xvlinedata,np.arange(self.ImageData.shape[0]), 'r-')
self.h_cross, = self.top_ax.plot(np.arange(self.ImageData.shape[1]),Norm_yhlinedata, 'g-')
Clear cross-section code:
def ClearCrossSection(self):
self.fig.canvas.mpl_disconnect(self.pick_event_v)
self.fig.canvas.mpl_disconnect(self.pick_event_h)
self.h_line.remove()
self.v_line.remove()
self.v_cross.remove()
self.h_cross.remove()
self.right_ax.remove()
self.top_ax.remove()
self.fig.canvas.draw_idle()
What I did:
Light_layout + subplot_adjust -----> does not work.
Constrained_layout -----> does not work.
Constrained_layout + GridSpec by declaring at beginning self.axes takes all cols and rows.-----> Does not work.
An exemple of the problem:
import sys
# GUI
from PyQt5.QtWidgets import*
from PyQt5.QtCore import *
from PyQt5.QtGui import *
# Matplotlib
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
from mpl_toolkits.axes_grid1 import make_axes_locatable
from PIL import Image
import matplotlib.lines as lines
# Generate data
import numpy as np
'''
need to update video frame, I'm using blitting, so better not clear the whole figure.
the whole code could be too long to show here.
'''
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.resize(800,600)
self.setGeometry(350,250,950,600)
# Creat MainWidget
self.MainWidget = QWidget()
self.LayoutMainWidget = QGridLayout()
self.MainWidget.setLayout(self.LayoutMainWidget)
# Matplotlib widget
self.MatplotViewer = VideoViewer()
self.FigureTool = NavigationToolbar(self.MatplotViewer, self)
# Button plot image
self.ButtPltImg = QPushButton("Plot Image")
# BUtton plot cross
self.ButtPltCross = QPushButton("Cross View")
self.ButtPltCross.setCheckable(True)
self.ButtPltCross.setStyleSheet("background-color: Green")
# add widgets
self.LayoutMainWidget.addWidget(self.MatplotViewer,0,0,7,7)
self.LayoutMainWidget.addWidget(self.FigureTool, 7,0,1,7)
self.LayoutMainWidget.addWidget(self.ButtPltImg, 2,7,1,1)
self.LayoutMainWidget.addWidget(self.ButtPltCross, 3,7,1,1)
# Set central widget
self.setCentralWidget(self.MainWidget)
self.connection()
def GenerateImage(self, state):
if self.ButtPltCross.isChecked():
self.ButtPltCross.setChecked(False)
self.MatplotViewer.ClearCrossSection()
self.MatplotViewer.UpdateFrame()
else:
self.MatplotViewer.UpdateFrame()
def PlotCrossSection(self, state):
if self.ButtPltCross.isChecked():
self.MatplotViewer.PlotCrossSection()
def ClearCrossSection(self, state):
if not(self.ButtPltCross.isChecked()):
self.MatplotViewer.ClearCrossSection()
def connection(self):
self.ButtPltImg.clicked.connect(lambda state=True: self.GenerateImage(state))
self.ButtPltCross.clicked.connect(lambda state=True: self.PlotCrossSection(state))
self.ButtPltCross.clicked.connect(lambda state=True: self.ClearCrossSection(state))
class VideoViewer(FigureCanvas):
def __init__(self, parent=None, width=4, height=4, dpi=70):
# Figure
self.fig = Figure(figsize=(width, height), dpi=dpi)
# Init Parent
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
# Ax
self.axes = self.fig.add_subplot(111)
self.fig.subplots_adjust(top=0.975,bottom=0.048,left=0.029,right=0.983,hspace=0.2,wspace=0.2)
# Plot init image
self.PlotInitFrame()
def PlotInitFrame(self):
self.ImageData = self.ImageGenerate()
self.image = self.axes.imshow(self.ImageData, cmap='Greys', interpolation='none')
self.fig.canvas.draw_idle()
def UpdateFrame(self):
self.ImageData = self.ImageGenerate()
self.image.set_data(self.ImageData)
self.fig.canvas.draw_idle()
def PlotCrossSection(self):
# create axes
self.divider = make_axes_locatable(self.axes)
self.top_ax = self.divider.append_axes("top", 1.05, pad=0.2,sharex=self.axes)
self.right_ax = self.divider.append_axes("right", 1.05, pad=0.2,sharey=self.axes)
self.top_ax.xaxis.set_tick_params(labelbottom=False)
self.right_ax.yaxis.set_tick_params(labelleft=False)
# set cross section limit
self.right_ax.set_xlim(right=1.05)
self.top_ax.set_ylim(top=1.05)
# some pars
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
v_mid = int((xmin + xmax)/2)
h_mid = int((ymin + ymax)/2)
# set line
self.v_line = lines.Line2D([v_mid, v_mid], [ymin, ymax], color='r', pickradius=5)
self.axes.add_line(self.v_line)
self.h_line = lines.Line2D([xmin, xmax], [h_mid, h_mid], color='g', pickradius=5)
self.axes.add_line(self.h_line)
# set cross section data
Norm_xvlinedata = self.NormalizeData(self.ImageData[:,v_mid])
self.v_cross, = self.right_ax.plot(Norm_xvlinedata, np.arange(self.ImageData.shape[0]), 'r-')
Norm_yhlinedata = self.NormalizeData(self.ImageData[h_mid,:])
self.h_cross, = self.top_ax.plot(np.arange(self.ImageData.shape[1]), Norm_yhlinedata, 'g-')
self.fig.canvas.draw_idle()
def NormalizeData(self, data_temp):
min_temp = np.min(data_temp)
max_temp = np.max(data_temp)
if min_temp != max_temp:
return (data_temp-min_temp)/(max_temp-min_temp)
else:
return data_temp/data_temp
def ClearCrossSection(self):
self.v_line.remove()
self.h_line.remove()
self.top_ax.remove()
self.right_ax.remove()
self.fig.canvas.draw_idle()
def ImageGenerate(self):
xx,yy = np.meshgrid(np.linspace(-502,502,1024),np.linspace(-502,502,1024))
r = np.sqrt(xx**2+yy**2)
AMP = np.random.randint(150,250)
SIG = np.random.randint(200,250)
T = np.random.randint(115,135)
return AMP*np.exp(-(r)**2/(2*SIG**2))*np.cos(2*np.pi/T*r)
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = Window()
MainWindow.showMaximized()
sys.exit(app.exec_())
Related
I'm trying to get the RectangleSelector form matplotlib.widgets to work with PySimpleGUI.
I'm basing my test code on the RectangleSelector demo shown in the accepted answer on this question.
I'm getting the plot to show in PySimpleGUI but it's not interactive. Is it even possible in PySimpleGUI to have interactive matplotlib widgets?
import PySimpleGUI as sg
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.widgets import RectangleSelector
import matplotlib
matplotlib.use('TkAgg')
xdata = np.linspace(0,9*np.pi, num=301)
ydata = np.sin(xdata)
fig, ax = plt.subplots()
line, = ax.plot(xdata, ydata)
def draw_figure(canvas, figure):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side="top", fill="both", expand=1)
return figure_canvas_agg
def line_select_callback(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
rect = plt.Rectangle( (min(x1,x2),min(y1,y2)), np.abs(x1-x2), np.abs(y1-y2) )
ax.add_patch(rect)
rs = RectangleSelector(ax, line_select_callback,
drawtype='box', useblit=False, button=[1],
minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
layout = [[sg.Canvas(key="-CANVAS-")]]
window = sg.Window('test', layout, finalize=True, element_justification='center', font='Helvetica 16')
draw_figure(window["-CANVAS-"].TKCanvas, fig)
event, values = window.read()
Edit: Thanks to MikeyB for the pointer, I now have the following code, which shows an interactive plot, but it's still not possible to draw rectangles. The callback function doesn't seem to be firing. New code below:
import PySimpleGUI as sg
import numpy as np
from matplotlib.widgets import RectangleSelector
import matplotlib.figure as figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
# instantiate matplotlib figure
fig = figure.Figure()
ax = fig.add_subplot(111)
DPI = fig.get_dpi()
fig.set_size_inches(505 * 2 / float(DPI), 707 / float(DPI))
# ------------------------------- This is to include a matplotlib figure in a Tkinter canvas
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
if canvas.children:
for child in canvas.winfo_children():
child.destroy()
if canvas_toolbar.children:
for child in canvas_toolbar.winfo_children():
child.destroy()
figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
figure_canvas_agg.draw()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
def line_select_callback(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
rect = plt.Rectangle( (min(x1,x2),min(y1,y2)), np.abs(x1-x2), np.abs(y1-y2) )
print(rect)
ax.add_patch(rect)
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
super(Toolbar, self).__init__(*args, **kwargs)
# ------------------------------- PySimpleGUI CODE
layout = [
[sg.B('start', key='start')],
[sg.Canvas(key='controls_cv')],
[sg.Column(
layout=[
[sg.Canvas(key='fig_cv',
# it's important that you set this size
size=(500 * 2, 700)
)]
],
background_color='#DAE0E6',
pad=(0, 0)
)],
]
window = sg.Window('Test', layout)
while True:
event, values = window.read()
print(event, values)
if event == sg.WIN_CLOSED:
break
elif event == 'start':
x = np.linspace(0, 2 * np.pi)
y = np.sin(x)
line, = ax.plot(x, y)
rs = RectangleSelector(ax, line_select_callback,
drawtype='box', useblit=False, button=[1],
minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig, window['controls_cv'].TKCanvas)
window.close()
Is it even possible in PySimpleGUI to have interactive matplotlib widgets?
Yes.
The demo program on the project's GitHub shows how to make an interactive Matplotlib drawing.
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
You need to embed the controls into the window.
you need to add
fig.canvas.draw()
to your callback-function if you want the plot to be updated after the callback has triggered!
here's an updated version of your code that works just fine:
import PySimpleGUI as sg
import numpy as np
from matplotlib.widgets import RectangleSelector
import matplotlib.figure as figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
# instantiate matplotlib figure
fig = figure.Figure()
ax = fig.add_subplot(111)
DPI = fig.get_dpi()
fig.set_size_inches(505 * 2 / float(DPI), 707 / float(DPI))
# ------------------------------- This is to include a matplotlib figure in a Tkinter canvas
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
if canvas.children:
for child in canvas.winfo_children():
child.destroy()
if canvas_toolbar.children:
for child in canvas_toolbar.winfo_children():
child.destroy()
figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
figure_canvas_agg.draw()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
def line_select_callback(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
rect = plt.Rectangle( (min(x1,x2),min(y1,y2)), np.abs(x1-x2), np.abs(y1-y2) )
print(rect)
ax.add_patch(rect)
fig.canvas.draw()
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
super(Toolbar, self).__init__(*args, **kwargs)
# ------------------------------- PySimpleGUI CODE
layout = [
[sg.B('start', key='start')],
[sg.Canvas(key='controls_cv')],
[sg.Column(
layout=[
[sg.Canvas(key='fig_cv',
# it's important that you set this size
size=(500 * 2, 700)
)]
],
background_color='#DAE0E6',
pad=(0, 0)
)],
]
window = sg.Window('Test', layout)
while True:
event, values = window.read()
print(event, values)
if event == sg.WIN_CLOSED:
break
elif event == 'start':
x = np.linspace(0, 2 * np.pi)
y = np.sin(x)
line, = ax.plot(x, y)
rs = RectangleSelector(ax, line_select_callback,
drawtype='box', useblit=False, button=[1],
minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig, window['controls_cv'].TKCanvas)
window.close()
I embedded a matplotlib in a window maked in qtdesigner, pyqt5. I have 3 files
The window:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MplMainWindow(object):
def setupUi(self, MplMainWindow):
MplMainWindow.setObjectName("MplMainWindow")
MplMainWindow.resize(628, 416)
self.centralwidget = QtWidgets.QWidget(MplMainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.mpl = MplWidgetTest(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.mpl.sizePolicy().hasHeightForWidth())
self.mpl.setSizePolicy(sizePolicy)
self.mpl.setObjectName("mpl")
self.gridLayout_2.addWidget(self.mpl, 0, 0, 1, 1)
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setMaximumSize(QtCore.QSize(95, 16777215))
self.groupBox.setObjectName("groupBox")
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout.setObjectName("gridLayout")
self.buttonDrawDate = QtWidgets.QPushButton(self.groupBox)
self.buttonDrawDate.setMaximumSize(QtCore.QSize(75, 16777215))
self.buttonDrawDate.setObjectName("buttonDrawDate")
self.gridLayout.addWidget(self.buttonDrawDate, 0, 0, 1, 1)
self.buttonErase = QtWidgets.QPushButton(self.groupBox)
self.buttonErase.setMaximumSize(QtCore.QSize(75, 16777215))
self.buttonErase.setObjectName("buttonErase")
self.gridLayout.addWidget(self.buttonErase, 1, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 2, 0, 1, 1)
self.gridLayout_2.addWidget(self.groupBox, 0, 1, 1, 1)
MplMainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MplMainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 628, 21))
self.menubar.setObjectName("menubar")
MplMainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MplMainWindow)
self.statusbar.setObjectName("statusbar")
MplMainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MplMainWindow)
QtCore.QMetaObject.connectSlotsByName(MplMainWindow)
def retranslateUi(self, MplMainWindow):
_translate = QtCore.QCoreApplication.translate
MplMainWindow.setWindowTitle(_translate("MplMainWindow", "MainWindow"))
self.groupBox.setTitle(_translate("MplMainWindow", "GroupBox"))
self.buttonDrawDate.setText(_translate("MplMainWindow", "Draw"))
self.buttonErase.setText(_translate("MplMainWindow", "Erase"))
from mplwidgettest import MplWidgetTest
The Matplot widget class:
from PyQt5.QtWidgets import QSizePolicy, QWidget, QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
class MplCanvas(FigureCanvas):
"""Class to represent the FigureCanvas widget"""
def __init__(self):
# setup Matplotlib Figure and Axis
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
# initialization of the canvas
FigureCanvas.__init__(self, self.fig)
# we define the widget as expandable
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
# notify the system of updated policy
FigureCanvas.updateGeometry(self)
class MplWidgetTest(QWidget):
"""Widget defined in Qt Designer"""
def __init__(self, parent = None):
# initialization of Qt MainWindow widget
QWidget.__init__(self, parent)
# set the canvas to the Matplotlib widget
self.canvas = MplCanvas()
# create a NavigatioToolbar
self.ntb=NavigationToolbar(self.canvas,self)
# create a vertical box layout
self.vbl = QVBoxLayout()
# add mpl widget to vertical box
self.vbl.addWidget(self.canvas)
# add NavigationToolBar to vertical box
self.vbl.addWidget(self.ntb)
# set the layout to th vertical box
self.setLayout(self.vbl)
And the main file that call others:
import sys
from IHMDrawDates import Ui_MplMainWindow
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget
import numpy as np
import datetime
class DesignerMainWindow(QMainWindow, Ui_MplMainWindow):
def __init__(self, parent = None):
super(DesignerMainWindow, self).__init__(parent)
self.setupUi(self)
# connect the signals with the slots
self.buttonDrawDate.clicked.connect(self.drawDate)
self.buttonErase.clicked.connect(self.eraseDate)
def drawDate(self):
# base = datetime.datetime(2018, 1, 1)
# x = np.array([base + datetime.timedelta(hours=i) for i in range(24)])
# y = np.random.rand(len(x))
x = np.arange(0,100,0.1)
y = np.sin(x)
self.mpl.canvas.ax.plot(x,y)
self.mpl.canvas.ax.relim()
self.mpl.canvas.ax.autoscale(True)
self.mpl.canvas.draw()
def eraseDate(self):
self.mpl.canvas.ax.clear()
self.mpl.canvas.draw()
if __name__ == '__main__':
app=0
app = QApplication(sys.argv)
dmw = DesignerMainWindow()
# show it
dmw.show()
sys.exit(app.exec_())
After update to matplotlib 2.1.0, the home button does not work correctly. It always return to the inicial clean axes. Example:
1.-Before click in drawing button:
2.-Click in draw button:
3.-Click in zoom:
4.-Click in home:
before the update, pressing the button HOME it returned to the image number 2, now with matplotlib 2.1.0 it returned to image 4. Any idea.
When I add:
def drawDate(self):
x = np.arange(0,100,0.1)
y = np.sin(x)
self.mpl.canvas.ax.plot(x,y)
self.mpl.canvas.ax.relim()
self.mpl.canvas.ax.autoscale(True)
self.mpl.ntb.update()
self.mpl.canvas.draw()
def eraseDate(self):
self.mpl.canvas.ax.clear()
self.mpl.ntb.update()
self.mpl.canvas.draw()
Then occurs:
I guess the answer to this question is given in this post, just for Tkinter, instead of PyQt:
NagivationToolbar fails when updating in Tkinter canvas
The home button restores the initial state of the plot, which is usually the desired functionality of a home button.
Here you apparently want it to restore the state of the plot, after the button has clicked. This would be done by calling the toolbar's update() method.
In this case you'd add
self.mpl.ntb.update()
inside the drawDate method.
The method could then look like
def drawDate(self):
x = np.arange(0,100,0.1)
y = np.sin(x)
self.mpl.canvas.ax.plot(x,y)
self.mpl.canvas.ax.relim()
self.mpl.canvas.ax.autoscale(True)
self.mpl.ntb.update() # <-- add this
#self.mpl.ntb.push_current() # and possibly this(?)
self.mpl.canvas.draw()
def eraseDate(self):
self.mpl.canvas.ax.clear()
self.mpl.ntb.update() # <-- add this
#self.mpl.ntb.push_current() # and possibly this(?)
self.mpl.canvas.draw()
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 Izoom 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?
I want to make imshow() function update (256*873)array quickly. set_array()'s speed is about 15 times a second. I hope it can be about 30 times a second. there is a demo which using imshow() and set_array().Anybody help?Thank you so much!
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
TIMER_ID = wx.NewId()
class CanvasFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'SinWave', size=(600,600))
self.figure = Figure()
self.canvas = FigureCanvas(self, -1, self.figure)
self.axes = self.figure.add_subplot(111)
mapdata = np.random.rand(256, 873)
self.img_artist = self.axes.imshow(mapdata, origin='lower', interpolation='nearest', aspect='auto',extent=[0,873,0,256])
self.img_artist.set_clim(vmin=0, vmax=1)
self.timer = wx.Timer(self) # 创建定时器
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(100) # 设定时间间隔
self.cnt=0
def OnTimer(self, evt):
mapdata = np.random.rand(256, 873)
self.img_artist.set_data(mapdata)
self.canvas.draw()
self.cnt +=1
print self.cnt
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = CanvasFrame()
frame.Show()
app.MainLoop()
I'm writing a python application using pyside and matplotlib. Following a combination of this tutorial and this SO post, I have created a matplotlib widget that I can successfully add to a parent. However when I go to actually add data to it, nothing seems to get displayed.
If I add static data like the SO post had, it shows up, but when I change it to update on the fly (currently every second on a timer, but it will eventually be using a signal from another class), I never get anything but the empty axes to appear. I suspect that I'm missing a call to force a draw or invalidate or that there is something wrong with the way I'm calling update_datalim (though the values that get passed to it seem correct).
from PySide import QtCore, QtGui
import matplotlib
import random
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
from collections import namedtuple
DataModel = namedtuple('DataModel', ['start_x', 'start_y', 'width', 'height'])
class BaseWidget(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# We want the axes cleared every time plot() is called
self.axes.hold(False)
self.axes.set_xlabel('X Label')
self.axes.set_ylabel('Y Label')
self.axes.set_title('My Data')
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class DynamicWidget(BaseWidget):
def set_data(self, the_data):
self.axes.clear()
xys = list()
cmap = plt.cm.hot
for datum in the_data:
bottom_left = (datum.start_x, datum.start_y)
top_right = (bottom_left[0] + datum.width, bottom_left[1] + datum.height)
rect = Rectangle(
xy=bottom_left,
width=datum.width, height=datum.height, color=cmap(100)
)
xys.append(bottom_left)
xys.append(top_right)
self.axes.add_artist(rect)
self.axes.update_datalim(xys)
self.axes.figure.canvas.draw_idle()
class RandomDataWidget(DynamicWidget):
def __init__(self, *args, **kwargs):
DynamicWidget.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.generate_and_set_data)
timer.start(1000)
def generate_and_set_data(self):
fake_data = [DataModel(
start_x=random.randint(1, 100),
width=random.randint(20, 40),
start_y=random.randint(80, 160),
height=random.randint(20, 90)
) for i in range(100)]
self.set_data(fake_data)
Edit: I'm suspecting that there's an issue with updating the limits of the plot. When running the above code, the plot opens with limits of 0 and 1 on both the x and y axis. Since none of my generated data falls into that range, I created another subclass of DynamicWidget that plots only data between 0 and 1 (the same data from the linked SO post). When instantiating the class below, the data shows up successfully. Do I need to do something more than calling update_datalim to get the graph to re-bound itself?
class StaticWidget(DynamicWidget):
def __init__(self):
DynamicWidget.__init__(self)
static_data = [
DataModel(0.5, 0.05, 0.2, 0.05),
DataModel(0.1, 0.2, 0.7, 0.2),
DataModel(0.3, 0.1, 0.8, 0.1)
]
self.set_data(static_data)
Yes, update_datalim only updates the bounding box that is kept internally by the axes. You also need to enable auto scaling for it to be used. Add self.axes.autoscale(enable=True) after the self.axes.clear() statement and it will work. Or you can set the axes' range to a fixed value by using self.axes.set_xlim and self.axes.set_ylim.
edit: here is my code, which works for me
from PySide import QtCore, QtGui
import matplotlib
import random, sys
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
from collections import namedtuple
DataModel = namedtuple('DataModel', ['start_x', 'start_y', 'width', 'height'])
class BaseWidget(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# We want the axes cleared every time plot() is called
self.axes.hold(False)
#self.axes.autoscale(enable=True)
self.axes.set_xlabel('X Label')
self.axes.set_ylabel('Y Label')
self.axes.set_title('My Data')
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class DynamicWidget(BaseWidget):
def set_data(self, the_data):
self.axes.clear()
self.axes.autoscale(enable=True)
#self.axes.set_xlim(0, 300)
#self.axes.set_ylim(0, 300)
xys = list()
cmap = plt.cm.hot
for datum in the_data:
print datum
bottom_left = (datum.start_x, datum.start_y)
top_right = (bottom_left[0] + datum.width, bottom_left[1] + datum.height)
rect = Rectangle(
xy=bottom_left,
width=datum.width, height=datum.height, color=cmap(100)
)
xys.append(bottom_left)
xys.append(top_right)
self.axes.add_artist(rect)
self.axes.update_datalim(xys)
self.axes.figure.canvas.draw_idle()
class RandomDataWidget(DynamicWidget):
def __init__(self, *args, **kwargs):
DynamicWidget.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.generate_and_set_data)
timer.start(1000)
def generate_and_set_data(self):
fake_data = [DataModel(
start_x=random.randint(1, 100),
width=random.randint(20, 40),
start_y=random.randint(80, 160),
height=random.randint(20, 90)) for i in range(100)]
self.set_data(fake_data)
print "done:...\n\n"
def main():
qApp = QtGui.QApplication(sys.argv)
aw = RandomDataWidget()
aw.show()
aw.raise_()
sys.exit(qApp.exec_())
if __name__ == "__main__":
main()