← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-dev/yade/trunk] Rev 2325: 1. LIVE PLOTTING (thanks to Janek for the idea and the suggestion of mtTkinter). Report bugs.

 

------------------------------------------------------------
revno: 2325
committer: Václav Šmilauer <eudoxos@xxxxxxxx>
branch nick: trunk
timestamp: Tue 2010-07-06 13:31:29 +0200
message:
  1. LIVE PLOTTING (thanks to Janek for the idea and the suggestion of mtTkinter). Report bugs.
added:
  py/3rd-party/mtTkinter-0.3/
  py/3rd-party/mtTkinter-0.3/mtTkinter.py
modified:
  examples/concrete/uniax.py
  examples/simple-scene/simple-scene-plot.py
  py/3rd-party/README
  py/SConscript
  py/plot.py
  py/yadeWrapper/yadeWrapper.cpp


--
lp:yade
https://code.launchpad.net/~yade-dev/yade/trunk

Your team Yade developers is subscribed to branch lp:yade.
To unsubscribe from this branch go to https://code.launchpad.net/~yade-dev/yade/trunk/+edit-subscription
=== modified file 'examples/concrete/uniax.py'
--- examples/concrete/uniax.py	2010-06-29 14:32:03 +0000
+++ examples/concrete/uniax.py	2010-07-06 11:31:29 +0000
@@ -103,7 +103,7 @@
 #O.miscParams=[Gl1_CpmPhys(dmgLabel=False,colorStrain=False,epsNLabel=False,epsT=False,epsTAxes=False,normal=False,contactLine=True)]
 
 # plot stresses in ¼, ½ and ¾ if desired as well; too crowded in the graph that includes confinement, though
-plot.plots={'eps':('sigma',)} #'sigma.25','sigma.50','sigma.75')}
+plot.plots={'eps':('sigma','sigma.50'),'t':('eps')} #'sigma.25','sigma.50','sigma.75')}
 plot.maxDataLen=4000
 
 O.saveTmp('initial');

=== modified file 'examples/simple-scene/simple-scene-plot.py'
--- examples/simple-scene/simple-scene-plot.py	2010-05-29 20:52:10 +0000
+++ examples/simple-scene/simple-scene-plot.py	2010-07-06 11:31:29 +0000
@@ -25,7 +25,7 @@
 from yade import utils
 O.bodies.append(utils.box(center=[0,0,0],extents=[.5,.5,.5],dynamic=False,color=[1,0,0]))
 O.bodies.append(utils.sphere([0,0,2],1,color=[0,1,0]))
-O.dt=.2*utils.PWaveTimeStep()
+O.dt=.002*utils.PWaveTimeStep()
 
 
 ############################################
@@ -46,9 +46,9 @@
 	sph=O.bodies[1]
 	## store some numbers under some labels
 	plot.addData(t=O.time,i=O.iter,z_sph=sph.state.pos[2],z_sph_half=.5*sph.state.pos[2],v_sph=sph.state.vel.norm())
-
-O.run(int(2./O.dt),True);
-print "Now calling plot.plot() to show the figures (close them to continue)."
+print "Now calling plot.plot() to show the figures. The timestep is artificially low so that you can watch graphs being updated live."
+plot.liveInterval=.2
 plot.plot()
-plot.saveGnuplot('/tmp/a')
+O.run(int(2./O.dt));
+#plot.saveGnuplot('/tmp/a')
 ## you can also access the data in plot.data['i'], plot.data['t'] etc, under the labels they were saved.

=== modified file 'py/3rd-party/README'
--- py/3rd-party/README	2009-08-23 15:50:47 +0000
+++ py/3rd-party/README	2010-07-06 11:31:29 +0000
@@ -11,3 +11,7 @@
 	license: Boost software license 1.0
 	note: only headers are copied here
 
+mtTkinter-0.3:
+	homepage: http://tkinter.unpythonic.net/wiki/mtTkinter
+	source: http://tkinter.unpythonic.net/attach//mtTkinter/attachments/mtTkinter-0.3.tar.gz
+	license: unspecified

=== added directory 'py/3rd-party/mtTkinter-0.3'
=== added file 'py/3rd-party/mtTkinter-0.3/mtTkinter.py'
--- py/3rd-party/mtTkinter-0.3/mtTkinter.py	1970-01-01 00:00:00 +0000
+++ py/3rd-party/mtTkinter-0.3/mtTkinter.py	2010-07-06 11:31:29 +0000
@@ -0,0 +1,227 @@
+'''Thread-safe version of Tkinter.
+
+Usage:
+
+    import mtTkinter as Tkinter
+    # Use "Tkinter." as usual.
+
+or
+
+    from mtTkinter import *
+    # Use Tkinter module definitions as usual.
+
+This module modifies the original Tkinter module in memory, making all
+functionality thread-safe. It does this by wrapping the Tk class' tk
+instance with an object that diverts calls through an event queue when
+the call is issued from a thread other than the thread in which the Tk
+instance was created. The events are processed in the creation thread
+via an 'after' event.
+
+The modified Tk class accepts two additional keyword parameters on its
+__init__ method:
+    mtDebug:
+        0 = No debug output (default)
+        1 = Minimal debug output
+        ...
+        9 = Full debug output
+    mtCheckPeriod:
+        Amount of time in milliseconds (default 100) between checks for
+        out-of-thread events when things are otherwise idle. Decreasing
+        this value can improve GUI responsiveness, but at the expense of
+        consuming more CPU cycles.
+
+Note that, because it modifies the original Tkinter module (in memory),
+other modules that use Tkinter (e.g., Pmw) reap the benefits automagically
+as long as mtTkinter is imported at some point before extra threads are
+created.
+
+Author: Allen B. Taylor, a.b.taylor@xxxxxxxxx
+'''
+
+from Tkinter import *
+import threading
+import Queue
+
+class _Tk(object):
+    """
+    Wrapper for underlying attribute tk of class Tk.
+    """
+
+    def __init__(self, tk, mtDebug = 0, mtCheckPeriod = 10):
+        self._tk = tk
+
+        # Create the incoming event queue.
+        self._eventQueue = Queue.Queue(1)
+
+        # Identify the thread from which this object is being created so we can
+        # tell later whether an event is coming from another thread.
+        self._creationThread = threading.currentThread()
+
+        # Store remaining values.
+        self._debug = mtDebug
+        self._checkPeriod = mtCheckPeriod
+
+    def __getattr__(self, name):
+        # Divert attribute accesses to a wrapper around the underlying tk
+        # object.
+        return _TkAttr(self, getattr(self._tk, name))
+
+class _TkAttr(object):
+    """
+    Thread-safe callable attribute wrapper.
+    """
+
+    def __init__(self, tk, attr):
+        self._tk = tk
+        self._attr = attr
+
+    def __call__(self, *args, **kwargs):
+        """
+        Thread-safe method invocation.
+        Diverts out-of-thread calls through the event queue.
+        Forwards all other method calls to the underlying tk object directly.
+        """
+
+        # Check if we're in the creation thread.
+        if threading.currentThread() == self._tk._creationThread:
+            # We're in the creation thread; just call the event directly.
+            if self._tk._debug >= 8 or \
+               self._tk._debug >= 3 and self._attr.__name__ == 'call' and \
+               len(args) >= 1 and args[0] == 'after':
+                print 'Calling event directly:', \
+                    self._attr.__name__, args, kwargs
+            return self._attr(*args, **kwargs)
+        else:
+            # We're in a different thread than the creation thread; enqueue
+            # the event, and then wait for the response.
+            responseQueue = Queue.Queue(1)
+            if self._tk._debug >= 1:
+                print 'Marshalling event:', self._attr.__name__, args, kwargs
+            self._tk._eventQueue.put((self._attr, args, kwargs, responseQueue))
+            isException, response = responseQueue.get()
+
+            # Handle the response, whether it's a normal return value or
+            # an exception.
+            if isException:
+                exType, exValue, exTb = response
+                raise exType, exValue, exTb
+            else:
+                return response
+
+# Define a hook for class Tk's __init__ method.
+def _Tk__init__(self, *args, **kwargs):
+    # We support some new keyword arguments that the original __init__ method
+    # doesn't expect, so separate those out before doing anything else.
+    new_kwnames = ('mtCheckPeriod', 'mtDebug')
+    new_kwargs = {}
+    for name, value in kwargs.items():
+        if name in new_kwnames:
+            new_kwargs[name] = value
+            del kwargs[name]
+
+    # Call the original __init__ method, creating the internal tk member.
+    self.__original__init__mtTkinter(*args, **kwargs)
+
+    # Replace the internal tk member with a wrapper that handles calls from
+    # other threads.
+    self.tk = _Tk(self.tk, **new_kwargs)
+
+    # Set up the first event to check for out-of-thread events.
+    self.after_idle(_CheckEvents, self)
+
+# Replace Tk's original __init__ with the hook.
+Tk.__original__init__mtTkinter = Tk.__init__
+Tk.__init__ = _Tk__init__
+
+def _CheckEvents(tk):
+    "Event checker event."
+
+    used = False
+    try:
+        # Process all enqueued events, then exit.
+        while True:
+            try:
+                # Get an event request from the queue.
+                method, args, kwargs, responseQueue = \
+                    tk.tk._eventQueue.get_nowait()
+            except:
+                # No more events to process.
+                break
+            else:
+                # Call the event with the given arguments, and then return
+                # the result back to the caller via the response queue.
+                used = True
+                if tk.tk._debug >= 2:
+                    print 'Calling event from main thread:', \
+                        method.__name__, args, kwargs
+                try:
+                    responseQueue.put((False, method(*args, **kwargs)))
+                except SystemExit, ex:
+                    raise SystemExit, ex
+                except Exception, ex:
+                    # Calling the event caused an exception; return the
+                    # exception back to the caller so that it can be raised
+                    # in the caller's thread.
+                    from sys import exc_info
+                    exType, exValue, exTb = exc_info()
+                    responseQueue.put((True, (exType, exValue, exTb)))
+    finally:
+        # Schedule to check again. If we just processed an event, check
+        # immediately; if we didn't, check later.
+        if used:
+            tk.after_idle(_CheckEvents, tk)
+        else:
+            tk.after(tk.tk._checkPeriod, _CheckEvents, tk)
+
+# Test thread entry point.
+def _testThread(root):
+    text = "This is Tcl/Tk version %s" % TclVersion
+    if TclVersion >= 8.1:
+        try:
+            text = text + unicode("\nThis should be a cedilla: \347",
+                                  "iso-8859-1")
+        except NameError:
+            pass # no unicode support
+    try:
+        if root.globalgetvar('tcl_platform(threaded)'):
+            text = text + "\nTcl is built with thread support"
+        else:
+            raise RuntimeError
+    except:
+        text = text + "\nTcl is NOT built with thread support"
+    text = text + "\nmtTkinter works with or without Tcl thread support"
+    label = Label(root, text=text)
+    label.pack()
+    button = Button(root, text="Click me!",
+              command=lambda root=root: root.button.configure(
+                  text="[%s]" % root.button['text']))
+    button.pack()
+    root.button = button
+    quit = Button(root, text="QUIT", command=root.destroy)
+    quit.pack()
+    # The following three commands are needed so the window pops
+    # up on top on Windows...
+    root.iconify()
+    root.update()
+    root.deiconify()
+    # Simulate button presses...
+    button.invoke()
+    root.after(1000, _pressOk, root, button)
+
+# Test button continuous press event.
+def _pressOk(root, button):
+    button.invoke()
+    try:
+        root.after(1000, _pressOk, root, button)
+    except:
+        pass # Likely we're exiting
+
+# Test. Mostly borrowed from the Tkinter module, but the important bits moved
+# into a separate thread.
+if __name__ == '__main__':
+    import threading
+    root = Tk(mtDebug = 1)
+    thread = threading.Thread(target = _testThread, args=(root,))
+    thread.start()
+    root.mainloop()
+    thread.join()

=== modified file 'py/SConscript'
--- py/SConscript	2010-06-18 13:02:01 +0000
+++ py/SConscript	2010-07-06 11:31:29 +0000
@@ -51,6 +51,7 @@
 # ==================
 env.Install('$PREFIX/lib/yade$SUFFIX/py',[
 	env.SharedLibrary('miniEigen',['mathWrap/miniEigen.cpp'],SHLIBPREFIX='',CPPPATH=env['CPPPATH']+['../lib/'],LIBS=env['LIBS']+['core']),
+	env.File('mtTkinter.py','3rd-party/mtTkinter-0.3'),
 ])
 
 if 'YADE_GTS' in env['CPPDEFINES']:

=== modified file 'py/plot.py'
--- py/plot.py	2010-07-01 09:56:50 +0000
+++ py/plot.py	2010-07-06 11:31:29 +0000
@@ -4,7 +4,16 @@
 Module containing utility functions for plotting inside yade. See :ysrc:`scripts/simple-scene-plot.py` or :ysrc:`examples/concrete/uniax.py` for example of usage.
 
 """
-import matplotlib,os
+
+## all exported names
+__all__=['data','plots','labels','live','liveInterval','autozoom','plot','reset','resetData','splitData','reverse','addData','saveGnuplot']
+
+# multi-threaded support for Tk
+# safe to import even if Tk will not be used
+import mtTkinter as Tkinter
+
+import matplotlib,os,time
+
 # running in batch
 #
 # If GtkAgg is the default, X must be working, which is not the case
@@ -20,8 +29,8 @@
 	matplotlib.use('Agg')
 
 #matplotlib.use('TkAgg')
-#matplotlib.use('GTKCairo')
-#matplotlib.use('QtAgg')
+#matplotlib.use('GTKAgg')
+##matplotlib.use('QtAgg')
 matplotlib.rc('axes',grid=True) # put grid in all figures
 import pylab
 
@@ -32,6 +41,13 @@
 labels={}
 "Dictionary converting names in data to human-readable names (TeX names, for instance); if a variable is not specified, it is left untranslated."
 
+live=True
+"Enable/disable live plot updating. Disabled by default for now, since it has a few rough edges."
+liveInterval=1
+"Interval for the live plot updating, in seconds."
+autozoom=True
+"Enable/disable automatic plot rezooming after data update."
+
 def reset():
 	"Reset all plot-related variables (data, plots, labels)"
 	global data, plots, labels # plotLines
@@ -78,84 +94,133 @@
 		if name in d: data[name].append(d[name]) #numpy.append(data[name],[d[name]],1)
 		else: data[name].append(nan)
 
-def _addPointTypeSpecifier(o):
+# not public functions
+def addPointTypeSpecifier(o):
 	"""Add point type specifier to simple variable name"""
 	if type(o) in [tuple,list]: return o
 	else: return (o,'')
-def _tuplifyYAxis(pp):
+def tuplifyYAxis(pp):
 	"""convert one variable to a 1-tuple"""
 	if type(pp) in [tuple,list]: return pp
 	else: return (pp,)
-def _xlateLabel(l):
+def xlateLabel(l):
 	"Return translated label; return l itself if not in the labels dict."
 	global labels
 	if l in labels.keys(): return labels[l]
 	else: return l
 
-def plot(noShow=False):
-	"""Do the actual plot, which is either shown on screen (and nothing is returned: if *noShow* is False) or returned as object (if *noShow* is True).
-	
-	You can use 
-	
-		>>> from yade import plot
-		>>> plot.plot(noShow=True).savefig('someFile.pdf')
-		>>> import os
-		>>> os.path.exists('someFile.pdf')
-		True
-		
-	to save the figure to file automatically.
-	"""
-	if not noShow: pylab.ion() ## # no interactive mode (hmmm, I don't know why actually...)
+class LineRef:
+	"""Holds reference to plot line and to original data arrays (which change during the simulation),
+	and updates the actual line using those data upon request."""
+	def __init__(self,line,xdata,ydata):
+		self.line,self.xdata,self.ydata=line,xdata,ydata
+	def update(self):
+		self.line.set_xdata(self.xdata)
+		self.line.set_ydata(self.ydata)
+
+currLineRefs=[]
+liveTimeStamp=0 # timestamp when live update was started, so that the old thread knows to stop if that changes
+nan=float('nan')
+
+def createPlots():
+	global currLineRefs
+	figs=set([l.line.get_axes().get_figure() for l in currLineRefs]) # get all current figures
+	for f in figs: pylab.close(f) # close those
+	currLineRefs=[] # remove older plots (breaks live updates of windows that are still open)
+	if len(plots)==0: return # nothing to plot
 	for p in plots:
 		p=p.strip()
 		pylab.figure()
-		plots_p=[_addPointTypeSpecifier(o) for o in _tuplifyYAxis(plots[p])]
+		plots_p=[addPointTypeSpecifier(o) for o in tuplifyYAxis(plots[p])]
 		plots_p_y1,plots_p_y2=[],[]; y1=True
+		missing={} # missing data columns
+		if p not in data.keys(): missing[p]=nan
 		for d in plots_p:
 			if d[0]=='|||' or d[0]==None:
 				y1=False; continue
 			if y1: plots_p_y1.append(d)
 			else: plots_p_y2.append(d)
-		#plotLines[p]=
-		pylab.plot(*sum([[data[p],data[d[0]],d[1]] for d in plots_p_y1],[]))
-		pylab.legend([_xlateLabel(_p[0]) for _p in plots_p_y1],loc=('upper left' if len(plots_p_y2)>0 else 'best'))
-		pylab.ylabel(','.join([_xlateLabel(_p[0]) for _p in plots_p_y1]))
+			if d[0] not in data.keys(): missing[d[0]]=nan
+		addData(missing)
+		# create y1 lines
+		for d in plots_p_y1:
+			line,=pylab.plot(data[p],data[d[0]],d[1])
+			currLineRefs.append(LineRef(line,data[p],data[d[0]]))
+		# create the legend
+		pylab.legend([xlateLabel(_p[0]) for _p in plots_p_y1],loc=('upper left' if len(plots_p_y2)>0 else 'best'))
+		pylab.ylabel(','.join([xlateLabel(_p[0]) for _p in plots_p_y1]))
+		# create y2 lines, if any
 		if len(plots_p_y2)>0:
 			# try to move in the color palette a little further (magenta is 5th): r,g,b,c,m,y,k
 			origLinesColor=pylab.rcParams['lines.color']; pylab.rcParams['lines.color']='m'
 			# create the y2 axis
 			pylab.twinx()
-			#plotLines[p]+=
-			[pylab.plot(*sum([[data[p],data[d[0]],d[1]] for d in plots_p_y2],[]))]
-			pylab.legend([_xlateLabel(_p[0]) for _p in plots_p_y2],loc='upper right')
+			for d in plots_p_y2:
+				line,=pylab.plot(data[p],data[d[0]],d[1])
+				currLineRefs.append(LineRef(line,data[p],data[d[0]]))
+			# legend
+			pylab.legend([xlateLabel(_p[0]) for _p in plots_p_y2],loc='upper right')
 			pylab.rcParams['lines.color']=origLinesColor
-			pylab.ylabel(','.join([_xlateLabel(_p[0]) for _p in plots_p_y2]))
-		pylab.xlabel(_xlateLabel(p))
+			pylab.ylabel(','.join([xlateLabel(_p[0]) for _p in plots_p_y2]))
+		pylab.xlabel(xlateLabel(p))
 		if 'title' in O.tags.keys(): pylab.title(O.tags['title'])
-	if not noShow: pylab.show()
-	else: return pylab.gcf() # return current figure
-updatePeriod=0
+
+def liveUpdate(timestamp):
+	global liveTimeStamp
+	liveTimeStamp=timestamp
+	while True:
+		if not live or liveTimeStamp!=timestamp: return
+		figs,axes=set(),set()
+		for l in currLineRefs:
+			l.update()
+			figs.add(l.line.get_figure())
+			axes.add(l.line.get_axes())
+		if autozoom:
+			for ax in axes:
+				try:
+					ax.relim() # recompute axes limits
+					ax.autoscale_view()
+				except RuntimeError: pass # happens if data are being updated and have not the same dimension at the very moment
+		for fig in figs:
+			fig.canvas.draw()
+		time.sleep(liveInterval)
+	
+
+def plot(noShow=False):
+	"""Do the actual plot, which is either shown on screen (and nothing is returned: if *noShow* is False) or returned as object (if *noShow* is True).
+	
+	You can use 
+	
+		>>> from yade import plot
+		>>> plot.plot(noShow=True).savefig('someFile.pdf')
+		>>> import os
+		>>> os.path.exists('someFile.pdf')
+		True
+		
+	to save the figure to file automatically.
+	"""
+	createPlots()
+	global currLineRefs
+	if not noShow:
+		if live:
+			import thread
+			thread.start_new_thread(liveUpdate,(time.time(),))
+		# pylab.show() # this blocks for some reason; call show on figures directly
+		figs=set([l.line.get_axes().get_figure() for l in currLineRefs])
+		for f in figs: f.show()
+	else: return pylab.gcf() # return the current figure
 
 def saveGnuplot(baseName,term='wxt',extension=None,timestamp=False,comment=None,title=None,varData=False):
 	"""Save data added with :yref:`yade.plot.addData` into (compressed) file and create .gnuplot file that attempts to mimick plots specified with :yref:`yade.plot.plots`.
 
-:parameters:
-	baseName:
-		used for creating baseName.gnuplot (command file for gnuplot),
-		associated baseName.data (data) and output files (if applicable) in the form baseName.[plot number].extension
-	term:
-		specify the gnuplot terminal;
-		defaults to x11, in which case gnuplot will draw persistent windows to screen and terminate; other useful terminals are 'png', 'cairopdf' and so on
-	extension:
-		defaults to terminal name; fine for png for example; if you use 'cairopdf', you should also say extension='pdf' however
-	timestamp:
-		append numeric time to the basename
-	varData:
-		whether file to plot will be declared as variable or be in-place in the plot expression
-	comment:
-		a user comment (may be multiline) that will be embedded in the control file
+:param baseName: used for creating baseName.gnuplot (command file for gnuplot), associated ``baseName.data.bz2`` (data) and output files (if applicable) in the form ``baseName.[plot number].extension``
+:param term: specify the gnuplot terminal; defaults to ``x11``, in which case gnuplot will draw persistent windows to screen and terminate; other useful terminals are ``png``, ``cairopdf`` and so on
+:param extension: extension for ``baseName`` defaults to terminal name; fine for png for example; if you use ``cairopdf``, you should also say ``extension='pdf'`` however
+:param bool timestamp: append numeric time to the basename
+:param bool varData: whether file to plot will be declared as variable or be in-place in the plot expression
+:param comment: a user comment (may be multiline) that will be embedded in the control file
 
-Returns name fo the gnuplot file created.
+:return: name of the gnuplot file created.
 	"""
 	import time,bz2
 	if len(data.keys())==0: raise RuntimeError("No data for plotting were saved.")
@@ -178,10 +243,10 @@
 	i=0
 	for p in plots:
 		p=p.strip()
-		plots_p=[_addPointTypeSpecifier(o) for o in _tuplifyYAxis(plots[p])]
+		plots_p=[addPointTypeSpecifier(o) for o in tuplifyYAxis(plots[p])]
 		if term in ['wxt','x11']: fPlot.write("set term %s %d persist\n"%(term,i))
 		else: fPlot.write("set term %s; set output '%s.%d.%s'\n"%(term,baseNameNoPath,i,extension))
-		fPlot.write("set xlabel '%s'\n"%_xlateLabel(p))
+		fPlot.write("set xlabel '%s'\n"%xlateLabel(p))
 		fPlot.write("set grid\n")
 		fPlot.write("set datafile missing 'nan'\n")
 		if title: fPlot.write("set title '%s'\n"%title)
@@ -191,46 +256,14 @@
 				y1=False; continue
 			if y1: plots_y1.append(d)
 			else: plots_y2.append(d)
-		fPlot.write("set ylabel '%s'\n"%(','.join([_xlateLabel(_p[0]) for _p in plots_y1]))) 
+		fPlot.write("set ylabel '%s'\n"%(','.join([xlateLabel(_p[0]) for _p in plots_y1]))) 
 		if len(plots_y2)>0:
-			fPlot.write("set y2label '%s'\n"%(','.join([_xlateLabel(_p[0]) for _p in plots_y2])))
+			fPlot.write("set y2label '%s'\n"%(','.join([xlateLabel(_p[0]) for _p in plots_y2])))
 			fPlot.write("set y2tics\n")
 		ppp=[]
-		for pp in plots_y1: ppp.append(" %s using %d:%d title '← %s(%s)' with lines"%(dataFile,vars.index(p)+1,vars.index(pp[0])+1,_xlateLabel(pp[0]),_xlateLabel(p),))
-		for pp in plots_y2: ppp.append(" %s using %d:%d title '%s(%s) →' with lines axes x1y2"%(dataFile,vars.index(p)+1,vars.index(pp[0])+1,_xlateLabel(pp[0]),_xlateLabel(p),))
+		for pp in plots_y1: ppp.append(" %s using %d:%d title '← %s(%s)' with lines"%(dataFile,vars.index(p)+1,vars.index(pp[0])+1,xlateLabel(pp[0]),xlateLabel(p),))
+		for pp in plots_y2: ppp.append(" %s using %d:%d title '%s(%s) →' with lines axes x1y2"%(dataFile,vars.index(p)+1,vars.index(pp[0])+1,xlateLabel(pp[0]),xlateLabel(p),))
 		fPlot.write("plot "+",".join(ppp)+"\n")
 		i+=1
 	fPlot.close()
 	return baseName+'.gnuplot'
-
-
-	
-import random
-if __name__ == "__main__":
-	for i in range(10):
-		addData({'a':random.random(),'b':random.random(),'t':i*.001,'i':i})
-	print data
-	for i in range(15):
-		addData({'a':random.random(),'c':random.random(),'d':random.random(),'one':1,'t':(i+10)*.001,'i':i+10})
-	print data
-	# all lists must have the same length
-	l=set([len(data[n]) for n in data])
-	print l
-	assert(len(l)==1)
-	plots={'t':('a',('b','g^'),'d'),'i':('a',('one','g^'))}
-	fullPlot()
-	print "PLOT DONE!"
-	fullPlot()
-	plots['t']=('a',('b','r^','d'))
-	print "FULL PLOT DONE!"
-	for i in range(20):
-		addData({'d':.1,'a':.5,'c':.6,'c':random.random(),'t':(i+25)*0.001,'i':i+25})
-	updatePlot()
-	print "UPDATED!"
-	print data['d']
-	import time
-	#time.sleep(60)
-	killPlots()
-	#pylab.clf()
-
-

=== modified file 'py/yadeWrapper/yadeWrapper.cpp'
--- py/yadeWrapper/yadeWrapper.cpp	2010-06-13 21:25:46 +0000
+++ py/yadeWrapper/yadeWrapper.cpp	2010-07-06 11:31:29 +0000
@@ -9,7 +9,10 @@
 
 #include<boost/python.hpp>
 #include<boost/python/raw_function.hpp>
-#include<boost/python/suite/indexing/vector_indexing_suite.hpp>
+// unused now
+#if 0
+	#include<boost/python/suite/indexing/vector_indexing_suite.hpp>
+#endif
 #include<boost/bind.hpp>
 #include<boost/lambda/bind.hpp>
 #include<boost/thread/thread.hpp>


Follow ups