I'd like a rectangle to appear every time the user clicks on the line. I've gotten this to work procedurally like in this example: http://www.daniweb.com/software-development/python/code/216648 but once I implemented iPython compatibility and started using classes, I could no longer use the app.MainLoop() without the program crashing. How do I refresh a wx.Frame object from inside a class? Why does the self.figure.canvas.draw() not work?
The code is below. Open ipython with the -pylab option. x = [-10,10] and y = x are decent parameters for this problem.
import wx
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanv
from pylab import *
import IPython.ipapi
ip = IPython.ipapi.get()
import sys
class MainCanvas(wx.Frame):
""" Set up the canvas and plot on which the rectangle will lie """
def __init__(self, *args):
wx.Frame.__init__(self,None,-1, size=(550,350))
self.x = args[0]
self.y = args[1]
self.figure = plt.figure()
self.axes = self.figure.add_subplot(111)
self.axes.plot(*args)
self.line, = self.axes.plot(self.x, self.y, picker = 3,
visible = False)
self.canvas = FigCanv(self, -1, self.figure)
self.rect = patches.Rectangle((0, 0), 2, 2, visible=True)
self.axes.add_patch(self.rect)
self.figure.canvas.mpl_connect('pick_event', self.onPick)
def onPick(self, event):
""" Move rectangle to last click on line """
self.rect.set_x(event.mouseevent.xdata)
self.rect.set_y(event.mouseevent.ydata)
self.rect.set_visible(True)
print "rect x: ", self.rect.get_x()
print "rect y: ", self.rect.get_y()
self.figure.canvas.draw()
def run_this_plot(self, arg_s=''):
""" Run in iPython
Examples
In [1]: import demo
In [2]: runplot x y <z>
Where x, y, and z are numbers of any type
"""
args = []
for arg in arg_s.split():
try:
args.append(self.shell.user_ns[arg])
except KeyError:
raise ValueError("Invalid argument: %r" % arg)
mc = MainCanvas(*args)
ip.expose_magic("runplot", run_this_plot)
Thanks!
--Erin
It seems likely that matplotlib is set to use a backend other than wx. Try either setting this in the matplotlibrc file, or it can be set in the program (but it must be set before matplotlib is imported). Instructions are here.
Related
I would like to use matplotlib in my code but without FigureCanvasKivyAgg. I use kivy and I tried this but the plot it is not visible. This is my code:
import matplotlib
import matplotlib.pyplot as plt
x = [1,2,3,4]
y = [5,10,12,9]
plt.plot(x,y)
class ClassForPlot(Screen):
def __init__(self, **kw):
super().__init__(**kw)
self._app = App.get_running_app()
plt.show()
plt.show() doesn't work in init?
By using this answer to produce a LiveGraph and this answer to update variables to a thread, I was able to generate a graph that updates itself each second and whose amplitude is determined by a slider (code below). Both answers were incredibly helpful!
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from threading import Thread, Lock
import time
import ipywidgets as widgets
from IPython.display import display
import numpy as np
'''#################### Live Graph ####################'''
# Create a new class which is a live graph
class LiveGraph(object):
def __init__(self, baseline):
self.x_data, self.y_data = [], []
self.figure = plt.figure()
self.line, = plt.plot(self.x_data, self.y_data)
self.animation = FuncAnimation(self.figure, self.update, interval=1200)
# define variable to be updated as a list
self.baseline = [baseline]
self.lock = Lock()
self.th = Thread(target=self.thread_f, args = (self.baseline,), daemon=True)
# start thread
self.th.start()
def update_baseline(self,baseline):
# updating a list updates the thread argument
with self.lock:
self.baseline[0] = baseline
# Updates animation
def update(self, frame):
self.line.set_data(self.x_data, self.y_data)
self.figure.gca().relim()
self.figure.gca().autoscale_view()
return self.line,
def show(self):
plt.show()
# Function called by thread that updates variables
def thread_f(self, base):
x = 0
while True:
self.x_data.append(x)
x += 1
self.y_data.append(base[0])
time.sleep(1)
'''#################### Slider ####################'''
# Function that updates baseline to slider value
def update_baseline(v):
global g
new_value = v['new']
g.update_baseline(new_value)
slider = widgets.IntSlider(
value=10,
min=0,
max=200,
step=1,
description='value:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
slider.observe(update_baseline, names = 'value')
'''#################### Display ####################'''
display(slider)
g = LiveGraph(slider.value)
Still, I would like to put the graph inside a bigger interface which has other widgets. It seems that I should put the LiveGraph inside the Output widget, but when I replace the 'Display section' of my code by the code shown below, no figure is displayed.
out = widgets.Output(layout={'border': '1px solid black'})
with out:
g = LiveGraph(slider.value)
vbox = widgets.VBox([slider,out], align_self='stretch',justify_content='center')
vbox
Is there a way to embed this LiveGraph in the output widget or in a box widget?
I found a solution by avoiding using FuncAnimation and the Output widget altogether while keeping my backend as inline.
Also changing from matplotlib to bqplot was essential!
The code is shown below (be careful because, as it is, it keeps increasing a list).
Details of things I tried:
I had no success updating the graph by a thread when using the Output Widget (tried clearing axes with ax.clear, redrawing the whole plot - since it is a static backend - and also using clear_output() command).
Also, ipywidgets does not allow placing a matplotlib figure straight inside a container, but it does if it is a bqplot figure!
I hope this answer helps anyone trying to integrate ipywidgets with a plot that constantly updates itself within an interface full of other widgets.
%matplotlib inline
import bqplot.pyplot as plt
from threading import Thread, Lock
import time
import ipywidgets as widgets
from IPython.display import display
import numpy as np
fig = plt.figure()
t, value = [], []
lines = plt.plot(x=t, y=value)
# Function that updates baseline to slider value
def update_baseline(v):
global base, lock
with lock:
new_value = v['new']
base = new_value
slider = widgets.IntSlider(
value=10,
min=0,
max=200,
step=1,
description='value:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
base = slider.value
slider.observe(update_baseline, names = 'value')
def thread_f():
global t, value, base, lines
x = 0
while True:
t.append(x)
x += 1
value.append(base)
with lines.hold_sync():
lines.x = t
lines.y = value
time.sleep(0.1)
lock = Lock()
th = Thread(target=thread_f, daemon=True)
# start thread
th.start()
vbox = widgets.VBox([slider,fig], align_self='stretch',justify_content='center')
vbox
P.S. I'm new to using threads, so be careful as the thread may not be properly stopped with this code.
bqplot==0.12.29
ipywidgets==7.6.3
numpy==1.20.2
I did the following example code just to test how to integrate an animated matplotlib plot with pygtk. However, I get some unexpected behaviors when I run it.
First, when I run my program and click on the button (referred to as button1 in the code), there is another external blank window which shows up and the animated plot starts only after closing this window.
Secondly, when I click on the button many times, it seems that there is more animations which are created on top of each other (which gives the impression that the animated plot speeds up). I have tried to call animation.FuncAnimation inside a thread (as you can see in the comment at end of the function on_button1_clicked), but the problem still the same.
Thirdly, is it a good practice to call animation.FuncAnimation in a thread to allow the user to use the other functions of the gui ? Or should I rather create a thread inside the method animate (I guess this will create too many threads quickly) ? I am not sure how to proceed.
Here is my code:
import gtk
from random import random
import numpy as np
from multiprocessing.pool import ThreadPool
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
#from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas
class HelloWorld:
def __init__(self):
interface = gtk.Builder()
interface.add_from_file('interface.glade')
self.dialog1 = interface.get_object("dialog1")
self.label1 = interface.get_object("label1")
self.entry1 = interface.get_object("entry1")
self.button1 = interface.get_object("button1")
self.hbox1 = interface.get_object("hbox1")
self.fig, self.ax = plt.subplots()
self.X = [random() for x in range(10)]
self.Y = [random() for x in range(10)]
self.line, = self.ax.plot(self.X, self.Y)
self.canvas = FigureCanvas(self.fig)
# self.hbox1.add(self.canvas)
self.hbox1.pack_start(self.canvas)
interface.connect_signals(self)
self.dialog1.show_all()
def gtk_widget_destroy(self, widget):
gtk.main_quit()
def on_button1_clicked(self, widget):
name = self.entry1.get_text()
self.label1.set_text("Hello " + name)
self.ani = animation.FuncAnimation(self.fig, self.animate, np.arange(1, 200), init_func=self.init, interval=25, blit=True)
'''
pool = ThreadPool(processes=1)
async_result = pool.apply_async(animation.FuncAnimation, args=(self.fig, self.animate, np.arange(1, 200)), kwds={'init_func':self.init, 'interval':25, 'blit':True} )
self.ani = async_result.get()
'''
plt.show()
def animate(self, i):
# Read XX and YY from a file or whateve
XX = [random() for x in range(10)] # just as an example
YY = [random() for x in range(10)] # just as an example
self.line.set_xdata( XX )
self.line.set_ydata( YY )
return self.line,
def init(self):
self.line.set_ydata(np.ma.array(self.X, mask=True))
return self.line,
if __name__ == "__main__":
HelloWorld()
gtk.main()
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'm trying to add vertical lines to a matplotlib plot dynmically when a user clicks on a particular point.
import matplotlib.pyplot as plt
import matplotlib.dates as mdate
class PointPicker(object):
def __init__(self,dates,values):
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.lines2d, = self.ax.plot_date(dates, values, linestyle='-',picker=5)
self.fig.canvas.mpl_connect('pick_event', self.onpick)
self.fig.canvas.mpl_connect('key_press_event', self.onpress)
def onpress(self, event):
"""define some key press events"""
if event.key.lower() == 'q':
sys.exit()
def onpick(self,event):
x = event.mouseevent.xdata
y = event.mouseevent.ydata
print self.ax.axvline(x=x, visible=True)
x = mdate.num2date(x)
print x,y,type(x)
if __name__ == '__main__':
import numpy as np
import datetime
dates=[datetime.datetime.now()+i*datetime.timedelta(days=1) for i in range(100)]
values = np.random.random(100)
plt.ion()
p = PointPicker(dates,values)
plt.show()
Here's an (almost) working example. When I click a point, the onpick method is indeed called and the data seems to be correct, but no vertical line shows up. What do I need to do to get the vertical line to show up?
Thanks
You need to update the canvas drawing (self.fig.canvas.draw()):
def onpick(self,event):
x = event.mouseevent.xdata
y = event.mouseevent.ydata
L = self.ax.axvline(x=x)
self.fig.canvas.draw()