Hi I searched nice module to save subplot with scrollbar. but this module can only show plot not save file showing with 'Segmentation fault (core dumped)' message...
I don't know why.. Can you help me..? Actually when I saved only one plot, It was working well. but When I saved multiple plot with for loop function, that message show and block script running..
```python````
import matplotlib.pyplot as plt
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class ScrollableWindow(QtGui.QMainWindow):
def __init__(self, fig, savefile):
self.qapp = QtGui.QApplication([])
QtGui.QMainWindow.__init__(self)
self.widget = QtGui.QWidget()
self.setCentralWidget(self.widget)
self.widget.setLayout(QtGui.QVBoxLayout())
self.widget.layout().setContentsMargins(0,0,0,0)
self.widget.layout().setSpacing(0)
self.fig = fig
self.canvas = FigureCanvas(self.fig)
self.canvas.draw()
self.scroll = QtGui.QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
self.nav = NavigationToolbar(self.canvas, self.widget)
self.widget.layout().addWidget(self.nav)
self.widget.layout().addWidget(self.scroll)
#self.show()
plt.savefig(savefile)
#exit()
#exit(self.qapp.exec_())
# create a figure and some subplots
fig, axes = plt.subplots(ncols=4, nrows=5, figsize=(16,16))
for ax in axes.flatten():
ax.plot([2,3,5,1])
# pass the figure to the custom window
a = ScrollableWindow(fig,'test.png')
Related
I am plotting some data in cartopy. I would like to be able to zoom in on a region of the map and have the latitude/longitude axes update to reflect the zoomed in region. Instead, they just dissapear altogether when I zoom in. How do I fix this?
Here is my code for generating the axes
plt.figure()
ax = plt.axes(projection=cartopy.crs.PlateCarree())
ax.add_feature(cartopy.feature.LAND, edgecolor='black')
gl = ax.gridlines(crs=cartopy.crs.PlateCarree(), draw_labels=True,
linewidth=2, color='gray', alpha=0.5, linestyle='--')
# plot some stuff here
It is possible to update the cartopy gridliners in interactive mode, but you need to subclass the Navigation toolbar.
In this example below I have used a PySide/QT5 example code that allows me to substitute a subclassed toolbar, then merged in the gridliner example code. The overloaded toolbar callbacks recreate the gridlines everytime zoom/pan/home is used.
I used python3.8, matplotlib-3.4.2, cartopy-0.20
import sys
from PySide2 import QtWidgets
from PySide2.QtWidgets import QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import cartopy.crs as ccrs
class CustomNavigationToolbar(NavigationToolbar):
toolitems = [t for t in NavigationToolbar.toolitems if t[0] in ('Home', 'Pan', 'Zoom', 'Save')]
def __init__(self, canvas, parent, coordinates=True, func_recreate_gridlines=None):
print('CustomNavigationToolbar::__init__')
super(CustomNavigationToolbar, self).__init__(canvas, parent, coordinates)
self.func_recreate_gridlines = func_recreate_gridlines
def home(self, *args):
print('CustomNavigationToolbar::home')
super(CustomNavigationToolbar, self).home(*args)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
def release_pan(self, event):
print('CustomNavigationToolbar::release_pan')
super(CustomNavigationToolbar, self).release_pan(event)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
def release_zoom(self, event):
print('CustomNavigationToolbar::release_zoom')
super(CustomNavigationToolbar, self).release_zoom(event)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
print('ApplicationWindow::__init__')
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
self.layout = QVBoxLayout(self._main)
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.toolbar = CustomNavigationToolbar(self.canvas, self,
coordinates=True,
func_recreate_gridlines=self.recreate_gridlines)
self.layout.addWidget(self.canvas)
self.addToolBar(self.toolbar)
# figure setup taken from gridlines example at
# https://scitools.org.uk/cartopy/docs/latest/matplotlib/gridliner.html
projection = ccrs.RotatedPole(pole_longitude=120.0, pole_latitude=70.0)
self.ax = self.canvas.figure.add_subplot(1, 1, 1, projection=projection)
self.ax.set_extent([-6, 3, 48, 58], crs=ccrs.PlateCarree())
self.ax.coastlines(resolution='10m')
self._gl = None
self.recreate_gridlines()
def recreate_gridlines(self):
print('ApplicationWindow::recreate_gridlines')
print(' remove old gridliner artists')
if self._gl is not None:
for artist_coll in [self._gl.xline_artists, self._gl.yline_artists, self._gl.xlabel_artists, self._gl.ylabel_artists]:
for a in artist_coll:
a.remove()
self.ax._gridliners = []
print(' self.ax.gridlines()')
self._gl = self.ax.gridlines(crs=ccrs.PlateCarree(),
draw_labels=True, dms=True, x_inline=False, y_inline=False)
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
Regarding to this question and the answer here, is there a way to pass the wheel scroll event to the scrollbar when the mouse is located over the plots? I've tried using an event filter in the Main Widget, but it doesn't registered that the wheel is scrolled in the Main, only in the canvas/plots. I don't need the plots to know that it is being scrolled, only the GUI. Any help would be greatly appreciated, thank you.
One solution to scroll the FigureCanvas inside a QScrollArea in PyQt is to use matplotlib's "scroll_event" (see Event handling tutorial) and connect it to a function which scrolls the scrollBar of the QScrollArea.
The example (from my answer to this question) can be extended to connect to a function scrolling via
self.canvas.mpl_connect("scroll_event", self.scrolling)
inside this function the scrollbar value is updated.
import matplotlib.pyplot as plt
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class ScrollableWindow(QtGui.QMainWindow):
def __init__(self, fig):
self.qapp = QtGui.QApplication([])
QtGui.QMainWindow.__init__(self)
self.widget = QtGui.QWidget()
self.setCentralWidget(self.widget)
self.widget.setLayout(QtGui.QVBoxLayout())
self.widget.layout().setContentsMargins(0,0,0,0)
self.widget.layout().setSpacing(0)
self.fig = fig
self.canvas = FigureCanvas(self.fig)
self.canvas.draw()
self.scroll = QtGui.QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
self.nav = NavigationToolbar(self.canvas, self.widget)
self.widget.layout().addWidget(self.nav)
self.widget.layout().addWidget(self.scroll)
self.canvas.mpl_connect("scroll_event", self.scrolling)
self.show()
exit(self.qapp.exec_())
def scrolling(self, event):
val = self.scroll.verticalScrollBar().value()
if event.button =="down":
self.scroll.verticalScrollBar().setValue(val+100)
else:
self.scroll.verticalScrollBar().setValue(val-100)
# create a figure and some subplots
fig, axes = plt.subplots(ncols=4, nrows=5, figsize=(16,16))
for ax in axes.flatten():
ax.plot([2,3,5,1])
# pass the figure to the custom window
a = ScrollableWindow(fig)
I am using PyQt 4 for a basic GUI and matplotlib for a plot from which I want to read the coordinates of the plotted data points. Based on these examples (simple picking example), I have the simple problem that I cannot display the coordinates of a data point in a text field such as QtGui.QLabel(). I do not understand why I cannot call the instance Window.msg in the method onpick(). Probably it is because the instance it not given to the method. I only have a basic understanding of object oriented programming (but I am working on it), so the problem is my lack of knowledge.
My question: How to display the coordinates of chosen data (by clicking on it) from a matplotlib plot in my GUI based on PyQT (in that case in my label lbl)?
Also, it would be nice to highlight the chosen data point in the plot.
Here is my code (working):
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# This does not work:
#Window.lbl.setText(self.msg)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
The self is being overlapped by the picker (not sure why). In any case this should work:
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def changelabel(arg):
main.lbl.setText(str(arg[0])+' '+str(arg[1]))
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# Window.changelabel(self.msg)
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
, the change is in the setText function, since I call it directly from the variable (no self or Window).
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))
Starting with the working Matplotlib animation code shown below, my goal is to embed this animation (which is just a circle moving across the screen) within a PyQT4 GUI.
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import animation
fig,ax = plt.subplots()
ax.set_aspect('equal','box')
circle = Circle((0,0), 1.0)
ax.add_artist(circle)
ax.set_xlim([0,10])
ax.set_ylim([-2,2])
def animate(i):
circle.center=(i,0)
return circle,
anim = animation.FuncAnimation(fig,animate,frames=10,interval=100,repeat=False,blit=True)
plt.show()
I am able to accomplish this using the following code, but there is one hitch: I cannot get blitting to work.
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation
class Window(QtGui.QDialog): #or QtGui.QWidget ???
def __init__(self):
super(Window, self).__init__()
self.fig = Figure(figsize=(5,4),dpi=100)
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111) # create an axis
self.ax.hold(False) # discards the old graph
self.ax.set_aspect('equal','box')
self.circle = Circle((0,0), 1.0)
self.ax.add_artist(self.circle)
self.ax.set_xlim([0,10])
self.ax.set_ylim([-2,2])
self.button = QtGui.QPushButton('Animate')
self.button.clicked.connect(self.animate)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def animate(self):
self.anim = animation.FuncAnimation(self.fig,self.animate_loop,frames=10,interval=100,repeat=False,blit=False)
self.canvas.draw()
def animate_loop(self,i):
self.circle.center=(i,0)
return self.circle,
def main():
app = QtGui.QApplication(sys.argv)
ex = Window()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
When I set blit=True, after pressing the Animate button I get the following error:
a.figure.canvas.restore_region(bg_cache[a])
KeyError: matplotlib.axes._subplots.AxesSubplot object at 0x00000000095F1D30
In searching this error, I find many posts about how blitting does not work on Macs, but I am using Windows 7. I have tried replacing self.canvas.draw() with self.canvas.update(), but this does not work.
After looking at the source code of the animation module, I realized that there is an error in the Animation class (the dictionary bg_cache is empty, when it is accessed for the first time with blitting switched on).
This is fixed in the git version of matplotlib; however, in the most recent stable version 1.5.1, the bug is still present. You can either fix the bug in the matplotlib code itself or you can make a subclass to FuncAnimation. I chose that way, because it should still work after updating matplotlib.
from matplotlib import animation
class MyFuncAnimation(animation.FuncAnimation):
"""
Unfortunately, it seems that the _blit_clear method of the Animation
class contains an error in several matplotlib verions
That's why, I fork it here and insert the latest git version of
the function.
"""
def _blit_clear(self, artists, bg_cache):
# Get a list of the axes that need clearing from the artists that
# have been drawn. Grab the appropriate saved background from the
# cache and restore.
axes = set(a.axes for a in artists)
for a in axes:
if a in bg_cache: # this is the previously missing line
a.figure.canvas.restore_region(bg_cache[a])
Then, simpy use MyFuncAnimation instead of animation.FuncAnimation.
Took me a while to figure it out, but I hope it helps anybody.
After some time I managed to recreate the animation by using the underlying functions directly and not using the animation wrapper:
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation
from time import sleep
class Window(QtGui.QDialog): #or QtGui.QWidget ???
def __init__(self):
super(Window, self).__init__()
self.fig = Figure(figsize=(5, 4), dpi=100)
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111) # create an axis
self.ax.hold(False) # discards the old graph
self.ax.set_aspect('equal', 'box')
self.circle = Circle((0,0), 1.0, animated=True)
self.ax.add_artist(self.circle)
self.ax.set_xlim([0, 10])
self.ax.set_ylim([-2, 2])
self.button = QtGui.QPushButton('Animate')
self.button.clicked.connect(self.animate)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
self.canvas.draw()
self.ax_background = self.canvas.copy_from_bbox(self.ax.bbox)
def animate(self):
self.animate_loop(0)
def animate_loop(self,begin):
for i in range(begin,10):
self.canvas.restore_region(self.ax_background)
self.circle.center=(i,0)
self.ax.draw_artist(self.circle)
self.canvas.blit(self.ax.bbox)
self.canvas.flush_events()
sleep(0.1)
def main():
app = QtGui.QApplication(sys.argv)
ex = Window()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Maybe this will be of use to you.
I've created a qt app that can be used to display matplotlib figures in multiple tabs. Now I'm trying to get the standard matplotlib navigation toolbar to work for all the figures in the various tabs. So far I've only managed to get it working in one of the figures, but not all.
Here's the code:
from PyQt4 import QtCore
from PyQt4 import QtGui as qt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.figure import Figure
import itertools
class MultiTabNavTool(NavigationToolbar):
#====================================================================================================
def __init__(self, canvases, tabs, parent=None):
self.canvases = canvases
self.tabs = tabs
NavigationToolbar.__init__(self, canvases[0], parent)
#====================================================================================================
def get_canvas(self):
return self.canvases[self.tabs.currentIndex()]
def set_canvas(self, canvas):
self._canvas = canvas
canvas = property(get_canvas, set_canvas)
class MplMultiTab(qt.QMainWindow):
#====================================================================================================
def __init__(self, parent=None, figures=None, labels=None):
qt.QMainWindow.__init__(self, parent)
self.main_frame = qt.QWidget()
self.tabWidget = qt.QTabWidget( self.main_frame )
self.create_tabs( figures, labels )
# Create the navigation toolbar, tied to the canvas
self.mpl_toolbar = MultiTabNavTool(self.canvases, self.tabWidget, self.main_frame)
self.vbox = vbox = qt.QVBoxLayout()
vbox.addWidget(self.mpl_toolbar)
vbox.addWidget(self.tabWidget)
self.main_frame.setLayout(vbox)
self.setCentralWidget(self.main_frame)
#====================================================================================================
def create_tabs(self, figures, labels ):
if labels is None: labels = []
figures = [Figure()] if figures is None else figures #initialise with empty figure in first tab if no figures provided
self.canvases = [self.add_tab(fig, lbl)
for (fig, lbl) in itertools.zip_longest(figures, labels) ]
#====================================================================================================
def add_tab(self, fig=None, name=None):
'''dynamically add tabs with embedded matplotlib canvas with this function.'''
# Create the mpl Figure and FigCanvas objects.
if fig is None:
fig = Figure()
ax = fig.add_subplot(111)
canvas = fig.canvas if fig.canvas else FigureCanvas(fig)
canvas.setParent(self.tabWidget)
canvas.setFocusPolicy( QtCore.Qt.ClickFocus )
#self.tabs.append( tab )
name = 'Tab %i'%(self.tabWidget.count()+1) if name is None else name
self.tabWidget.addTab(canvas, name)
return canvas
A basic usage example would be:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 2*np.pi, 100)
figures = []
for i in range(1,3):
fig, ax = plt.subplots()
y = np.sin(np.pi*i*x)+0.1*np.random.randn(100)
ax.plot(x,y)
figures.append( fig )
app = qt.QApplication(sys.argv)
ui = MplMultiTab( figures=figures )
ui.show()
app.exec_()
Are there any matplotlib ninjas out there who might know how I can get the navigation toolbar to play with the multiple figure canvasses?
I think you can create toolbar for every canvas and show/hide them when tabs.currentTab changed:
class MultiTabNavTool(qt.QWidget):
def __init__(self, canvases, tabs, parent=None):
qt.QWidget.__init__(self, parent)
self.canvases = canvases
self.tabs = tabs
self.toolbars = [NavigationToolbar(canvas, parent) for canvas in self.canvases]
vbox = qt.QVBoxLayout()
for toolbar in self.toolbars:
vbox.addWidget(toolbar)
self.setLayout(vbox)
self.switch_toolbar()
self.tabs.currentChanged.connect(self.switch_toolbar)
def switch_toolbar(self):
for toolbar in self.toolbars:
toolbar.setVisible(False)
self.toolbars[self.tabs.currentIndex()].setVisible(True)