7b80c0d
diff -r bcb9acfa66de -r effd078610a7 docs/manual-latex/build-latex.sh
7b80c0d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
70a58bb
+++ docs/manual-latex/build-latex.sh	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -0,0 +1,37 @@
7b80c0d
+#!/bin/bash
7b80c0d
+LATEXBIN=pdflatex
7b80c0d
+BIBTEXBIN=bibtex
7b80c0d
+if [ x`which $LATEXBIN` == x"" ] ; then
7b80c0d
+	echo "no $LATEXBIN" 
7b80c0d
+	exit 1
7b80c0d
+fi
7b80c0d
+
7b80c0d
+if [ x`which $BIBTEXBIN` == x"" ] ; then
7b80c0d
+	echo "no $BIBTEXBIN" 
7b80c0d
+	exit 1
7b80c0d
+fi
7b80c0d
+
7b80c0d
+MANUAL=manual.tex
7b80c0d
+
7b80c0d
+#check to see if the manual and its bib file are around
7b80c0d
+if [ ! -f $MANUAL  ] ; then
7b80c0d
+	echo "$MANUAL is missing"
7b80c0d
+	exit 1
7b80c0d
+fi
7b80c0d
+
7b80c0d
+#Remove the old manul
7b80c0d
+rm -f manual.pdf
7b80c0d
+
7b80c0d
+
7b80c0d
+
7b80c0d
+#run the multi-pass latex build. 
7b80c0d
+$LATEXBIN $MANUAL || { echo "failed latex build (pass 1 of 2)"; exit 1 ; }
7b80c0d
+$BIBTEXBIN `basename -s .tex $MANUAL` || { echo "failed bibtex build"; exit 1 ; }
7b80c0d
+$LATEXBIN $MANUAL || { echo "failed latex build (pass 2 of 2)"; exit 1 ; }
7b80c0d
+
7b80c0d
+
7b80c0d
+if [ ! -f manual.pdf ] ; then
7b80c0d
+	echo "latex ran, but somehow we did not output the PDF...."
7b80c0d
+	exit 1
7b80c0d
+fi
7b80c0d
+
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filter.cpp
70a58bb
--- src/backend/filter.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filter.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -553,7 +553,7 @@
7b80c0d
 		ASSERT(scatterIntensity.empty());
7b80c0d
 
7b80c0d
 
7b80c0d
-	ASSERT(plotType < PLOT_TYPE_ENUM_END);
7b80c0d
+	ASSERT(plotStyle < PLOT_TYPE_ENUM_END);
7b80c0d
 }
7b80c0d
 void RangeStreamData::checkSelfConsistent() const
7b80c0d
 {
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filter.h
70a58bb
--- src/backend/filter.h	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filter.h	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -395,7 +395,7 @@
7b80c0d
 		//Label for X, Y axes
7b80c0d
 		std::string xLabel,yLabel;
7b80c0d
 
7b80c0d
-		unsigned int plotType;
7b80c0d
+		unsigned int plotStyle;
7b80c0d
 
7b80c0d
 		//!Structured XY data pairs for plotting curve
7b80c0d
 		Array2D<float> xyData;
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/algorithms/rdf.cpp
70a58bb
--- src/backend/filters/algorithms/rdf.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/algorithms/rdf.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -862,6 +862,8 @@
7b80c0d
 		return RDF_ABORT_FAIL;
7b80c0d
 #endif
7b80c0d
 
7b80c0d
+	*progressPtr=100;
7b80c0d
+
7b80c0d
 	return 0;
7b80c0d
 }
7b80c0d
 
7b80c0d
@@ -1003,6 +1005,8 @@
7b80c0d
 #endif
7b80c0d
 
7b80c0d
 	//Calculations complete!
7b80c0d
+	*progressPtr=100;
7b80c0d
+
7b80c0d
 	return 0;
7b80c0d
 }
7b80c0d
 
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/annotation.cpp
70a58bb
--- src/backend/filters/annotation.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/annotation.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -167,8 +167,15 @@
7b80c0d
 
7b80c0d
 	//If we are not enabled, do not draw anything into the output
7b80c0d
 	if(!active)
7b80c0d
+	{
7b80c0d
+		progress.filterProgress=100;
7b80c0d
 		return 0;
7b80c0d
+	}
7b80c0d
 
7b80c0d
+	progress.step=1;
7b80c0d
+	progress.maxStep=1;
7b80c0d
+	progress.stepName=TRANS("Draw");
7b80c0d
+	
7b80c0d
 	DrawStreamData *d; 
7b80c0d
 	d = new DrawStreamData;
7b80c0d
 	d->parent=this;
7b80c0d
@@ -505,6 +512,7 @@
7b80c0d
 	d->cached=0;
7b80c0d
 	getOut.push_back(d);
7b80c0d
 
7b80c0d
+	progress.filterProgress=100;
7b80c0d
 	return 0;
7b80c0d
 }
7b80c0d
 
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/boundingBox.cpp
70a58bb
--- src/backend/filters/boundingBox.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/boundingBox.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -510,7 +510,6 @@
7b80c0d
 				}
7b80c0d
 #endif
7b80c0d
 				bTotal.expand(bThis);
7b80c0d
-				progress.filterProgress=100;
7b80c0d
 				break;
7b80c0d
 			}
7b80c0d
 			default:
7b80c0d
@@ -521,6 +520,7 @@
7b80c0d
 		getOut.push_back(dataIn[ui]);	
7b80c0d
 	}
7b80c0d
 
7b80c0d
+	progress.filterProgress=100;
7b80c0d
 	//Append the bounding box if it is valid
7b80c0d
 	if(bTotal.isValid())
7b80c0d
 	{
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/clusterAnalysis.cpp
70a58bb
--- src/backend/filters/clusterAnalysis.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/clusterAnalysis.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -685,7 +685,7 @@
7b80c0d
 		Plot2DStreamData *p = new Plot2DStreamData;
7b80c0d
 		p->parent=this;
7b80c0d
 
7b80c0d
-		p->plotType=PLOT_2D_SCATTER;
7b80c0d
+		p->plotStyle=PLOT_2D_SCATTER;
7b80c0d
 		p->dataLabel=TRANS("Morphology Plot");
7b80c0d
 		p->xLabel=TRANS("\\lambda_1:\\lambda_2 ratio");
7b80c0d
 		p->yLabel=TRANS("\\lambda_2:\\lambda_3 ratio");
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/ionColour.cpp
70a58bb
--- src/backend/filters/ionColour.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/ionColour.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -271,12 +271,14 @@
7b80c0d
 	
7b80c0d
 
7b80c0d
 	p.name=TRANS("Show Bar");
7b80c0d
+	p.helpText=TRANS("Display the colour legend in the 3D view");
7b80c0d
 	p.key=KEY_IONCOLOURFILTER_SHOWBAR;
7b80c0d
 	p.data=boolStrEnc(showColourBar);
7b80c0d
 	p.type=PROPERTY_TYPE_BOOL;
7b80c0d
 	propertyList.addProperty(p,curGroup);
7b80c0d
 	
7b80c0d
 	p.name=TRANS("Opacity");
7b80c0d
+	p.helpText=TRANS("How see-through to make the legend (0- transparent, 1- solid)");
7b80c0d
 	p.key=KEY_IONCOLOURFILTER_ALPHA;
7b80c0d
 	stream_cast(p.data,alpha);
7b80c0d
 	p.type=PROPERTY_TYPE_REAL;
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/ionDownsample.cpp
70a58bb
--- src/backend/filters/ionDownsample.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/ionDownsample.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -88,7 +88,7 @@
7b80c0d
 			rsdIncoming = new RangeStreamData;
7b80c0d
 			*rsdIncoming=*c;
7b80c0d
 
7b80c0d
-			if(ionFractions.size() != c->rangeFile->getNumIons())
7b80c0d
+			if(ionFractions.size() != c->rangeFile->getNumIons()+1)
7b80c0d
 			{
7b80c0d
 				//set up some defaults; seeded from normal
7b80c0d
 				ionFractions.resize(c->rangeFile->getNumIons()+1,fraction);
7b80c0d
@@ -464,7 +464,8 @@
7b80c0d
 
7b80c0d
 	propertyList.setGroupTitle(curGroup,TRANS("Mode"));
7b80c0d
 	curGroup++;
7b80c0d
-	if(rsdIncoming && perSpecies)
7b80c0d
+
7b80c0d
+	if(rsdIncoming && perSpecies && rsdIncoming->enabledIons.size())
7b80c0d
 	{
7b80c0d
 		unsigned int typeVal;
7b80c0d
 		if(fixedNumOut)
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/ionInfo.cpp
70a58bb
--- src/backend/filters/ionInfo.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/ionInfo.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -398,7 +398,7 @@
7b80c0d
 
7b80c0d
 
7b80c0d
 	//"Pairwise events" - where we perform an action if both 
7b80c0d
-	//These
7b80c0d
+	//these are set
7b80c0d
 	if(wantIonCounts && wantVolume)
7b80c0d
 	{
7b80c0d
 		if(computedVol > sqrtf(std::numeric_limits<float>::epsilon()))
7b80c0d
@@ -420,6 +420,9 @@
7b80c0d
 		}
7b80c0d
 	}
7b80c0d
 
7b80c0d
+
7b80c0d
+	progress.filterProgress=100;
7b80c0d
+	
7b80c0d
 	return 0;
7b80c0d
 }
7b80c0d
 
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/spatialAnalysis.cpp
70a58bb
--- src/backend/filters/spatialAnalysis.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/spatialAnalysis.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -655,115 +655,180 @@
7b80c0d
 		return ERR_FILE_READ_FAIL;
7b80c0d
 
7b80c0d
 
7b80c0d
-
7b80c0d
-	progress.step=3;
7b80c0d
-	progress.stepName=TRANS("Build");
7b80c0d
-	progress.filterProgress=0;
7b80c0d
-
7b80c0d
-	//Build the search tree we will use to perform replacement
7b80c0d
-	K3DTreeMk2 tree;
7b80c0d
-	tree.resetPts(fileIons,false);
7b80c0d
-	if(!tree.build())
7b80c0d
-		return ERR_ABORT_FAIL;
7b80c0d
-	BoundCube b;
7b80c0d
-	tree.getBoundCube(b);
7b80c0d
-
7b80c0d
-	//map the offset of the nearest to
7b80c0d
-	//the tree ID 
7b80c0d
-	vector<size_t > nearestVec;
7b80c0d
-	nearestVec.resize(inIons.size());
7b80c0d
-
7b80c0d
-	//TODO: pair vector might be faster
7b80c0d
-	// as we can use it in sequence, and can use openmp
7b80c0d
-	map<size_t,size_t> matchedMap;
7b80c0d
-
7b80c0d
-	//Find the nearest point for all points in the dataset
7b80c0d
-
7b80c0d
-	#pragma omp parallel for 
7b80c0d
-	for(size_t ui=0;ui
7b80c0d
+	vector<IonHit> outIons;
7b80c0d
+	if(inIons.empty() || fileIons.empty())
7b80c0d
 	{
7b80c0d
-		nearestVec[ui]=tree.findNearestUntagged(inIons[ui].getPos(),b,false);
7b80c0d
-	}
7b80c0d
-
7b80c0d
-	float sqrReplaceTol=replaceTolerance*replaceTolerance;
7b80c0d
-
7b80c0d
-	//Filter this to only points that had an NN within range
7b80c0d
-	#pragma omp parallel for 
7b80c0d
-	for(size_t ui=0;ui
7b80c0d
-	{
7b80c0d
-		if(nearestVec[ui]!=(size_t)-1 && inIons[ui].getPos().sqrDist(*tree.getPt(nearestVec[ui])) <=sqrReplaceTol)
7b80c0d
+		//Performance increase if we have an empty item.
7b80c0d
+		// - in this case we can swap sets around
7b80c0d
+		switch(replaceMode)
7b80c0d
 		{
7b80c0d
-			#pragma omp critical
7b80c0d
-			matchedMap[ui]=tree.getOrigIndex(nearestVec[ui]);
7b80c0d
+			case REPLACE_MODE_UNION:
7b80c0d
+			{
7b80c0d
+				//If the local data is empty, then the union is just the "b" data (swap).
7b80c0d
+				// if nonempty, then it is simply the "a" data 
7b80c0d
+				if(inIons.empty())
7b80c0d
+					outIons.swap(fileIons);
7b80c0d
+				else
7b80c0d
+					outIons.swap(inIons);
7b80c0d
+				break;
7b80c0d
+			}
7b80c0d
+			case REPLACE_MODE_SUBTRACT:
7b80c0d
+			{
7b80c0d
+				//if either localdata OR bdata is empty, then we don't need to do anything.
7b80c0d
+				// either way, input stays as it is
7b80c0d
+				outIons.swap(inIons);
7b80c0d
+				break;
7b80c0d
+			}
7b80c0d
+			case REPLACE_MODE_INTERSECT:
7b80c0d
+			{
7b80c0d
+				//intersection with empty set is empty set.
7b80c0d
+				// might as well clear the ions incoming
7b80c0d
+				inIons.clear();
7b80c0d
+				break;
7b80c0d
+			}
7b80c0d
+			default:
7b80c0d
+				ASSERT(false);
7b80c0d
+
7b80c0d
 		}
7b80c0d
 	}
7b80c0d
-
7b80c0d
-	nearestVec.clear();
7b80c0d
-
7b80c0d
-
7b80c0d
-	progress.step=4;
7b80c0d
-	progress.stepName=TRANS("Compute");
7b80c0d
-	progress.filterProgress=0;
7b80c0d
-
7b80c0d
-	//Finish if no matches
7b80c0d
-	if(matchedMap.empty())
7b80c0d
+	else
7b80c0d
 	{
7b80c0d
-		progress.filterProgress=100;
7b80c0d
-		return 0;
7b80c0d
-	}
7b80c0d
-
7b80c0d
-	vector<IonHit> outIons;
7b80c0d
-	switch(replaceMode)
7b80c0d
-	{
7b80c0d
-		case REPLACE_MODE_SUBTRACT:
7b80c0d
+
7b80c0d
+		progress.step=3;
7b80c0d
+		progress.stepName=TRANS("Build");
7b80c0d
+		progress.filterProgress=0;
7b80c0d
+
7b80c0d
+		//TODO: Possible speed increase by finding the smaller of
7b80c0d
+		// the two inputs, and using that to build the tree
7b80c0d
+
7b80c0d
+		//Build the search tree we will use to perform replacement
7b80c0d
+		K3DTreeMk2 tree;
7b80c0d
+		tree.resetPts(fileIons,false);
7b80c0d
+		if(!tree.build())
7b80c0d
+			return ERR_ABORT_FAIL;
7b80c0d
+		BoundCube b;
7b80c0d
+		tree.getBoundCube(b);
7b80c0d
+
7b80c0d
+		//map the offset of the nearest to
7b80c0d
+		//the tree ID 
7b80c0d
+		vector<size_t > nearestVec;
7b80c0d
+		nearestVec.resize(inIons.size());
7b80c0d
+
7b80c0d
+		//TODO: pair vector might be faster
7b80c0d
+		// as we can use it in sequence, and can use openmp
7b80c0d
+		map<size_t,size_t> matchedMap;
7b80c0d
+
7b80c0d
+		//Find the nearest point for all points in the dataset
7b80c0d
+		// maps the ith ion in "inions" to the tree value
7b80c0d
+		#pragma omp parallel for 
7b80c0d
+		for(size_t ui=0;ui
7b80c0d
 		{
7b80c0d
-			//In subtraction mode, we should have
7b80c0d
-			// at least this many ions
7b80c0d
-			if(inIons.size() > matchedMap.size())
7b80c0d
-				outIons.reserve(inIons.size()-matchedMap.size());
7b80c0d
-			
7b80c0d
-			//
7b80c0d
-			#pragma omp parallel for
7b80c0d
-			for(unsigned int ui=0;ui
7b80c0d
+			nearestVec[ui]=tree.findNearestUntagged(inIons[ui].getPos(),b,false);
7b80c0d
+		}
7b80c0d
+
7b80c0d
+		float sqrReplaceTol=replaceTolerance*replaceTolerance;
7b80c0d
+
7b80c0d
+		//Filter this to only points that had an NN within range
7b80c0d
+		#pragma omp parallel for 
7b80c0d
+		for(size_t ui=0;ui
7b80c0d
+		{
7b80c0d
+			if(nearestVec[ui]!=(size_t)-1 && inIons[ui].getPos().sqrDist(*tree.getPt(nearestVec[ui])) <=sqrReplaceTol)
7b80c0d
 			{
7b80c0d
-				map<size_t,size_t>::iterator it;
7b80c0d
-				it=matchedMap.find(ui);
7b80c0d
-				if(it != matchedMap.end())
7b80c0d
-					continue;
7b80c0d
-
7b80c0d
 				#pragma omp critical
7b80c0d
-				outIons.push_back(inIons[ui]);
7b80c0d
+				matchedMap[ui]=tree.getOrigIndex(nearestVec[ui]);
7b80c0d
 			}
7b80c0d
-			break;
7b80c0d
 		}
7b80c0d
-		case REPLACE_MODE_INTERSECT:
7b80c0d
+
7b80c0d
+		nearestVec.clear();
7b80c0d
+
7b80c0d
+
7b80c0d
+		progress.step=4;
7b80c0d
+		progress.stepName=TRANS("Compute");
7b80c0d
+		progress.filterProgress=0;
7b80c0d
+
7b80c0d
+
7b80c0d
+		//now we have a map that matches as so:
7b80c0d
+		// map ( "inIon" ID -> "fileIon" ID)
7b80c0d
+		// inIon should be our "A" in "A operator B"
7b80c0d
+		switch(replaceMode)
7b80c0d
 		{
7b80c0d
-			outIons.reserve(matchedMap.size());
7b80c0d
-
7b80c0d
-			if(replaceMass)
7b80c0d
+			case REPLACE_MODE_SUBTRACT:
7b80c0d
 			{
7b80c0d
-				for(map<size_t,size_t>::const_iterator it=matchedMap.begin();it!=matchedMap.end();++it)
7b80c0d
+				//If no matches, A-0 = A. Just return input
7b80c0d
+				if(matchedMap.empty())
7b80c0d
 				{
7b80c0d
-					outIons.push_back(fileIons[it->second]);
7b80c0d
-					ASSERT(fileIons[it->second].getPosRef().sqrDist(inIons[it->first].getPosRef()) < sqrReplaceTol);
7b80c0d
+					outIons.swap(inIons);
7b80c0d
+					break;
7b80c0d
 				}
7b80c0d
+				//In subtraction mode, we should have
7b80c0d
+				// at least this many ions
7b80c0d
+				if(inIons.size() > matchedMap.size())
7b80c0d
+					outIons.reserve(inIons.size()-matchedMap.size());
7b80c0d
+				
7b80c0d
+				//
7b80c0d
+				#pragma omp parallel for
7b80c0d
+				for(unsigned int ui=0;ui
7b80c0d
+				{
7b80c0d
+					map<size_t,size_t>::iterator it;
7b80c0d
+					it=matchedMap.find(ui);
7b80c0d
+					if(it != matchedMap.end())
7b80c0d
+						continue;
7b80c0d
+
7b80c0d
+					#pragma omp critical
7b80c0d
+					outIons.push_back(inIons[ui]);
7b80c0d
+				}
7b80c0d
+				break;
7b80c0d
 			}
7b80c0d
-			else
7b80c0d
+			case REPLACE_MODE_INTERSECT:
7b80c0d
 			{
7b80c0d
-				for(map<size_t,size_t>::const_iterator it=matchedMap.begin();it!=matchedMap.end();++it)
7b80c0d
+				//Finish if no matches
7b80c0d
+				if(matchedMap.empty())
7b80c0d
+					break;
7b80c0d
+				
7b80c0d
+				outIons.reserve(matchedMap.size());
7b80c0d
+
7b80c0d
+				if(replaceMass)
7b80c0d
 				{
7b80c0d
-					outIons.push_back(inIons[it->first]);
7b80c0d
+					for(map<size_t,size_t>::const_iterator it=matchedMap.begin();it!=matchedMap.end();++it)
7b80c0d
+					{
7b80c0d
+						outIons.push_back(fileIons[it->second]);
7b80c0d
+						ASSERT(fileIons[it->second].getPosRef().sqrDist(inIons[it->first].getPosRef()) < sqrReplaceTol);
7b80c0d
+					}
7b80c0d
 				}
7b80c0d
+				else
7b80c0d
+				{
7b80c0d
+					for(map<size_t,size_t>::const_iterator it=matchedMap.begin();it!=matchedMap.end();++it)
7b80c0d
+					{
7b80c0d
+						outIons.push_back(inIons[it->first]);
7b80c0d
+					}
7b80c0d
+				}
7b80c0d
+				break;
7b80c0d
 			}
7b80c0d
-			break;
7b80c0d
+			case REPLACE_MODE_UNION:
7b80c0d
+			{
7b80c0d
+				outIons.swap(fileIons);
7b80c0d
+				outIons.reserve(outIons.size() + fileIons.size() - matchedMap.size());
7b80c0d
+				map<size_t,size_t>::const_iterator it=matchedMap.begin();
7b80c0d
+				
7b80c0d
+
7b80c0d
+				for(unsigned int ui=0;ui
7b80c0d
+				{
7b80c0d
+					if(it !=matchedMap.end() && (it->first == ui) )
7b80c0d
+					{
7b80c0d
+						it++;
7b80c0d
+						continue;
7b80c0d
+					}
7b80c0d
+
7b80c0d
+
7b80c0d
+					outIons.push_back(inIons[ui]);
7b80c0d
+				}
7b80c0d
+
7b80c0d
+
7b80c0d
+				break;
7b80c0d
+			}
7b80c0d
+			default:
7b80c0d
+				ASSERT(false);
7b80c0d
 		}
7b80c0d
-		case REPLACE_MODE_UNION:
7b80c0d
-		{
7b80c0d
-			ASSERT(false);
7b80c0d
-			break;
7b80c0d
-		}
7b80c0d
-		default:
7b80c0d
-			ASSERT(false);
7b80c0d
 	}
7b80c0d
 
7b80c0d
 	//Only output ions if any were found
7b80c0d
@@ -2779,6 +2844,8 @@
7b80c0d
 				newD->data.resize(d->data.size());
7b80c0d
 				if(stopMode == STOP_MODE_NEIGHBOUR)
7b80c0d
 				{
7b80c0d
+					unsigned int nProg=0;
7b80c0d
+
7b80c0d
 					bool spin=false;
7b80c0d
 					#pragma omp parallel for shared(spin)
7b80c0d
 					for(size_t uj=0;uj<d->data.size();uj++)
7b80c0d
@@ -2811,14 +2878,15 @@
7b80c0d
 						}
7b80c0d
 
7b80c0d
 						res.clear();
7b80c0d
+						#pragma atomic
7b80c0d
+						nProg++;
7b80c0d
 						
7b80c0d
 						//Update progress as needed
7b80c0d
 						if(!curProg--)
7b80c0d
 						{
7b80c0d
 							#pragma omp critical 
7b80c0d
 							{
7b80c0d
-							n+=NUM_CALLBACK/(nnMax);
7b80c0d
-							progress.filterProgress= (unsigned int)(((float)n/(float)totalDataSize)*100.0f);
7b80c0d
+							progress.filterProgress= (unsigned int)(((float)nProg/(float)totalDataSize)*100.0f);
7b80c0d
 							if(*Filter::wantAbort)
7b80c0d
 								spin=true;
7b80c0d
 							curProg=NUM_CALLBACK/(nnMax);
7b80c0d
@@ -3063,9 +3131,10 @@
7b80c0d
 				IonStreamData *newD = new IonStreamData;
7b80c0d
 				newD->parent=this;
7b80c0d
 
7b80c0d
-				//Adjust this number to provide more update thanusual, because we
7b80c0d
+				//Adjust this number to provide more update than usual, because we
7b80c0d
 				//are not doing an o(1) task between updates; yes, it is a hack
7b80c0d
-				unsigned int curProg=NUM_CALLBACK/(10*nnMax);
7b80c0d
+				const unsigned int PROG_PER_PASS=NUM_CALLBACK/(10*nnMax);
7b80c0d
+				unsigned int curProg=PROG_PER_PASS;
7b80c0d
 				newD->data.reserve(d->data.size());
7b80c0d
 				if(stopMode == STOP_MODE_NEIGHBOUR)
7b80c0d
 				{
7b80c0d
@@ -3113,11 +3182,11 @@
7b80c0d
 						{
7b80c0d
 							#pragma omp critical 
7b80c0d
 							{
7b80c0d
-							n+=NUM_CALLBACK/(nnMax);
7b80c0d
+							n+=PROG_PER_PASS;
7b80c0d
 							progress.filterProgress= (unsigned int)(((float)n/(float)totalDataSize)*100.0f);
7b80c0d
 							if(*Filter::wantAbort)
7b80c0d
 								spin=true;
7b80c0d
-							curProg=NUM_CALLBACK/(nnMax);
7b80c0d
+							curProg=PROG_PER_PASS;
7b80c0d
 							}
7b80c0d
 						}
7b80c0d
 					}
7b80c0d
@@ -3263,6 +3332,8 @@
7b80c0d
 				break;
7b80c0d
 		}
7b80c0d
 	}
7b80c0d
+	progress.filterProgress=100;
7b80c0d
+
7b80c0d
 	//If we have bad points, let the user know.
7b80c0d
 	if(!badPts.empty())
7b80c0d
 	{
7b80c0d
@@ -4230,11 +4301,8 @@
7b80c0d
 				}
7b80c0d
 
7b80c0d
 				//distance between search pt and found pt
7b80c0d
-				float sqrDistance;
7b80c0d
-				sqrDistance = searchTree.getPtRef(ptIdx).sqrDist(pSource[ui].getPosRef());
7b80c0d
-
7b80c0d
-				if(sqrDistance > DISTANCE_EPSILON)
7b80c0d
-					ptsFound.insert(ptIdx);
7b80c0d
+
7b80c0d
+				ptsFound.insert(ptIdx);
7b80c0d
 			}
7b80c0d
 
7b80c0d
 
7b80c0d
@@ -4244,8 +4312,14 @@
7b80c0d
 			//Count the number of numerator and denominator ions, using the masses we set aside earlier
7b80c0d
 			for(set<size_t>::iterator it=ptsFound.begin(); it!=ptsFound.end(); ++it)
7b80c0d
 			{
7b80c0d
+
7b80c0d
+				//check that the distance is non-zero, to force no self-matching
7b80c0d
+				float sqrDistance;
7b80c0d
+				sqrDistance = searchTree.getPtRef(*it).sqrDist(pSource[ui].getPosRef());
7b80c0d
+				if(sqrDistance < DISTANCE_EPSILON)
7b80c0d
+					continue;
7b80c0d
+
7b80c0d
 				float ionMass;
7b80c0d
-				//check that the distance is non-zero, to force no self-matching
7b80c0d
 				ionMass = dataMasses[searchTree.getOrigIndex(*it)];
7b80c0d
 
7b80c0d
 				unsigned int ionID;
7b80c0d
@@ -4326,9 +4400,11 @@
7b80c0d
 bool nnHistogramTest();
7b80c0d
 bool rdfPlotTest();
7b80c0d
 bool axialDistTest();
7b80c0d
-bool replaceTest();
7b80c0d
+bool replaceIntersectAndUnionTest();
7b80c0d
 bool localConcTestRadius();
7b80c0d
 bool localConcTestNN();
7b80c0d
+bool replaceSubtractTest();
7b80c0d
+bool replaceUnionTest();
7b80c0d
 
7b80c0d
 bool SpatialAnalysisFilter::runUnitTests()
7b80c0d
 {
7b80c0d
@@ -4343,8 +4419,15 @@
7b80c0d
 
7b80c0d
 	if(!axialDistTest())
7b80c0d
 		return false;
7b80c0d
-	if(!replaceTest())
7b80c0d
+	if(!replaceIntersectAndUnionTest())
7b80c0d
 		return false;
7b80c0d
+
7b80c0d
+	if(!replaceSubtractTest())
7b80c0d
+		return false;
7b80c0d
+
7b80c0d
+	if(!replaceUnionTest())
7b80c0d
+		return false;
7b80c0d
+
7b80c0d
 	if(!localConcTestRadius())
7b80c0d
 		return false;
7b80c0d
 
7b80c0d
@@ -4617,7 +4700,7 @@
7b80c0d
 	return true;
7b80c0d
 }
7b80c0d
 
7b80c0d
-bool replaceTest()
7b80c0d
+bool replaceIntersectAndUnionTest()
7b80c0d
 {
7b80c0d
 	std::string ionFile=createTmpFilename(NULL,".pos");
7b80c0d
 		
7b80c0d
@@ -4637,6 +4720,157 @@
7b80c0d
 	//Create a spatial analysis filter
7b80c0d
 	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
7b80c0d
 	f->setCaching(false);	
7b80c0d
+	//Set it to do a union calculation 
7b80c0d
+	bool needUp;
7b80c0d
+	string s;
7b80c0d
+	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_REPLACE]);
7b80c0d
+	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set prop");
7b80c0d
+	TEST(f->setProperty(KEY_REPLACE_FILE,ionFile,needUp),"Set prop");
7b80c0d
+	s="1";
7b80c0d
+	TEST(f->setProperty(KEY_REPLACE_VALUE,s,needUp),"Set prop");
7b80c0d
+
7b80c0d
+	vector<unsigned int> opVec;
7b80c0d
+	opVec.push_back(REPLACE_MODE_INTERSECT);
7b80c0d
+	opVec.push_back(REPLACE_MODE_UNION);
7b80c0d
+	
7b80c0d
+	ProgressData p;
7b80c0d
+	vector<const FilterStreamData*> streamIn,streamOut;
7b80c0d
+	streamIn.push_back(d);
7b80c0d
+	for(unsigned int opId=0;opId
7b80c0d
+	{
7b80c0d
+		s=TRANS(REPLACE_ALGORITHMS[opVec[opId]]);
7b80c0d
+		TEST(f->setProperty(KEY_REPLACE_ALGORITHM,s,needUp),"Set prop");
7b80c0d
+
7b80c0d
+		//Do the refresh
7b80c0d
+		TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");
7b80c0d
+
7b80c0d
+		TEST(streamOut.size() == 1,"stream count");
7b80c0d
+		TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
7b80c0d
+		TEST(streamOut[0]->getNumBasicObjects() == NIONS,"Number objects");
7b80c0d
+
7b80c0d
+		//we should have taken the mass-to-charge from the file
7b80c0d
+		const IonStreamData *outIons = (const IonStreamData*)streamOut[0];
7b80c0d
+		for(unsigned int ui=0;ui
7b80c0d
+		{
7b80c0d
+			ASSERT(outIons->data[ui].getMassToCharge() == 1); 
7b80c0d
+		}
7b80c0d
+		delete streamOut[0];
7b80c0d
+		streamOut.clear();
7b80c0d
+	}
7b80c0d
+	delete f;
7b80c0d
+	delete d;
7b80c0d
+	
7b80c0d
+	wxRemoveFile(ionFile);
7b80c0d
+
7b80c0d
+	
7b80c0d
+	return true;
7b80c0d
+}
7b80c0d
+
7b80c0d
+bool replaceSubtractTest()
7b80c0d
+{
7b80c0d
+	std::string ionFile=createTmpFilename(NULL,".pos");
7b80c0d
+		
7b80c0d
+	vector<IonHit> ions;
7b80c0d
+	const unsigned int NIONS=10;
7b80c0d
+	const unsigned int DIFF_COUNT=5;	
7b80c0d
+	for(unsigned int ui=0;ui
7b80c0d
+	{
7b80c0d
+		IonHit h;
7b80c0d
+		h = IonHit(Point3D(ui,ui,ui),1);
7b80c0d
+
7b80c0d
+		//make some ions different to the (x,x,x) pattern
7b80c0d
+		if(ui < DIFF_COUNT)
7b80c0d
+		{
7b80c0d
+			h.setPos(h.getPos() - Point3D(0,0,100));
7b80c0d
+			ions.push_back(h);
7b80c0d
+		}
7b80c0d
+		else
7b80c0d
+			ions.push_back(h);
7b80c0d
+	}
7b80c0d
+
7b80c0d
+	vector<IonHit> tmpI;
7b80c0d
+	for(unsigned int ui=0;ui
7b80c0d
+	{
7b80c0d
+		if(ions[ui][2] < 0 )
7b80c0d
+		{
7b80c0d
+			tmpI.push_back(ions[ui]);
7b80c0d
+			tmpI.back().setMassToCharge(2);
7b80c0d
+		}
7b80c0d
+	}
7b80c0d
+
7b80c0d
+	IonHit::makePos(tmpI,ionFile.c_str());
7b80c0d
+	tmpI.clear();
7b80c0d
+
7b80c0d
+	IonStreamData *d = new IonStreamData;
7b80c0d
+	d->data.swap(ions);
7b80c0d
+
7b80c0d
+	//Create a spatial analysis filter
7b80c0d
+	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
7b80c0d
+	f->setCaching(false);	
7b80c0d
+	
7b80c0d
+	//Set it to do a subtraction calculation 
7b80c0d
+	bool needUp;
7b80c0d
+	string s;
7b80c0d
+	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_REPLACE]);
7b80c0d
+	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set prop");
7b80c0d
+	TEST(f->setProperty(KEY_REPLACE_FILE,ionFile,needUp),"Set prop");
7b80c0d
+	s=TRANS(REPLACE_ALGORITHMS[REPLACE_MODE_SUBTRACT]);
7b80c0d
+	TEST(f->setProperty(KEY_REPLACE_ALGORITHM,s,needUp),"Set prop");
7b80c0d
+
7b80c0d
+	//Do the refresh
7b80c0d
+	ProgressData p;
7b80c0d
+	vector<const FilterStreamData*> streamIn,streamOut;
7b80c0d
+	streamIn.push_back(d);
7b80c0d
+	TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");
7b80c0d
+	delete f;
7b80c0d
+	delete d;
7b80c0d
+	streamIn.clear();
7b80c0d
+
7b80c0d
+	TEST(streamOut.size() == 1,"stream count");
7b80c0d
+	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
7b80c0d
+	TEST(streamOut[0]->getNumBasicObjects() == DIFF_COUNT,"Number objects");
7b80c0d
+
7b80c0d
+	//we should have taken the mass-to-charge from the original data,
7b80c0d
+	// not the file
7b80c0d
+	const IonStreamData *outIons = (const IonStreamData*)streamOut[0];
7b80c0d
+	for(unsigned int ui=0;ui<outIons->getNumBasicObjects(); ui++)
7b80c0d
+	{
7b80c0d
+		ASSERT(outIons->data[ui].getMassToCharge() == 1); 
7b80c0d
+		ASSERT(outIons->data[ui].getPos()[2] >= 0); 
7b80c0d
+	}
7b80c0d
+
7b80c0d
+	wxRemoveFile(ionFile);
7b80c0d
+
7b80c0d
+	delete streamOut[0];
7b80c0d
+	
7b80c0d
+	return true;
7b80c0d
+}
7b80c0d
+
7b80c0d
+bool replaceUnionTest()
7b80c0d
+{
7b80c0d
+	std::string ionFile=createTmpFilename(NULL,".pos");
7b80c0d
+	
7b80c0d
+	//"B" dataset	
7b80c0d
+	vector<IonHit> ions;
7b80c0d
+	ions.push_back(IonHit(Point3D(0,0,0),1));
7b80c0d
+	ions.push_back(IonHit(Point3D(1,0,1),1));
7b80c0d
+	ions.push_back(IonHit(Point3D(0,1,1),1));
7b80c0d
+
7b80c0d
+	IonHit::makePos(ions,ionFile.c_str());
7b80c0d
+	ions.clear();
7b80c0d
+
7b80c0d
+	//"A" dataset	
7b80c0d
+	ions.push_back(IonHit(Point3D(0,0,0),2));
7b80c0d
+	ions.push_back(IonHit(Point3D(1,0,-1),2));
7b80c0d
+	ions.push_back(IonHit(Point3D(0,1,-1),2));
7b80c0d
+
7b80c0d
+
7b80c0d
+	IonStreamData *d = new IonStreamData;
7b80c0d
+	d->data.swap(ions);
7b80c0d
+
7b80c0d
+	//Create a spatial analysis filter
7b80c0d
+	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
7b80c0d
+	f->setCaching(false);	
7b80c0d
 	
7b80c0d
 	//Set it to do a union calculation 
7b80c0d
 	bool needUp;
7b80c0d
@@ -4644,13 +4878,11 @@
7b80c0d
 	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_REPLACE]);
7b80c0d
 	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set prop");
7b80c0d
 	TEST(f->setProperty(KEY_REPLACE_FILE,ionFile,needUp),"Set prop");
7b80c0d
-	s=TRANS(REPLACE_ALGORITHMS[REPLACE_MODE_INTERSECT]);
7b80c0d
+	s=TRANS(REPLACE_ALGORITHMS[REPLACE_MODE_UNION]);
7b80c0d
 	TEST(f->setProperty(KEY_REPLACE_ALGORITHM,s,needUp),"Set prop");
7b80c0d
-	
7b80c0d
 	s="1";
7b80c0d
 	TEST(f->setProperty(KEY_REPLACE_VALUE,s,needUp),"Set prop");
7b80c0d
 
7b80c0d
-
7b80c0d
 	//Do the refresh
7b80c0d
 	ProgressData p;
7b80c0d
 	vector<const FilterStreamData*> streamIn,streamOut;
7b80c0d
@@ -4662,14 +4894,16 @@
7b80c0d
 
7b80c0d
 	TEST(streamOut.size() == 1,"stream count");
7b80c0d
 	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
7b80c0d
-	TEST(streamOut[0]->getNumBasicObjects() == NIONS,"Number objects");
7b80c0d
-
7b80c0d
-	//we should have taken the mass-to-charge from the file
7b80c0d
+	TEST(streamOut[0]->getNumBasicObjects() == 5,"Number objects");
7b80c0d
+
7b80c0d
+	//There should be
7b80c0d
 	const IonStreamData *outIons = (const IonStreamData*)streamOut[0];
7b80c0d
-	for(unsigned int ui=0;ui
7b80c0d
+	float sumV=0;
7b80c0d
+	for(unsigned int ui=0;ui<outIons->getNumBasicObjects(); ui++)
7b80c0d
 	{
7b80c0d
-		ASSERT(outIons->data[ui].getMassToCharge() == 1); 
7b80c0d
+		sumV+=outIons->data[ui].getMassToCharge();
7b80c0d
 	}
7b80c0d
+	TEST( EQ_TOL(sumV,7.0f),"mass-to-charge check");
7b80c0d
 
7b80c0d
 	wxRemoveFile(ionFile);
7b80c0d
 
7b80c0d
@@ -4678,7 +4912,6 @@
7b80c0d
 	return true;
7b80c0d
 }
7b80c0d
 
7b80c0d
-
7b80c0d
 //--- Local concentration tests --
7b80c0d
 const IonStreamData *createLCIonStream()
7b80c0d
 {
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/spectrumPlot.cpp
70a58bb
--- src/backend/filters/spectrumPlot.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/spectrumPlot.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -517,6 +517,8 @@
7b80c0d
 	
7b80c0d
 	getOut.push_back(d);
7b80c0d
 
7b80c0d
+	progress.filterProgress=100;
7b80c0d
+
7b80c0d
 	return 0;
7b80c0d
 }
7b80c0d
 
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/filters/transform.cpp
70a58bb
--- src/backend/filters/transform.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/filters/transform.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -998,8 +998,10 @@
7b80c0d
 					break;
7b80c0d
 				}
7b80c0d
 			
7b80c0d
+			}
7b80c0d
 		}
7b80c0d
-		}
7b80c0d
+
7b80c0d
+		progress.filterProgress=100;
7b80c0d
 	}
7b80c0d
 	else
7b80c0d
 	{
7b80c0d
@@ -1526,6 +1528,11 @@
7b80c0d
 		case KEY_CROP_MINIMUM:
7b80c0d
 		{
7b80c0d
 			ASSERT(scalarParams.size() ==2);
7b80c0d
+			float tmp;
7b80c0d
+			if(stream_cast(tmp,value) || tmp >=scalarParams[1])
7b80c0d
+				return false;
7b80c0d
+
7b80c0d
+
7b80c0d
 			if(!applyPropertyNow(scalarParams[0],value,needUpdate))
7b80c0d
 				return false;
7b80c0d
 			break;
7b80c0d
@@ -1533,6 +1540,9 @@
7b80c0d
 		case KEY_CROP_MAXIMUM:
7b80c0d
 		{
7b80c0d
 			ASSERT(scalarParams.size() ==2);
7b80c0d
+			float tmp;
7b80c0d
+			if(stream_cast(tmp,value) || tmp <=scalarParams[0])
7b80c0d
+				return false;
7b80c0d
 			if(!applyPropertyNow(scalarParams[1],value,needUpdate))
7b80c0d
 				return false;
7b80c0d
 			break;
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/plot.cpp
70a58bb
--- src/backend/plot.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/plot.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -29,7 +29,7 @@
7b80c0d
 				NTRANS("Moving avg.")
7b80c0d
 				};
7b80c0d
 
7b80c0d
-const char *plotTypeStrings[]= {
7b80c0d
+const char *traceStyleStrings[]= {
7b80c0d
 	NTRANS("Lines"),
7b80c0d
 	NTRANS("Bars"),
7b80c0d
 	NTRANS("Steps"),
7b80c0d
@@ -119,15 +119,15 @@
7b80c0d
 string plotString(unsigned int plotMode)
7b80c0d
 {
7b80c0d
 	ASSERT(plotMode< PLOT_TYPE_ENUM_END);
7b80c0d
-	return TRANS(plotTypeStrings[plotMode]); 
7b80c0d
+	return TRANS(traceStyleStrings[plotMode]); 
7b80c0d
 }
7b80c0d
 
7b80c0d
 unsigned int plotID(const std::string &plotString)
7b80c0d
 {
7b80c0d
-	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(plotTypeStrings) == PLOT_TYPE_ENUM_END);
7b80c0d
+	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(traceStyleStrings) == PLOT_TYPE_ENUM_END);
7b80c0d
 	for(unsigned int ui=0;ui
7b80c0d
 	{
7b80c0d
-		if(plotString==TRANS(plotTypeStrings[ui]))
7b80c0d
+		if(plotString==TRANS(traceStyleStrings[ui]))
7b80c0d
 			return ui;
7b80c0d
 	}
7b80c0d
 
7b80c0d
@@ -298,7 +298,7 @@
7b80c0d
 
7b80c0d
 PlotWrapper::PlotWrapper()
7b80c0d
 {
7b80c0d
-	//COMPILE_ASSERT(THREEDEP_ARRAYSIZE(plotTypeStrings) == PLOT_TYPE_ENUM_END);
7b80c0d
+	//COMPILE_ASSERT(THREEDEP_ARRAYSIZE(traceStyleStrings) == PLOT_TYPE_ENUM_END);
7b80c0d
 
7b80c0d
 	applyUserBounds=false;
7b80c0d
 	plotChanged=true;
7b80c0d
@@ -684,6 +684,12 @@
7b80c0d
 	return visibleMode;
7b80c0d
 }
7b80c0d
 
7b80c0d
+unsigned int PlotWrapper::getPlotMode(unsigned int plotId) const
7b80c0d
+{
7b80c0d
+	ASSERT(plotId < plottingData.size());
7b80c0d
+	return plottingData[plotId]->getPlotMode();
7b80c0d
+}
7b80c0d
+
7b80c0d
 void PlotWrapper::getVisibleIDs(vector<unsigned int> &visiblePlotIDs ) const
7b80c0d
 {
7b80c0d
 
7b80c0d
@@ -791,7 +797,7 @@
7b80c0d
 				if(!plottingData[ui]->visible)
7b80c0d
 					continue;
7b80c0d
 
7b80c0d
-				if(plottingData[ui]->getType()!= PLOT_MODE_1D)
7b80c0d
+				if(plottingData[ui]->getMode()!= PLOT_MODE_1D)
7b80c0d
 					continue;
7b80c0d
 			
7b80c0d
 				if(((Plot1D*)plottingData[ui])->wantLogPlot()) 
7b80c0d
@@ -809,7 +815,7 @@
7b80c0d
 				float minYVal=0.1;
7b80c0d
 				for(size_t ui=0;ui
7b80c0d
 				{
7b80c0d
-					if(!plottingData[ui]->visible || plottingData[ui]->getType() !=PLOT_MODE_1D)
7b80c0d
+					if(!plottingData[ui]->visible || plottingData[ui]->getMode() !=PLOT_MODE_1D)
7b80c0d
 						continue;
7b80c0d
 
7b80c0d
 					float tmp ;
7b80c0d
@@ -929,7 +935,7 @@
7b80c0d
 				Plot2DFunc *curPlot;
7b80c0d
 				curPlot=(Plot2DFunc*)plottingData[ui];
7b80c0d
 
7b80c0d
-				if(curPlot->getType() == PLOT_2D_DENS)
7b80c0d
+				if(curPlot->getMode() == PLOT_2D_DENS)
7b80c0d
 				{
7b80c0d
 					wantColourbar=true;
7b80c0d
 				}
7b80c0d
@@ -1047,11 +1053,6 @@
7b80c0d
 	plottingData[plotIDHandler.getPos(plotId)]->regionGroup.getRegion(regionId,region);
7b80c0d
 }
7b80c0d
 
7b80c0d
-unsigned int PlotWrapper::plotType(unsigned int plotId) const
7b80c0d
-{
7b80c0d
-	return plottingData[plotIDHandler.getPos(plotId)]->getPlotMode();
7b80c0d
-}
7b80c0d
-
7b80c0d
 
7b80c0d
 void PlotWrapper::moveRegion(unsigned int plotID, unsigned int regionId, bool regionSelfUpdate,
7b80c0d
 		unsigned int movementType, float newX, float newY) const
7b80c0d
@@ -1135,7 +1136,7 @@
7b80c0d
 
7b80c0d
 void PlotBase::copyBase(PlotBase *target) const
7b80c0d
 {
7b80c0d
-	target->plotType=plotType;
7b80c0d
+	target->traceStyle=traceStyle;
7b80c0d
 	target->minX=minX;
7b80c0d
 	target->maxX=maxX;
7b80c0d
 	target->minY=minY;
7b80c0d
@@ -1157,12 +1158,12 @@
7b80c0d
 
7b80c0d
 unsigned int PlotBase::getType() const
7b80c0d
 {
7b80c0d
-	return plotType;
7b80c0d
+	return traceStyle;
7b80c0d
 }
7b80c0d
 
7b80c0d
 unsigned int PlotBase::getMode() const
7b80c0d
 {
7b80c0d
-	switch(plotType)
7b80c0d
+	switch(traceStyle)
7b80c0d
 	{
7b80c0d
 		case PLOT_LINE_LINES:
7b80c0d
 		case PLOT_LINE_BARS:
7b80c0d
@@ -1181,7 +1182,7 @@
7b80c0d
 Plot1D::Plot1D()
7b80c0d
 {
7b80c0d
 	//Set the default plot properties
7b80c0d
-	plotType=PLOT_LINE_LINES;
7b80c0d
+	traceStyle=PLOT_LINE_LINES;
7b80c0d
 	plotMode=PLOT_MODE_1D;
7b80c0d
 	xLabel="";
7b80c0d
 	yLabel="";
7b80c0d
@@ -1489,7 +1490,7 @@
7b80c0d
 
7b80c0d
 
7b80c0d
 	//Plot the appropriate form	
7b80c0d
-	switch(plotMode)
7b80c0d
+	switch(traceStyle)
7b80c0d
 	{
7b80c0d
 		case PLOT_LINE_LINES:
7b80c0d
 			//Unfortunately, when using line plots, mathgl moves the data points to the plot boundary,
7b80c0d
@@ -1632,7 +1633,7 @@
7b80c0d
 Plot2DFunc::Plot2DFunc()
7b80c0d
 {
7b80c0d
 	plotMode = PLOT_MODE_2D;
7b80c0d
-	plotType=PLOT_2D_DENS;
7b80c0d
+	traceStyle=PLOT_2D_DENS;
7b80c0d
 }
7b80c0d
 
7b80c0d
 void Plot2DFunc::setData(const Array2D<float> &a,
7b80c0d
@@ -1705,7 +1706,8 @@
7b80c0d
 
7b80c0d
 Plot2DScatter::Plot2DScatter()
7b80c0d
 {
7b80c0d
-	plotType=PLOT_2D_SCATTER;
7b80c0d
+	plotMode=PLOT_2D_SCATTER;
7b80c0d
+	traceStyle=PLOT_LINE_POINTS;
7b80c0d
 	scatterIntensityLog=false;
7b80c0d
 }
7b80c0d
 
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/plot.h
70a58bb
--- src/backend/plot.h	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/plot.h	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -217,7 +217,7 @@
7b80c0d
 class PlotBase
7b80c0d
 {
7b80c0d
 	protected:
7b80c0d
-		//!Sub type of plot (eg lines, bars for 1D)
7b80c0d
+		//!Type of plot 
7b80c0d
 		unsigned int plotMode;
7b80c0d
 		//!xaxis label
7b80c0d
 		std::string xLabel;
7b80c0d
@@ -229,8 +229,9 @@
7b80c0d
 		//plot colour (for single coloured plots)
7b80c0d
 		float r,g,b;
7b80c0d
 		
7b80c0d
-		//The type of plot (ie what class is it?)	
7b80c0d
-		unsigned int plotType;
7b80c0d
+		//The sub-style of the plot trace (eg lines, points, bars, etc)
7b80c0d
+		// FIXME: This is badly named, change to traceStyle, or dataStyle, or something
7b80c0d
+		unsigned int traceStyle;
7b80c0d
 		
7b80c0d
 		void copyBase(PlotBase *target) const;
7b80c0d
 
7b80c0d
@@ -297,8 +298,12 @@
7b80c0d
 		void setStrings(const std::string &x, 
7b80c0d
 			const std::string &y,const std::string &t);
7b80c0d
 
7b80c0d
+		//Set the colour of the plot trace
7b80c0d
 		void setColour(float rNew, float gNew, float bNew);
7b80c0d
 
7b80c0d
+		//set the visual style for the trace (dots, lines, etc)
7b80c0d
+		void setTraceStyle(unsigned int newStyle) { traceStyle=newStyle;}
7b80c0d
+
7b80c0d
 		std::string getXLabel() const { return xLabel;}
7b80c0d
 		std::string getTitle() const { return title;}
7b80c0d
 		std::string getYLabel() const { return yLabel;}
7b80c0d
@@ -309,6 +314,7 @@
7b80c0d
 		void setPlotMode(unsigned int newMode) { plotMode= newMode;}
7b80c0d
 
7b80c0d
 
7b80c0d
+		//get the  colour of the trace
7b80c0d
 		void getColour(float &r, float &g, float &b) const ;
7b80c0d
 
7b80c0d
 #ifdef DEBUG
7b80c0d
@@ -613,7 +619,7 @@
7b80c0d
 	
7b80c0d
 
7b80c0d
 		//!obtain the type of a plot, given the plot's uniqueID
7b80c0d
-		unsigned int plotType(unsigned int plotId) const;
7b80c0d
+		unsigned int getPlotMode(unsigned int plotId) const;
7b80c0d
 
7b80c0d
 		//Retrieve the types of visible plots
7b80c0d
 		unsigned int getVisibleMode() const;
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/backend/viscontrol.cpp
70a58bb
--- src/backend/viscontrol.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/backend/viscontrol.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -249,7 +249,7 @@
7b80c0d
 						plotData->yLabel,plotData->dataLabel);
7b80c0d
 					
7b80c0d
 					//set the appearance of the plot
7b80c0d
-					//plotNew->setTraceStyle(plotStyle);
7b80c0d
+					plotNew->setTraceStyle(plotData->plotStyle);
7b80c0d
 					plotNew->setColour(plotData->r,plotData->g,plotData->b);
7b80c0d
 					
7b80c0d
 					
7b80c0d
@@ -275,7 +275,7 @@
7b80c0d
 					unsigned int plotID;
7b80c0d
 		
7b80c0d
 					PlotBase *plotNew;
7b80c0d
-					switch(plotData->plotType) 
7b80c0d
+					switch(plotData->plotStyle) 
7b80c0d
 					{
7b80c0d
 						case PLOT_2D_DENS:
7b80c0d
 						{
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/common/basics.cpp
70a58bb
--- src/common/basics.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/common/basics.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -1347,7 +1347,8 @@
7b80c0d
 	while(CFile.good() && !CFile.eof() && atHeader)
7b80c0d
 	{
7b80c0d
 		//Grab a line from the file
7b80c0d
-		CFile.getline(inBuffer,BUFFER_SIZE);
7b80c0d
+		if(!CFile.getline(inBuffer,BUFFER_SIZE))
7b80c0d
+			break;
7b80c0d
 
7b80c0d
 		if(!CFile.good())
7b80c0d
 			return ERR_FILE_FORMAT;
7b80c0d
@@ -1457,10 +1458,8 @@
7b80c0d
 			
7b80c0d
 		}
7b80c0d
 		//Grab a line from the file
7b80c0d
-		CFile.getline(inBuffer,BUFFER_SIZE);
7b80c0d
-		
7b80c0d
-		if(!CFile.good() && !CFile.eof())
7b80c0d
-			return ERR_FILE_FORMAT;
7b80c0d
+		if(!CFile.getline(inBuffer,BUFFER_SIZE))
7b80c0d
+			break;
7b80c0d
 	}
7b80c0d
 
7b80c0d
 	return 0;
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/gui/mainFrame.cpp
70a58bb
--- src/gui/mainFrame.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/gui/mainFrame.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -1391,7 +1391,7 @@
7b80c0d
 	updateWxTreeCtrl(treeFilters);
7b80c0d
 
7b80c0d
 	if(!noUpdate)
7b80c0d
-		return doSceneUpdate(true);
7b80c0d
+		doSceneUpdate(true);
7b80c0d
 
7b80c0d
 	return true;
7b80c0d
 }	
7b80c0d
@@ -3834,7 +3834,7 @@
7b80c0d
 	
7b80c0d
 }
7b80c0d
 
7b80c0d
-bool MainWindowFrame::doSceneUpdate(bool ensureVisible)
7b80c0d
+void MainWindowFrame::doSceneUpdate(bool ensureVisible)
7b80c0d
 {
7b80c0d
 	//Update scene
7b80c0d
 	ASSERT(!currentlyUpdatingScene);
7b80c0d
@@ -3864,6 +3864,11 @@
7b80c0d
 	ensureResultVisible=ensureVisible;
7b80c0d
 
7b80c0d
 	ASSERT(!refreshControl);
7b80c0d
+
7b80c0d
+	//Hack to prevent crash on double-refresh
7b80c0d
+	if(refreshControl)
7b80c0d
+		return;
7b80c0d
+
7b80c0d
 	refreshControl = new RefreshController(visControl.state.treeState);
7b80c0d
 	refreshThread=new RefreshThread(this,refreshControl);
7b80c0d
 	progressTimer->Start(PROGRESS_TIMER_DELAY);
7b80c0d
@@ -3871,7 +3876,8 @@
7b80c0d
 	refreshThread->Create();
7b80c0d
 	refreshThread->Run();
7b80c0d
 
7b80c0d
-	return true;
7b80c0d
+	cerr << "Updating scene complete"<< endl;
7b80c0d
+	return;
7b80c0d
 }
7b80c0d
 
7b80c0d
 void MainWindowFrame::updateWxTreeCtrl( wxTreeCtrl *t, const Filter *f)
7b80c0d
@@ -3971,6 +3977,11 @@
7b80c0d
 	ASSERT(!visControl.state.treeState.isRefreshing());
7b80c0d
 	progressTimer->Stop();
7b80c0d
 
7b80c0d
+	//Hack to prevent crash on re-entry during refresh. Should never trigger.
7b80c0d
+	if(!refreshControl)
7b80c0d
+		return;
7b80c0d
+
7b80c0d
+
7b80c0d
 	vector<std::pair<const Filter*, std::string> > consoleMessages;
7b80c0d
 	consoleMessages=refreshControl->getConsoleMessages();
7b80c0d
 
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/gui/mainFrame.h
70a58bb
--- src/gui/mainFrame.h	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/gui/mainFrame.h	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -124,7 +124,7 @@
7b80c0d
 	//!Update the progress information in the status bar
7b80c0d
 	void updateProgressStatus();
7b80c0d
 	//!Perform an update to the 3D Scene. Returns false if refresh failed
7b80c0d
-	bool doSceneUpdate(bool ensureResultVisible=false);
7b80c0d
+	void doSceneUpdate(bool ensureResultVisible=false);
7b80c0d
 	
7b80c0d
 	//!Complete the scene update. Returns false if failed
7b80c0d
 	void finishSceneUpdate(unsigned int errCode);
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/gui/mathglPane.cpp
70a58bb
--- src/gui/mathglPane.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/gui/mathglPane.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -627,7 +627,7 @@
7b80c0d
 			thePlot->getRegion(plotId,regionId,r);
7b80c0d
 
7b80c0d
 			//TODO: Implement a more generic region handler?
7b80c0d
-			ASSERT(thePlot->plotType(plotId) == PLOT_MODE_1D);
7b80c0d
+			ASSERT(thePlot->getPlotMode(plotId) == PLOT_MODE_1D);
7b80c0d
 
7b80c0d
 			float mglStartX,mglStartY;
7b80c0d
 			toPlotCoords(draggingStart.x, draggingStart.y,mglStartX,mglStartY);
7b80c0d
@@ -1471,7 +1471,7 @@
7b80c0d
 		return;
7b80c0d
 
7b80c0d
 
7b80c0d
-	ASSERT(thePlot->plotType(startMousePlot) == PLOT_MODE_1D);
7b80c0d
+	ASSERT(thePlot->getPlotMode(startMousePlot) == PLOT_MODE_1D);
7b80c0d
 
7b80c0d
 	//See where extending the region is allowed up to.
7b80c0d
 	thePlot->findRegionLimit(startMousePlot,startMouseRegion,
7b80c0d
@@ -1550,7 +1550,7 @@
7b80c0d
 		{
7b80c0d
 			//This needs to be extended to support more
7b80c0d
 			//plot types.
7b80c0d
-			ASSERT(thePlot->plotType(startMousePlot) == PLOT_MODE_1D);
7b80c0d
+			ASSERT(thePlot->getPlotMode(startMousePlot) == PLOT_MODE_1D);
7b80c0d
 			
7b80c0d
 			//Draw "ghost" limits markers for move,
7b80c0d
 			//these appear as moving vertical bars to outline
7b80c0d
diff -r bcb9acfa66de -r effd078610a7 src/wx/wxcomponents.cpp
70a58bb
--- src/wx/wxcomponents.cpp	Tue May 31 13:04:58 2016 +1000
70a58bb
+++ src/wx/wxcomponents.cpp	Tue Jun 21 12:45:20 2016 +0100
7b80c0d
@@ -188,14 +188,14 @@
7b80c0d
 
7b80c0d
 void CopyGrid::saveData()
7b80c0d
 {
7b80c0d
-	wxFileDialog *wxF = new wxFileDialog(this,TRANS("Save Data..."), wxT(""),
7b80c0d
+	wxFileDialog wxF(this,TRANS("Save Data..."), wxT(""),
7b80c0d
 		wxT(""),TRANS("Text File (*.txt)|*.txt|All Files (*)|*"),wxFD_SAVE);
7b80c0d
 
7b80c0d
-	if( (wxF->ShowModal() == wxID_CANCEL))
7b80c0d
+	if( (wxF.ShowModal() == wxID_CANCEL))
7b80c0d
 		return;
7b80c0d
 	
7b80c0d
 
7b80c0d
-	std::string dataFile = stlStr(wxF->GetPath());
7b80c0d
+	std::string dataFile = stlStr(wxF.GetPath());
7b80c0d
 	ofstream f(dataFile.c_str());
7b80c0d
 
7b80c0d
 	if(!f)