← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-dev/yade/trunk] Rev 2710: 1. Add plot.autoAddData with docs. A limitation is that "=" should not be in names of data column...

 

------------------------------------------------------------
revno: 2710
committer: Václav Šmilauer <eu@xxxxxxxx>
branch nick: yade
timestamp: Mon 2011-02-07 10:00:07 +0100
message:
  1. Add plot.autoAddData with docs. A limitation is that "=" should not be in names of data columns anymore.
  2. Add Strong/weak fabric distinction to Gl1_NormPhys (not tested)
  3. Degrade VelocityBin's initialization message to debug
  4. Add .items() method to EnergyTracker, for fast conversion to list of tuples.
modified:
  core/EnergyTracker.hpp
  doc/sphinx/conf.py
  pkg/common/Gl1_NormPhys.cpp
  pkg/common/Gl1_NormPhys.hpp
  pkg/common/VelocityBins.cpp
  pkg/dem/Shop.cpp
  py/plot.py


--
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 'core/EnergyTracker.hpp'
--- core/EnergyTracker.hpp	2010-11-12 09:48:58 +0000
+++ core/EnergyTracker.hpp	2011-02-07 09:00:07 +0000
@@ -46,6 +46,7 @@
 	Real total(){ Real ret=0; size_t sz=energies.size(); for(size_t id=0; id<sz; id++) ret+=energies.get(id); return ret; }
 
 	py::list keys_py(){ py::list ret; FOREACH(pairStringInt p, names) ret.append(p.first); return ret; }
+	py::list items_py(){ py::list ret; FOREACH(pairStringInt p, names) ret.append(py::make_tuple(p.first,energies.get(p.second))); return ret; }
 	void resetResettables(){ size_t sz=energies.size(); for(size_t id=0; id<sz; id++){ if(resetStep[id]) energies.reset(id); } }
 
 	typedef std::map<std::string,int> mapStringInt;
@@ -61,6 +62,7 @@
 			.def("__setitem__",&EnergyTracker::setItem_py,"Set energy value for given name (will create a non-resettable item, if it does not exist yet).")
 			.def("clear",&EnergyTracker::clear,"Clear all stored values.")
 			.def("keys",&EnergyTracker::keys_py,"Return defined energies.")
+			.def("items",&EnergyTracker::items_py,"Return contents as list of (name,value) tuples.")
 			.def("total",&EnergyTracker::total,"Return sum of all energies.")
 	)
 };

=== modified file 'doc/sphinx/conf.py'
--- doc/sphinx/conf.py	2011-01-20 16:35:46 +0000
+++ doc/sphinx/conf.py	2011-02-07 09:00:07 +0000
@@ -382,10 +382,13 @@
 	 'sphinx.ext.graphviz',
 	 'sphinx.ext.viewcode',
 	 'sphinx.ext.inheritance_diagram',
+	 'matplotlib.sphinxext.plot_directive',
+	 'matplotlib.sphinxext.only_directives',
 	#'matplotlib.sphinxext.mathmpl',
     'ipython_directive',
     'ipython_console_highlighting',
-	 'youtube'
+	 'youtube',
+	 'sphinx.ext.todo',
     ]
 
 # the sidebar extension

=== modified file 'pkg/common/Gl1_NormPhys.cpp'
--- pkg/common/Gl1_NormPhys.cpp	2010-11-19 12:30:08 +0000
+++ pkg/common/Gl1_NormPhys.cpp	2011-02-07 09:00:07 +0000
@@ -16,6 +16,10 @@
 int Gl1_NormPhys::slices;
 int Gl1_NormPhys::stacks;
 
+Real Gl1_NormPhys::maxWeakFn;
+int Gl1_NormPhys::weakFilter;
+Real Gl1_NormPhys::weakScale;
+
 void Gl1_NormPhys::go(const shared_ptr<IPhys>& ip, const shared_ptr<Interaction>& i, const shared_ptr<Body>& b1, const shared_ptr<Body>& b2, bool wireFrame){
 	if(!gluQuadric){ gluQuadric=gluNewQuadric(); if(!gluQuadric) throw runtime_error("Gl1_NormPhys::go unable to allocate new GLUquadric object (out of memory?)."); }
 	NormPhys* np=static_cast<NormPhys*>(ip.get());
@@ -25,7 +29,18 @@
 	Real fnNorm=np->normalForce.dot(geom->normal);
 	if((signFilter>0 && fnNorm<0) || (signFilter<0 && fnNorm>0)) return;
 	int fnSign=fnNorm>0?1:-1;
-	fnNorm=abs(fnNorm); 
+	fnNorm=abs(fnNorm);
+	Real radiusScale=1.;
+	// weak/strong fabric, only used if maxWeakFn is set
+	if(!isnan(maxWeakFn)){
+		if(fnNorm*fnSign<maxWeakFn){ // weak fabric
+			if(weakFilter>0) return;
+			radiusScale=weakScale;
+		} else { // strong fabric
+			if(weakFilter<0) return;
+		}
+	}
+
 	maxFn=max(fnNorm,maxFn);
 	Real realMaxRadius;
 	if(maxRadius<0){
@@ -34,7 +49,7 @@
 		realMaxRadius=refRadius;
 	}
 	else realMaxRadius=maxRadius;
-	Real radius=realMaxRadius*(fnNorm/maxFn); // use logarithmic scale here?
+	Real radius=radiusScale*realMaxRadius*(fnNorm/maxFn); // use logarithmic scale here?
 	Vector3r color=Shop::scalarOnColorScale(fnNorm*fnSign,-maxFn,maxFn);
 	# if 0
 		// get endpoints from body positions
@@ -52,7 +67,6 @@
 		// get endpoints from geom
 		// max(r,0) handles r<0 which is the case for "radius" of the facet in Dem3DofGeom_FacetSphere
 		Vector3r cp=scene->isPeriodic? scene->cell->wrapShearedPt(geom->contactPoint) : geom->contactPoint;
-		//if(i->getId1()==0) cerr<<(scene->isPeriodic?"p":".");
 		Vector3r p1=cp-max(geom->refR1,0.)*geom->normal;
 		Vector3r relPos=/*p2*/(cp+max(geom->refR2,0.)*geom->normal)-p1;
 		Real dist=max(geom->refR1,0.)+max(geom->refR2,0.);

=== modified file 'pkg/common/Gl1_NormPhys.hpp'
--- pkg/common/Gl1_NormPhys.hpp	2011-01-10 08:08:23 +0000
+++ pkg/common/Gl1_NormPhys.hpp	2011-02-07 09:00:07 +0000
@@ -18,6 +18,10 @@
 		((Real,maxRadius,-1,,"Cylinder radius corresponding to the maximum normal force. If negative, auto-updated :yref:`refRadius<Gl1_NormPhys.refRadius>` will be used instead."))
 		((int,slices,6,,"Number of sphere slices; (see `glutCylinder reference <http://www.opengl.org/sdk/docs/man/xhtml/gluCylinder.xml>`__)"))
 		((int,stacks,1,,"Number of sphere stacks; (see `glutCylinder reference <http://www.opengl.org/sdk/docs/man/xhtml/gluCylinder.xml>`__)"))
+		// strong/weak fabric attributes
+		((Real,maxWeakFn,NaN,,"Value that divides contacts by their normal force into the ``weak fabric'' and ``strong fabric''. This value is set as side-effect by :yref:`utils.fabricTensor`."))
+		((int,weakFilter,0,,"If non-zero, only display contacts belonging to the ``weak'' (-1) or ``strong'' (+1) fabric."))
+		((Real,weakScale,1.,,"If :yref:`maxWeakFn<Gl1_NormPhys.maxWeakFn>` is set, scale radius of the weak fabric by this amount (usually smaller than 1). If zero, 1 pixel line is displayed. Colors are not affected by this value."))
 	);
 	RENDERS(NormPhys);
 };

=== modified file 'pkg/common/VelocityBins.cpp'
--- pkg/common/VelocityBins.cpp	2010-10-13 16:23:08 +0000
+++ pkg/common/VelocityBins.cpp	2011-02-07 09:00:07 +0000
@@ -30,7 +30,7 @@
 		if(binOverlap>=1 || binOverlap<=0){ LOG_ERROR("binOverlap set to 0.8 (was "<<binOverlap<<", not in range (0…1) )"); binOverlap=0.8;}
 		// number of bins changed
 		if(nBins!=bins.size()){
-			LOG_INFO("New number of bins: "<<nBins);
+			LOG_DEBUG("New number of bins: "<<nBins);
 			bins.resize(nBins);	
 		}
 		// number of bodies changed

=== modified file 'pkg/dem/Shop.cpp'
--- pkg/dem/Shop.cpp	2011-02-04 18:00:01 +0000
+++ pkg/dem/Shop.cpp	2011-02-07 09:00:07 +0000
@@ -42,6 +42,10 @@
 
 #include<yade/pkg/dem/Tetra.hpp>
 
+#ifdef YADE_OPENGL
+	#include<yade/pkg/common/Gl1_NormPhys.hpp>
+#endif
+
 #include<boost/foreach.hpp>
 #ifndef FOREACH
 	#define FOREACH BOOST_FOREACH
@@ -528,6 +532,7 @@
 }
 
 /* Return the fabric tensor as according to [Satake1982]. */
+/* as side-effect, set Gl1_NormShear::strongWeakThresholdForce */
 py::tuple Shop::fabricTensor(bool splitTensor){
 	Scene* scene=Omega::instance().getScene().get();
 	if (!scene->isPeriodic){ throw runtime_error("Can't compute fabric tensor of periodic cell in aperiodic simulation."); }
@@ -557,6 +562,10 @@
 		Fmean+=f;
 	}
 	Fmean/=count; 
+
+	#ifdef YADE_OPENGL
+		Gl1_NormPhys::maxWeakFn=Fmean;
+	#endif
 	
 	// evaluate two different parts of the fabric tensor 
 	// make distinction between strong and weak network of contact forces

=== modified file 'py/plot.py'
--- py/plot.py	2011-01-31 19:09:24 +0000
+++ py/plot.py	2011-02-07 09:00:07 +0000
@@ -1,12 +1,12 @@
 # encoding: utf-8
 # 2008 © Václav Šmilauer <eudoxos@xxxxxxxx> 
 """
-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.
+Module containing utility functions for plotting inside yade. See :ysrc:`examples/simple-scene/simple-scene-plot.py` or :ysrc:`examples/concrete/uniax.py` for example of usage.
 
 """
 
 ## all exported names
-__all__=['data','plots','labels','live','liveInterval','autozoom','plot','reset','resetData','splitData','reverseData','addData','saveGnuplot','saveDataTxt','savePlotSequence']
+__all__=['data','plots','labels','live','liveInterval','autozoom','plot','reset','resetData','splitData','reverseData','addData','addAutoData','saveGnuplot','saveDataTxt','savePlotSequence']
 
 # multi-threaded support for Tk
 # safe to import even if Tk will not be used
@@ -105,6 +105,93 @@
 		if d in data.keys(): continue
 		data[d]=[nan for i in range(numSamples)]
 
+def addAutoData():
+	"""Add data by evaluating contents of :yref:`yade.plot.plots`. Expressions rasing exceptions will be handled gracefully, but warning is printed for each.
+	
+	>>> from yade import plot
+	>>> from pprint import pprint
+	>>> O.reset()
+	>>> plot.resetData()
+	>>> plot.plots={'O.iter':('O.time',None,'numParticles=len(O.bodies)')}
+	>>> plot.addAutoData()
+	>>> pprint(plot.data)
+	{'O.iter': [0], 'O.time': [0.0], 'numParticles': [0]}
+
+	Note that each item in :yref:`yade.plot.plots` can be
+
+	* an expression to be evaluated (using the ``eval`` builtin);
+	* ``name=expression`` string, where ``name`` will appear as label in plots, and expression will be evaluated each time;
+	* a dictionary-like object -- current keys are labels of plots and current values are added to :yref:`yade.plot.data`. The contents of the dictionary can change over time, in which case new lines will be created as necessary.
+
+	A simple simulation with plot can be written in the following way; note how the energy plot is specified.
+
+	>>> from yade import plot, utils
+	>>> plot.plots={'i=O.iter':(O.energy,None,'total energy=O.energy.total()')}
+	>>> # we create a simple simulation with one ball falling down
+	>>> plot.resetData()
+	>>> O.bodies.append(utils.sphere((0,0,0),1))
+	0
+	>>> O.dt=utils.PWaveTimeStep()
+	>>> O.engines=[
+	...    ForceResetter(),
+	...    GravityEngine(gravity=(0,0,-10)),
+	...    NewtonIntegrator(damping=.4,kinSplit=True),
+	...    # get data required by plots at every step
+	...    PyRunner(command='yade.plot.addAutoData()',iterPeriod=1,initRun=True)
+	... ]
+	>>> O.trackEnergy=True
+	>>> O.run(2,True)
+	>>> pprint(plot.data)   #doctest: +ELLIPSIS
+	{'gravWork': [0.0, -25.13274...],
+	 'i': [0, 1],
+	 'kinRot': [0.0, 0.0],
+	 'kinTrans': [0.0, 7.5398...],
+	 'nonviscDamp': [0.0, 10.0530...],
+	 'total energy': [0.0, -7.5398...]}
+
+	.. plot::
+	
+		from yade import *
+		from yade import plot,utils
+		O.reset()
+		O.engines=[ForceResetter(),GravityEngine(gravity=(0,0,-10)),NewtonIntegrator(damping=.4,kinSplit=True),PyRunner(command='yade.plot.addAutoData()',iterPeriod=1,initRun=True)]
+		O.bodies.append(utils.sphere((0,0,0),1)); O.dt=utils.PWaveTimeStep()
+		plot.resetData()
+		plot.plots={'i=O.iter':(O.energy,None,'total energy=O.energy.total()')}
+		O.trackEnergy=True
+		O.run(50,True)
+		import pylab; pylab.grid(True)
+		plot.legendLoc=('lower left','upper right')
+		plot.plot(noShow=True)
+
+
+	"""
+	def colDictUpdate(col,dic):
+		'update *dic* with the value from col, which is a "expr" or "name=expr" string; all exceptions from  ``eval`` are caught and warning is printed without adding any data.'
+		name,expr=col.split('=',1) if '=' in col else (col,col)
+		try:
+			val=eval(expr)
+			dic.update({name:val})
+		except:
+			print 'WARN: ignoring exception raised while evaluating auto-column `'+expr+"'%s."%('' if name==expr else ' ('+name+')')
+	cols={}
+	for p in plots:
+		pp=plots[p]
+		colDictUpdate(p.strip(),cols)
+		for y in tuplifyYAxis(plots[p]):
+			# imgplot specifier
+			if y==None: continue
+			yy=addPointTypeSpecifier(y,noSplit=True)[0]
+			# dict-like object
+			if hasattr(yy,'keys'): cols.update(dict(yy))
+			# callable returning list sequence of expressions to evaluate
+			#elif callable(yy):
+			#	for yyy in yy(): colDictUpdate(yyy,cols)
+			# plain value
+			else: colDictUpdate(yy,cols)
+	addData(cols)
+
+
 def addData(*d_in,**kw):
 	"""Add data from arguments name1=value1,name2=value2 to yade.plot.data.
 	(the old {'name1':value1,'name2':value2} is deprecated, but still supported)
@@ -193,10 +280,12 @@
 
 
 # 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 addPointTypeSpecifier(o,noSplit=False):
+	"""Add point type specifier to simple variable name; optionally take only the part before '=' from the first item."""
+	if type(o) in [tuple,list]:
+		if noSplit or not type(o[0])==str: return o
+		else: return list(o[0].split('=',1)[0],o[1:])
+	else: return (o if (noSplit or not type(o)==str) else (o.split('=',1)[0]),'')
 def tuplifyYAxis(pp):
 	"""convert one variable to a 1-tuple"""
 	if type(pp) in [tuple,list]: return pp
@@ -250,7 +339,7 @@
 		subCols=int(round(math.sqrt(len(plots)))); subRows=int(math.ceil(len(plots)*1./subCols))
 		if wider: subRows,subCols=subCols,subRows
 	for nPlot,p in enumerate(plots.keys()):
-		pStrip=p.strip()
+		pStrip=p.strip().split('=',1)[0]
 		if not subPlots: pylab.figure()
 		else: pylab.subplot(subRows,subCols,nPlot)
 		if plots[p]==None: # image plot
@@ -258,7 +347,6 @@
 			# fake (empty) image if no data yet
 			if len(imgData[pStrip])==0 or imgData[pStrip][-1]==None: img=Image.new('RGBA',(1,1),(0,0,0,0))
 			else: img=Image.open(imgData[pStrip][-1])
-
 			img=pylab.imshow(img,origin='lower')
 			currLineRefs.append(LineRef(img,None,imgData[pStrip],None,pStrip))
 			pylab.gca().set_axis_off()
@@ -268,11 +356,11 @@
 		missing=set() # missing data columns
 		if pStrip not in data.keys(): missing.add(pStrip)
 		for d in plots_p:
-			if d[0]=='|||' or d[0]==None:
+			if d[0]==None:
 				y1=False; continue
 			if y1: plots_p_y1.append(d)
 			else: plots_p_y2.append(d)
-			if d[0] not in data.keys() and not callable(d[0]): missing.add(d[0])
+			if d[0] not in data.keys() and not callable(d[0]) and not hasattr(d[0],'keys'): missing.add(d[0])
 		if missing:
 			if len(data.keys())==0 or len(data[data.keys()[0]])==0: # no data at all yet, do not add garbage NaNs
 				for m in missing: data[m]=[]
@@ -286,13 +374,14 @@
 			# save the original specifications; they will be smuggled into the axes object
 			# the live updated will run yNameFuncs to see if there are new lines to be added
 			# and will add them if necessary
-			yNameFuncs=set([d[0] for d in ySpecs if callable(d[0])])
+			yNameFuncs=set([d[0] for d in ySpecs if callable(d[0])]) | set([d[0].keys for d in ySpecs if hasattr(d[0],'keys')])
 			yNames=set()
 			ySpecs2=[]
 			for ys in ySpecs:
-				if not callable(ys[0]): ySpecs2.append(ys)
 				# ys[0]() must return list of strings, which are added to ySpecs2; line specifier is synthesized by tuplifyYAxis and cannot be specified by the user
-				else: ySpecs2+=[(ret,ys[1]) for ret in ys[0]()]
+				if callable(ys[0]): ySpecs2+=[(ret,ys[1]) for ret in ys[0]()]
+				elif hasattr(ys[0],'keys'): ySpecs2+=[(yy,'') for yy in ys[0].keys()]
+				else: ySpecs2.append(ys)
 			if len(ySpecs2)==0:
 				print 'yade.plot: creating fake plot, since there are no y-data yet'
 				line,=pylab.plot([nan],[nan])
@@ -321,7 +410,7 @@
 				## should be done for y2 as well, but in that case the 10^.. label goes to y1 axis (bug in matplotlib, present in versions .99--1.0.5, and possibly beyond)
 			else:
 				pylab.ylabel((', '.join([xlateLabel(_p[0]) for _p in ySpecs2])) if (p not in xylabels or len(xylabels[p])<3 or not xylabels[p][2]) else xylabels[p][2])
-			# if there are callable ySpecs, save them inside the axes object, so that the live updater can use those
+			# if there are callable/dict ySpecs, save them inside the axes object, so that the live updater can use those
 			if yNameFuncs:
 				axes.yadeYNames,axes.yadeYFuncs,axes.yadeXName,axes.yadeLabelLoc=yNames,yNameFuncs,pStrip,labelLoc # prepend yade to avoid clashes
 		createLines(pStrip,plots_p_y1,isY1=True,y2Exists=len(plots_p_y2)>0)
@@ -351,7 +440,10 @@
 		for ax in axes:
 			if not hasattr(ax,'yadeYFuncs') or not ax.yadeYFuncs: continue # not defined of empty
 			yy=set();
-			for f in ax.yadeYFuncs: yy.update(f())
+			for f in ax.yadeYFuncs:
+				if callable(f): yy.update(f())
+				elif hasattr(f,'keys'): yy.update(f.keys())
+				else: raise ValueError("Internal error: ax.yadeYFuncs items must be callables or dictrionary-like objects and nothing else.")
 			#print 'callables y names:',yy
 			news=yy-ax.yadeYNames
 			if not news: continue
@@ -491,11 +583,12 @@
 	"""Save plot data into a (optionally compressed) text file. The first line contains a comment (starting with ``#``) giving variable name for each of the columns. This format is suitable for being loaded for further processing (outside yade) with ``numpy.genfromtxt`` function, which recognizes those variable names (creating numpy array with named entries) and handles decompression transparently.
 
 	>>> from yade import plot
+	>>> from pprint import pprint
 	>>> plot.reset()
 	>>> plot.addData(a=1,b=11,c=21,d=31)  # add some data here
 	>>> plot.addData(a=2,b=12,c=22,d=32)
-	>>> plot.data=={'a':[1,2],'b':[11,12],'c':[21,22],'d':[31, 32]} # cannot check output directly, since dict ordering is random and doctest would fail
-	True
+	>>> pprint(plot.data)
+	{'a': [1, 2], 'b': [11, 12], 'c': [21, 22], 'd': [31, 32]}
 	>>> plot.saveDataTxt('/tmp/dataFile.txt.bz2',vars=('a','b','c'))
 	>>> import numpy
 	>>> d=numpy.genfromtxt('/tmp/dataFile.txt.bz2',dtype=None,names=True)
@@ -520,6 +613,7 @@
 
 
 def savePylab(baseName,timestamp=False,title=None):
+	'''This function is not finished, do not use it.'''
 	import time
 	if len(data.keys())==0: raise RuntimeError("No data for plotting were saved.")
 	if timestamp: baseName+=_mkTimestamp()
@@ -531,7 +625,7 @@
 	py.write("data=numpy.genfromtxt('%s.data.bz2',dtype=None,names=True)\n"%baseName)
 	subCols=int(round(math.sqrt(len(plots)))); subRows=int(math.ceil(len(plots)*1./subCols))
 	for nPlot,p in enumerate(plots.keys()):
-		pStrip=p.strip()
+		pStrip=p.strip().split('=',1)[0]
 		if plots[p]==None: continue # image plots, which is not exported
 		if len(plots)==1: py.write('pylab.figure()\n')
 		else: py.write('pylab.subplot(%d,%d,%d)\n'%(subRows,subCols,nPlots))
@@ -566,8 +660,8 @@
 	if not extension: extension=term
 	i=0
 	for p in plots:
-		pStrip=p.strip()
-		if plots[pStrip]==None: continue  ## this plot is image plot, which is not applicable to gnuplot
+		pStrip=p.strip().split('=',1)[0]
+		if plots[p]==None: continue  ## this plot is image plot, which is not applicable to gnuplot
 		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))
@@ -576,10 +670,16 @@
 		fPlot.write("set datafile missing 'nan'\n")
 		if title: fPlot.write("set title '%s'\n"%title)
 		y1=True; plots_y1,plots_y2=[],[]
-		# replace callable data specifiers by the rults, it that particular data exists
-		plots_p=sum([([(pp,'') for pp in p[0]() if pp in data.keys()] if callable(p[0]) else [(p[0],p[1])] ) for p in plots_p],[])
+		# replace callable/dict-like data specifiers by the results, it that particular data exists
+		plots_p2=[]
+		for pp in plots_p:
+			if callable(pp[0]): plots_p2+=[(ppp,'') for ppp in pp[0]() if ppp in data.keys()]
+			elif hasattr(pp[0],'keys'): plots_p2+=[(name,val) for name,val in pp[0].items() if name in data.keys()]
+			else: plots_p2.append((pp[0],pp[1]))
+		plots_p=plots_p2
+		#plots_p=sum([([(pp,'') for pp in p[0]() if pp in data.keys()] if callable(p[0]) else [(p[0],p[1])] ) for p in plots_p],[])
 		for d in plots_p:
-			if d[0]=='|||' or d[0]==None:
+			if d[0]==None:
 				y1=False; continue
 			if y1: plots_y1.append(d)
 			else: plots_y2.append(d)