Python Traits GUI recursion depth - python

I'm developing a GUI in python using Enthought's Traits. I keep getting a "RuntimeError: maximum recursion depth exceeded in cmp" If I flip the order in which the "Item" labels are used in my MainWindow class, the code executes fine. I can't seem to find any documentation on why this would happen. It seems like it has something to do with the Chaco plot. Below is my test code.
from chaco.api import ArrayPlotData, Plot
from traits.api import HasTraits, Instance, String, Float, Enum, Button, Str
from traitsui.api import Handler, View, Item, Group, HSplit, NoButtons, VGroup, VGrid
from pyface.api import GUI
from threading import Thread
from time import sleep
from enthought.enable.component_editor import ComponentEditor
from scipy import rand, indices, exp, sqrt, sum
import numpy as np
from PIL import Image
import matplotlib.image as mpimg
from enthought.chaco.api import gray
from enthought.savage.traits.ui.svg_button import SVGButton
class User_Input_Panel(HasTraits):
User = Str(name='User', label="User Name")
Sample_Name = Str(name='Sample_Name',label="Sample Name")
Path = Str(name='Path', label="Save Location", style = 'readonly')
#I use this to create a folder icon on the button
#~ Save_Folder_Button = SVGButton(label='Choose save Location', \
#~ filename=Folder-drag-accept.svg', \
#~ width=28, \
#~ height=28 \
#~ )
#~ #Create the User Information panel
User_Information_Panel = View(VGroup(
VGrid(
Item('User'),
Item('Sample_Name'),
Item('Path', width=.700, visible_when = 'Save_Visible == True'),
#Item('Save_Folder_Button', show_label=False),
),
show_border=True, label="User Information"
))
def _Save_Folder_Button_fired(self, event):
print("Pushed the Save Folder")
#self.file, self.dir = wxOpenFile(multi=False)
#fdir = GUI_tools.wxOpenFile()
#fdir = GUI_tools.wxSavePath()
#I'm planning on setting up threading later
class MainWindowHandler(Handler):
def close(self, info, is_OK):
#~ if (info.object.user_input.acquisition_thread and \
#~ info.object.user_input.acquisition_thread.isAlive()):
#~ info.object.user_input.acquisition_thread.wants_abort = True
#~ while info.object.user_input.acquisition_thread.isAlive():
#~ sleep(0.1)
#~ GUI.process_events()
return True
class MainWindow(HasTraits):
plot = Instance(Plot)
plotdata = Instance(ArrayPlotData, ())
user_input = Instance(User_Input_Panel, ())
def _user_input_default(self):
return User_Input_Panel(plotdata = self.plotdata)
Save_Folder_Button = SVGButton(label='Choose save Location', \
filename='C:\Python27\Examples\Mill_GUI\Tescan_BatchScan\Folder-drag-accept.svg', \
width=28, \
height=28 \
)
def _plot_default(self):
self.plotdata = ArrayPlotData(imagedata=np.zeros((100,100)))
plot = Plot(self.plotdata)
plot.img_plot('imagedata')
self.plot = plot
return plot
# If I flip the "Item('user_input'" with "Item('plot') the code will run...
view = View(VGroup(Item('user_input', style = 'custom', show_label = False),
Item('plot', editor = ComponentEditor(), dock = 'vertical'),
show_labels = False),
resizable = True, handler = MainWindowHandler(),
buttons = NoButtons)
if __name__ == '__main__':
MainWindow().configure_traits()
Does anybody know why you would get recursion errors with this code? I have to display the plot below my user_input panel so the users can see the live time data they are gathering.
Cheers,
Shivels

You need to remove the following line from your _plot_default method:
self.plot = plot
For this to happen in traits, you simply need to return that plot object and the assignment to self.plot will be done by Traits. The recursion error happens because you as trying to access the plot attribute of the object inside the method that is trying to set it.

Related

Image for Canvas is not created

So I want a window that updates a shown picture after clicking.
It works fine as long as there is no further tk.Tk() instance (remove/add line 8 of the code below).
If one is created before, this error is raised:
line 29, in CreatePeakSelectionWindow
[...]
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.img1)
[...]
_tkinter.TclError: image "pyimage1" doesn't exist
I think I need to pass some argument to Tk()?
I don't know where to even look to address this issue and understand how it is caused.
Sadly this widget is to be used to allow manual selection of some peaks and should be done in an external window.
FYI all arrays are dummies (random arrays) for simplicities sake.
Thank you very much for any help!
The code which causes the issue is the following:
import tkinter as tk
import numpy as np
from PIL import Image,ImageTk
import matplotlib.pyplot as plt
class Dummy:
def __init__(self):
self.MainWin = tk.Tk() #>this line causes the issue
imgs = np.random.randint(0,255,(512,624,2))
self.img = imgs[:,:,0] #self.img is a numpy array in black and white
self.imgSize = self.img.shape
self.peakList = np.array([[200,200],[300,400]])
self.selectedIndexOfPeaksList = []
self.peakListGenerated = True
def CreatePeakSelectionWindow(self):
if self.peakListGenerated:
self.selectedIndexOfPeaksList = []
self.PeakSelectionWindow = tk.Tk()
self.PeakSelectionWindow.protocol("WM_DELETE_WINDOW",self.PeakSelectionWindowClose)
self.PeakSelectionWindow.geometry("%sx%s"%(self.imgSize[1],self.imgSize[0]))
self.PeakSelectionWindow.title("Peak Slection")
self.img1 = ImageTk.PhotoImage(image=Image.fromarray(self.img))
self.imgCanvas = tk.Canvas(self.PeakSelectionWindow,width=self.imgSize[1],height=self.imgSize[0])
self.imgCanvas.place(x=0,y=0)
self.PeakSelectionWindow.bind("<Button 1>",self.LeftClick)
self.PeakSelectionWindow.bind("<Button 3>",self.RightClick)
self.PeakSelectionWindow.update()
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.img1)
else:
print("List of peaks has not yet been generated!",file=sys.stderr)
def PeakSelectionWindowClose (self):
if len(self.selectedIndexOfPeaksList) > 0:
print("put extraction here")
#self.selectedPeaksEntry.insert(tk.END,", ".join(map(str,self.selectedIndexOfPeaksList)))
self.PeakSelectionWindow.destroy()
def LeftClick(self,event):
distance = np.sqrt((self.peakList[:,1]-event.x)**2+(self.peakList[:,0]-event.y)**2)
index = np.argmin(distance)
if index not in self.selectedIndexOfPeaksList:
self.peakList[index]
self.selectedIndexOfPeaksList += [index]
newImg = np.random.randint(0,255,(self.img.shape[0],self.img.shape[1],3))
self.PeakSelectionWindow.newImg = img = ImageTk.PhotoImage(image=Image.fromarray(newImg.astype("uint8"),mode="RGB"))
self.imgCanvas.delete("all")
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.PeakSelectionWindow.newImg)
self.imgCanvas.update()
def RightClick (self,event):
distance = np.sqrt((self.peakList[:,1]-event.x)**2+(self.peakList[:,0]-event.y)**2)
index = np.argmin(distance)
print(self.selectedIndexOfPeaksList)
if index in self.selectedIndexOfPeaksList:
if len(self.selectedIndexOfPeaksList) > 1:
self.selectedIndexOfPeaksList.remove(index)
newImg = np.random.randint(0,255,(self.img.shape[0],self.img.shape[1],3))
self.PeakSelectionWindow.newImg = img = ImageTk.PhotoImage(image=Image.fromarray(newImg.astype("uint8"),mode="RGB"))
self.imgCanvas.delete("all")
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.PeakSelectionWindow.newImg)
self.imgCanvas.update()
else:
self.selectedIndexOfPeaksList = []
self.PeakSelectionWindow.newImg = newImg = ImageTk.PhotoImage(image=Image.fromarray(self.img.astype("uint8")))
self.imgCanvas.delete("all")
self.imgCanvas.create_image((0,0),anchor=tk.NW,image=self.PeakSelectionWindow.newImg)
self.imgCanvas.update()
if __name__ == "__main__":
window = Dummy()
window.CreatePeakSelectionWindow()
tk.mainloop()
Okay so I found a solution.
The additional window needs to be a class with tk.Toplevel().
All changes for the code above are:
class Dummy: to class Dummy (tk.Toplevel()):
def __init__(self): to def __init__ (self,master):
self.peakSelectionWindow to self (as a reference to master
passed to the class)
any tk object (like buttons) also needs this master set as the window too to be rendered
Of course the creation of the first window should be handled outside of the class, passing the windowName = tk.Tk() onto the call of Dummy like a normal variable/reference.
In case you need to share variables of the master to this dummy class, I think windowName.variableName = 5 makes them known/accessable in dummy too (via self.variableName). However this might be messy, so instead pass them on normally if possible.

plotting multiple lines of streaming data in a bokeh server application

I'm trying to build a bokeh application with streaming data that tracks multiple "strategies" as they are generated in a prisoners-dilemma agent based model. I've run into a problem trying to get my line plots NOT to connect all the data points in one line. I put together this little demo script that replicates the issue. I've read lots of documentation on line and multi_line rendering in bokeh plots, but I just haven't found something that seems to match my simple case. You can run this code & it will automatically open a bokeh server at localhost:5004 ...
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import Button
from bokeh.layouts import column
import random
def make_document(doc):
# Create a data source
data_source = ColumnDataSource({'step': [], 'strategy': [], 'ncount': []})
# make a list of groups
strategies = ['DD', 'DC', 'CD', 'CCDD']
# Create a figure
fig = figure(title='Streaming Line Plot',
plot_width=800, plot_height=400)
fig.line(x='step', y='ncount', source=data_source)
global step
step = 0
def button1_run():
global callback_obj
if button1.label == 'Run':
button1.label = 'Stop'
button1.button_type='danger'
callback_obj = doc.add_periodic_callback(button2_step, 100)
else:
button1.label = 'Run'
button1.button_type = 'success'
doc.remove_periodic_callback(callback_obj)
def button2_step():
global step
step = step+1
for i in range(len(strategies)):
new = {'step': [step],
'strategy': [strategies[i]],
'ncount': [random.choice(range(1,100))]}
fig.line(x='step', y='ncount', source=new)
data_source.stream(new)
# add on_click callback for button widget
button1 = Button(label="Run", button_type='success', width=390)
button1.on_click(button1_run)
button2 = Button(label="Step", button_type='primary', width=390)
button2.on_click(button2_step)
doc.add_root(column(fig, button1, button2))
doc.title = "Now with live updating!"
apps = {'/': Application(FunctionHandler(make_document))}
server = Server(apps, port=5004)
server.start()
if __name__ == '__main__':
server.io_loop.add_callback(server.show, "/")
server.io_loop.start()
My hope was that by looping thru the 4 "strategies" in the example (after clicking button2), I could stream the new data coming out of the simulation into a line plot for that one strategy and step only. But what I get is one line with all four values connected vertically, then one of them connected to the first one at the next step. Here's what it looks like after a few steps:
I noticed that if I move data_source.stream(new) out of the for loop, I get a nice single line plot, but of course it is only for the last strategy coming out of the loop.
In all the bokeh multiple line plotting examples I've studied (not the multi_line glyph, which I can't figure out and which seems to have some issues with the Hover tool), the instructions seem pretty clear: if you want to render a second line, you add another fig.line renderer to an existing figure, and it draws a line with the data provided in source=data_source for this line. But even though my for-loop collects and adds data separately for each strategy, I don't get 4 line plots, I get only one.
Hoping I'm missing something obvious! Thanks in advance.
Seems like you need a line per strategy, not a line per step. If so, here's how I would do it:
import random
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.layouts import column
from bokeh.models import Button
from bokeh.palettes import Dark2
from bokeh.plotting import figure, ColumnDataSource
from bokeh.server.server import Server
STRATEGIES = ['DD', 'DC', 'CD', 'CCDD']
def make_document(doc):
step = 0
def new_step_data():
nonlocal step
result = [dict(step=[step],
ncount=[random.choice(range(1, 100))])
for _ in STRATEGIES]
step += 1
return result
fig = figure(title='Streaming Line Plot', plot_width=800, plot_height=400)
sources = []
for s, d, c in zip(STRATEGIES, new_step_data(), Dark2[4]):
# Generate the very first step right away
# to avoid having a completely empty plot.
ds = ColumnDataSource(d)
sources.append(ds)
fig.line(x='step', y='ncount', source=ds, color=c)
callback_obj = None
def button1_run():
nonlocal callback_obj
if callback_obj is None:
button1.label = 'Stop'
button1.button_type = 'danger'
callback_obj = doc.add_periodic_callback(button2_step, 100)
else:
button1.label = 'Run'
button1.button_type = 'success'
doc.remove_periodic_callback(callback_obj)
def button2_step():
for src, data in zip(sources, new_step_data()):
src.stream(data)
# add on_click callback for button widget
button1 = Button(label="Run", button_type='success', width=390)
button1.on_click(button1_run)
button2 = Button(label="Step", button_type='primary', width=390)
button2.on_click(button2_step)
doc.add_root(column(fig, button1, button2))
doc.title = "Now with live updating!"
apps = {'/': Application(FunctionHandler(make_document))}
server = Server(apps, port=5004)
if __name__ == '__main__':
server.io_loop.add_callback(server.show, "/")
server.start()
server.io_loop.start()
Thank you, Eugene. Your solution got me back on the right track. I played around with it a bit more and ended up with the following:
import colorcet as cc
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import Button
from bokeh.layouts import column
import random
def make_document(doc):
# make a list of groups
strategies = ['DD', 'DC', 'CD', 'CCDD']
# initialize some vars
step = 0
callback_obj = None
colors = cc.glasbey_dark
# create a list to hold all CDSs for active strategies in next step
sources = []
# Create a figure container
fig = figure(title='Streaming Line Plot - Step 0', plot_width=800, plot_height=400)
# get step 0 data for initial strategies
for i in range(len(strategies)):
step_data = dict(step=[step],
strategy = [strategies[i]],
ncount=[random.choice(range(1, 100))])
data_source = ColumnDataSource(step_data)
color = colors[i]
# this will create one fig.line renderer for each strategy & its data for this step
fig.line(x='step', y='ncount', source=data_source, color=color, line_width=2)
# add this CDS to the sources list
sources.append(data_source)
def button1_run():
nonlocal callback_obj
if button1.label == 'Run':
button1.label = 'Stop'
button1.button_type='danger'
callback_obj = doc.add_periodic_callback(button2_step, 100)
else:
button1.label = 'Run'
button1.button_type = 'success'
doc.remove_periodic_callback(callback_obj)
def button2_step():
nonlocal step
data = []
step += 1
fig.title.text = 'Streaming Line Plot - Step '+str(step)
for i in range(len(strategies)):
step_data = dict(step=[step],
strategy = [strategies[i]],
ncount=[random.choice(range(1, 100))])
data.append(step_data)
for source, data in zip(sources, data):
source.stream(data)
# add on_click callback for button widget
button1 = Button(label="Run", button_type='success', width=390)
button1.on_click(button1_run)
button2 = Button(label="Step", button_type='primary', width=390)
button2.on_click(button2_step)
doc.add_root(column(fig, button1, button2))
doc.title = "Now with live updating!"
apps = {'/': Application(FunctionHandler(make_document))}
server = Server(apps, port=5004)
server.start()
if __name__ == '__main__':
server.io_loop.add_callback(server.show, "/")
server.io_loop.start()
Result is just what I was looking for ...

PyobjC : NSTextField on NSPopover

I have the same problem mentioned here : Not being able to edit NSTextField on NSPopover even though Editable behavior is set. The solution seems to be to override the canBecomeKeyWindow of NSWindow. I am trying to do the same thing in PyObjC, but I am getting an error Python signature doesn't match implied objective-C signature.
In the following code, if I comment out canBecomeKeyWindow_(), then the app runs as expected, but I am not able to click and edit the textfields.
# from Cocoa import *
from AppKit import NSWindowController, NSApplication, NSApp, NSMaxYEdge, NSImage, NSStatusBar, NSMenu, NSMenuItem, NSVariableStatusItemLength, NSRect
from Cocoa import objc
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject
from PyObjCTools import AppHelper
import webbrowser
import subprocess
import os
global popover
class TestApp(NSApplication):
def finishLaunching(self):
# Make statusbar item
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
self.icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
self.icon.setScalesWhenResized_(True)
self.icon.setSize_((20, 20))
self.statusitem.setImage_(self.icon)
self.statusitem.setHighlightMode_(1)
# make the menu
self.menubarMenu = NSMenu.alloc().init()
self.menuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Login', 'loginCallback:', '')
self.menubarMenu.addItem_(self.menuItem)
self.quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '')
self.menubarMenu.addItem_(self.quit)
# add menu to statusitem
self.statusitem.setMenu_(self.menubarMenu)
def loginCallback_(self, notification):
# Initiate the contrller with a XIB
viewController = SimpleXibDemoController.alloc().initWithWindowNibName_("Login")
# Show the window
viewController.showWindow_(viewController)
rect = self.statusitem.valueForKey_('button').frame()
viewController.popover.showRelativeToRect_ofView_preferredEdge_(rect, self.statusitem.valueForKey_('button'), NSMaxYEdge)
class SimpleXibDemoController(NSWindowController):
popover = objc.IBOutlet()
counterTextField = objc.IBOutlet()
username_field = objc.IBOutlet()
password_field = objc.IBOutlet()
submit_button = objc.IBOutlet()
def canBecomeKeyWindow_(self):
return 1
def windowDidLoad(self):
NSWindowController.windowDidLoad(self)
#objc.IBAction
def submit_(self, sender):
username = self.username_field.stringValue()
password = self.password_field.stringValue()
self.updateDisplay(username + ' ' + password)
def updateDisplay(self, value):
self.counterTextField.setStringValue_(value)
if __name__ == "__main__":
app = TestApp.sharedApplication()
icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
app.setApplicationIconImage_(icon)
AppHelper.runEventLoop()
It looks like you're adding an underscore where you shouldn't. The PyObjC bridge will translate it into a colon. Besides that, the corresponding Python boolean value should be True. Thus, the correct function would look like this:
def canBecomeKeyWindow(self):
return True

Python Matplotlib interactive plotting - freezes after a few frames

I am having a problem getting matplotlib to work well with interactive plotting... what I see is that after displaying a few frames of my simulated data matplotlib hangs-and doesn't display any more.
Basically I've been playing around a bit with science simulations - and would like to be able to plot my results as they are being made - rather than at the end - using pylab.show().
I found a cookbook example from a while back that seems to do what I would want - in simple terms (although obv. the data is different). The cookbook is here...http://www.scipy.org/Cookbook/Matplotlib/Animations#head-2f6224cc0c133b6e35c95f4b74b1b6fc7d3edca4
I have searched around a little and I know that some people had these problems before - Matplotlib animation either freezes after a few frames or just doesn't work but it seems at the time there were no good solutions. I was wondering if someone has since found a good solution here.
I have tried a few 'backends' on matplotlib....TkAgg seems to work for a few frames.... qt4agg doesn't show the frames. I haven't yet got GTK to install properly.
I am running the most recent pythonxy(2.7.3).
Anyone have any advice?
import matplotlib
matplotlib.use('TkAgg') # 'Normal' Interactive backend. - works for several frames
#matplotlib.use('qt4agg') # 'QT' Interactive backend. - doesn't seem to work at all
#matplotlib.use('GTKAgg') # 'GTK' backend - can't seem to get this to work.... -
import matplotlib.pyplot as plt
import time
import numpy as np
plt.ion()
tstart = time.time() # for profiling
x = np.arange(0,2*np.pi,0.01) # x-array
line, = plt.plot(x,np.sin(x))
#plt.ioff()
for i in np.arange(1,200):
line.set_ydata(np.sin(x+i/10.0)) # update the data
line.axes.set_title('frame number {0}'.format(i))
plt.draw() # redraw the canvas
print 'FPS:' , 200/(time.time()-tstart)
EDIT:
edited code - to get rid of some style issues brought up.
Ok... So I have mangled together something that may sort of work for me....
Basically it is something like a watered down gui - but i'm hoping that it is a class i can import and basically forget about the details of (here's hoping).
I should say though - this is my first attempt at threading OR guis in python - so this code comes with a health warning.
** I'm not going to mark the question as answered though - because i'm sure someone more experienced will have a better solution.
'''
JP
Attempt to get multiple updating of matplotlibs working.
Uses WX to create an 'almost' gui with a mpl in the middle of it.
Data can be queued to this object - or you can directly plot to it.
Probably will have some limitations atm
- only really thinking about 2d plots for now -
but presumably can work around this for other implimentations.
- the working code seems to need to be put into another thread.
Tried to put the wx mainloop into another thread,
but it seemed unhappy. :(
Classes of Interest :
GraphData - A silly class that holds data to be plotted.
PlotFigure - Class of wx frame type.
Holds a mpl figure in it + queue to queue data to.
The frame will plot the data when it refreshes it's canvas
ThreadSimulation - This is not to do with the plotting
it is a test program.
Modified version of:
Copyright (C) 2003-2005 Jeremy O'Donoghue and others
License: This work is licensed under the PSF. A copy should be included
with this source code, and is also available at
http://www.python.org/psf/license.html
'''
import threading
import collections
import time
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
import wx
class GraphData(object):
'''
A silly class that holds data to be plotted.
'''
def __init__(self, xdatainit, ydatainit):
self.xdata = xdatainit
self.ydata = ydatainit
class PlotFigure(wx.Frame):
def __init__(self ):
'''
Initialises the frame.
'''
wx.Frame.__init__(self, None, -1, "Test embedded wxFigure")
self.timerid = wx.NewId()
self.fig = Figure((5,4), 75)
self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
self.toolbar = NavigationToolbar2Wx(self.canvas)
self.toolbar.Realize()
# On Windows, default frame size behaviour is incorrect
# you don't need this under Linux
tw, th = self.toolbar.GetSizeTuple()
fw, fh = self.canvas.GetSizeTuple()
self.toolbar.SetSize(wx.Size(fw, th))
# Now put all into a sizer
sizer = wx.BoxSizer(wx.VERTICAL)
# This way of adding to sizer allows resizing
sizer.Add(self.canvas, 1, wx.LEFT|wx.TOP|wx.GROW)
# Best to allow the toolbar to resize!
sizer.Add(self.toolbar, 0, wx.GROW)
self.SetSizer(sizer)
self.Fit()
wx.EVT_TIMER(self, self.timerid, self.onTimer)
self.dataqueue = collections.deque()
# Add an axes and a line to the figure.
self.axes = self.fig.add_subplot(111)
self.line, = self.axes.plot([],[])
def GetToolBar(self):
'''
returns default toolbar.
'''
return self.toolbar
def onTimer(self, evt):
'''
Every timer period this is called.
Want to redraw the canvas.
'''
#print "onTimer"
if len(self.dataqueue) > 0 :
data = self.dataqueue.pop()
x = data.xdata
y = data.ydata
xmax = max(x)
xmin = min(x)
ymin = round(min(y), 0) - 1
ymax = round(max(y), 0) + 1
self.axes.set_xbound(lower=xmin, upper=xmax)
self.axes.set_ybound(lower=ymin, upper=ymax)
self.line.set_xdata(x)
self.line.set_ydata(y)
# Redraws the canvas - does this even if the data isn't updated...
self.canvas.draw()
def onEraseBackground(self, evt):
'''
this is supposed to prevent redraw flicker on some X servers...
'''
pass
class ThreadSimulation(threading.Thread):
'''
Simulation Thread - produces data to be displayed in the other thread.
'''
def __init__(self, nsimloops, datastep, pltframe, slowloop = 0):
threading.Thread.__init__(self)
self.nsimloops = nsimloops
self.datastep = datastep
self.pltframe = pltframe
self.slowloop=slowloop
def run(self):
'''
This is the simulation function.
'''
nsimloops = self.nsimloops
datastep = self.datastep
pltframe = self.pltframe
print 'Sim Thread: Starting.'
tstart = time.time() # for profiling
# Define Data to share between threads.
x = np.arange(0,2*np.pi,datastep) # x-array
y = np.sin(x )
# Queues up the data and removes previous versions.
pltframe.dataqueue.append(GraphData(x,y))
for i in range(len(pltframe.dataqueue)-1):
pltframe.dataqueue.popleft()
pltframe.dataqueue
for i in np.arange(1, nsimloops):
x = x + datastep
y = np.sin(x)
# Queues up the data and removes previous versions.
pltframe.dataqueue.append(GraphData(x,y))
for i in range(len(pltframe.dataqueue)-1):
pltframe.dataqueue.popleft()
#pltframe.dataqueue
if self.slowloop > 0 :
time.sleep(self.slowloop)
tstop= time.time()
print 'Sim Thread: Complete.'
print 'Av Loop Time:' , (tstop-tstart)/ nsimloops
if __name__ == '__main__':
# Create the wx application.
app = wx.PySimpleApp()
# Create a frame with a plot inside it.
pltframe = PlotFigure()
pltframe1 = PlotFigure()
# Initialise the timer - wxPython requires this to be connected to
# the receiving event handler
t = wx.Timer(pltframe, pltframe.timerid)
t.Start(100)
pltframe.Show()
pltframe1.Show()
npoints = 100
nsimloops = 20000
datastep = 2 * np.pi/ npoints
slowloop = .1
#Define and start application thread
thrd = ThreadSimulation(nsimloops, datastep, pltframe,slowloop)
thrd.setDaemon(True)
thrd.start()
pltframe1.axes.plot(np.random.rand(10),np.random.rand(10))
app.MainLoop()

Dynamically create plots in Chaco

The Chaco plotting toolkit for Python includes examples that show how to dynamically update existing plots. However, my application requires that I dynamically create and destroy plots depending on the data. I am new to programming with Chaco and Traits, so a simple example that illustrates how to do this would be really helpful.
This is a bit late, but here's an example that creates and destroys Chaco plots. The main interface is PlotSelector, which defines some fake data and radio buttons to switch between two different plot styles (line and bar plots).
This example uses a Traits event to signal when to close a plot, and then handles that signal with PlotController. There may be a better way to close the window, but I couldn't find one.
Edit: Updated imports for newer versions of Traits, Chaco, and Enable (ETS 4 instead of 3).
import numpy as np
import traits.api as traits
import traitsui.api as ui
import chaco.api as chaco
from enable.api import ComponentEditor
class PlotController(ui.Controller):
view = ui.View(ui.Item('plot', editor=ComponentEditor(), show_label=False),
height=300, width=300, resizable=True)
def object_close_signal_changed(self, info):
info.ui.dispose()
class BasicPlot(traits.HasTraits):
close_signal = traits.Event()
plot = traits.Instance(chaco.Plot)
class LinePlot(BasicPlot):
def __init__(self, plotdata):
self.plot = chaco.Plot(plotdata)
self.plot.plot(('x', 'y'))
class BarPlot(BasicPlot):
def __init__(self, plotdata):
self.plot = chaco.Plot(plotdata)
self.plot.candle_plot(('x', 'ymin', 'ymax'))
available_plot_types = dict(line=LinePlot, bar=BarPlot)
class PlotSelector(traits.HasTraits):
plot_type = traits.Enum(['line', 'bar'])
traits_view = ui.View('plot_type', style='custom')
def __init__(self, x, y):
ymin = y - 1
ymax = y + 1
self.plotdata = chaco.ArrayPlotData(x=x, y=y, ymin=ymin, ymax=ymax)
self.figure = None
def _plot_type_changed(self):
plot_class = available_plot_types[self.plot_type]
if self.figure is not None:
self.figure.close_signal = True
self.figure = plot_class(self.plotdata)
controller = PlotController(model=self.figure)
controller.edit_traits()
N = 20
x = np.arange(N)
y = x + np.random.normal(size=N)
plot_selector = PlotSelector(x, y)
plot_selector.configure_traits()
Note that the main interface (PlotSelector) calls configure_traits (starts application), while the plots are viewed with edit_traits (called from within application). Also, note that this example calls edit_traits from PlotController instead of calling it from the model. You could instead move the view from PlotController to BasicPlot and set the handler method of that view to PlotController.
Finally, if you don't need to totally destroy the plot window, then you may want to look at the Plot object's delplot method, which destroys the *sub*plot (here the line plot or bar plot).
I hope that helps.

Categories

Resources