Dynamically update graphics in matplotlib - python

# when the code works but it's meaningless to include it
### When I can't get this part to work and I'd need your code
How do you hide or show an Axes object (subplot) in matplotlib so you can toggle between different Axes in the same figure?
I'm using matplotlib to display graphics in a Tkinter GUI and I'd like to use radiobuttons to switch between different axes in the same figure.
Basically I'll have some radiobuttons linked to a IntVar():
graphic_version = tk.IntVar()
tk.Radiobutton(root, text='Option1', variable=graphic_version, value=1).pack()
tk.Radiobutton(root, text='Option2', variable=graphic_version, value=2).pack()
Then I'd trace the IntVar() with a custom methods updating my figure with the requested graphic:
choice.trace("w", lambda choice: myGraphic.showGraphic(version))
so that everytime the user clicks a radiobutton the figure is updated with a different version of the plot. Now the problem is I have no idea how to do the showGraphic properly. Lets say I use this class system to get 2 different versions of plotting the same data:
class Version1():
def __init__(self, ax, data):
self.ax = ax #This is a Axes object
self.data = self._formatDataV1(data)
self._draw()
self._setOptions()
self.hide()
def _formatDataV1(self, data):
#Here I manipulate the raw data to extract the info I need for this version
#Just a bunch of math algorithms it works fine
def _setOptions(self):
#Here I can overwrite or change settings specific for this version
def _draw(self):
self.ax.bar(self.data[0], self.data[1], width=1, color='red')
self._setOptions()
def hide(self):
###How do I remove the ax without affecting the figure?
def show(self):
###If I want to see this version again I don't want the cost of redrawing
class Version2():
def __init__(self, ax, data):
self.ax = ax #This is a Axes object
self.data = self._formatDataV1(data)
self._draw()
self._setOptions()
self.hide()
def _formatDataV2(self, data):
#The data is manipulated differently here to extract new information
def _setOptions(self):
#These options are specific to the version2 to get the display right
def _draw(self): #Drawing a different version of the graphic with differently formated data
self.ax.plot(self.data[0], self.data[1])
self._setOptions()
def hide(self):
###How do I remove the ax without affecting the figure?
def show(self):
###If I want to see this version again I don't want the cost of redrawing
class MyGraphic(tk.LabelFrame):
def __init__(self, root, data, **options):
#I use the labelframe only as a container to make things pretty
tk.LabelFrame.__init__(self, root, text="My 1337 graphic : ", **options)
self.data = data
self.fig = mpl.figure.Figure()
self.ax = self.fig.add_subplot(111)
self._drawCanvas() #This is just for Tkinter compatibility
self.my_versions = {}
self.my_versions.update({'v1' : Version1(self.ax, self.data)})
self.my_versions.update({'v2' : Version2(self.ax, self.data)})
def _drawCanvas(self):
self.canvas = FigureCanvasTkAgg(self.figure, master=self)
self.canvas.show()
self.canvas.get_tk_widget().grid()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def _setOptions(self, **kwargs):
#Here I can set options common to both versions of the graphic
def showGraphic(self, graphic_version):
for i, item in enumerate(self.my_versions):
item.hide()
if graphic_version == 1:
self.my_versions['v1'].show()
elif graphic_version == 2:
self.my_versions['v2'].show()
self._setOptions()
Sorry for the lengthy post but I rather include too many details and edit out those who are not necessary when it's solved.
Basically I want to be able to hide and show different ax on the same figure depending on the choice made by my user. The missing parts of the puzzle are myGraphic.show() and myGraphic.hide().
I'm also a complete matplotlib newby I tried this design because it seemed clear and easy to implement additional versions when needed but design inputs are also really appreciated.

You can remove axes from a figure with figure.delaxes (and add with add_axes):
http://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.delaxes

I managed to solve it with figure.clear() and figure.add_axes(ax)
I'll try to edit a clean question-answer tomorrow when I have time with a minimal example of how to toggle different version of a plot on the same figure in Tkinter.

Related

how to promote widget to different classes with different selection?

I am using pyqt5 to plot stock trend on a widget. Sometime the widget only has one plot, sometimes I'd like to have 3 subplots. I designed two classes for same widget like below. Firstly I promote the widget to the below first class with only one plot. Later when I select different button to plot different plots, I'd like to change the plot to have 3 subplots, which means I need to change class for the widget. Is it possible to change class for same widget with different selections? or Is there any alternative way?
class gui_stock_oneplot(QWidget):
def __init__(self,parent=None):
QWidget.__init__(self,parent)
self.canvas=FigureCanvas(Figure())
vertical_layout=QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axis1=self.canvas.figure.add_subplot(111)
self.canvas.axis2=self.canvas.axis1.twinx()
self.canvas.axis3=self.canvas.axis1.twinx()
self.canvas.figure.set_facecolor("white") #lightblue
self.setLayout(vertical_layout)
want to change widget class to:
class gui_stock_3plot(QWidget):
def __init__(self,parent=None):
QWidget.__init__(self,parent)
self.canvas=FigureCanvas(Figure())
vertical_layout=QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axis1=self.canvas.figure.add_subplot(311)
self.canvas.axis2=self.canvas.figure.add_subplot(312)
self.canvas.axis3=self.canvas.figure.add_subplot(313)
self.canvas.figure.set_facecolor("white") #lightblue
self.setLayout(vertical_layout)
If by "promote" the OP means using a custom widget in .ui then the answer is no, you can't.
Clearly the OP has an XY problem since he is wondering about a possible solution: how to switch between 2 classes a widget that was promoted, instead of the background problem: How to change the plots in a custom widget?
For the underlying problem there are at least 2 possible solutions:
You can implement all the logic in the same widget first by cleaning the figure and then repainting:
class Widget(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
self.canvas = FigureCanvas(Figure())
layout.addWidget(self.canvas)
def one_plot(self):
self.canvas.figure.clear()
self.canvas.axis1 = self.canvas.figure.add_subplot(111)
self.canvas.axis2 = self.canvas.axis1.twinx()
self.canvas.axis3 = self.canvas.axis1.twinx()
self.canvas.figure.set_facecolor("white") # lightblue
self.canvas.draw()
def three_plot(self):
self.canvas.figure.clear()
self.canvas.axis1 = self.canvas.figure.add_subplot(311)
self.canvas.axis2 = self.canvas.figure.add_subplot(312)
self.canvas.axis3 = self.canvas.figure.add_subplot(313)
self.canvas.figure.set_facecolor("white") # lightblue
self.canvas.draw()
Then just use the clicked signal of each function to change plots:
self.one_button.clicked.connect(self.foo_widget.one_plot)
self.three_button.clicked.connect(self.foo_widget.three_plot)
The other solution is not to promote but to use a QStackedWidget. The logic is to add each widget to the QStackedWidget and change the index with the buttons.
self.one_widget = gui_stock_oneplot()
one_index = self.stacked_widget.addWidget(self.one_widget)
self.one_button.clicked.connect(
lambda *args, index=one_index: self.stacked_widget.setCurrentIndex(index)
)
self.three_widget = gui_stock_3plot()
three_index = self.stacked_widget.addWidget(self.three_widget)
self.three_button.clicked.connect(
lambda *args, index=three_index: self.stacked_widget.setCurrentIndex(index)
)

Matplotlib imshow not updating when canvas.draw() is called

Thanks for the help in advance.
I'm trying to update a matplotlib imshow plot when a slider is moved (Python 3.7.4), but nothing is changing when the update function is called, despite calling canvas.draw which I thought would be all I needed to solve the problem. The code also needs to be embedded in Tkinter. This code will reproduce the problem:
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class mainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.image = np.zeros((10, 10))
fig = Figure()
self.mainAx = fig.add_subplot(111)
self.drawing = self.mainAx.imshow(self.image)
self.graphCanvas = FigureCanvasTkAgg(fig, master=self)
self.graphCanvas.draw()
self.graphCanvas.get_tk_widget().pack(side="top", fill="both", expand=True)
self.slider = tk.Scale(master=self, command=self.updateGraph, orient="horizontal")
self.slider.pack(fill="x")
def updateGraph(self, e):
self.image = np.zeros((10, 10))
self.image[self.slider.get()//10, self.slider.get()%10] = 1
self.drawing.set_data(self.image)
self.graphCanvas.draw_idle()
main = mainApp()
main.mainloop()
Calling mainAx.imshow(self.image) works but this is far slower and I would like this program to be as fast as possible. I think the issue lies with the draw_idle but I don't know what else I should be doing. It also doesn't work with the regular canvas.draw() function
Thanks,
Adam
change your update function to this.
def updateGraph(self, e):
self.image = np.zeros((10, 10))
self.image[self.slider.get()//10, self.slider.get()%10] = 1
self.drawing = self.mainAx.imshow(self.image)
self.graphCanvas.draw()
I would also modify your command to wait until a final value is chosen on the slider, unless you want to see it change, you can find how to do this here with an event binding or a delay. TkInter, slider: how to trigger the event only when the iteraction is complete?

Add a Grid in a matplotlib plot between different classes using a button

I want to connect a QPushButton in my QMainWindow with a class that i created using Matplotlib so i can show a grid when i push the button. This is a part of the code:
class Ventana(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.channel = ViewWidget()
#I add a toolbar and i put the button in here
self.toolbar2.addWidget(self.btn_showgrid)
self.btn_showgrid.setEnabled(True)
self.connect(self.btn_showgrid, SIGNAL("clicked()"), self.showGrid)
def showGrid(self):
self.btn_showgrid.setEnabled(False)
self.channel.axes.grid(True)
class ViewWidget(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.mainWidget = QWidget()
self.setCentralWidget(self.mainWidget)
layout = QVBoxLayout()
self.mainWidget.setLayout(layout)
self.figure_canvas = FigureCanvas(Figure())
layout.addWidget(self.figure_canvas, 10)
self.axes = self.figure_canvas.figure.add_subplot(111)
x = np.arange(0,5,0.1)
y = np.sin(x)
self.axes.plot(x,y)
My method called showGrid set the button to "disable" (is what i want to when the button is pushed) but it does not shows the grid. What am i doing wrong?
Hope you can help me. Thanks for your answers
------------------------- EDIT --------------------------------
I´ve made a few changes. I created the QPushButton and i added it to the toolbar of the plot.
# create a simple widget to extend the navigation toolbar
self.btn_showgrid = QPushButton("Show Grid")
self.btn_showgrid.setEnabled(True)
self.btn_hidegrid = QPushButton("Hide Grid")
self.btn_hidegrid.setEnabled(False)
layout = QVBoxLayout()
self.mainWidget.setLayout(layout)
self.figure_canvas = FigureCanvas(Figure())
layout.addWidget(self.figure_canvas, 10)
self.axes = self.figure_canvas.figure.add_subplot(111)
self.axes.grid(False)
x = np.arange(0,5,0.1)
y = np.sin(x)
self.axes.plot(x,y)
I also put a line: self.axes.grid(False) as you can see above. And at last i created this method:
def showGrid(self):
self.btn_showgrid.setEnabled(False)
self.btn_hidegrid.setEnabled(True)
self.axes.grid(True)
self.axes.draw()
The problem now is that when i push the button, it only hides like it should, but the grid does no show. But if create a new plot in the same `QMainWindow, it works!!!!
I think i need to refresh the plot at the moment i make self.axes.grid(True), but the draw() does not work. How can i accomplish this? I mean, refresh the plot?
You need to tell the canvas to re-draw. Drawing can be expensive so updating the state of the artists does not trigger a re-draw (the pyplot API does do that but you should not use that here). I think
def showGrid(self):
self.btn_showgrid.setEnabled(False)
self.channel.axes.grid(True)
self.channel.canvas.draw_idle()
will do the trick. The call to draw_idle tells Qt to, the next time it re-paints window to also trigger an Agg redraw for mpl, and to please schedule a repaint 'soon'.

Embed matplotlib in PyQt

I try to embed matplotlib into PyQT GUI made in Qt designer. In .ui on a widget tab I defined:
GraphWidget, that is a QWidget
and
Graph1Layout, that is a QVBoxLayout.
The code is below. I took some parts of the code from other application written by other person (class ParentCanvas and class PlotCanvas) so I do not completely understand what is going on there, but as far as I googled, that is the almost standard way to include matplotlib widget in GUI. I shortened my existing code and existing GUI to do a little example.
What is intended: I want to plot some points whenever I click run and delete the graph when I click stop (I tried just to clear the points from graph but without success).
Is this correct implementation?
Maybe somebody can make it more simple or better?
Is there a better way to delete widget?
How to delete only points on the plot?
I am a beginner in OOP and python in general. Any other comments for improving the code are welcome.
from __future__ import division
import sys,os
from PyQt4 import QtGui, QtCore, uic # Used to create and modify PyQt4 Objects.
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas # Used in creating the plots and graphs.
from matplotlib.figure import Figure
class ParentCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
fig.set_edgecolor('w')
fig.set_facecolor('w')
self.axes = fig.add_subplot(111)
self.axes.hold(False)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class PlotCanvas(ParentCanvas):
""" Creates a matplotlib canvas QWidget inheriting the features set out in ParentCanvas."""
def compute_initial_figure(self):
self.axes.set_xbound(lower=0, upper=1)
self.axes.set_ybound(lower=0, upper=1)
self.axes.set_ylabel('Best Fitness Function')
self.axes.set_xlabel('Population')
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
uic.loadUi('Test.ui', self)
self.SignalsAndSlots()
self.firstrun = True
self.iter=0
def SignalsAndSlots(self): # Function setting up the signals and slots.
self.btnStopOpt.clicked.connect(self.clickStop)
self.btnRun.clicked.connect(self.clickRun)
def clickStop(self): # Function that runs when the "Stop" button is clicked.
self.firstrun = True
self.iter =0
self.gb.setParent(None)
def clickRun(self): # Function that runs when the "Run Optimization" button is clicked.
if self.firstrun == True :
#graph setup
self.gb = PlotCanvas(self.Graph1Widget, width=10, height=7, dpi=70)
self.gb.axes.set_ylabel('Objective Value')
self.gb.axes.set_xlabel('Generation')
self.Graph1Layout.addWidget(self.gb)
self.firstrun = False
self.UpdateGraph(self.iter)
self.iter+=1
def UpdateGraph(self, iter): # Function that creates a graph
best = [0,0,0,3,5,9,12,30]
average = [0,0,0,1,2,3,14,20]
t = range(iter+1) # Will be used to store the generation numbers.
s = best[:iter+1] # Will be used to store the global best values.
self.gb.axes.hold(True) # Holds the graph so we can plot the global best and average scores as two separate scatter plots on to one graph.
self.gb.axes.scatter(t, s, c='b', label='Best objective value')
if iter == 0: # In the first generation we need to make a legend for the graph, this only need to be made once.
self.gb.axes.legend(loc='upper center', bbox_to_anchor=(0.5, 1.09), fancybox=True, shadow=True, ncol=5) # Legend content is determined by the labels above. Location is determined by bbox_to_anchor.
self.gb.axes.autoscale_view(True,False,True)
self.gb.draw()
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Main(None) # instantiation
app.setActiveWindow(window)
window.show() # show window
sys.exit(app.exec_()) # Exit from Python

Reopening a GTK & matplotlib window - GTK window is blank

My program (developed with GTK using glade) receives some data and has the option to display a seperate window containing a matplotlib scatterplot that represents the data.
My problem is that if the user closes the graph window and reopens it, no graph is displayed. It is just a blank GTK Window. I'm sure there is a simple fix, but there aren't many resources available that are relevant to my issue (or GTK and matlplotlib integration for that matter).
I have created a Module for my scatterplot so I can easily reuse it. I am just trying to get it to work, so the code isn't structured perfectly.
##Scatterplot Module:
import gtk
import matplotlib
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.figure import Figure
class ScatterPlot:
def __init__(self):
self.window = gtk.Window()
self.window.connect("destroy", lambda x: self.destroy())
self.window.set_default_size(500,400)
self.is_hidden = False
self.figure = Figure(figsize = (5,4), dpi=100)
self.ax = self.figure
self.ax = self.ax.add_subplot(111)
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
self.Xs = list()
self.Ys = list()
def set_axis(self, xLimit = (0,384) , yLimit = (0,100)):
self.ax.set_xlim(xLimit)
self.ax.set_ylim(yLimit)
def plot(self, xs, ys):
self.Xs.extend([xs])
self.Ys.extend([ys])
self.ax.plot(xs,ys,'bo')
def update(self):
self.window.add(self.canvas)
def set_title(self, title):
self.ax.set_title(title)
def show(self):
self.window.show_all()
self.is_hidden = False
def hide(self):
self.window.hide()
self.is_hidden = True
def destroy(self):
self.window.destroy()
I call the module like so:
class GUI:
def __init__(self):
self.scatterplot = scatterplot.ScatterPlot()
#When the user presses the "Graph" button it calls the following function
def graph():
self.scatterplot.plot(someDataX, someDataY)
self.scatterplot.set_axis()
self.scatterplot.set_title("Some Title")
self.scatterplot.show()
(This was just an example of what my code looks like.)
When the scatterplot is closed, I am calling self.window.destroy instead of self.window.hide. When reopening is attempted, I call the same graph() function but, as stated above, the GTK Window does not display the graph. (When I first open it, it displays perfectly)
My speculations:
Should I be calling .hide() instead of .destroy()?
Is there a piece of code in scatterplot's constructor that needs to be called again to create the plot?
Or should I just re-instantiate the plot every time graph() is called?
My Solution:
From:
class ScatterPlot:
def __init__(self):
#remove the following two lines
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
Move the two lines of code to show()
def show(self):
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
self.window.show_all()
self.is_hidden = False
Moving these two lines of code allows the graph to be displayed when re-opening the window.
Sidenote: Calling both .destroy() or .show() when closing the window will work. I'm not sure which one is better though.

Categories