From e729387d0813f82e9cd907dfeeef1afab399ba84 Mon Sep 17 00:00:00 2001 From: westcott Date: Fri, 11 Sep 2009 16:28:24 +0000 Subject: [PATCH] chimeracheck method added --- chimera.h | 6 +- chimeracheckrdp.cpp | 381 ++++++++++++++++++++++++++++++----------- chimeracheckrdp.h | 25 ++- chimeraseqscommand.cpp | 19 +- chimeraseqscommand.h | 4 +- heatmap.cpp | 4 +- heatmapsim.cpp | 2 +- venn.cpp | 12 +- 8 files changed, 324 insertions(+), 129 deletions(-) diff --git a/chimera.h b/chimera.h index 8d7e08d..5df383d 100644 --- a/chimera.h +++ b/chimera.h @@ -69,6 +69,8 @@ class Chimera { virtual void setIncrement(int i) { increment = i; } virtual void setNumWanted(int n) { numWanted = n; } virtual void setKmerSize(int k) { kmerSize = k; } + virtual void setSVG(int s) { svg = s; } + virtual void setName(string n) { name = n; } virtual void setCons(string){}; virtual void setQuantiles(string){}; @@ -85,9 +87,9 @@ class Chimera { protected: - bool filter, correction; + bool filter, correction, svg; int processors, window, increment, numWanted, kmerSize; - string seqMask, quanfile, filterString; + string seqMask, quanfile, filterString, name; }; diff --git a/chimeracheckrdp.cpp b/chimeracheckrdp.cpp index 73bc1a0..acc3aa2 100644 --- a/chimeracheckrdp.cpp +++ b/chimeracheckrdp.cpp @@ -29,48 +29,42 @@ void ChimeraCheckRDP::print(ostream& out) { try { mothurOutEndLine(); - /* + + //vector isChimeric; isChimeric.resize(querySeqs.size(), false); + for (int i = 0; i < querySeqs.size(); i++) { - int j = 0; float largest = -10; - //find largest sim value - for (int k = 0; k < IS[i].size(); k++) { - //is this score larger - if (IS[i][k].score > largest) { - j = k; - largest = IS[i][k].score; + out << querySeqs[i]->getName() << endl; + out << "IS scores: " << '\t'; + + //int lastChimericWindowFound = 0; + + for (int k = 0; k < IS[i].size(); k++) { + out << IS[i][k].score << '\t'; + //if (IS[i][k].score > chimeraCutoff) { isChimeric[i] = true; lastChimericWindowFound = k; } } - } - - //find parental similarity - distCalc->calcDist(*(IS[i][j].leftParent), *(IS[i][j].rightParent)); - float dist = distCalc->getDist(); - - //convert to similarity - dist = (1 - dist) * 100; - - //warn about parental similarity - if its above 82% may not detect a chimera - if (dist >= 82) { mothurOut("When the chimeras parental similarity is above 82%, detection rates drop signifigantly."); mothurOutEndLine(); } - - int index = ceil(dist); - - if (index == 0) { index=1; } - - //is your DE value higher than the 95% - string chimera; - if (IS[i][j].score > quantile[index-1][4]) { chimera = "Yes"; } - else { chimera = "No"; } - - out << querySeqs[i]->getName() << "\tparental similarity: " << dist << "\tIS: " << IS[i][j].score << "\tbreakpoint: " << IS[i][j].midpoint << "\tchimera flag: " << chimera << endl; - - if (chimera == "Yes") { - mothurOut(querySeqs[i]->getName() + "\tparental similarity: " + toString(dist) + "\tIS: " + toString(IS[i][j].score) + "\tbreakpoint: " + toString(IS[i][j].midpoint) + "\tchimera flag: " + chimera); mothurOutEndLine(); - } - out << "Improvement Score\t"; - - for (int r = 0; r < IS[i].size(); r++) { out << IS[i][r].score << '\t'; } - out << endl; - }*/ + + //if (isChimeric[i]) { + //mothurOut(querySeqs[i]->getName() + "\tIS: " + toString(IS[i][lastChimericWindowFound].score) + "\tbreakpoint: " + toString(IS[i][lastChimericWindowFound].midpoint) + "\tleft parent: " + IS[i][lastChimericWindowFound].leftParent + "\tright parent: " + IS[i][lastChimericWindowFound].rightParent); mothurOutEndLine(); + //out << endl << "chimera: YES" << endl; + //}else{ + //out << endl << "chimera: NO" << endl; + //} + + if (svg) { + + if (name != "") { //if user has specific names + map::iterator it = names.find(querySeqs[i]->getName()); + + if (it != names.end()) { //user wants pic of this + makeSVGpic(IS[i], i); //zeros out negative results + } + }else{//output them all + makeSVGpic(IS[i], i); //zeros out negative results + } + } + } + } catch(exception& e) { errorOut(e, "ChimeraCheckRDP", "print"); @@ -116,28 +110,24 @@ void ChimeraCheckRDP::getChimeras() { kmer = new Kmer(kmerSize); - //find closest seq to each querySeq - for (int i = 0; i < querySeqs.size(); i++) { - closest[i] = templateDB->findClosestSequence(querySeqs[i]); + if (name != "") { + readName(name); //fills name map with names of seqs the user wants to have .svg for. } - //fill seqKmerInfo for query seqs + //find closest seq to each querySeq for (int i = 0; i < querySeqs.size(); i++) { - seqKmerInfo[querySeqs[i]->getName()] = kmer->getKmerCounts(querySeqs[i]->getUnaligned()); - } - - //fill seqKmerInfo for closest - for (int i = 0; i < closest.size(); i++) { - seqKmerInfo[closest[i].getName()] = kmer->getKmerCounts(closest[i].getUnaligned()); + closest[i] = templateDB->findClosestSequence(querySeqs[i]); } + //for each query find IS value + if (processors == 1) { + for (int i = 0; i < querySeqs.size(); i++) { + IS[i] = findIS(i); + } + }else { createProcessesIS(); } - //for each query find IS value - this should be paralellized, - //but paralellizing may cause you to have to recalculate some seqKmerInfo since the separate processes don't share memory after they split - for (int i = 0; i < querySeqs.size(); i++) { - IS[i] = findIS(i); //fills seqKmerInfo - } - + //determine chimera report cutoff - window score above 95% + //getCutoff(); - not very acurate predictor //free memory for (int i = 0; i < lines.size(); i++) { delete lines[i]; } @@ -153,25 +143,37 @@ void ChimeraCheckRDP::getChimeras() { vector ChimeraCheckRDP::findIS(int query) { try { + + vector< map > queryKmerInfo; //vector of maps - each entry in the vector is a map of the kmers up to that spot in the unaligned seq + //example: seqKmerInfo[50] = map containing the kmers found in the first 50 + kmersize characters of ecoli. + //i chose to store the kmers numbers in a map so you wouldn't have to check for dupilcate entries and could easily find the + //kmers 2 seqs had in common. There may be a better way to do this thats why I am leaving so many comments... + vector< map > subjectKmerInfo; + vector isValues; string queryName = querySeqs[query]->getName(); string seq = querySeqs[query]->getUnaligned(); - mothurOut("Finding IS values for sequence " + query); mothurOutEndLine(); + mothurOut("Finding IS values for sequence " + toString(query+1)); mothurOutEndLine(); - //find total kmers you have in common with closest[query] by looking at the last entry in the vector of maps for each - int nTotal = calcKmers(seqKmerInfo[queryName][(seqKmerInfo[queryName].size()-1)], seqKmerInfo[closest[query].getName()][(seqKmerInfo[closest[query].getName()].size()-1)]); + queryKmerInfo = kmer->getKmerCounts(seq); + subjectKmerInfo = kmer->getKmerCounts(closest[query].getUnaligned()); + //find total kmers you have in common with closest[query] by looking at the last entry in the vector of maps for each + int nTotal = calcKmers(queryKmerInfo[(queryKmerInfo.size()-1)], subjectKmerInfo[(subjectKmerInfo.size()-1)]); + //you don't want the starting point to be virtually at hte end so move it in 10% int start = seq.length() / 10; //for each window for (int m = start; m < (seq.length() - start); m+=increment) { + if ((m - kmerSize) < 0) { mothurOut("Your sequence is too short for your kmerSize."); mothurOutEndLine(); exit(1); } + sim temp; string fragLeft = seq.substr(0, m); //left side of breakpoint - string fragRight = seq.substr(m, seq.length()); //right side of breakpoint + string fragRight = seq.substr(m); //right side of breakpoint //make a sequence of the left side and right side Sequence* left = new Sequence(queryName, fragLeft); @@ -179,60 +181,50 @@ vector ChimeraCheckRDP::findIS(int query) { //find seqs closest to each fragment Sequence closestLeft = templateDB->findClosestSequence(left); + Sequence closestRight = templateDB->findClosestSequence(right); + + //get kmerinfo for the closest left + vector< map > closeLeftKmerInfo = kmer->getKmerCounts(closestLeft.getUnaligned()); - map::iterator itleft; - map::iterator itleftclose; - - //get kmer in the closest seqs - //if it's not found calc kmer info and save, otherwise use already calculated data - //left - it = seqKmerInfo.find(closestLeft.getName()); - if (it == seqKmerInfo.end()) { //you have to calc it - seqKmerInfo[closestLeft.getName()] = kmer->getKmerCounts(closestLeft.getUnaligned()); - } - - //right - it = seqKmerInfo.find(closestRight.getName()); - if (it == seqKmerInfo.end()) { //you have to calc it - seqKmerInfo[closestRight.getName()] = kmer->getKmerCounts(closestRight.getUnaligned()); - } + //get kmerinfo for the closest right + vector< map > closeRightKmerInfo = kmer->getKmerCounts(closestRight.getUnaligned()); //right side is tricky - since the counts grow on eachother to find the correct counts of only the right side you must subtract the counts of the left side //iterate through left sides map to subtract the number of times you saw things before you got the the right side - map rightside; - for (itleft = seqKmerInfo[queryName][m-kmerSize].begin(); itleft != seqKmerInfo[queryName][m-kmerSize].end(); itleft++) { - int howManyTotal = seqKmerInfo[queryName][seqKmerInfo[queryName].size()-1][itleft->first]; //times that kmer was seen in total - + map rightside = queryKmerInfo[queryKmerInfo.size()-1]; + for (map::iterator itleft = queryKmerInfo[m-kmerSize].begin(); itleft != queryKmerInfo[m-kmerSize].end(); itleft++) { + int howManyTotal = queryKmerInfo[queryKmerInfo.size()-1][itleft->first]; //times that kmer was seen in total + //itleft->second is times it was seen in left side, so howmanytotal - leftside should give you right side int howmanyright = howManyTotal - itleft->second; - //if any were seen just on the right add that ammount to map - if (howmanyright > 0) { - rightside[itleft->first] = howmanyright; + //if any were seen just on the left erase + if (howmanyright == 0) { + rightside.erase(itleft->first); } } - //iterate through left side of the seq closest to the right fragment of query to subtract the number you saw before you reached the right side of the closest right - //this way you can get the map for just the fragment you want to compare and not hte whole sequence - map rightsideclose; - for (itleftclose = seqKmerInfo[closestRight.getName()][m-kmerSize].begin(); itleftclose != seqKmerInfo[closestRight.getName()][m-kmerSize].end(); itleftclose++) { - int howManyTotal = seqKmerInfo[closestRight.getName()][seqKmerInfo[closestRight.getName()].size()-1][itleftclose->first]; //times that kmer was seen in total - + map closerightside = closeRightKmerInfo[closeRightKmerInfo.size()-1]; + for (map::iterator itright = closeRightKmerInfo[m-kmerSize].begin(); itright != closeRightKmerInfo[m-kmerSize].end(); itright++) { + int howManyTotal = closeRightKmerInfo[(closeRightKmerInfo.size()-1)][itright->first]; //times that kmer was seen in total + //itleft->second is times it was seen in left side, so howmanytotal - leftside should give you right side - int howmanyright = howManyTotal - itleftclose->second; + int howmanyright = howManyTotal - itright->second; - //if any were seen just on the right add that ammount to map - if (howmanyright > 0) { - rightsideclose[itleftclose->first] = howmanyright; + //if any were seen just on the left erase + if (howmanyright == 0) { + closerightside.erase(itright->first); } } - int nLeft = calcKmers(seqKmerInfo[closestLeft.getName()][m-kmerSize], seqKmerInfo[queryName][m-kmerSize]); - int nRight = calcKmers(rightsideclose, rightside); + int nLeft = calcKmers(closeLeftKmerInfo[m-kmerSize], queryKmerInfo[m-kmerSize]); + + int nRight = calcKmers(closerightside, rightside); + int is = nLeft + nRight - nTotal; - + //save IS, leftparent, rightparent, breakpoint temp.leftParent = closestLeft.getName(); temp.rightParent = closestRight.getName(); @@ -253,6 +245,28 @@ vector ChimeraCheckRDP::findIS(int query) { exit(1); } } +//*************************************************************************************************************** +void ChimeraCheckRDP::readName(string namefile) { + try{ + ifstream in; + openInputFile(namefile, in); + string name; + + while (!in.eof()) { + + in >> name; + + names[name] = name; + + gobble(in); + } + + } + catch(exception& e) { + errorOut(e, "ChimeraCheckRDP", "readName"); + exit(1); + } +} //*************************************************************************************************************** //find the smaller map and iterate through it and count kmers in common @@ -278,7 +292,7 @@ int ChimeraCheckRDP::calcKmers(map query, map subject) { large = query.find(small->first); //if you found it they have that kmer in common - if (large != query.end()) { common++; } + if (large != query.end()) { common++; } } } @@ -292,4 +306,179 @@ int ChimeraCheckRDP::calcKmers(map query, map subject) { } //*************************************************************************************************************** +void ChimeraCheckRDP::getCutoff() { + try{ + + vector temp; + + //store all is scores for all windows + for (int i = 0; i < IS.size(); i++) { + for (int j = 0; j < IS[i].size(); j++) { + temp.push_back(IS[i][j].score); + } + } + + //sort them + sort(temp.begin(), temp.end()); + + //get 95% + chimeraCutoff = temp[int(temp.size() * 0.95)]; + + } + catch(exception& e) { + errorOut(e, "ChimeraCheckRDP", "getCutoff"); + exit(1); + } +} + +//*************************************************************************************************************** +void ChimeraCheckRDP::makeSVGpic(vector info, int query) { + try{ + + string file = querySeqs[query]->getName() + ".chimeracheck.svg"; + ofstream outsvg; + openOutputFile(file, outsvg); + + int width = (info.size()*5) + 150; + + outsvg << "\n"; + outsvg << "\n"; + outsvg << "Plotted IS values for " + querySeqs[query]->getName() + "\n"; + + outsvg << "\n"; + outsvg << "\n"; + + outsvg << "" + toString(info[0].midpoint) + "\n"; + outsvg << "" + toString(info[info.size()-1].midpoint) + "\n"; + outsvg << "Base Positions\n"; + + outsvg << "0\n"; + + outsvg << "IS\n"; + + + //find max is score + float biggest = 0.0; + for (int i = 0; i < info.size(); i++) { + if (info[i].score > biggest) { + biggest = info[i].score; + } + } + + outsvg << "" + toString(biggest) + "\n"; + + int scaler2 = 500 / biggest; + + + outsvg << " "; + for (int i = 0; i < info.size(); i++) { + if(info[i].score < 0) { info[i].score = 0; } + outsvg << ((i*5) + 75) << "," << (600 - (info[i].score * scaler2)) << " "; + } + + outsvg << "\"/> "; + outsvg << "\n\n"; + + outsvg.close(); + + } + catch(exception& e) { + errorOut(e, "ChimeraCheckRDP", "makeSVGpic"); + exit(1); + } +} +//*************************************************************************************************************** +void ChimeraCheckRDP::createProcessesIS() { + try { + #if defined (__APPLE__) || (__MACH__) || (linux) || (__linux) + int process = 0; + vector processIDS; + + //loop through and create all the processes you want + while (process != processors) { + int pid = fork(); + + if (pid > 0) { + processIDS.push_back(pid); + process++; + }else if (pid == 0){ + + for (int i = lines[process]->start; i < lines[process]->end; i++) { + IS[i] = findIS(i); + } + + //write out data to file so parent can read it + ofstream out; + string s = toString(getpid()) + ".temp"; + openOutputFile(s, out); + + //output pairs + for (int i = lines[process]->start; i < lines[process]->end; i++) { + out << IS[i].size() << endl; + for (int j = 0; j < IS[i].size(); j++) { + out << IS[i][j].leftParent << '\t'<< IS[i][j].rightParent << '\t' << IS[i][j].midpoint << '\t' << IS[i][j].score << endl; + } + } + + out.close(); + + exit(0); + }else { mothurOut("unable to spawn the necessary processes."); mothurOutEndLine(); exit(0); } + } + + //force parent to wait until all the processes are done + for (int i=0;istart; k < lines[i]->end; k++) { + + int size; + in >> size; gobble(in); + + string left, right; + int mid; + float score; + + IS[k].clear(); + + for (int j = 0; j < size; j++) { + in >> left >> right >> mid >> score; gobble(in); + + sim temp; + temp.leftParent = left; + temp.rightParent = right; + temp.midpoint = mid; + temp.score = score; + + IS[k].push_back(temp); + } + } + + in.close(); + remove(s.c_str()); + } +#else + for (int i = 0; i < querySeqs.size(); i++) { + IS[i] = findIS(i); + } +#endif + } + catch(exception& e) { + errorOut(e, "ChimeraCheckRDP", "createProcessesIS"); + exit(1); + } +} + +//*************************************************************************************************************** + diff --git a/chimeracheckrdp.h b/chimeracheckrdp.h index e31cae7..7563c02 100644 --- a/chimeracheckrdp.h +++ b/chimeracheckrdp.h @@ -42,27 +42,24 @@ class ChimeraCheckRDP : public Chimera { Kmer* kmer; vector< vector > IS; //IS[0] is the vector of IS values for each window for querySeqs[0] + float chimeraCutoff; - //map of vector of maps- I know its a little convaluted but I am trying to save time - //I think that since the window is only sliding 10 bases there is a good probability that the closest seq to each fragment - //will be the same for several windows so I want to save the vector of maps containing its kmer info rather than regenerating it. - //So... - map > > seqKmerInfo; // outer map - sequence name -> kmer info - // kmer info: inner vector of maps - each entry in the vector is a map of the kmers up to that spot in the unaligned seq - //example: seqKmerInfo["ecoli"][50] = map containing the kmers found in the first 50 + kmersize characters of ecoli. - //i chose to store the kmers numbers in a map so you wouldn't have to check for dupilcate entries and could easily find the - //kmers 2 seqs had in common. There may be a better way to do this thats why I am leaving so many comments... - map > >:: iterator it; - map::iterator it2; + + //map > >:: iterator it; + //map::iterator it2; vector closest; //closest[0] is the closest overall seq to querySeqs[0]. string fastafile, templateFile; - + map names; vector findIS(int); - int calcKmers(map, map); - vector< vector > createProcessesIS(vector, vector); + int calcKmers(map, map); + void getCutoff(); + void makeSVGpic(vector, int); + void readName(string); + + void createProcessesIS(); }; diff --git a/chimeraseqscommand.cpp b/chimeraseqscommand.cpp index e8fa34d..2efed60 100644 --- a/chimeraseqscommand.cpp +++ b/chimeraseqscommand.cpp @@ -25,7 +25,7 @@ ChimeraSeqsCommand::ChimeraSeqsCommand(string option){ else { //valid paramters for this command - string Array[] = {"fasta", "filter", "correction", "processors", "method", "window", "increment", "template", "conservation", "quantile", "mask", "numwanted", "ksize" }; + string Array[] = {"fasta", "filter", "correction", "processors", "method", "window", "increment", "template", "conservation", "quantile", "mask", "numwanted", "ksize", "svg", "name" }; vector myArray (Array, Array+(sizeof(Array)/sizeof(string))); OptionParser parser(option); @@ -54,7 +54,11 @@ ChimeraSeqsCommand::ChimeraSeqsCommand(string option){ quanfile = validParameter.validFile(parameters, "quantile", true); if (quanfile == "not open") { abort = true; } else if (quanfile == "not found") { quanfile = ""; } - + + namefile = validParameter.validFile(parameters, "name", true); + if (namefile == "not open") { abort = true; } + else if (namefile == "not found") { namefile = ""; } + maskfile = validParameter.validFile(parameters, "mask", false); if (maskfile == "not found") { maskfile = ""; } else if (maskfile != "default") { @@ -79,6 +83,9 @@ ChimeraSeqsCommand::ChimeraSeqsCommand(string option){ temp = validParameter.validFile(parameters, "ksize", false); if (temp == "not found") { temp = "7"; } convert(temp, ksize); + temp = validParameter.validFile(parameters, "svg", false); if (temp == "not found") { temp = "F"; } + svg = isTrue(temp); + temp = validParameter.validFile(parameters, "window", false); if (temp == "not found") { temp = "0"; } convert(temp, window); @@ -153,12 +160,10 @@ int ChimeraSeqsCommand::execute(){ if (maskfile == "default") { mothurOut("I am using the default 236627 EU009184.1 Shigella dysenteriae str. FBD013."); mothurOutEndLine(); } //saves time to avoid generating it - if (consfile != "") { chimera->setCons(consfile); } - else { chimera->setCons(""); } + chimera->setCons(consfile); //saves time to avoid generating it - if (quanfile != "") { chimera->setQuantiles(quanfile); } - else { chimera->setQuantiles(""); } + chimera->setQuantiles(quanfile); chimera->setMask(maskfile); chimera->setFilter(filter); @@ -168,6 +173,8 @@ int ChimeraSeqsCommand::execute(){ chimera->setIncrement(increment); chimera->setNumWanted(numwanted); chimera->setKmerSize(ksize); + chimera->setSVG(svg); + chimera->setName(namefile); //find chimeras chimera->getChimeras(); diff --git a/chimeraseqscommand.h b/chimeraseqscommand.h index b2bce81..98504b0 100644 --- a/chimeraseqscommand.h +++ b/chimeraseqscommand.h @@ -28,8 +28,8 @@ public: private: bool abort; - string method, fastafile, templatefile, consfile, quanfile, maskfile; - bool filter, correction; + string method, fastafile, templatefile, consfile, quanfile, maskfile, namefile; + bool filter, correction, svg; int processors, midpoint, averageLeft, averageRight, window, iters, increment, numwanted, ksize; Chimera* chimera; diff --git a/heatmap.cpp b/heatmap.cpp index 6fd6861..52a29e7 100644 --- a/heatmap.cpp +++ b/heatmap.cpp @@ -61,7 +61,7 @@ void HeatMap::getPic(RAbundVector* rabund) { openOutputFile(filenamesvg, outsvg); //svg image - outsvg << "getNumBins()*5 + 120)) + "\">\n"; + outsvg << "getNumBins()*5 + 120)) + "\">\n"; outsvg << "\n"; //white backround @@ -137,7 +137,7 @@ void HeatMap::getPic(vector lookup) { openOutputFile(filenamesvg, outsvg); //svg image - outsvg << "getNumBins()*5 + 120)) + "\">\n"; + outsvg << "getNumBins()*5 + 120)) + "\">\n"; outsvg << "\n"; //white backround diff --git a/heatmapsim.cpp b/heatmapsim.cpp index ea01304..1544dc2 100644 --- a/heatmapsim.cpp +++ b/heatmapsim.cpp @@ -37,7 +37,7 @@ void HeatMapSim::getPic(vector lookup, vector openOutputFile(filenamesvg, outsvg); //svg image - outsvg << "\n"; + outsvg << "\n"; outsvg << "\n"; //white backround diff --git a/venn.cpp b/venn.cpp index 7cb2134..bd40869 100644 --- a/venn.cpp +++ b/venn.cpp @@ -37,7 +37,7 @@ void Venn::getPic(SAbundVector* sabund, vector vCalcs) { vector data = vCalcs[i]->getValues(sabund); //svg image - outsvg << "\n"; + outsvg << "\n"; outsvg << "\n"; outsvg << ""; @@ -88,7 +88,7 @@ void Venn::getPic(vector lookup, vector vCalcs vector data = singleCalc->getValues(sabund); //svg image - outsvg << "\n"; + outsvg << "\n"; outsvg << "\n"; outsvg << ""; @@ -144,7 +144,7 @@ void Venn::getPic(vector lookup, vector vCalcs vector numB = singleCalc->getValues(sabundB); //image window - outsvg << "\n"; + outsvg << "\n"; outsvg << "\n"; //draw circles @@ -284,7 +284,7 @@ void Venn::getPic(vector lookup, vector vCalcs } //image window - outsvg << "\n"; + outsvg << "\n"; outsvg << "\n"; //draw circles @@ -367,7 +367,7 @@ void Venn::getPic(vector lookup, vector vCalcs vector sharedabc = vCalcs[i]->getValues(subset); //image window - outsvg << "\n"; + outsvg << "\n"; outsvg << "\n"; //draw circles @@ -536,7 +536,7 @@ void Venn::getPic(vector lookup, vector vCalcs //image window - outsvg << "\n"; + outsvg << "\n"; outsvg << "\n"; outsvg << ""; outsvg << "Venn Diagram at distance " + lookup[0]->getLabel() + "\n"; -- 2.39.2