Fitting matplotlib figure to wxpnel - python

I am trying to display a plot in a wxpanel so that when initialized it has 5 pixels gap from left top and right edges.
I tried the code below but it looks like when the plot is created it sticks right to the right edge of the panel. After resizing the gap appear.
# -*- coding: utf-8 -*-
import wx
#import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from relpermtable import RelPermTable as RelPermCl
class KrPanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
main_sizer = wx.BoxSizer(wx.VERTICAL)
self.list_ctrl = wx.ListCtrl(
self, size = (-1, 300),
style = wx.LC_REPORT | wx.BORDER_SUNKEN
)
self.list_ctrl.InsertColumn(0, 'Sw', width=100)
self.list_ctrl.InsertColumn(1, 'Krw', width=100)
self.list_ctrl.InsertColumn(2, 'Krg', width=100)
self.list_ctrl.InsertColumn(3, 'Pc', width=100)
main_sizer.Add(self.list_ctrl, 0, wx.ALL | wx.EXPAND, 5)
self.SetSizer(main_sizer)
def load_KrPc_data(self, in_dfr):
for index, row in in_dfr.iterrows():
self.list_ctrl.InsertItem(index, str(row["Sg"]))
self.list_ctrl.SetItem(index, 1, str(row["Krg"]))
self.list_ctrl.SetItem(index, 2, str(row["Krw"]))
self.list_ctrl.SetItem(index, 3, str(row["Pc"]))
class PlotPanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
def PlotKrPcData(self, df_in, in_swco, in_sgco):
right_sizer = wx.BoxSizer(wx.VERTICAL)
self.fig, self.ax1 = plt.subplots() # figsize=(5,5)
# self.ax2 = self.ax1.twinx()
self.ax1.plot(df_in['Sg'], df_in['Krg'], 'r-')
self.ax1.plot(df_in['Sg'], df_in['Krw'], 'g-')
# self.ax2.plot(new_df['Sg'], new_df['Pc'], 'b-')
self.ax1.plot((1-in_swco), 0, 'go')
self.ax1.annotate('1-SWL', ((1-in_swco), 0.05))
self.ax1.plot((in_sgco), 0, 'go', label = 'SGL')
self.ax1.annotate('SGL', (in_sgco, 0.05))
self.canvas = FigureCanvas(self, -1, self.fig)
right_sizer.Add(self.canvas, 0, wx.ALL | wx.EXPAND, 5)
self.SetSizer(right_sizer)
class KrFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None,
title='Gas Relative Permeability Editor', size=(900, 800))
self.sp = wx.SplitterWindow(self)
self.panel01 = KrPanel(self.sp)
self.panel02 = PlotPanel(self.sp)
self.sp.SplitVertically(self.panel01, self.panel02, 450)
self.create_menu()
self.Show()
def create_menu(self):
menu_bar = wx.MenuBar()
file_menu = wx.Menu()
open_file_menu_item = file_menu.Append(
wx.ID_ANY, 'Open File', 'Open a Relative Permeability File'
)
exit_menu_item = file_menu.Append(
wx.ID_EXIT, "Exit", "Exit the application"
)
menu_bar.Append(file_menu, '&File')
self.Bind(
event = wx.EVT_MENU,
handler = self.on_open_file,
source = open_file_menu_item
)
self.Bind(
event = wx.EVT_MENU,
handler = self.on_exit,
source = exit_menu_item
)
self.SetMenuBar(menu_bar)
def on_open_file(self, event):
title = "Choose a Relative Permeability file:"
dlg = wx.FileDialog(self, title, "", "",
"Eclipse include files (*.INC) | *.INC", style = wx.FD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
object = RelPermCl(dlg.GetPath())
new_df = object.load_KrPc_data()
new_sgco = object.calc_sgco()
new_swco = object.calc_swco()
# new_sgmax = object.calc_sgmax()
# new_swmax = object.calc_swmax()
# new_sgcr = object.calc_sgcr()
# new_swcr = object.calc_swcr()
self.panel01.load_KrPc_data(new_df)
self.panel02.PlotKrPcData(new_df, new_sgco, new_swco)
# print(new_df.head())
dlg.Destroy()
def on_exit(self, e):
self.Close()
if __name__ == '__main__':
app = wx.App(False)
frame = KrFrame()
app.MainLoop()
del app
I'd like to have 5 pixels gap on the right of the plot when the plot is created. But somehow the gap is created only after resize. Is there a way to set plot size let's say to the size of the panel independently of panel size and specify border sizes?

Try to put a frame.SendSizeEvent() before the Show call. If it doesn’t work, try with wx.CallAfter(frame.SendSizeEvent)

Related

wxpython - panel not clearing when resizing

This code successfully draws the SVG onto the screen. However, when I resize the window, the original image stays superimposed over the screen, while it redraws it underneath.
Before Resizing:
After Maximizing:
It's especially noticable if you drag to resize
# import igraph # either uncomment and install igraph or provide output.svg in the same dir
import wx
import wx.svg
class NNGui(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title, size=(800, 600))
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
# main graphics box
self.screen = MainScreen(self.panel)
vbox.Add(self.screen, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
self.Bind(wx.EVT_SIZE, self.on_resize)
self.Bind(wx.EVT_MAXIMIZE, self.on_resize)
# command box
self.cmd_box = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB)
vbox.Add(self.cmd_box, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=10)
self.cmd_box.Bind(wx.EVT_CHAR, self.do_char)
self.panel.SetSizer(vbox)
self.Layout()
self.Centre()
def do_char(self, e):
# handle keypresses
e.Skip()
def on_resize(self, e):
print('resize!')
# self.screen = MainScreen(self.panel)
self.screen.Refresh() # thank you Rolf-of-Saxony
# self.panel.Refresh()
# self.Refresh()
self.screen.Update()
# self.panel.Update()
# self.Update()
e.Skip()
class MainScreen(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
self.img = wx.svg.SVGimage.CreateFromFile('output.svg')
self.Bind(wx.EVT_PAINT, self.on_paint)
def on_paint(self, e):
print('screen painted!')
dc = wx.PaintDC(self)
dc.SetBackground(wx.Brush('black'))
dc.Clear()
dc_dim = min(self.Size.width, self.Size.height)
img_dim = min(self.img.width, self.img.height)
scale = dc_dim / img_dim
width = int(self.img.width * scale)
height = int(self.img.height * scale)
# ctx = wx.GraphicsContext.Create(dc)
# self.img.RenderToGC(ctx, scale)
bmp = self.img.ConvertToBitmap(scale=scale, width=width, height=height)
px_to_center = int((self.Size.width - width) / 2)
dc.DrawBitmap(bmp, px_to_center, 0)
e.Skip()
class NNGraph:
def __init__(self):
self.g = igraph.Graph.GRG(50, 0.2)
def write_svg(self, filename='output.svg'):
assert filename.endswith('.svg')
igraph.plot(self.g, filename)
def main():
# graph = NNGraph() # either uncomment and install igraph or provide output.svg in the same dir
# graph.write_svg()
app = wx.App()
frame = NNGui(None, title='NeuronicNodes')
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()
I've tried applying Update() to several panels, bound events for EVT_SIZE, EVT_PAINT, tried recreating the panel... I'm not sure what I'm missing.

matplotlib Embedded in wxPython with Navigation Toolbar Coordinates

I've come across and implemented a few scripts with matplotlib figures embedded in a wxPython panel. The embedding of the actual plot is fine but when I add a navigation toolbar NavigationToolbar2WxAgg much of the toolbar functionality is lost. I can pan and zoom, but there are no coordinates displayed, and the default shortcut keys do not work. The same behavior occurs in embedding_in_wx4_sgskip.py from the example/user_interfaces folder for matplotlib:
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np
import matplotlib as mpl
import wx
import wx.lib.mixins.inspection as WIT
class CanvasFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
'CanvasFrame', size=(550, 350))
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01)
s = np.sin(2 * np.pi * t)
self.axes.fmt_xdata = lambda x: "{0:f}".format(x)
self.axes.fmt_ydata = lambda x: "{0:f}".format(x)
self.axes.plot(t, s)
self.canvas = FigureCanvas(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
self.SetSizer(self.sizer)
self.Fit()
self.add_toolbar() # comment this out for no toolbar
def add_toolbar(self):
self.toolbar = NavigationToolbar(self.canvas)
self.toolbar.Realize()
# By adding toolbar in sizer, we are able to put it at the bottom
# of the frame - so appearance is closer to GTK version.
self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
# update the axes menu on the toolbar
self.toolbar.update()
# alternatively you could use
#class App(wx.App):
class App(WIT.InspectableApp):
def OnInit(self):
'Create the main window and insert the custom frame'
self.Init()
frame = CanvasFrame()
frame.Show(True)
return True
app = App(0)
app.MainLoop()
How do I restore or add this functionality to my navigation bar?
You can put this code here:
#Create 'Position Display'
self.Text = wx.StaticText( self, wx.ID_ANY, u" Available Channels ", wx.DefaultPosition, wx.DefaultSize, 0 )
self.Text.Wrap( -1 )
mouseMoveID = self.canvas.mpl_connect('motion_notify_event',
self.onMotion)
Before you create your sizer. Then add this to the end of your init definition:
self.sizer.Add(self.Text,0, wx.LEFT | wx.EXPAND)
Finally, add this function to capture mouse movement on the frame:
def onMotion(self, evt):
"""This is a bind event for the mouse moving on the MatPlotLib graph
screen. It will give the x,y coordinates of the mouse pointer.
"""
xdata = evt.xdata
ydata = evt.ydata
try:
x = round(xdata,4)
y = round(ydata,4)
except:
x = ""
y = ""
self.Text.SetLabelText("%s (s), %s" % (x,y))
This is what worked for me, good luck!

Multi lines realtime plotting

I would like to plot multi-lines in Python in a real-time manner though I'm just too new to this language.
I have found some codes as example to work fine but can only get one line plotted. Would someone help me with multi-lines please? Also, I need to adjust the line width, color, etc. of the lines.
The code is as follows:
# -*- coding: utf-8 -*-
"""
This demo demonstrates how to draw a dynamic mpl (matplotlib)
plot in a wxPython application.
It allows "live" plotting as well as manual zooming to specific
regions.
Both X and Y axes allow "auto" or "manual" settings. For Y, auto
mode sets the scaling of the graph to see all the data points.
For X, auto mode makes the graph "follow" the data. Set it X min
to manual 0 to always see the whole data from the beginning.
Note: press Enter in the 'manual' text box to make a new value
affect the plot.
Eli Bendersky (eliben#gmail.com)
License: this code is in the public domain
Last modified: 31.07.2008
"""
import os
import pprint
import random
import sys
import wx
# The recommended way to use wx with mpl is with the WXAgg
# backend.
#
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
import numpy as np
import pylab
class DataGen(object):
""" A silly class that generates pseudo-random data for
display in the plot.
"""
def __init__(self, init=50):
self.data = self.init = init
def next(self):
self._recalc_data()
return self.data
def _recalc_data(self):
delta = random.uniform(-0.5, 0.5)
r = random.random()
if r > 0.9:
self.data += delta * 15
elif r > 0.8:
# attraction to the initial value
delta += (0.5 if self.init > self.data else -0.5)
self.data += delta
else:
self.data += delta
class BoundControlBox(wx.Panel):
""" A static box with a couple of radio buttons and a text
box. Allows to switch between an automatic mode and a
manual mode with an associated value.
"""
def __init__(self, parent, ID, label, initval):
wx.Panel.__init__(self, parent, ID)
self.value = initval
box = wx.StaticBox(self, -1, label)
sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
self.radio_auto = wx.RadioButton(self, -1,
label="Auto", style=wx.RB_GROUP)
self.radio_manual = wx.RadioButton(self, -1,
label="Manual")
self.manual_text = wx.TextCtrl(self, -1,
size=(35,-1),
value=str(initval),
style=wx.TE_PROCESS_ENTER)
self.Bind(wx.EVT_UPDATE_UI, self.on_update_manual_text, self.manual_text)
self.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter, self.manual_text)
manual_box = wx.BoxSizer(wx.HORIZONTAL)
manual_box.Add(self.radio_manual, flag=wx.ALIGN_CENTER_VERTICAL)
manual_box.Add(self.manual_text, flag=wx.ALIGN_CENTER_VERTICAL)
sizer.Add(self.radio_auto, 0, wx.ALL, 10)
sizer.Add(manual_box, 0, wx.ALL, 10)
self.SetSizer(sizer)
sizer.Fit(self)
def on_update_manual_text(self, event):
self.manual_text.Enable(self.radio_manual.GetValue())
def on_text_enter(self, event):
self.value = self.manual_text.GetValue()
def is_auto(self):
return self.radio_auto.GetValue()
def manual_value(self):
return self.value
class GraphFrame(wx.Frame):
""" The main frame of the application
"""
title = 'Demo: dynamic matplotlib graph'
#修改下面self.redraw_timer.Start(100)数值影响plot下一个数据点的速度,越大速度越慢
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.datagen = DataGen()
self.data = [self.datagen.next()]
self.paused = False
self.create_menu()
self.create_status_bar()
self.create_main_panel()
self.redraw_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
self.redraw_timer.Start(100)
def create_menu(self):
self.menubar = wx.MenuBar()
menu_file = wx.Menu()
m_expt = menu_file.Append(-1, "&Save plot\tCtrl-S", "Save plot to file")
self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt)
menu_file.AppendSeparator()
m_exit = menu_file.Append(-1, "E&xit\tCtrl-X", "Exit")
self.Bind(wx.EVT_MENU, self.on_exit, m_exit)
self.menubar.Append(menu_file, "&File")
self.SetMenuBar(self.menubar)
def create_main_panel(self):
self.panel = wx.Panel(self)
self.init_plot()
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.xmin_control = BoundControlBox(self.panel, -1, "X min", 0)
self.xmax_control = BoundControlBox(self.panel, -1, "X max", 50)
self.ymin_control = BoundControlBox(self.panel, -1, "Y min", 0)
self.ymax_control = BoundControlBox(self.panel, -1, "Y max", 100)
self.pause_button = wx.Button(self.panel, -1, "Pause")
self.Bind(wx.EVT_BUTTON, self.on_pause_button, self.pause_button)
self.Bind(wx.EVT_UPDATE_UI, self.on_update_pause_button, self.pause_button)
self.cb_grid = wx.CheckBox(self.panel, -1,
"Show Grid",
style=wx.ALIGN_RIGHT)
self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid)
self.cb_grid.SetValue(True)
self.cb_xlab = wx.CheckBox(self.panel, -1,
"Show X labels",
style=wx.ALIGN_RIGHT)
self.Bind(wx.EVT_CHECKBOX, self.on_cb_xlab, self.cb_xlab)
self.cb_xlab.SetValue(True)
self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
self.hbox1.Add(self.pause_button, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
self.hbox1.AddSpacer(20)
self.hbox1.Add(self.cb_grid, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
self.hbox1.AddSpacer(10)
self.hbox1.Add(self.cb_xlab, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
self.hbox2 = wx.BoxSizer(wx.HORIZONTAL)
self.hbox2.Add(self.xmin_control, border=5, flag=wx.ALL)
self.hbox2.Add(self.xmax_control, border=5, flag=wx.ALL)
self.hbox2.AddSpacer(24)
self.hbox2.Add(self.ymin_control, border=5, flag=wx.ALL)
self.hbox2.Add(self.ymax_control, border=5, flag=wx.ALL)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW)
self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
self.vbox.Add(self.hbox2, 0, flag=wx.ALIGN_LEFT | wx.TOP)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def create_status_bar(self):
self.statusbar = self.CreateStatusBar()
def init_plot(self):
self.dpi = 100
self.fig = Figure((3.0, 3.0), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.set_axis_bgcolor('gainsboro')
self.axes.set_title('Very important random data', size=12)
pylab.setp(self.axes.get_xticklabels(), fontsize=8)
pylab.setp(self.axes.get_yticklabels(), fontsize=8)
# plot the data as a line series, and save the reference
# to the plotted line series
#
self.plot_data = self.axes.plot(
self.data,
linewidth=1,
color=(1, 1, 0),
)[0]
# 修改下面1000可以修改图标put出来的数据量
def draw_plot(self):
""" Redraws the plot
"""
# when xmin is on auto, it "follows" xmax to produce a
# sliding window effect. therefore, xmin is assigned after
# xmax.
#
if self.xmax_control.is_auto():
xmax = len(self.data) if len(self.data) > 1000 else 1000
else:
xmax = int(self.xmax_control.manual_value())
if self.xmin_control.is_auto():
xmin = xmax - 1000
else:
xmin = int(self.xmin_control.manual_value())
# for ymin and ymax, find the minimal and maximal values
# in the data set and add a mininal margin.
#
# note that it's easy to change this scheme to the
# minimal/maximal value in the current display, and not
# the whole data set.
#
if self.ymin_control.is_auto():
ymin = round(min(self.data), 0) - 1
else:
ymin = int(self.ymin_control.manual_value())
if self.ymax_control.is_auto():
ymax = round(max(self.data), 0) + 1
else:
ymax = int(self.ymax_control.manual_value())
self.axes.set_xbound(lower=xmin, upper=xmax)
self.axes.set_ybound(lower=ymin, upper=ymax)
# anecdote: axes.grid assumes b=True if any other flag is
# given even if b is set to False.
# so just passing the flag into the first statement won't
# work.
#
if self.cb_grid.IsChecked():
self.axes.grid(True, color='gray')
else:
self.axes.grid(False)
# Using setp here is convenient, because get_xticklabels
# returns a list over which one needs to explicitly
# iterate, and setp already handles this.
#
pylab.setp(self.axes.get_xticklabels(),
visible=self.cb_xlab.IsChecked())
self.plot_data.set_xdata(np.arange(len(self.data)))
self.plot_data.set_ydata(np.array(self.data))
self.canvas.draw()
def on_pause_button(self, event):
self.paused = not self.paused
def on_update_pause_button(self, event):
label = "Resume" if self.paused else "Pause"
self.pause_button.SetLabel(label)
def on_cb_grid(self, event):
self.draw_plot()
def on_cb_xlab(self, event):
self.draw_plot()
def on_save_plot(self, event):
file_choices = "PNG (*.png)|*.png"
dlg = wx.FileDialog(
self,
message="Save plot as...",
defaultDir=os.getcwd(),
defaultFile="plot.png",
wildcard=file_choices,
style=wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.canvas.print_figure(path, dpi=self.dpi)
self.flash_status_message("Saved to %s" % path)
def on_redraw_timer(self, event):
# if paused do not add data, but still redraw the plot
# (to respond to scale modifications, grid change, etc.)
#
if not self.paused:
self.data.append(self.datagen.next())
self.draw_plot()
def on_exit(self, event):
self.Destroy()
def flash_status_message(self, msg, flash_len_ms=1500):
self.statusbar.SetStatusText(msg)
self.timeroff = wx.Timer(self)
self.Bind(
wx.EVT_TIMER,
self.on_flash_status_off,
self.timeroff)
self.timeroff.Start(flash_len_ms, oneShot=True)
def on_flash_status_off(self, event):
self.statusbar.SetStatusText('')
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = GraphFrame()
app.frame.Show()
app.MainLoop()
You may find wxmplot (http://newville.github.io/wxmplot/) useful for this. It exposes and simplifies many aspects of using matplotlib with wxPython. It supports setting line attributes either programmatically or from a GUI panel for each plot. The demo includes a doing relatively fast "updating" for real-time plotting (10 fps or better, usually depending on hardware).

Draggable Matplotlib Subplot using wxPython

I'm attempting to expand on a draggable plot tutorial by creating a subplot that can be dragged (the matplotlib curve, not the whole window). I feel like I'm close but just missing a critical detail.
Most of the code is just creating cookie cutter subplots, figure 3 is the only one where I'm trying to drag the plot data.
Any help would be appreciated!
import wxversion
wxversion.ensureMinimal('2.8')
import numpy as np
import matplotlib
matplotlib.interactive(True)
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import wx
class DraggableCurve:
def __init__(self,curve):
self.curve = curve[0]
self.press = None
def connect(self):
'connect to all the events we need'
self.cidpress = self.curve.figure.canvas.mpl_connect(
'button_press_event', self.on_press)
self.cidrelease = self.curve.figure.canvas.mpl_connect(
'button_release_event', self.on_release)
self.cidmotion = self.curve.figure.canvas.mpl_connect(
'motion_notify_event', self.on_motion)
def on_press(self, event):
print "on_press"
'on button press we will see if the mouse is over us and store some data'
if event.inaxes != self.curve.axes: return
contains, attrd = self.curve.contains(event)
if not contains: return
print 'event contains', self.curve.xy
x0, y0 = self.curve.xy
# print x0,y0
self.press = x0, y0, event.xdata, event.ydata
def on_motion(self, event):
print "on_motion"
'on motion we will move the curve if the mouse is over us'
if self.press is None: return
if event.inaxes != self.curve.axes: return
x0, y0, xpress, ypress = self.press
print xpress, ypress
dx = event.xdata - xpress
dy = event.ydata - ypress
#print 'x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f'%(x0, xpress, event.xdata, dx, x0+dx)
self.curve.set_x(x0+dx)
self.curve.set_y(y0+dy)
# print x0+dx, y0+dy
#self.curve.figure.canvas.draw()
self.curve.figure.canvas.draw_idle()
def on_release(self, event):
print "on_release"
'on release we reset the press data'
self.press = None
#self.curve.figure.canvas.draw()
self.curve.figure.canvas.draw_idle()
def disconnect(self):
'disconnect all the stored connection ids'
self.curve.figure.canvas.mpl_disconnect(self.cidpress)
self.curve.figure.canvas.mpl_disconnect(self.cidrelease)
self.curve.figure.canvas.mpl_disconnect(self.cidmotion)
class CanvasFrame(wx.Frame):
def __init__(self):
#create frame
frame = wx.Frame.__init__(self,None,-1,
'Test',size=(550,350))
#set background
self.SetBackgroundColour(wx.NamedColour("WHITE"))
#initialize figures
self.figure1 = Figure()
self.figure2 = Figure()
self.figure3 = Figure()
self.figure4 = Figure()
#initialize figure1
self.axes1 = self.figure1.add_subplot(111)
self.axes1.text(0.5,0.5, 'Test 1', horizontalalignment='center', fontsize=15)
self.axes1.get_xaxis().set_visible(False)
self.axes1.get_yaxis().set_visible(False)
self.canvas1 = FigureCanvas(self, -1, self.figure1)
#initialize figure2
self.axes2 = self.figure2.add_subplot(111)
self.axes2.text(0.5,0.5, 'Test 2', horizontalalignment='center', fontsize=15)
self.axes2.get_xaxis().set_visible(False)
self.axes2.get_yaxis().set_visible(False)
self.canvas2 = FigureCanvas(self, -1, self.figure2)
#initialize figure3
self.axes3 = self.figure3.add_subplot(111)
curve = self.axes3.plot(np.arange(1,11),10*np.random.rand(10),color='r',marker='o')
self.canvas3 = FigureCanvas(self, -1, self.figure3)
# self.axes3.get_xaxis().set_visible(True)
# self.axes3.get_yaxis().set_visible(True)
# self.canvas3.draw()
# self.canvas3.draw_idle()
dc = DraggableCurve(curve)
dc.connect()
#initialize figure4
self.axes4 = self.figure4.add_subplot(111)
self.axes4.text(0.5,0.5, 'Test4', horizontalalignment='center', fontsize=15)
self.axes4.get_xaxis().set_visible(False)
self.axes4.get_yaxis().set_visible(False)
self.canvas4 = FigureCanvas(self, -1, self.figure4)
#create figures into the 2x2 grid
self.sizer = wx.GridSizer(rows=2, cols=2, hgap=5, vgap=5)
self.sizer.Add(self.canvas1, 1, wx.EXPAND)
self.sizer.Add(self.canvas2, 1, wx.EXPAND)
self.sizer.Add(self.canvas3, 1, wx.EXPAND)
self.sizer.Add(self.canvas4, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.Fit()
return
class App(wx.App):
def OnInit(self):
'Create the main window and insert the custom frame'
frame = CanvasFrame()
frame.Show(True)
return True
app = App(0)
app.MainLoop()
Check this example:
# -*- coding: utf-8 -*-
import wxversion
wxversion.ensureMinimal('2.8')
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
class FigureCanvas(FigureCanvasWxAgg):
def __init__(self,parent,id,figure,**kwargs):
FigureCanvasWxAgg.__init__(self,parent=parent, id=id, figure=figure,**kwargs)
self.figure = figure
self.axes = self.figure.get_axes()[0] # Get axes
self.connect() # Connect event
def connect(self):
"""Connect pick event"""
self.MOVE_LINE_EVT = self.mpl_connect("pick_event", self.on_pick)
def on_pick(self,event):
self._selected_line = event.artist # Get selected line
# Get initial x,y data
self._p0 = (event.mouseevent.xdata, event.mouseevent.ydata)
self._xdata0 = self._selected_line.get_xdata()
self._ydata0 = self._selected_line.get_ydata()
# Connect events for motion and release.
self._on_motion = self.mpl_connect("motion_notify_event", self.on_motion)
self._on_release = self.mpl_connect("button_release_event", self.on_release)
def on_motion(self,event):
cx = event.xdata # Current xdata
cy = event.ydata # Current ydata
deltax = cx - self._p0[0]
deltay = cy - self._p0[1]
self._selected_line.set_xdata(self._xdata0 + deltax)
self._selected_line.set_ydata(self._ydata0 + deltay)
self.draw()
def on_release(self,event):
"""On release, disconnect motion and release"""
self.mpl_disconnect(self._on_motion)
self.mpl_disconnect(self._on_release)
self.axes.relim()
self.axes.autoscale_view(True,True,True)
self.draw()
class Frame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent,title=title,size=(800,600))
self.initCtrls()
self.plotting()
self.Centre(True)
self.Show()
def initCtrls(self):
self.mainsizer = wx.GridSizer(rows=2, cols=2, hgap=2, vgap=2)
# 1
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, wx.ID_ANY, self.figure)
# 2
self.figure2 = Figure()
self.axes2 = self.figure2.add_subplot(111)
self.canvas2 = FigureCanvas(self, wx.ID_ANY, self.figure2)
self.figure3 = Figure()
self.axes3 = self.figure3.add_subplot(111)
self.canvas3 = FigureCanvas(self, wx.ID_ANY, self.figure3)
self.figure4 = Figure()
self.axes4 = self.figure4.add_subplot(111)
self.canvas4 = FigureCanvas(self, wx.ID_ANY, self.figure4)
self.mainsizer.Add(self.canvas, 1, wx.EXPAND)
self.mainsizer.Add(self.canvas2, 1, wx.EXPAND)
self.mainsizer.Add(self.canvas3, 1, wx.EXPAND)
self.mainsizer.Add(self.canvas4, 1, wx.EXPAND)
self.SetSizer(self.mainsizer)
def plotting(self):
# Set picker property -> true
self.axes2.plot(np.arange(1,11),10*np.random.rand(10),color='b',
marker='o', picker=True)
self.axes3.plot(np.arange(1,11),10*np.random.rand(10),color='r',
marker='o', picker=True)
self.canvas.draw()
if __name__=='__main__':
app = wx.App()
frame = Frame(None, "Matplotlib Demo")
frame.Show()
app.MainLoop()
Basically the idea is to define a custom FigureCanvas, which supports the selection and movement of the lines using the pick event.
Obviously this code still needs a lot of review, to work properly.

Python/Matplotlib - Quickly Updating Text on Axes

I have a matplotlib figure/canvas in a wxpython window. I want to update some information on the plot as the mouse moves around. I've connected to 'motion_notify_event' to get this information.
In the code below, a lot of random data is plotted and then the x,y location of the cursor is displayed in the statusbar of the window. This is very smooth and works well. However, I really want to display this information at the top of the plot. The behavior I want is shown if you uncomment the last two lines of cbUpdateCursor. However, when this is done, the response time to moving the cursor is terribly slow (because draw gets called and there is a lot of data, but draw must be called or the text doesn't get updated).
How can I speed this up so the cursor position can be displayed on the plot, but not slow it down so much? I think I might need to do something with bbox?
Code:
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
class wxPlotting(wx.Frame):
title = 'Test'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.time = np.arange(10000)
self.data = np.random.random(10000)
self.sb = self.CreateStatusBar()
self.create_main_panel()
self.axes.plot(self.time, self.data)
self.canvas.draw()
def create_main_panel(self):
self.panel = wx.Panel(self)
self.fig = Figure((5.0, 4.0), dpi=100)
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.axes = self.fig.add_subplot(111)
self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes)
self.cursor = Cursor(self.axes, useblit=True, color='red')
self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def cbUpdateCursor(self, event):
if event.inaxes:
text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
self.sb.SetStatusText(text)
#self.text.set_text(text)
#self.canvas.draw()
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = wxPlotting()
app.frame.Show()
app.MainLoop()
Basically I want something similar to the text that gets displayed using pyplot, i.e. the bottom right corner when the code below is run:
Code:
import matplotlib.pyplot as plt
plt.plot(range(10000), range(10000))
plt.show()
EDIT:
In my actual program, I want the static text to be within the matplotlib axes, not really above it. So I don't think I can just use a wxpython statictext to display it.
You could use blitting, similar to the animation examples here.
This make a very large performance difference in this case, as only a small portion of the window needs to be redrawn.
Unfortunately, I can't figure out how to get a gray background behind the text when it's redrawn, to match the default figure background behind it... The performance is excellent, though.
As a stand-alone example based on your code above:
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
class wxPlotting(wx.Frame):
title = 'Test'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.time = np.arange(10000)
self.data = np.random.random(10000)
self.sb = self.CreateStatusBar()
self.create_main_panel()
self.axes.plot(self.time, self.data)
self.background = self.canvas.copy_from_bbox(self.fig.bbox)
self.canvas.draw()
def create_main_panel(self):
self.panel = wx.Panel(self)
self.fig = Figure((5.0, 4.0), dpi=100)
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.axes = self.fig.add_subplot(111)
self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes, animated=True)
self.cursor = Cursor(self.axes, useblit=True, color='red')
self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def cbUpdateCursor(self, event):
if event.inaxes:
text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
self.sb.SetStatusText(text)
self.canvas.restore_region(self.background)
self.text.set_text(text)
self.axes.draw_artist(self.text)
self.canvas.blit(self.text.get_window_extent())
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = wxPlotting()
app.frame.Show()
app.MainLoop()
You could add a static text box on top, and just update it's label:
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
class wxPlotting(wx.Frame):
title = 'Test'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.time = np.arange(10000)
self.data = np.random.random(10000)
self.sb = self.CreateStatusBar()
self.create_main_panel()
self.axes.plot(self.time, self.data)
self.canvas.draw()
def create_main_panel(self):
self.panel = wx.Panel(self)
self.fig = Figure((5.0, 4.0), dpi=100)
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.axes = self.fig.add_subplot(111)
self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes)
self.cursor = Cursor(self.axes, useblit=True, color='red')
self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.cursor_pos = wx.StaticText(self.panel,-1, label="")
self.vbox.Add(self.cursor_pos, 0, wx.LEFT | wx.TOP | wx.GROW)
self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def cbUpdateCursor(self, event):
if event.inaxes:
text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
self.sb.SetStatusText(text)
self.cursor_pos.SetLabel(text)
#self.text.set_text(text)
#self.canvas.draw()
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = wxPlotting()
app.frame.Show()
app.MainLoop()

Categories