Reputation: 1472
I have a Matplotlib figure with a lot of subplots which needs a ScrolledPanel
. Now I want a navigation toolbar
for the figure which remains static when the figure panel
is scrolled.
I tried adding it to another panel than the canvas's parent but that does not work(navigation toolbar remains inside canvas's parent only).
Is there any way to have the matplotlib navigation toolbar remain static but making rest of the canvas scrollable?
Here's my complete code:
import wx
import wx.lib.scrolledpanel
import matplotlib
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar
from matplotlib.figure import Figure
import wx.lib.inspection
matplotlib.use('WXAgg')
class PlotDemoApp(object):
def __init__(self, data):
self.app = wx.App()
self.frame = PlotCanvas(None, -1, "PlotCanvas", data)
self.frame.Show(True)
wx.lib.inspection.InspectionTool().Show()
self.app.MainLoop()
class PlotCanvas(wx.Frame):
def __init__(self, parent, wxid, title, data):
wx.Frame.__init__(self, parent, wxid, title)
self.box_main = wx.BoxSizer(wx.VERTICAL)
panel_lower = wx.lib.scrolledpanel.ScrolledPanel(self, size=(2500,-1))
panel_lower.SetupScrolling()
panel_lower.SetBackgroundColour("White")
self.box_lower = wx.BoxSizer(wx.HORIZONTAL)
box_local = wx.BoxSizer(wx.VERTICAL)
plt = Figure(figsize=(95,10))
num_columns = len(data.columns)
axes_1 = plt.add_subplot(1, num_columns, 1)
data_numpy = data[data.columns[0]].to_numpy()
depth = data.index.to_numpy()
plt.gca().invert_yaxis()
plt.subplots_adjust(left=0.01, right=1.00, top=0.95, bottom=0.05)
axes_1.plot(data_numpy, depth)
for i in range(1, num_columns):
axes_tmp = plt.add_subplot(1, num_columns, i+1, sharey=axes_1)
axes_tmp.set(xlabel=data.columns[i], ylabel='Depth', title='Depth vs ' + data.columns[i])
data_numpy = data[data.columns[i]].to_numpy()
plt.gca().invert_yaxis()
axes_tmp.plot(data_numpy, depth)
canvas = FigureCanvas(panel_lower, -1, plt)
canvas.draw()
box_local.Add(canvas, 1, wx.EXPAND)
panel_nav = wx.Panel(self)
box_nav = wx.BoxSizer(wx.HORIZONTAL)
toolbar = NavigationToolbar(canvas)
box_nav.Add(toolbar)
panel_nav.SetSizer(box_nav)
toolbar.Realize()
self.box_main.Add(panel_nav, 0, wx.CENTER)
self.box_lower.Add(box_local, 1, wx.EXPAND)
self.box_main.Add(panel_lower, 1, wx.EXPAND)
panel_lower.SetSizer(self.box_lower)
self.SetSizer(self.box_main)
self.box_main.Layout()
data
here is a pandas dataframe
Sample data:
| index | BS | CAL |
|-------|------|------|
| 162 | 17.5 | 17.4 |
| 163 | 17.8 | 17.7 |
| 164 | 17.8 | 17.9 |
Upvotes: 1
Views: 607
Reputation: 22443
Hopefully this is what you are after or at the least points you in the right direction.
The matplotlib navigation toolbar seems to be welded to the plot itself but it can be hidden.
Whilst it remains hidden, its functions are still available, so we can create our own toolbar
and assign to it the functions in the hidden toolbar.
Like this:
import wx
import wx.lib.scrolledpanel as scrolled
import pandas as pd
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar
from matplotlib.figure import Figure
# images in /usr/share/matplotlib/mpl-data/images
# Your system may differ!
class PlotPanel(scrolled.ScrolledPanel):
def __init__(self,parent):
scrolled.ScrolledPanel.__init__(self, parent)
self.SetupScrolling(scroll_x=True, scroll_y=True, scrollToTop=False, scrollIntoView=False)
self.ShowScrollbars(True,True)
self.plt = Figure(figsize=(95,10))
self.canvas = FigureCanvas(self,-1, self.plt)
self.toolbar = NavigationToolbar(self.canvas)
#Hide the Matplotlib toolbar because we are going to create own own
#but use the functions of this toolbar.
self.toolbar.Hide()
def plot(self):
d = {'BS':[17.54,17.55,17.54,17.53,17.55,17.54],'CAL':[17.46,17.47,17.49,17.44,17.47,17.49]}
data = pd.DataFrame(d, index=['1','2','3','4','5','6'])
num_columns = len(data.columns)
axes_1 = self.plt.add_subplot(1, num_columns, 1)
#data_numpy = data[data.columns[0]].to_numpy() # My version of pandas doesn't have .to_numpy()
data_numpy = data[data.columns[0]]
#depth = data.index.to_numpy()
depth = data.index
self.plt.gca().invert_yaxis()
self.plt.subplots_adjust(left=0.01, right=0.50, top=0.95, bottom=0.05)
axes_1.plot(data_numpy, depth)
for i in range(0, num_columns):
axes_tmp = self.plt.add_subplot(1, num_columns, i+1, sharey=axes_1)
axes_tmp.set(xlabel=data.columns[i], ylabel='Depth', title='Depth vs ' + data.columns[i])
#data_numpy = data[data.columns[i]].to_numpy()
data_numpy = data[data.columns[i]]
self.plt.gca().invert_yaxis()
axes_tmp.plot(data_numpy, depth)
self.canvas.draw()
self.SetSize(self.canvas.GetSize())
class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent,title=title,size=(600,600))
self.p1 = PlotPanel(self)
#Create our own toolbar
toolbar = self.CreateToolBar(style=wx.TB_HORIZONTAL|wx.TB_DOCKABLE|wx.TB_TEXT)
hometool = toolbar.AddTool(wx.ID_ANY, 'Home', wx.Bitmap('/usr/share/matplotlib/mpl-data/images/home.png'))
backtool = toolbar.AddTool(wx.ID_ANY, 'Back', wx.Bitmap('/usr/share/matplotlib/mpl-data/images/back.png'))
fwdtool = toolbar.AddTool(wx.ID_ANY, 'Forward', wx.Bitmap('/usr/share/matplotlib/mpl-data/images/forward.png'))
pantool = toolbar.AddTool(wx.ID_ANY, 'Pan', wx.Bitmap('/usr/share/matplotlib/mpl-data/images/move.png'))
zoomtool = toolbar.AddTool(wx.ID_ANY, 'Zoom', wx.Bitmap('/usr/share/matplotlib/mpl-data/images/zoom_to_rect.png'))
subtool = toolbar.AddTool(wx.ID_ANY, 'Subplots', wx.Bitmap('/usr/share/matplotlib/mpl-data/images/subplots.png'))
savetool = toolbar.AddTool(wx.ID_ANY, 'Save', wx.Bitmap('/usr/share/matplotlib/mpl-data/images/filesave.png'))
self.Bind(wx.EVT_TOOL, self.home, hometool)
self.Bind(wx.EVT_TOOL, self.back, backtool)
self.Bind(wx.EVT_TOOL, self.fwd, fwdtool)
self.Bind(wx.EVT_TOOL, self.pan, pantool)
self.Bind(wx.EVT_TOOL, self.zoom, zoomtool)
self.Bind(wx.EVT_TOOL, self.sub, subtool)
self.Bind(wx.EVT_TOOL, self.save, savetool)
toolbar.Realize()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.p1,1,wx.EXPAND,10)
self.statusbar=self.CreateStatusBar()
self.SetSizer(sizer)
self.Show()
self.plot()
#Self defined Navigation Toolbar controls used to call hidden matplotlib Toolbar functions
def home(self,event):
self.statusbar.SetStatusText("Home")
self.p1.toolbar.home()
# Also scroll panel to start position
self.p1.Scroll(0,0)
def back(self,event):
self.statusbar.SetStatusText("Back")
self.p1.toolbar.back()
def fwd(self,event):
self.statusbar.SetStatusText("Fwd")
self.p1.toolbar.forward()
def pan(self,event):
self.statusbar.SetStatusText("Pan")
self.p1.toolbar.pan()
def zoom(self,event):
self.statusbar.SetStatusText("Zoom")
self.p1.toolbar.zoom()
def sub(self,event):
self.statusbar.SetStatusText("Subplots")
self.p1.toolbar.configure_subplots(event)
def save(self,event):
self.statusbar.SetStatusText("Save")
self.p1.toolbar.save_figure()
def plot(self):
self.p1.plot()
app = wx.App(redirect=False)
frame = TestFrame(None,"Plot in Scrolled panel with replacement Navigation")
app.MainLoop()
You will notice that I made the replacement toolbar
Dockable, so I can move the toolbar
anywhere I want on the screen. It is re-attachable.
Because we are defining our own toolbar it can be anywhere we want, just look into the options for wx.Toolbar
.
Here, I lost the text and made it vertical.
Upvotes: 2