diff --git a/Bandage.pro b/Bandage.pro
index 05378e53..62eb442c 100644
--- a/Bandage.pro
+++ b/Bandage.pro
@@ -31,6 +31,7 @@ INCLUDEPATH += ui
SOURCES += \
program/main.cpp\
+ program/dotplot.cpp \
program/settings.cpp \
program/globals.cpp \
program/graphlayoutworker.cpp \
@@ -121,6 +122,7 @@ SOURCES += \
HEADERS += \
program/settings.h \
+ program/dotplot.h \
program/globals.h \
program/graphlayoutworker.h \
graph/debruijnnode.h \
@@ -259,10 +261,6 @@ FORMS += \
RESOURCES += \
images/images.qrc
-
-unix:INCLUDEPATH += /usr/include/
-unix:LIBS += -L/usr/lib
-
# The following settings are compatible with OGDF being built in 64 bit release mode using Visual Studio 2013
win32:LIBS += -lpsapi
win32:RC_FILE = images/myapp.rc
diff --git a/BandageTests.pro b/BandageTests.pro
index dcfb5d9a..adbc84e0 100644
--- a/BandageTests.pro
+++ b/BandageTests.pro
@@ -33,6 +33,7 @@ SOURCES += \
program/settings.cpp \
program/globals.cpp \
program/graphlayoutworker.cpp \
+ program/dotplot.cpp \
graph/debruijnnode.cpp \
graph/debruijnedge.cpp \
graph/graphicsitemnode.cpp \
@@ -123,6 +124,7 @@ HEADERS += \
program/settings.h \
program/globals.h \
program/graphlayoutworker.h \
+ program/dotplot.h \
graph/debruijnnode.h \
graph/debruijnedge.h \
graph/graphicsitemnode.h \
diff --git a/README.md b/README.md
index db53b3c1..b0a0048b 100644
--- a/README.md
+++ b/README.md
@@ -37,16 +37,26 @@ The Linux binaries comes in two varieties: dynamically-linked and statically-lin
If the compiled binaries do not work for you, the instructions below should help you build Bandage on most common OSs. If you are having difficulties building Bandage for your OS, feel free to contact me (Ryan) at rrwick@gmail.com and I'll do my best to help you out!
-###
Ubuntu
+###
Ubuntu & Fedora
-The following instructions successfully build Bandage on a fresh installation of Ubuntu 14.04:
+The following instructions successfully build Bandage on a fresh installation of Ubuntu 14.04 or fedora 33:
1. Ensure the package lists are up-to-date: `sudo apt-get update`
-2. Install prerequisite packages: `sudo apt-get install build-essential git qtbase5-dev libqt5svg5-dev`
+2. Install prerequisite packages:
+
+ For Ubuntu:
+ ```
+ sudo apt-get install build-essential git qtbase5-dev libqt5svg5-dev
+ ```
+ For Fedora:
+ ```
+ sudo dnf groupinstall "Development Tools" "Development Libraries"
+ sudo dnf install qt5-qtsvg-devel git qt5-qtbase-devel
+ ```
3. Download the Bandage code from GitHub: `git clone https://github.com/rrwick/Bandage.git`
4. Open a terminal in the Bandage directory.
5. Set the environment variable to specify that you will be using Qt 5, not Qt 4: `export QT_SELECT=5`
-6. Run qmake to generate a Makefile: `qmake`
+6. Run qmake to generate a Makefile: `qmake` or in Fedora `qmake-qt5`
7. Build the program: `make`
8. `Bandage` should now be an executable file.
9. Optionally, copy the program into /usr/local/bin: `sudo make install`. The Bandage build directory can then be deleted.
diff --git a/blast/blastsearch.cpp b/blast/blastsearch.cpp
index 4e88ed0b..d72028a6 100644
--- a/blast/blastsearch.cpp
+++ b/blast/blastsearch.cpp
@@ -298,7 +298,7 @@ int BlastSearch::loadBlastQueriesFromFastaFile(QString fullFileName)
std::vector queryNames;
std::vector querySequences;
- AssemblyGraph::readFastaFile(fullFileName, &queryNames, &querySequences);
+ AssemblyGraph::readFastaOrFastqFile(fullFileName, &queryNames, &querySequences);
for (size_t i = 0; i < queryNames.size(); ++i)
{
diff --git a/command_line/image.cpp b/command_line/image.cpp
index fcd89861..a8a8505f 100644
--- a/command_line/image.cpp
+++ b/command_line/image.cpp
@@ -130,6 +130,26 @@ int bandageImage(QStringList arguments)
g_settings->startingNodes,
"all");
+ QString errormsg;
+ QStringList columns;
+ bool coloursLoaded = false;
+ QString csvPath = parseColorsOption(arguments);
+ if (csvPath != "")
+ {
+ if(!g_assemblyGraph->loadCSV(csvPath, &columns, &errormsg, &coloursLoaded))
+ {
+ err << errormsg << endl;
+ return 1;
+ }
+
+ if(coloursLoaded == false)
+ {
+ err << csvPath << " didn't contains color" << endl;
+ return 1;
+ }
+ g_settings->nodeColourScheme = CUSTOM_COLOURS;
+ }
+
if (errorMessage != "")
{
err << errorMessage << endl;
@@ -212,6 +232,7 @@ void printImageUsage(QTextStream * out, bool all)
text << "";
text << "Options: --height Image height (default: 1000)";
text << "--width Image width (default: not set)";
+ text << "--color csv file with 2 column first the node name second the node color";
text << "";
text << "If only height or width is set, the other will be determined automatically. If both are set, the image will be exactly that size.";
text << "";
@@ -232,6 +253,9 @@ QString checkForInvalidImageOptions(QStringList arguments)
error = checkOptionForInt("--width", &arguments, IntSetting(0, 1, 32767), false);
if (error.length() > 0) return error;
+ error = checkOptionForString("--colors", &arguments, QStringList(), "a path of csv file");
+ if (error.length() > 0) return error;
+
return checkForInvalidOrExcessSettings(&arguments);
}
@@ -251,3 +275,15 @@ void parseImageOptions(QStringList arguments, int * width, int * height)
parseSettings(arguments);
}
+//This function parses the command line options. It assumes that the options
+//have already been checked for correctness.
+QString parseColorsOption(QStringList arguments)
+{
+ QString path = "";
+ if (isOptionPresent("--colors", &arguments))
+ path = getStringOption("--colors", &arguments);
+
+ parseSettings(arguments);
+
+ return path;
+}
diff --git a/command_line/image.h b/command_line/image.h
index 41d26291..59af21c9 100644
--- a/command_line/image.h
+++ b/command_line/image.h
@@ -28,5 +28,6 @@ int bandageImage(QStringList arguments);
void printImageUsage(QTextStream * out, bool all);
QString checkForInvalidImageOptions(QStringList arguments);
void parseImageOptions(QStringList arguments, int * width, int * height);
+QString parseColorsOption(QStringList arguments);
#endif // IMAGE_H
diff --git a/command_line/info.cpp b/command_line/info.cpp
index 815f4750..830815ce 100644
--- a/command_line/info.cpp
+++ b/command_line/info.cpp
@@ -118,7 +118,7 @@ int bandageInfo(QStringList arguments)
out << median << "\t";
out << thirdQuartile << "\t";
out << longestNode << "\t";
- out << medianDepthByBase << "\n";
+ out << medianDepthByBase << "\t";
out << estimatedSequenceLength << "\n";
}
else
diff --git a/graph/assemblygraph.cpp b/graph/assemblygraph.cpp
index a839d528..cd30158f 100644
--- a/graph/assemblygraph.cpp
+++ b/graph/assemblygraph.cpp
@@ -616,7 +616,9 @@ void AssemblyGraph::buildDeBruijnGraphFromGfa(QString fullFileName, bool *unsupp
QString nodeName = lineParts.at(1);
if (nodeName.isEmpty())
- nodeName = "node";
+ nodeName = getUniqueNodeName("node");
+ if (m_deBruijnGraphNodes.contains(nodeName + "+"))
+ throw "load error";
QByteArray sequence = lineParts.at(2).toLocal8Bit();
@@ -972,6 +974,8 @@ void AssemblyGraph::buildDeBruijnGraphFromFastg(QString fullFileName)
nodeName += "-";
else
nodeName += "+";
+ if (m_deBruijnGraphNodes.contains(nodeName))
+ throw "load error";
QString nodeDepthString = thisNodeDetails.at(5);
if (negativeNode)
@@ -1414,6 +1418,16 @@ void AssemblyGraph::buildDeBruijnGraphFromPlainFasta(QString fullFileName)
m_depthTag = "KC";
}
+ // Check to see if the name matches SKESA format, in which case we can get the depth and node number.
+ else if (thisNodeDetails.size() >= 3 && thisNodeDetails[0] == "Contig" && thisNodeDetails[1].toInt() > 0) {
+ name = thisNodeDetails[1];
+ bool ok;
+ double convertedDepth = thisNodeDetails[2].toDouble(&ok);
+ if (ok)
+ depth = convertedDepth;
+ m_depthTag = "KC";
+ }
+
// If it doesn't match, then we will use the sequence name up to the first space.
else {
QStringList nameParts = name.split(" ");
@@ -1439,6 +1453,10 @@ void AssemblyGraph::buildDeBruijnGraphFromPlainFasta(QString fullFileName)
if (lowerName.contains("circular=true"))
circularNodeNames.push_back(name);
+ // SKESA circularity
+ if (thisNodeDetails.size() == 4 and thisNodeDetails[3] == "Circ")
+ circularNodeNames.push_back(name);
+
if (name.length() < 1)
throw "load error";
@@ -1692,6 +1710,12 @@ bool AssemblyGraph::loadCSV(QString filename, QStringList * columns, QString * e
//If the node name it finds does not end in a '+' or '-', it will add '+'.
QString AssemblyGraph::getNodeNameFromString(QString string)
{
+ // First check for the most obvious case, where the string is already a node name.
+ if (m_deBruijnGraphNodes.contains(string))
+ return string;
+ if (m_deBruijnGraphNodes.contains(string + "+"))
+ return string + "+";
+
QStringList parts = string.split("_");
if (parts.size() == 0)
return "";
@@ -2375,7 +2399,25 @@ QString AssemblyGraph::getOppositeNodeName(QString nodeName)
}
-void AssemblyGraph::readFastaFile(QString filename, std::vector * names, std::vector *sequences)
+void AssemblyGraph::readFastaOrFastqFile(QString filename, std::vector * names,
+ std::vector * sequences) {
+ QChar firstChar = 0;
+ QFile inputFile(filename);
+ if (inputFile.open(QIODevice::ReadOnly)) {
+ QTextStream in(&inputFile);
+ QString firstLine = in.readLine();
+ firstChar = firstLine.at(0);
+ inputFile.close();
+ }
+ if (firstChar == '>')
+ readFastaFile(filename, names, sequences);
+ else if (firstChar == '@')
+ readFastqFile(filename, names, sequences);
+}
+
+
+
+void AssemblyGraph::readFastaFile(QString filename, std::vector * names, std::vector * sequences)
{
QFile inputFile(filename);
if (inputFile.open(QIODevice::ReadOnly))
@@ -2423,6 +2465,36 @@ void AssemblyGraph::readFastaFile(QString filename, std::vector * names
}
+void AssemblyGraph::readFastqFile(QString filename, std::vector * names, std::vector * sequences)
+{
+ QFile inputFile(filename);
+ if (inputFile.open(QIODevice::ReadOnly))
+ {
+ QTextStream in(&inputFile);
+ while (!in.atEnd())
+ {
+ QApplication::processEvents();
+
+ QString name = in.readLine().simplified();
+ QByteArray sequence = in.readLine().simplified().toLocal8Bit();
+ in.readLine(); // separator
+ in.readLine(); // qualities
+
+ if (name.length() == 0)
+ continue;
+ if (sequence.length() == 0)
+ continue;
+ if (name.at(0) != '@')
+ continue;
+ name.remove(0, 1); //Remove '@' from start
+ names->push_back(name);
+ sequences->push_back(sequence);
+ }
+ inputFile.close();
+ }
+}
+
+
void AssemblyGraph::recalculateAllDepthsRelativeToDrawnMean()
{
double meanDrawnDepth = getMeanDepth(true);
diff --git a/graph/assemblygraph.h b/graph/assemblygraph.h
index d1a63227..426e7532 100644
--- a/graph/assemblygraph.h
+++ b/graph/assemblygraph.h
@@ -128,8 +128,12 @@ class AssemblyGraph : public QObject
void setAllEdgesExactOverlap(int overlap);
void autoDetermineAllEdgesExactOverlap();
+ static void readFastaOrFastqFile(QString filename, std::vector * names,
+ std::vector * sequences);
static void readFastaFile(QString filename, std::vector * names,
std::vector * sequences);
+ static void readFastqFile(QString filename, std::vector * names,
+ std::vector * sequences);
int getDrawnNodeCount() const;
void deleteNodes(std::vector * nodes);
diff --git a/program/dotplot.cpp b/program/dotplot.cpp
new file mode 100644
index 00000000..44911b7e
--- /dev/null
+++ b/program/dotplot.cpp
@@ -0,0 +1,175 @@
+/*
+ * dotplot.cpp
+ *
+ * Created on: Oct 16, 2017
+ * Author: Ivan Sovic
+ * GitHub: @isovic
+ * Copyright: Ivan Sovic, 2017
+ * Licence: MIT
+ *
+ * Simple tool that collects all kmer hits between
+ * two sequences. If drawn, this represents a dotplot
+ * between two sequences. Can be used for very simple
+ * mapping as well.
+ */
+
+#include "dotplot.h"
+
+#include
+#include
+#include
+#include
+
+const int8_t nuc_to_2bit[256] = {
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 0 - 15
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 16 - 31
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 32 - 47
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 48 - 63
+ 4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, // 64 - 79 (A, C, G)
+ 4, 4, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 80 - 95 (T)
+ 4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, // 96 - 111
+ 4, 4, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 112 - 127
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 128 - 143
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 144 - 159
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 160 - 176
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 176 - 191
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 192 - 208
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 208 - 223
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 224 - 239
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 // 240 - 256
+};
+
+const int8_t nuc_to_complement[256] = {
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 0 - 15
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 16 - 31
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 32 - 47
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 48 - 63
+ 78, 84, 78, 71, 78, 78, 78, 67, 78, 78, 78, 78, 78, 78, 78, 78, // 64 - 79 (A, C, G)
+ 78, 78, 78, 78, 65, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 80 - 95 (T)
+ 78, 84, 78, 71, 78, 78, 78, 67, 78, 78, 78, 78, 78, 78, 78, 78, // 96 - 111
+ 78, 78, 78, 78, 65, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 112 - 127
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 128 - 143
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 144 - 159
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 160 - 176
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 176 - 191
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 192 - 208
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 208 - 223
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, // 224 - 239
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78 // 240 - 256
+};
+
+
+std::string reverseComplement(const std::string& seq) {
+ std::stringstream ss;
+ for (int32_t i = ((int32_t) seq.size()) - 1; i >= 0; i--) {
+ ss << nuc_to_complement[(int32_t) seq[i]];
+ }
+ return ss.str();
+}
+
+std::vector hashKmers(const std::string& seq, int32_t k, bool seq_is_rev) {
+ std::vector ret;
+
+ if (((int32_t) seq.size()) < k) {
+ return ret;
+ }
+
+ if (k <= 0 || k >= 31) {
+ return ret;
+ }
+
+ // Pre-scan the sequence and check whether it contains
+ // only [ACTG] bases. We do not allow other bases, because
+ // they will be packed as 2-bit values in a hash key.
+ for (size_t i = 0; i < seq.size(); i++) {
+ if (nuc_to_2bit[(int32_t) seq[i]] > 3) {
+ return ret;
+ }
+ }
+
+ int64_t buff = 0x0;
+ int64_t buff_mask = (((int64_t) 1) << (2 * k)) - 1; // Clear the upper bits.
+
+ ret.reserve(seq.size() - k + 1);
+
+ // Initialize the buffer.
+ for (int32_t i = 0; i < (k - 1); i++) {
+ int64_t conv_val = nuc_to_2bit[(int32_t) seq[i]];
+ buff = (((int64_t) buff) << 2) | (conv_val & 0x03);
+ }
+
+ for (int32_t i = (k - 1); i < (int32_t) seq.size(); i++) {
+ // Update the buffer
+ int64_t conv_val = nuc_to_2bit[(int32_t) seq[i]];
+ buff = (((int64_t) buff) << 2) | (conv_val & 0x03);
+ buff &= buff_mask;
+
+ int32_t pos = (seq_is_rev == false) ? (i - k + 1) : (seq.size() - (i - k + 1) - 1);
+ ret.emplace_back(KmerPos(buff, pos));
+ }
+
+ return ret;
+}
+
+std::vector findHits(const std::vector& sorted_kmers_seq1, const std::vector& sorted_kmers_seq2) {
+ int32_t k1 = 0, k2 = 0;
+
+ int32_t n_kmers1 = sorted_kmers_seq1.size();
+ int32_t n_kmers2 = sorted_kmers_seq2.size();
+
+ std::vector hits;
+
+ // Check the sortedness of input.
+ for (size_t i = 1; i < sorted_kmers_seq1.size(); i++) {
+ if(sorted_kmers_seq1[i].kmer < sorted_kmers_seq1[i - 1].kmer) {
+ return hits;
+ }
+ }
+ for (size_t i = 1; i < sorted_kmers_seq2.size(); i++) {
+ if(sorted_kmers_seq2[i].kmer < sorted_kmers_seq2[i - 1].kmer) {
+ return hits;
+ }
+ }
+
+ while (k1 < n_kmers1 && k2 < n_kmers2) {
+ while (k1 < n_kmers1 && sorted_kmers_seq1[k1].kmer < sorted_kmers_seq2[k2].kmer) {
+ k1 += 1;
+ }
+ if (k1 >= n_kmers1) { break; }
+
+ while (k2 < n_kmers2 && sorted_kmers_seq2[k2].kmer < sorted_kmers_seq1[k1].kmer) {
+ k2 += 1;
+ }
+ if (k2 >= n_kmers2) { break; }
+
+ // If the values are not the same, just keep on gliding.
+ if (sorted_kmers_seq1[k1].kmer != sorted_kmers_seq2[k2].kmer) {
+ continue;
+ }
+
+ // Find n^2 exact hits.
+ for (int32_t i = k2; i < n_kmers2 && sorted_kmers_seq2[i].kmer == sorted_kmers_seq1[k1].kmer; i++) {
+ hits.emplace_back(KmerHit(sorted_kmers_seq1[k1].pos, sorted_kmers_seq2[i].pos));
+ }
+ k1 += 1;
+ }
+
+ return hits;
+}
+
+std::vector findKmerMatches(const std::string& seq1, const std::string& seq2, int32_t k) {
+ auto kmers_seq1 = hashKmers(seq1, k, false);
+ auto kmers_seq2 = hashKmers(seq2, k, false);
+ auto kmers_seq2_rev = hashKmers(reverseComplement(seq2), k, true);
+
+ std::sort(kmers_seq1.begin(), kmers_seq1.end(), [](const KmerPos& a, const KmerPos& b) { return (a.kmer < b.kmer || (a.kmer == b.kmer && a.pos < b.pos)); } );
+ std::sort(kmers_seq2.begin(), kmers_seq2.end(), [](const KmerPos& a, const KmerPos& b) { return (a.kmer < b.kmer || (a.kmer == b.kmer && a.pos < b.pos)); } );
+ std::sort(kmers_seq2_rev.begin(), kmers_seq2_rev.end(), [](const KmerPos& a, const KmerPos& b) { return (a.kmer < b.kmer || (a.kmer == b.kmer && a.pos < b.pos)); } );
+
+ auto hits = findHits(kmers_seq1, kmers_seq2);
+ auto hits_rev = findHits(kmers_seq1, kmers_seq2_rev);
+
+ hits.insert(hits.end(), hits_rev.begin(), hits_rev.end());
+
+ return hits;
+}
diff --git a/program/dotplot.h b/program/dotplot.h
new file mode 100644
index 00000000..2d33188e
--- /dev/null
+++ b/program/dotplot.h
@@ -0,0 +1,58 @@
+/*
+ * dotplot.h
+ *
+ * Created on: Oct 16, 2017
+ * Author: Ivan Sovic
+ * GitHub: @isovic
+ * Copyright: Ivan Sovic, 2017
+ * Licence: MIT
+ *
+ * Simple tool that collects all kmer hits between
+ * two sequences. If drawn, this represents a dotplot
+ * between two sequences. Can be used for very simple
+ * mapping as well.
+ */
+
+#ifndef SRC_PROGRAM_DOTPLOT_H_
+#define SRC_PROGRAM_DOTPLOT_H_
+
+#include
+#include
+#include
+
+class KmerPos {
+ public:
+ KmerPos() : kmer(0), pos(0) {}
+ KmerPos(int64_t _kmer, int32_t _pos) : kmer(_kmer), pos(_pos) { }
+ bool operator==(const KmerPos& b) const {
+ return (this->kmer == b.kmer && this->pos == b.pos);
+ }
+ bool operator<(const KmerPos& b) const {
+ return (this->kmer < b.kmer || (this->kmer == b.kmer && this->pos < b.pos));
+ }
+
+ int64_t kmer;
+ int32_t pos;
+};
+
+struct KmerHit {
+ KmerHit() : x(0), y(0) { }
+ KmerHit(int32_t _x, int32_t _y) : x(_x), y(_y) { }
+ bool operator==(const KmerHit& b) const {
+ return (this->x == b.x && this->y == b.y);
+ }
+ bool operator<(const KmerHit& b) const {
+ return (this->x < b.x || (this->x == b.x && this->y < b.y));
+ }
+
+ int32_t x;
+ int32_t y;
+};
+
+std::string reverseComplement(const std::string& seq);
+std::vector hashKmers(const std::string& seq, int32_t k, bool seq_is_rev);
+std::vector findHits(const std::vector& sorted_kmers_seq1, const std::vector& sorted_kmers_seq2);
+std::vector findKmerMatches(const std::string& seq1, const std::string& seq2, int32_t k);
+std::vector findKmerMatches(const std::string& seq1, const std::string& seq2, int32_t k);
+
+#endif
diff --git a/tests/bandagetests.cpp b/tests/bandagetests.cpp
index 388cd427..92d05875 100644
--- a/tests/bandagetests.cpp
+++ b/tests/bandagetests.cpp
@@ -29,6 +29,7 @@
#include "../graph/debruijnedge.h"
#include "../program/globals.h"
#include "../command_line/commoncommandlinefunctions.h"
+#include "../program/dotplot.h"
class BandageTests : public QObject
{
@@ -58,7 +59,10 @@ private slots:
void changeNodeDepths();
void blastQueryPaths();
void bandageInfo();
-
+ void testReverseComplement();
+ void testHashKmers();
+ void testFindHits();
+ void testFindKmerMatches();
private:
void createGlobals();
@@ -1430,7 +1434,6 @@ void BandageTests::blastQueryPaths()
QCOMPARE(query7Paths.size(), 1);
}
-
void BandageTests::bandageInfo()
{
int n50 = 0;
@@ -1480,6 +1483,236 @@ void BandageTests::bandageInfo()
QCOMPARE(9398, largestComponentLength);
}
+void BandageTests::testReverseComplement()
+{
+ QCOMPARE( reverseComplement("A"), std::string("T"));
+ QCOMPARE( reverseComplement("C"), std::string("G"));
+ QCOMPARE( reverseComplement("T"), std::string("A"));
+ QCOMPARE( reverseComplement("G"), std::string("C"));
+ QCOMPARE( reverseComplement(""), std::string(""));
+ QCOMPARE( reverseComplement("ACTG"), std::string("CAGT"));
+}
+
+void BandageTests::testHashKmers()
+{
+ { // Test for an empty string.
+ std::string seq = "";
+ int32_t k = 10;
+ bool seq_is_rev = false;
+ std::vector expected_kmers = { };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE((int64_t) kmers.size(), (int64_t) 0);
+ }
+
+ { // Test the behaviour when a non [ACTG] character is given.
+ std::string seq = "AAAAANAAAAA";
+ int32_t k = 10;
+ bool seq_is_rev = false;
+ std::vector expected_kmers = { };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE((int64_t) kmers.size(), (int64_t) 0);
+ }
+
+ { // A simple normal test case. Entire seq should be only one kmer.
+ std::string seq = "AAAAAAAAAA";
+ int32_t k = 10;
+ bool seq_is_rev = false;
+ std::vector expected_kmers = { KmerPos(0x0, 0) };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE(kmers, expected_kmers);
+ }
+
+ { // Similar to before, but 6 kmers.
+ std::string seq = "AAAAAAAAAAAAAAA";
+ int32_t k = 10;
+ bool seq_is_rev = false;
+ std::vector expected_kmers = { KmerPos(0x0, 0), KmerPos(0x0, 1),
+ KmerPos(0x0, 2), KmerPos(0x0, 3),
+ KmerPos(0x0, 4), KmerPos(0x0, 5) };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE(kmers, expected_kmers);
+ }
+
+ { // Test the reverse complement.
+ std::string seq = "AAAAAAAAAA";
+ int32_t k = 10;
+ bool seq_is_rev = true;
+ std::vector expected_kmers = { KmerPos(0x0, 9) };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE(kmers, expected_kmers);
+ }
+
+ { // Test what happens if an invalid kmer size is given.
+ std::string seq = "AAAAAAAAAA";
+ int32_t k = 0;
+ bool seq_is_rev = false;
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE((int64_t) kmers.size(), (int64_t) 0);
+ }
+
+ { // Test for a normal sequence, but a more complex variation.
+ std::string seq = "ACTGAAAGACT";
+ int32_t k = 10;
+ bool seq_is_rev = false;
+ std::vector expected_kmers = { KmerPos(0x1E021, 0), KmerPos(0x78087, 1) };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE(kmers, expected_kmers);
+ }
+
+ { // Similar as before, but in reverse. Hash keys should be same, but positions different.
+ std::string seq = "ACTGAAAGACT";
+ int32_t k = 10;
+ bool seq_is_rev = true;
+ std::vector expected_kmers = { KmerPos(0x1E021, 10), KmerPos(0x78087, 9) };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE(kmers, expected_kmers);
+ }
+
+ { // Test a normal sequence and a normal (smaller) kmer size.
+ std::string seq = "ACTGAAAGACT";
+ int32_t k = 4;
+ bool seq_is_rev = false;
+ std::vector expected_kmers = { KmerPos(0x1E, 0), KmerPos(0x78, 1), KmerPos(0xE0, 2),
+ KmerPos(0x80, 3), KmerPos(0x2, 4), KmerPos(0x8, 5),
+ KmerPos(0x21, 6), KmerPos(0x87, 7) };
+ std::vector kmers = hashKmers(seq, k, seq_is_rev);
+ QCOMPARE(kmers, expected_kmers);
+ }
+}
+
+void BandageTests::testFindHits()
+{
+ { // Test on empty inputs.
+ std::vector sorted_kmers_seq1 = { };
+ std::vector sorted_kmers_seq2 = { };
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ QCOMPARE((int64_t) result.size(), (int64_t) 0);
+ }
+
+ { // No hits are output if any of the input arrays are not sorted.
+ std::vector sorted_kmers_seq1 = {KmerPos(1, 0), KmerPos(0, 1)};
+ std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)};
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ QCOMPARE((int64_t) result.size(), (int64_t) 0);
+ }
+
+ { /// Sorted but no hits.
+ std::vector sorted_kmers_seq1 = {KmerPos(2, 0), KmerPos(3, 1)};
+ std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)};
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ QCOMPARE((int64_t) result.size(), (int64_t) 0);
+ }
+
+ { // There should be two hits.
+ std::vector sorted_kmers_seq1 = {KmerPos(0, 0), KmerPos(1, 1)};
+ std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)};
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ std::vector expected = {KmerHit(0, 0), KmerHit(1, 1)};
+ QCOMPARE(result, expected);
+ }
+
+ { // One is empty, the other is not.
+ std::vector sorted_kmers_seq1 = { };
+ std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1)};
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ QCOMPARE((int64_t) result.size(), (int64_t) 0);
+ }
+
+ { // One is empty, the other is not.
+ std::vector sorted_kmers_seq1 = {KmerPos(0, 0), KmerPos(1, 1)};
+ std::vector sorted_kmers_seq2 = { };
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ QCOMPARE((int64_t) result.size(), (int64_t) 0);
+ }
+
+ { // Only a subset matches.
+ std::vector sorted_kmers_seq1 = {KmerPos(3, 1), KmerPos(4, 2)};
+ std::vector sorted_kmers_seq2 = {KmerPos(0, 0), KmerPos(1, 1),
+ KmerPos(3, 3), KmerPos(4, 7),
+ KmerPos(5, 8) };
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ std::vector expected = {KmerHit(1, 3), KmerHit(2, 7)};
+ QCOMPARE(result, expected);
+ }
+
+ { // Test multiple successive hits.
+ std::vector sorted_kmers_seq1 = { KmerPos(2, 0), KmerPos(3, 1), KmerPos(4, 2) };
+ std::vector sorted_kmers_seq2 = { KmerPos(0, 0), KmerPos(1, 1),
+ KmerPos(3, 3), KmerPos(3, 7),
+ KmerPos(3, 8), KmerPos(5, 9) };
+ std::vector result = findHits(sorted_kmers_seq1, sorted_kmers_seq2);
+ std::vector expected = { KmerHit(1, 3), KmerHit(1, 7), KmerHit(1, 8)};
+ QCOMPARE(result, expected);
+ }
+}
+
+void BandageTests::testFindKmerMatches()
+{
+ { // Test a simple basic match case.
+ std::string seq1 = "CT";
+ std::string seq2 = "CT";
+ int32_t k = 2;
+ std::vector result = findKmerMatches(seq1, seq2, k);
+ std::vector expected = {KmerHit(0, 0)};
+ QCOMPARE(result, expected);
+ }
+
+ { // Test a case with no hits.
+ std::string seq1 = "AAAAA";
+ std::string seq2 = "CT";
+ int32_t k = 2;
+ std::vector result = findKmerMatches(seq1, seq2, k);
+ std::vector expected = { };
+ QCOMPARE((int64_t) result.size(), (int64_t) expected.size());
+ }
+
+ { // Test a normal one-hit case.
+ std::string seq1 = "ACTTGGGA";
+ std::string seq2 = "CT";
+ int32_t k = 2;
+ std::vector result = findKmerMatches(seq1, seq2, k);
+ std::vector expected = {KmerHit(1, 0)};
+ QCOMPARE((int64_t) result.size(), (int64_t) expected.size());
+ QCOMPARE(result, expected);
+ }
+
+ { // Test matching empty sequences.
+ std::string seq1 = "";
+ std::string seq2 = "";
+ int32_t k = 10;
+ std::vector result = findKmerMatches(seq1, seq2, k);
+ std::vector expected = { };
+ QCOMPARE((int64_t) result.size(), (int64_t) expected.size());
+ QCOMPARE(result, expected);
+ }
+
+ { // Test finding of hits in a larger sequence.
+ std::string seq1 = "CTCGCACTTGGGGAATCGCGCAGACCTCACCCGGTTTGCAGGCTTGCGCCGGGCGGTAGATGCGCCGCCAGGCGAAAAACAGCGCGACCAGCGCTGCGCC";
+ std::string seq2 = "CTCGCACTTG" "AGACCTCACC" "TAGATGCGCCG";
+ // Reverse complement:
+ // CGGCGCATCTA GGTGAGGTCT CAAGTGCGAG
+ int32_t k = 10;
+ std::vector result = findKmerMatches(seq1, seq2, k);
+ std::vector expected = {KmerHit(0, 0), KmerHit(21, 10),
+ KmerHit(56, 20), KmerHit(57, 21) };
+ std::sort(result.begin(), result.end());
+ std::sort(expected.begin(), expected.end());
+ QCOMPARE((int64_t) result.size(), (int64_t) expected.size());
+ QCOMPARE(result, expected);
+ }
+
+ { // Test the reverse complement matching.
+ std::string seq1 = "CTCGCACTTG";
+ std::string seq2 = "CAAGTGCGAG";
+ int32_t k = 10;
+ std::vector result = findKmerMatches(seq1, seq2, k);
+ std::vector expected = {KmerHit(0, 9) };
+ std::sort(result.begin(), result.end());
+ std::sort(expected.begin(), expected.end());
+ QCOMPARE((int64_t) result.size(), (int64_t) expected.size());
+ QCOMPARE(result, expected);
+ }
+}
diff --git a/ui/blastsearchdialog.cpp b/ui/blastsearchdialog.cpp
index 9594c9db..a223fb96 100644
--- a/ui/blastsearchdialog.cpp
+++ b/ui/blastsearchdialog.cpp
@@ -159,6 +159,7 @@ void BlastSearchDialog::clearBlastHits()
ui->blastHitsTableWidget->clearContents();
while (ui->blastHitsTableWidget->rowCount() > 0)
ui->blastHitsTableWidget->removeRow(0);
+ g_assemblyGraph->clearAllBlastHitPointers();
}
void BlastSearchDialog::fillTablesAfterBlastSearch()
diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp
index 9d1008c5..376276c7 100644
--- a/ui/mainwindow.cpp
+++ b/ui/mainwindow.cpp
@@ -67,6 +67,7 @@
#include "changenodedepthdialog.h"
#include
#include "graphinfodialog.h"
+#include "program/dotplot.h"
MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) :
QMainWindow(0),
@@ -165,6 +166,7 @@ MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) :
connect(ui->contiguityButton, SIGNAL(clicked()), this, SLOT(determineContiguityFromSelectedNode()));
connect(ui->actionBring_selected_nodes_to_front, SIGNAL(triggered()), this, SLOT(bringSelectedNodesToFront()));
connect(ui->actionSelect_nodes_with_BLAST_hits, SIGNAL(triggered()), this, SLOT(selectNodesWithBlastHits()));
+ connect(ui->actionSelect_nodes_with_dead_ends, SIGNAL(triggered()), this, SLOT(selectNodesWithDeadEnds()));
connect(ui->actionSelect_all, SIGNAL(triggered()), this, SLOT(selectAll()));
connect(ui->actionSelect_none, SIGNAL(triggered()), this, SLOT(selectNone()));
connect(ui->actionInvert_selection, SIGNAL(triggered()), this, SLOT(invertSelection()));
@@ -194,6 +196,7 @@ MainWindow::MainWindow(QString fileToLoadOnStartup, bool drawGraphAfterLoad) :
connect(ui->actionChange_node_name, SIGNAL(triggered(bool)), this, SLOT(changeNodeName()));
connect(ui->actionChange_node_depth, SIGNAL(triggered(bool)), this, SLOT(changeNodeDepth()));
connect(ui->moreInfoButton, SIGNAL(clicked(bool)), this, SLOT(openGraphInfoDialog()));
+ connect(ui->drawDotplotButton, SIGNAL(clicked()), this, SLOT(drawDotplot()));
connect(this, SIGNAL(windowLoaded()), this, SLOT(afterMainWindowShow()), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));
}
@@ -717,6 +720,60 @@ void MainWindow::setDepthRangeWidgetVisibility(bool visible)
ui->maxDepthSpinBox->setVisible(visible);
}
+void MainWindow::drawDotplotPoweredByLogo(double x, double y, double w) {
+ QPen pen;
+ pen.setWidth(0);
+ pen.setColor(QColor("#BBBBBB"));
+ QBrush brush(QColor("#BBBBBB"));
+
+ QPen outline;
+ outline.setWidth(1);
+ outline.setColor(QColor("#888888"));
+
+ m_dotplotScene->addRect(x + w, y, w, w, pen, brush);
+ m_dotplotScene->addRect(x + 3*w, y, w, w, pen, brush);
+ m_dotplotScene->addRect(x, y + w, 5 * w, w, pen, brush);
+ m_dotplotScene->addRect(x + w, y + 2 * w, w, w, pen, brush);
+ m_dotplotScene->addRect(x + 3*w, y + 2 * w, w, w, pen, brush);
+ m_dotplotScene->addRect(x, y + 3 * w, 5 * w, w, pen, brush);
+ m_dotplotScene->addRect(x, y + 4 * w, w, w, pen, brush);
+ m_dotplotScene->addRect(x + 2*w, y + 4 * w, w, w, pen, brush);
+ m_dotplotScene->addRect(x + 4*w, y + 4 * w, w, w, pen, brush);
+
+ m_dotplotScene->addLine(x + 1 * w, y + 0 * w, x + 2 * w, y + 0 * w, outline);
+ m_dotplotScene->addLine(x + 2 * w, y + 0 * w, x + 2 * w, y + 1 * w, outline);
+ m_dotplotScene->addLine(x + 2 * w, y + 1 * w, x + 3 * w, y + 1 * w, outline);
+ m_dotplotScene->addLine(x + 3 * w, y + 1 * w, x + 3 * w, y + 0 * w, outline);
+ m_dotplotScene->addLine(x + 3 * w, y + 0 * w, x + 4 * w, y + 0 * w, outline);
+ m_dotplotScene->addLine(x + 4 * w, y + 0 * w, x + 4 * w, y + 1 * w, outline);
+ m_dotplotScene->addLine(x + 4 * w, y + 1 * w, x + 5 * w, y + 1 * w, outline);
+ m_dotplotScene->addLine(x + 5 * w, y + 1 * w, x + 5 * w, y + 2 * w, outline);
+ m_dotplotScene->addLine(x + 5 * w, y + 2 * w, x + 4 * w, y + 2 * w, outline);
+ m_dotplotScene->addLine(x + 4 * w, y + 2 * w, x + 4 * w, y + 3 * w, outline);
+ m_dotplotScene->addLine(x + 4 * w, y + 3 * w, x + 5 * w, y + 3 * w, outline);
+ m_dotplotScene->addLine(x + 5 * w, y + 3 * w, x + 5 * w, y + 5 * w, outline);
+ m_dotplotScene->addLine(x + 5 * w, y + 5 * w, x + 4 * w, y + 5 * w, outline);
+ m_dotplotScene->addLine(x + 4 * w, y + 5 * w, x + 4 * w, y + 4 * w, outline);
+ m_dotplotScene->addLine(x + 4 * w, y + 4 * w, x + 3 * w, y + 4 * w, outline);
+ m_dotplotScene->addLine(x + 3 * w, y + 4 * w, x + 3 * w, y + 5 * w, outline);
+ m_dotplotScene->addLine(x + 3 * w, y + 5 * w, x + 2 * w, y + 5 * w, outline);
+ m_dotplotScene->addLine(x + 2 * w, y + 5 * w, x + 2 * w, y + 4 * w, outline);
+ m_dotplotScene->addLine(x + 2 * w, y + 4 * w, x + 1 * w, y + 4 * w, outline);
+ m_dotplotScene->addLine(x + 1 * w, y + 4 * w, x + 1 * w, y + 5 * w, outline);
+ m_dotplotScene->addLine(x + 1 * w, y + 5 * w, x + 0 * w, y + 5 * w, outline);
+ m_dotplotScene->addLine(x + 0 * w, y + 5 * w, x + 0 * w, y + 3 * w, outline);
+ m_dotplotScene->addLine(x + 0 * w, y + 3 * w, x + 1 * w, y + 3 * w, outline);
+ m_dotplotScene->addLine(x + 1 * w, y + 3 * w, x + 1 * w, y + 2 * w, outline);
+ m_dotplotScene->addLine(x + 1 * w, y + 2 * w, x + 0 * w, y + 2 * w, outline);
+ m_dotplotScene->addLine(x + 0 * w, y + 2 * w, x + 0 * w, y + 1 * w, outline);
+ m_dotplotScene->addLine(x + 0 * w, y + 1 * w, x + 1 * w, y + 1 * w, outline);
+ m_dotplotScene->addLine(x + 1 * w, y + 1 * w, x + 1 * w, y + 0 * w, outline);
+ m_dotplotScene->addLine(x + 2 * w, y + 2 * w, x + 3 * w, y + 2 * w, outline);
+ m_dotplotScene->addLine(x + 3 * w, y + 2 * w, x + 3 * w, y + 3 * w, outline);
+ m_dotplotScene->addLine(x + 3 * w, y + 3 * w, x + 2 * w, y + 3 * w, outline);
+ m_dotplotScene->addLine(x + 2 * w, y + 3 * w, x + 2 * w, y + 2 * w, outline);
+
+}
void MainWindow::drawGraph()
{
@@ -738,6 +795,158 @@ void MainWindow::drawGraph()
layoutGraph();
}
+void MainWindow::drawDotplot()
+{
+ // Fetch the selected nodes.
+ std::vector selectedNodes = m_scene->getSelectedNodes();
+
+ // Limit the number of nodes that need to be selected, and notify the user.
+ if (selectedNodes.size() < 1 || selectedNodes.size() > 2) {
+ QString infoTitle = "Draw dotplot";
+ QString infoMessage = "Select either one (for self-dotplot) or two nodes to dotplot.";
+ QMessageBox::information(this, infoTitle, infoMessage);
+ return;
+ }
+
+ // Placeholder for the sequences which will be dotplotted.
+ std::vector headers;
+ std::vector seqs;
+
+ // Enable self-dotplots.
+ std::vector nodes_to_process = selectedNodes;
+ if (selectedNodes.size() == 1) {
+ nodes_to_process.push_back(selectedNodes[0]);
+ }
+
+ // Get the sequences and the headers of the nodes to draw.
+ for (size_t i=0; isequenceIsMissing()) {
+ QString infoTitle = "Draw dotplot";
+ QString infoMessage = "Error: The GFA node does not contain a valid sequence!";
+ QMessageBox::information(this, infoTitle, infoMessage);
+ return;
+ }
+
+ QByteArray nodeSequence = node->getSequence();
+ QString nodeHeader = node->getName();
+ std::string seq(nodeSequence.constData(), nodeSequence.length());
+ std::string header = nodeHeader.toLocal8Bit().constData();
+
+ seqs.push_back(seq);
+ headers.push_back(header);
+ }
+
+ int32_t k = ui->kmerSizeInput->value();
+
+ // Sanity check for the k-mer size.
+ if (k <= 0 || k > 30) {
+ QString infoTitle = "Draw dotplot";
+ QString infoMessage = "Error: k-mer size should be > 0 and <= 30.";
+ QMessageBox::information(this, infoTitle, infoMessage);
+ return;
+ }
+
+ // Calculate the dotplot.
+ auto hits = findKmerMatches(seqs[0], seqs[1], k);
+
+ // Prepare the scene and plot.
+ m_dotplotScene = std::shared_ptr(new QGraphicsScene());
+ ui->dotplotGraphicsView->setScene(m_dotplotScene.get());
+
+ // Calculate the starts and ends of the dotplot coordinate system.
+ int32_t max_len = std::max(seqs[0].size(), seqs[1].size());
+ double begin_offset = 40;
+ double end_offset = 10;
+ double x_begin = begin_offset;
+ double y_begin = begin_offset;
+ double max_size = (float) std::min(ui->dotplotGraphicsView->maximumWidth(), ui->dotplotGraphicsView->maximumHeight()) - end_offset - begin_offset;
+ double scale = max_size / ((double) max_len);
+ double x_end = x_begin + seqs[0].size() * scale;
+ double y_end = y_begin + seqs[1].size() * scale;
+
+ // Make the scene not move.
+ ui->dotplotGraphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ ui->dotplotGraphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_dotplotScene->setSceneRect(0, 0, 290, 290);
+
+ // Add bounds to the dotplot graph.
+ double overhang = 5;
+ m_dotplotScene->addLine(x_begin - overhang, y_begin, x_end, y_begin);
+ m_dotplotScene->addLine(x_end, y_begin - overhang, x_end, y_end);
+ m_dotplotScene->addLine(x_begin - overhang, y_end, x_end, y_end);
+ m_dotplotScene->addLine(x_begin, y_begin - overhang, x_begin, y_end);
+
+ double logo_w = 2;
+ double logo_x = x_begin - 0 - 6 * logo_w;
+ double logo_y = x_begin - 0 - 6 * logo_w;
+ drawDotplotPoweredByLogo(logo_x, logo_y, logo_w);
+
+ // Annotate the graph.
+ QFont font;
+ font.setPixelSize(8);
+ font.setFamily("Monospace");
+
+ {
+ QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(seqs[0].size()));
+ text->setFont(font);
+ text->setPos(x_end - text->boundingRect().width(), y_begin - text->boundingRect().height());
+ }
+
+ {
+ QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(0));
+ text->setFont(font);
+ text->setPos(x_begin + 1, y_begin - text->boundingRect().height());
+ }
+
+ {
+ QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(0));
+ text->setFont(font);
+ text->setPos(x_begin - text->boundingRect().width(), y_begin);
+ }
+
+ {
+ QGraphicsTextItem *text = m_dotplotScene->addText(QString("%1").arg(seqs[1].size()));
+ text->setFont(font);
+ text->setPos(x_begin - text->boundingRect().width(), y_end - text->boundingRect().height());
+ }
+
+ {
+ std::string trimmed_header = (headers[0].size() > 20) ? headers[0].substr(0, 20) : headers[0];
+ QGraphicsTextItem *text = m_dotplotScene->addText(QString(trimmed_header.c_str()));
+ text->setFont(font);
+ text->setPos((x_end + x_begin - text->boundingRect().width()) / 2.0, y_begin - text->boundingRect().height());
+ }
+
+ {
+ std::string trimmed_header = (headers[1].size() > 20) ? (headers[1].substr(0, 20)) : (headers[1]);
+ QGraphicsTextItem *text = m_dotplotScene->addText(QString(trimmed_header.c_str()));
+ text->setFont(font);
+ QTransform t;
+ t.rotate(270);
+ text->setTransform(t);
+ text->setPos(x_begin - text->boundingRect().height(), (y_begin + y_end + text->boundingRect().width()) / 2.0);
+ }
+
+ // Generate the actual dotplot.
+ QPen pen(Qt::black);
+ QBrush brush(Qt::black);
+ for (auto& hit: hits) {
+ m_dotplotScene->addEllipse(hit.x * scale + x_begin, hit.y * scale + y_begin, 2.0 * scale, 2.0 * scale,
+ pen, brush);
+ }
+
+ // Scale the dotplot so it fits in the view with just a bit of margin.
+ QRectF sceneRectangle = m_dotplotScene->sceneRect();
+ sceneRectangle.setWidth(sceneRectangle.width() * 1.05);
+ sceneRectangle.setHeight(sceneRectangle.height() * 1.05);
+ ui->dotplotGraphicsView->fitInView(sceneRectangle);
+
+ ui->dotplotGraphicsView->show();
+}
+
+
void MainWindow::graphLayoutFinished()
{
@@ -1897,6 +2106,54 @@ void MainWindow::selectNodesWithBlastHits()
}
+void MainWindow::selectNodesWithDeadEnds()
+{
+ m_scene->blockSignals(true);
+ m_scene->clearSelection();
+
+ bool atLeastOneNodeHasDeadEnd = false;
+ bool atLeastOneNodeSelected = false;
+
+ QMapIterator i(g_assemblyGraph->m_deBruijnGraphNodes);
+ while (i.hasNext())
+ {
+ i.next();
+ DeBruijnNode * node = i.value();
+
+ bool nodeHasDeadEnd = node->getDeadEndCount() > 0;
+ if (nodeHasDeadEnd)
+ atLeastOneNodeHasDeadEnd = true;
+
+ GraphicsItemNode * graphicsItemNode = node->getGraphicsItemNode();
+
+ if (graphicsItemNode == 0)
+ continue;
+
+ if (nodeHasDeadEnd)
+ {
+ graphicsItemNode->setSelected(true);
+ atLeastOneNodeSelected = true;
+ }
+ }
+ m_scene->blockSignals(false);
+ g_graphicsView->viewport()->update();
+ selectionChanged();
+
+ if (!atLeastOneNodeHasDeadEnd)
+ {
+ QMessageBox::information(this, "No dead ends", "Nothing was selected because this graph has no dead ends.");
+ return;
+ }
+
+ if (!atLeastOneNodeSelected)
+ QMessageBox::information(this, "No dead ends in visible nodes",
+ "Nothing was selected because no dead ends are currently visible. "
+ "Adjust the graph scope to make the nodes with dead ends hits visible.");
+ else
+ zoomToSelection();
+}
+
+
void MainWindow::selectAll()
{
m_scene->blockSignals(true);
@@ -2252,7 +2509,7 @@ void MainWindow::webBlastSelectedNodes()
QByteArray urlSafeFasta = makeStringUrlSafe(selectedNodesFasta);
QByteArray url = "http://blast.ncbi.nlm.nih.gov/Blast.cgi?PROGRAM=blastn&PAGE_TYPE=BlastSearch&LINK_LOC=blasthome&QUERY=" + urlSafeFasta;
-
+
if (url.length() < 8190)
QDesktopServices::openUrl(QUrl(url));
diff --git a/ui/mainwindow.h b/ui/mainwindow.h
index fafc0864..04383ed1 100644
--- a/ui/mainwindow.h
+++ b/ui/mainwindow.h
@@ -62,6 +62,7 @@ class MainWindow : public QMainWindow
UiState m_uiState;
BlastSearchDialog * m_blastSearchDialog;
bool m_alreadyShown;
+ std::shared_ptr m_dotplotScene;
void cleanUp();
void displayGraphDetails();
@@ -132,6 +133,7 @@ private slots:
void graphLayoutCancelled();
void bringSelectedNodesToFront();
void selectNodesWithBlastHits();
+ void selectNodesWithDeadEnds();
void selectAll();
void selectNone();
void invertSelection();
@@ -159,6 +161,8 @@ private slots:
void changeNodeName();
void changeNodeDepth();
void openGraphInfoDialog();
+ void drawDotplotPoweredByLogo(double x, double y, double w);
+ void drawDotplot();
protected:
void showEvent(QShowEvent *ev);
diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui
index 7ac8182f..f1e2b6b6 100644
--- a/ui/mainwindow.ui
+++ b/ui/mainwindow.ui
@@ -58,7 +58,7 @@
0
- 0
+ -194
289
1058
@@ -195,6 +195,9 @@
-
+
+ Qt::StrongFocus
+
More info
@@ -349,6 +352,9 @@
0
+
+ Qt::StrongFocus
+
Exact
@@ -365,6 +371,9 @@
0
+
+ Qt::StrongFocus
+
Partial
@@ -434,6 +443,9 @@
0
+
+ Qt::StrongFocus
+
Single
@@ -450,6 +462,9 @@
0
+
+ Qt::StrongFocus
+
Double
@@ -460,6 +475,9 @@
-
+
+ Qt::StrongFocus
+
Draw graph
@@ -489,6 +507,9 @@
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -524,6 +545,9 @@
0
+
+ Qt::StrongFocus
+
-
@@ -534,6 +558,9 @@
0
+
+ Qt::StrongFocus
+
-
Entire graph
@@ -597,6 +624,9 @@
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -684,6 +714,9 @@
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -823,6 +856,9 @@
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -848,6 +884,9 @@
-
+
+ Qt::StrongFocus
+
Determine contiguity
@@ -855,6 +894,9 @@
-
+
+ Qt::StrongFocus
+
-
Random colours
@@ -936,6 +978,9 @@
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -1065,6 +1110,9 @@
-
+
+ Qt::StrongFocus
+
Font
@@ -1072,6 +1120,9 @@
-
+
+ Qt::StrongFocus
+
Text outline
@@ -1097,6 +1148,9 @@
-
+
+ Qt::StrongFocus
+
Custom
@@ -1104,6 +1158,9 @@
-
+
+ Qt::StrongFocus
+
Name
@@ -1111,6 +1168,9 @@
-
+
+ Qt::StrongFocus
+
Length
@@ -1118,6 +1178,9 @@
-
+
+ Qt::StrongFocus
+
Depth
@@ -1125,6 +1188,9 @@
-
+
+ Qt::StrongFocus
+
BLAST hits
@@ -1135,10 +1201,16 @@
false
+
+ Qt::StrongFocus
+
-
+
+ Qt::StrongFocus
+
CSV data:
@@ -1272,6 +1344,9 @@
0
+
+ Qt::StrongFocus
+
-
none
@@ -1281,6 +1356,9 @@
-
+
+ Qt::StrongFocus
+
Create/view BLAST search
@@ -1355,8 +1433,8 @@
0
0
- 249
- 899
+ 326
+ 1204
@@ -1431,6 +1509,9 @@
-
+
+ Qt::StrongFocus
+
Exact
@@ -1483,6 +1564,9 @@
-
+
+ Qt::StrongFocus
+
Partial
@@ -1499,6 +1583,9 @@
0
+
+ Qt::StrongFocus
+
@@ -1506,14 +1593,43 @@
-
+
+ Qt::StrongFocus
+
Find node(s)
-
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Dotplot
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
0
@@ -1527,17 +1643,50 @@
0
-
-
+
- Qt::Vertical
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ k-mer size:
+
+
+
+ -
+
+
+ Qt::StrongFocus
-
- QSizePolicy::Fixed
+
+ 1
+
+
+ 30
+
+
+ 15
+
+
+
+ -
+
+
+ Qt::Horizontal
- 20
- 60
+ 0
+ 20
@@ -1545,6 +1694,86 @@
+ -
+
+
+ Qt::StrongFocus
+
+
+ Draw dotplot
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 20
+
+
+
+
+ -
+
+
+
+ 300
+ 300
+
+
+
+
+ 300
+ 300
+
+
+
+ QPainter::Antialiasing|QPainter::TextAntialiasing
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
-
@@ -1656,6 +1885,9 @@
-
+
+ Qt::StrongFocus
+
Ctrl+L
@@ -1666,6 +1898,9 @@
-
+
+ Qt::StrongFocus
+
Ctrl+O
@@ -1692,22 +1927,6 @@
0
-
-
-
-
- Qt::Vertical
-
-
- QSizePolicy::Fixed
-
-
-
- 20
- 60
-
-
-
-
@@ -1828,6 +2047,7 @@
+
@@ -2244,11 +2469,14 @@
controlsScrollArea
+ moreInfoButton
graphScopeComboBox
startingNodesLineEdit
startingNodesExactMatchRadioButton
startingNodesPartialMatchRadioButton
nodeDistanceSpinBox
+ minDepthSpinBox
+ maxDepthSpinBox
singleNodesRadioButton
doubleNodesRadioButton
drawGraphButton
@@ -2261,6 +2489,9 @@
nodeLengthsCheckBox
nodeDepthCheckBox
blastHitsCheckBox
+ setNodeCustomLabelButton
+ csvCheckBox
+ csvComboBox
fontButton
textOutlineCheckBox
blastSearchButton
@@ -2270,10 +2501,12 @@
selectionSearchNodesExactMatchRadioButton
selectionSearchNodesPartialMatchRadioButton
selectNodesButton
+ kmerSizeInput
+ drawDotplotButton
+ dotplotGraphicsView
selectedNodesTextEdit
- setNodeCustomColourButton
- setNodeCustomLabelButton
selectedEdgesTextEdit
+ setNodeCustomColourButton
diff --git a/ui/settingsdialog.ui b/ui/settingsdialog.ui
index 2c27a310..b3b6f2c4 100644
--- a/ui/settingsdialog.ui
+++ b/ui/settingsdialog.ui
@@ -31,6 +31,9 @@
-
+
+ Qt::NoFocus
+
true
@@ -38,8 +41,8 @@
0
- -62
- 400
+ -2839
+ 402
3466
@@ -155,6 +158,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
Auto
@@ -165,6 +171,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
Manual
@@ -188,6 +197,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -253,6 +265,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -304,6 +319,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -329,6 +347,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -473,6 +494,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -593,6 +617,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
On
@@ -603,6 +630,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
Off
@@ -648,6 +678,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -718,6 +751,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
4
@@ -785,6 +821,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -935,6 +974,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
@@ -1009,6 +1051,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
@@ -1067,6 +1112,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
@@ -1080,6 +1128,9 @@ per megabase:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -1114,6 +1165,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
On
@@ -1124,6 +1178,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
Off
@@ -1158,6 +1215,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
On
@@ -1168,6 +1228,9 @@ per megabase:
-
+
+ Qt::StrongFocus
+
Off
@@ -1397,6 +1460,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
@@ -1426,6 +1492,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -1451,6 +1520,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
@@ -1555,6 +1627,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Over visible regions
@@ -1565,6 +1640,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
On node centre
@@ -1709,6 +1787,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -1737,6 +1818,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2076,6 +2160,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
255
@@ -2092,6 +2179,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
255
@@ -2163,6 +2253,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
255
@@ -2173,6 +2266,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2202,6 +2298,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
255
@@ -2257,6 +2356,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2267,6 +2369,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2299,6 +2404,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
255
@@ -2309,6 +2417,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2319,6 +2430,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2364,6 +2478,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
255
@@ -2374,6 +2491,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2483,6 +2603,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
@@ -2519,6 +2642,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
@@ -2548,6 +2674,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
@@ -2739,6 +2868,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
@@ -2791,6 +2923,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
@@ -2944,6 +3079,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2957,6 +3095,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -2987,6 +3128,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Auto
@@ -2997,6 +3141,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Manual
@@ -3144,6 +3291,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
@@ -3333,6 +3483,9 @@ single node style:
0
+
+ Qt::StrongFocus
+
@@ -3401,6 +3554,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
@@ -3447,6 +3603,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
@@ -3467,6 +3626,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
@@ -3487,6 +3649,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
@@ -3494,6 +3659,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3590,6 +3758,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3622,6 +3793,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3641,6 +3815,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3654,6 +3831,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Min mean hit identity:
@@ -3661,6 +3841,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Min query path hit coverage:
@@ -3668,6 +3851,9 @@ single node style:
-
+
+ Qt::StrongFocus
+
Maximum length
discrepancy (bases):
@@ -3676,6 +3862,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Max e-value product:
@@ -3683,6 +3872,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Maximum path length:
@@ -3697,6 +3889,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Minimum path length:
@@ -3704,6 +3899,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Minimum length
discrepancy (bases):
@@ -3712,6 +3910,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3725,6 +3926,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3747,6 +3951,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3760,6 +3967,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3936,6 +4146,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -3971,6 +4184,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
1
@@ -3981,6 +4197,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -4010,6 +4229,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Qt::AlignCenter
@@ -4072,6 +4294,9 @@ discrepancy (bases):
-
+
+ Qt::StrongFocus
+
Restore defaults
@@ -4079,6 +4304,9 @@ discrepancy (bases):
-
+
+ Qt::NoFocus
+
Qt::Horizontal
@@ -4123,12 +4351,79 @@ discrepancy (bases):
- scrollArea
+ nodeLengthPerMegabaseAutoRadioButton
+ nodeLengthPerMegabaseManualRadioButton
+ nodeLengthPerMegabaseManualSpinBox
+ minimumNodeLengthSpinBox
+ edgeLengthSpinBox
+ edgeWidthSpinBox
+ doubleModeNodeSeparationSpinBox
+ nodeSegmentLengthSpinBox
+ graphLayoutQualitySlider
+ linearLayoutOnRadioButton
+ linearLayoutOffRadioButton
+ componentSeparationSpinBox
+ edgeColourButton
+ outlineColourButton
+ outlineThicknessSpinBox
+ selectionColourButton
antialiasingOnRadioButton
antialiasingOffRadioButton
+ singleNodeArrowHeadsOnRadioButton
+ singleNodeArrowHeadsOffRadioButton
+ textColourButton
+ textOutlineThicknessSpinBox
+ textOutlineColourButton
positionVisibleRadioButton
positionCentreRadioButton
+ depthEffectOnWidthSpinBox
+ depthPowerSpinBox
+ randomColourPositiveSaturationSlider
+ randomColourPositiveSaturationSpinBox
+ randomColourNegativeSaturationSlider
+ randomColourNegativeSaturationSpinBox
+ randomColourPositiveLightnessSlider
+ randomColourPositiveLightnessSpinBox
+ randomColourNegativeLightnessSlider
+ randomColourNegativeLightnessSpinBox
+ randomColourPositiveOpacitySlider
+ randomColourPositiveOpacitySpinBox
+ randomColourNegativeOpacitySlider
+ randomColourNegativeOpacitySpinBox
+ uniformPositiveNodeColourButton
+ uniformNegativeNodeColourButton
+ uniformNodeSpecialColourButton
+ lowDepthColourButton
+ highDepthColourButton
depthValueAutoRadioButton
+ depthValueManualRadioButton
+ lowDepthValueSpinBox
+ highDepthValueSpinBox
+ noBlastHitsColourButton
+ contiguitySearchDepthSpinBox
+ contiguousStrandSpecificColourButton
+ contiguousEitherStrandColourButton
+ maybeContiguousColourButton
+ notContiguousColourButton
+ contiguityStartingColourButton
+ maxHitsForQueryPathSpinBox
+ maxPathNodesSpinBox
+ minQueryCoveredByPathSpinBox
+ minQueryCoveredByHitsCheckBox
+ minQueryCoveredByHitsSpinBox
+ minMeanHitIdentityCheckBox
+ minMeanHitIdentitySpinBox
+ maxEValueProductCheckBox
+ maxEValueCoefficientSpinBox
+ maxEValueExponentSpinBox
+ minLengthPercentageCheckBox
+ minLengthPercentageSpinBox
+ maxLengthPercentageCheckBox
+ maxLengthPercentageSpinBox
+ minLengthBaseDiscrepancyCheckBox
+ minLengthBaseDiscrepancySpinBox
+ maxLengthBaseDiscrepancyCheckBox
+ maxLengthBaseDiscrepancySpinBox
restoreDefaultsButton