I am creating a image viewer using wxPython. I want to view multiple images individually, so I wrote a code below (partially).
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
class CanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figs = []
self.axes = []
self.canvases = []
self.panelsizers = []
def draw(self, data):
"""data is 2D numpy array"""
fig = Figure()
self.figs.append(fig)
ax = fig.add_subplot(111)
ax.imshow(data, interpolation='none')
self.axes.append(ax)
canvas = FigureCanvas(self, wx.ID_ANY, fig)
self.canvases.append(canvas)
panelsizer = wx.BoxSizer()
panelsizer.Add(canvas, 1, wx.GROW)
self.panelsizers.append(panelsizer)
self.SetSizer(panelsizer)
This works almost perfectly except a slight problem.
When I ran the code and open a image, the window looks like below.
enter image description here
This window consists of three wx.Panels and the center one is CanvasPanel. You can see that the size of CanvasPanel is a bit small even though proportion=1 and style=wx.GROW. Moreover, when I resize this window by dragging the corner of window, it looks like below.
enter image description here
The size of CanvasPanel changes correctly! Why? And how can I revise my code to fit the CanvasPanel in the viewer without resizing.
Related
I have an application that allows the user to make some vertical line plots among other data. Since I need to use multiple graphs (on multiple pages of a ttk.notebook), I created a class that handles all the gui elements needed to achieve my goals. When I launched the class as its own application things were fine. Once I added the class to my larger gui I encountered an error: the lines created by the user persist as long as the iPython kernal remains open.
I have two questions:
What is causing the new lines to persist on my graph?
Will the issue persist once I turn the larger gui into a standalone executable?
Edit: I've added some code as suggested by j_4321. Its not the exact code but I feel I have the important bits listed. I'm having some trouble making my own executable so I'm still working on that.
Edit 2: The Sequel
Thanks to j_4321 for being patient. I Have updated the code. The error occurs when the classes are in different files (maybe only). This may be a solution to my problem but being that I am new to Python I'd still like to understand what the deal is.
sample.py:
from tkinter import Tk
from tkinter.ttk import Frame, Notebook
from Sample1 import ContainsPlot
class MainUserInterface(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.grid()
self.parent = parent
self.initUI()
def initUI(self):
plotsTab = Notebook(self, name ='controlTab') #create notebook in the GUI
plotsTab.grid(column = 1, row =0, columnspan = 5, rowspan = 14)
plotsFrame = Frame(plotsTab, name = "plotsFrame")
plotsFrame.grid(column = 1, row = 0, rowspan = 14)
plotsTab.add(plotsFrame, text = "ContainsPlot class lives here")
plotFrameElements = ContainsPlot(plotsFrame)
plotFrameElements.grid(column =0, row = 0)
def main():
root = Tk()
app = MainUserInterface(root)
root.mainloop()
if __name__ == '__main__':
main()
Sample1.py:
from tkinter.ttk import Frame
import matplotlib
matplotlib.use("TkAgg")
#from matplotlib import cbook
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,\
NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class ContainsPlot(Frame, object):
TestFig = Figure(figsize = (5,5), dpi = 100, tight_layout = True)
TestPlotA = TestFig.add_subplot(111)
dist = range(0,10)
dataListA = range(0,10)
TestPlotA.plot(dist, dataListA)
def __init__(self, parent):
Frame.__init__(self, parent)
self.initUI()
def initUI(self):
TestPlot = FigureCanvasTkAgg(self.TestFig, self)
TestPlot.get_tk_widget().grid(column = 2, row = 0, columnspan = 3,\
rowspan = 4)
TestFrame = Frame(self)
TestFrame.grid(column = 2, row =6, columnspan = 3)
nuToolbar = PlotToolbar(TestPlot, TestFrame, self)
nuToolbar.update()
class PlotToolbar(NavigationToolbar2TkAgg, object):
toolitems = [t for t in NavigationToolbar2TkAgg.toolitems if
t[0] in ('Home', 'Pan', 'Zoom', 'Save')]
toolitems.append(('Add Vert Line', 'Add Line', 'move', 'AddLine'))
def __init__(self, plotCanvas, frame, parent):
self.parent = parent
self.fig = plotCanvas.figure
for foo in self.fig.axes:
self.ax = foo
NavigationToolbar2TkAgg.__init__(self, plotCanvas, frame)
def AddLine(self):
yMin, yMax = self.ax.get_ylim()
self.parent.TestPlotA.axvline(x = 3, ymin = yMin, ymax = yMax, color = 'm')
self.fig.canvas.draw()
To reproduce the bug, click the "add line" button (the rightmost) and close the Gui Window. Launch the program again and the line persists. I am using spyder to run my program. Thanks!
I'm struggling on an application combining wxPython and matplotlib.
I want to embed an animated matplotlib object in an wxPanel. The Data should be added on runtime.
My Module Code:
(i cant get the correct formatting, see http://pastebin.com/PU5QFEzG)
'''
a panel to display a given set of data in a wxframe as a heatmap, using pcolor
from the matplotlib
#author: me
'''
import wx
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas #todo: OW 26.10.15 needed?
class plotPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = plt.Figure()
self.subplot = self.figure.add_subplot(111)
plt.title('test')
self.canvas = FigureCanvas(self, -1, self.figure) #ToDo: OW 26.10.15 Verstehen
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
self.dataSet = []
self.animator = animation.FuncAnimation(self.figure,self.anim, interval=1000)
def anim(self, a):
if(len(self.dataSet) == 0):
return 0
i = a % len(self.dataSet)
obj = self.subplot.pcolor(self.dataSet[i], cmap='RdBu')
return obj
def add_data(self, data):
self.dataSet.append(data)
#
# Code for a standalone test run
#
class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent,title=title,size=(1000,1000))
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Status Bar')
if __name__ == '__main__':
from numpy.random import rand #todo: OW 26.10.15 remove
app = wx.App(redirect=False)
frame = TestFrame(None, 'Debug Frame')
panel = plotPanel(frame)
frame.Show()
C = rand(10,10)
panel.add_data(C)
C = rand(10,10)
panel.add_data(C)
C = rand(10,10)
panel.add_data(C)
app.MainLoop()
Im now struggeling on adding more Details to the Graph, eg a colorbar or a title.
If I add self.subplot.title = 'test' in the anim_Fkt, i get "'str' object has no attribute 'get_animated'". If i try plt.title('test'), it has no effect.
Whats the correct way to add a title or a colorbar or a legend?
To add the features to an embedded matplotlib graph, you have to use object-oriented matplotlib API. A matplotlib graph consists of a figure (a matplotlib.figure.Figure instance), which has one or several axes (matplotlib.axes.Axes instances) in it. Each of them, in their turn, contains Drawables (lines, images, scatter plots etc).
A title can be added (or modified) to the Figure or to each Axes with setter methods, such as Figure.suptitle or Axes.set_title (or, in your case, self.figure.suptitle() or self.subplot.set_title).
The colorbar is a little bit trickier, as it needs a data object (a mappable) to be created: we can create it only in the anim function. Also, wo do not want to create many colorbar instances; once created, we only need to update it. Achieving that is easy: instantiate self.colorbar with None in constructor, and then check it against None in animation function: if it is None, then create colorbar, if it is not, then update it:
class plotPanel(wx.Panel):
def __init__(self, parent):
...
self.colorbar = None
def anim(self, a):
...
self.subplot.set_title("Frame %i" % a)
if self.colorbar is None:
self.colorbar = self.figure.colorbar(obj)
else:
self.colorbar.update_normal(obj)
self.colorbar.update_normal(obj)
return obj
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)
I have multiple plots each displayed in each tab(each tab is a page in wx.Notebook). I have attached the code snippet used to plot the figure in each tab. Everything is fine till I try to save a figure using the option in navigation toolbar. When I try to save a figure, the figure in that plot gets messed up and the tab looks like the image attached. How can I fix this issue?
Any help is highly appreciated. Thanks.
self.fig = plt.figure(figsize=(hor_pix, ver_pix), dpi=self.dpi)
self.canvas = FigCanvas(self, -1, self.fig)
self.axes = self.fig.add_subplot(111)
self.toolbar = NavigationToolbar(self.canvas)
line_ico = wx.Image('line.bmp', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.line_button = self.toolbar.AddSimpleTool(wx.ID_ANY, line_ico, "Line", "Extract using line Marker")
paste_ico = wx.Image('paste2.bmp', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.paste_button = self.toolbar.AddSimpleTool(wx.ID_ANY, paste_ico, "Paste gates", "Copy gates from other plots.")
self.toolbar.Realize()
self.SetSizer(self.vbox)
self.vbox.Fit(self)
self.axes.hist(datam3,bins,color='b',ec='b',fc='b')
self.canvas.draw()
self.Refresh()
self.Update()
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.