//Copyright 2017 Ryan Wick //This file is part of Bandage //Bandage is free software: you can redistribute it and/or modify //it under the terms of the GNU General Public License as published by //the Free Software Foundation, either version 3 of the License, or //(at your option) any later version. //Bandage is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU General Public License for more details. //You should have received a copy of the GNU General Public License //along with Bandage. If not, see . #include "debruijnnode.h" #include "debruijnedge.h" #include "ogdfnode.h" #include "graphicsitemnode.h" #include #include "../blast/blasthit.h" #include "../blast/blastquery.h" #include "assemblygraph.h" #include #include #include //The length parameter is optional. If it is set, then the node will use that //for its length. If not set, it will just use the sequence length. DeBruijnNode::DeBruijnNode(QString name, double depth, QByteArray sequence, int length) : m_name(name), m_depth(depth), m_depthRelativeToMeanDrawnDepth(1.0), m_sequence(sequence), m_length(sequence.length()), m_contiguityStatus(NOT_CONTIGUOUS), m_reverseComplement(0), m_ogdfNode(0), m_graphicsItemNode(0), m_specialNode(false), m_drawn(false), m_highestDistanceInNeighbourSearch(0), m_csvData() { if (length > 0) m_length = length; } DeBruijnNode::~DeBruijnNode() { if (m_ogdfNode != 0) delete m_ogdfNode; } //This function adds an edge to the Node, but only if the edge hasn't already //been added. void DeBruijnNode::addEdge(DeBruijnEdge * edge) { if (std::find(m_edges.begin(), m_edges.end(), edge) == m_edges.end()) m_edges.push_back(edge); } //This function deletes an edge from the node, if it exists. void DeBruijnNode::removeEdge(DeBruijnEdge * edge) { m_edges.erase(std::remove(m_edges.begin(), m_edges.end(), edge), m_edges.end()); } //This function resets the node to the state it would be in after a graph //file was loaded - no contiguity status and no OGDF nodes. void DeBruijnNode::resetNode() { if (m_ogdfNode != 0) delete m_ogdfNode; m_ogdfNode = 0; m_graphicsItemNode = 0; resetContiguityStatus(); setAsNotDrawn(); setAsNotSpecial(); m_highestDistanceInNeighbourSearch = 0; } void DeBruijnNode::addToOgdfGraph(ogdf::Graph * ogdfGraph, ogdf::GraphAttributes * graphAttributes, ogdf::EdgeArray * edgeArray, double xPos, double yPos) { //If this node or its reverse complement is already in OGDF, then //it's not necessary to make the node. if (thisOrReverseComplementInOgdf()) return; //Create the OgdfNode object m_ogdfNode = new OgdfNode(); //Each node in the Velvet sense is made up of multiple nodes in the //OGDF sense. This way, Velvet nodes appear as lines whose length //corresponds to the sequence length. double drawnNodeLength = getDrawnNodeLength(); int numberOfGraphEdges = getNumberOfOgdfGraphEdges(drawnNodeLength); int numberOfGraphNodes = numberOfGraphEdges + 1; double drawnLengthPerEdge = drawnNodeLength / numberOfGraphEdges; ogdf::node newNode = 0; ogdf::node previousNode = 0; for (int i = 0; i < numberOfGraphNodes; ++i) { newNode = ogdfGraph->newNode(); m_ogdfNode->addOgdfNode(newNode); if (g_assemblyGraph->useLinearLayout()) { graphAttributes->x(newNode) = xPos; graphAttributes->y(newNode) = yPos; xPos += g_settings->nodeSegmentLength; } if (i > 0) { ogdf::edge newEdge = ogdfGraph->newEdge(previousNode, newNode); (*edgeArray)[newEdge] = drawnLengthPerEdge; } previousNode = newNode; } } double DeBruijnNode::getDrawnNodeLength() const { double drawnNodeLength = getNodeLengthPerMegabase() * double(getLength()) / 1000000.0; if (drawnNodeLength < g_settings->minimumNodeLength) drawnNodeLength = g_settings->minimumNodeLength; return drawnNodeLength; } int DeBruijnNode::getNumberOfOgdfGraphEdges(double drawnNodeLength) const { int numberOfGraphEdges = ceil(drawnNodeLength / g_settings->nodeSegmentLength); if (numberOfGraphEdges <= 0) numberOfGraphEdges = 1; return numberOfGraphEdges; } //This function determines the contiguity of nodes relative to this one. //It has two steps: // -First, for each edge leaving this node, all paths outward are found. // Any nodes in any path are MAYBE_CONTIGUOUS, and nodes in all of the // paths are CONTIGUOUS. // -Second, it is necessary to check in the opposite direction - for each // of the MAYBE_CONTIGUOUS nodes, do they have a path that unambiguously // leads to this node? If so, then they are CONTIGUOUS. void DeBruijnNode::determineContiguity() { upgradeContiguityStatus(STARTING); //A set is used to store all nodes found in the paths, as the nodes //that show up as MAYBE_CONTIGUOUS will have their paths checked //to this node. std::set allCheckedNodes; //For each path leaving this node, find all possible paths //outward. Nodes in any of the paths for an edge are //MAYBE_CONTIGUOUS. Nodes in all of the paths for an edge //are CONTIGUOUS. for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; bool outgoingEdge = (this == edge->getStartingNode()); std::vector< std::vector > allPaths; edge->tracePaths(outgoingEdge, g_settings->contiguitySearchSteps, &allPaths, this); //Set all nodes in the paths as MAYBE_CONTIGUOUS for (size_t j = 0; j < allPaths.size(); ++j) { QApplication::processEvents(); for (size_t k = 0; k < allPaths[j].size(); ++k) { DeBruijnNode * node = allPaths[j][k]; node->upgradeContiguityStatus(MAYBE_CONTIGUOUS); allCheckedNodes.insert(node); } } //Set all common nodes as CONTIGUOUS_STRAND_SPECIFIC std::vector commonNodesStrandSpecific = getNodesCommonToAllPaths(&allPaths, false); for (size_t j = 0; j < commonNodesStrandSpecific.size(); ++j) (commonNodesStrandSpecific[j])->upgradeContiguityStatus(CONTIGUOUS_STRAND_SPECIFIC); //Set all common nodes (when including reverse complement nodes) //as CONTIGUOUS_EITHER_STRAND std::vector commonNodesEitherStrand = getNodesCommonToAllPaths(&allPaths, true); for (size_t j = 0; j < commonNodesEitherStrand.size(); ++j) { DeBruijnNode * node = commonNodesEitherStrand[j]; node->upgradeContiguityStatus(CONTIGUOUS_EITHER_STRAND); node->getReverseComplement()->upgradeContiguityStatus(CONTIGUOUS_EITHER_STRAND); } } //For each node that was checked, then we check to see if any //of its paths leads unambiuously back to the starting node (this node). for (std::set::iterator i = allCheckedNodes.begin(); i != allCheckedNodes.end(); ++i) { QApplication::processEvents(); DeBruijnNode * node = *i; ContiguityStatus status = node->getContiguityStatus(); //First check without reverse complement target for //strand-specific contiguity. if (status != CONTIGUOUS_STRAND_SPECIFIC && node->doesPathLeadOnlyToNode(this, false)) node->upgradeContiguityStatus(CONTIGUOUS_STRAND_SPECIFIC); //Now check including the reverse complement target for //either strand contiguity. if (status != CONTIGUOUS_STRAND_SPECIFIC && status != CONTIGUOUS_EITHER_STRAND && node->doesPathLeadOnlyToNode(this, true)) { node->upgradeContiguityStatus(CONTIGUOUS_EITHER_STRAND); node->getReverseComplement()->upgradeContiguityStatus(CONTIGUOUS_EITHER_STRAND); } } } //This function differs from the above by including all reverse complement //nodes in the path search. std::vector DeBruijnNode::getNodesCommonToAllPaths(std::vector< std::vector > * paths, bool includeReverseComplements) const { std::vector commonNodes; //If there are no paths, then return the empty vector. if (paths->size() == 0) return commonNodes; //If there is only one path in path, then they are all common nodes commonNodes = (*paths)[0]; if (paths->size() == 1) return commonNodes; //If there are two or more paths, it's necessary to find the intersection. for (size_t i = 1; i < paths->size(); ++i) { QApplication::processEvents(); std::vector * path = &((*paths)[i]); //If we are including reverse complements in the search, //then it is necessary to build a new vector that includes //reverse complement nodes and then use that vector. std::vector pathWithReverseComplements; if (includeReverseComplements) { for (size_t j = 0; j < path->size(); ++j) { DeBruijnNode * node = (*path)[j]; pathWithReverseComplements.push_back(node); pathWithReverseComplements.push_back(node->getReverseComplement()); } path = &pathWithReverseComplements; } //Combine the commonNodes vector with the path vector, //excluding any repeats. std::sort(commonNodes.begin(), commonNodes.end()); std::sort(path->begin(), path->end()); std::vector newCommonNodes; std::set_intersection(commonNodes.begin(), commonNodes.end(), path->begin(), path->end(), std::back_inserter(newCommonNodes)); commonNodes = newCommonNodes; } return commonNodes; } //This function checks whether this node has any path leading outward that //unambiguously leads to the given node. //It checks a number of steps as set by the contiguitySearchSteps setting. //If includeReverseComplement is true, then this function returns true if //all paths lead either to the node or its reverse complement node. bool DeBruijnNode::doesPathLeadOnlyToNode(DeBruijnNode * node, bool includeReverseComplement) { for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; bool outgoingEdge = (this == edge->getStartingNode()); std::vector pathSoFar; pathSoFar.push_back(this); if (edge->leadsOnlyToNode(outgoingEdge, g_settings->contiguitySearchSteps, node, pathSoFar, includeReverseComplement)) return true; } return false; } //This function only upgrades a node's status, never downgrades. void DeBruijnNode::upgradeContiguityStatus(ContiguityStatus newStatus) { if (newStatus < m_contiguityStatus) m_contiguityStatus = newStatus; } //It is expected that the argument connectedNode is either in incomingNodes or //outgoingNodes. If that node is the only one in whichever container it is in, //this function returns true. bool DeBruijnNode::isOnlyPathInItsDirection(DeBruijnNode * connectedNode, std::vector * incomingNodes, std::vector * outgoingNodes) const { std::vector * container; if (std::find(incomingNodes->begin(), incomingNodes->end(), connectedNode) != incomingNodes->end()) container = incomingNodes; else container = outgoingNodes; return (container->size() == 1 && (*container)[0] == connectedNode); } bool DeBruijnNode::isNotOnlyPathInItsDirection(DeBruijnNode * connectedNode, std::vector * incomingNodes, std::vector * outgoingNodes) const { return !isOnlyPathInItsDirection(connectedNode, incomingNodes, outgoingNodes); } QByteArray DeBruijnNode::getFasta(bool sign, bool newLines, bool evenIfEmpty) const { QByteArray sequence = getSequence(); if (sequence.isEmpty() && !evenIfEmpty) return QByteArray(); QByteArray fasta = ">"; fasta += getNodeNameForFasta(sign).toUtf8(); fasta += "\n"; if (newLines) fasta += AssemblyGraph::addNewlinesToSequence(sequence); else { fasta += sequence; fasta += "\n"; } return fasta; } QByteArray DeBruijnNode::getGfaSegmentLine(QString depthTag) const { QByteArray gfaSequence = getSequenceForGfa(); QByteArray gfaSegmentLine = "S"; gfaSegmentLine += "\t" + getNameWithoutSign().toUtf8(); gfaSegmentLine += "\t" + gfaSequence; gfaSegmentLine += "\tLN:i:" + QString::number(gfaSequence.length()).toUtf8(); //We use the depthTag to guide how we save the node depth. //If it is empty, that implies that the loaded graph did not have depth //information and so we don't save depth. if (depthTag == "DP") gfaSegmentLine += "\tDP:f:" + QString::number(getDepth()).toUtf8(); else if (depthTag == "KC") gfaSegmentLine += "\tKC:i:" + QString::number(int(getDepth() * gfaSequence.length() + 0.5)).toUtf8(); else if (depthTag == "RC") gfaSegmentLine += "\tRC:i:" + QString::number(int(getDepth() * gfaSequence.length() + 0.5)).toUtf8(); else if (depthTag == "FC") gfaSegmentLine += "\tFC:i:" + QString::number(int(getDepth() * gfaSequence.length() + 0.5)).toUtf8(); //If the user has included custom labels or colours, include those. if (!m_customLabel.isEmpty()) gfaSegmentLine += "\tLB:z:" + getCustomLabel().toUtf8(); if (!m_reverseComplement->m_customLabel.isEmpty()) gfaSegmentLine += "\tL2:z:" + m_reverseComplement->getCustomLabel().toUtf8(); if (hasCustomColour()) gfaSegmentLine += "\tCL:z:" + getColourName(getCustomColour()).toUtf8(); if (m_reverseComplement->hasCustomColour()) gfaSegmentLine += "\tC2:z:" + getColourName(m_reverseComplement->getCustomColour()).toUtf8(); gfaSegmentLine += "\n"; return gfaSegmentLine; } //This function gets the node's sequence for a GFA file. It has two main //differences from getSequence: // -If the graph is from Velvet, it will extend the node sequences // -If the sequence is missing, it will just give "*" QByteArray DeBruijnNode::getSequenceForGfa() const { if (sequenceIsMissing()) return QByteArray("*"); if (g_assemblyGraph->m_graphFileType != LAST_GRAPH) return getSequence(); //If the code got here, then we are getting a full sequence from a Velvet //LastGraph graph, so we need to extend the beginning of the sequence. int extensionLength = g_assemblyGraph->m_kmer - 1; //If the node is at least k-1 in length, then the necessary sequence can be //deduced from the reverse complement node. if (getLength() >= extensionLength) { QByteArray revCompSeq = getReverseComplement()->getSequence(); QByteArray endOfRevCompSeq = revCompSeq.right(extensionLength); QByteArray extension = AssemblyGraph::getReverseComplement(endOfRevCompSeq); return extension + getSequence(); } //If the node is not long enough, then we must look in upstream nodes for //the rest of the sequence. else { QByteArray extension = getUpstreamSequence(extensionLength); if (extension.length() < extensionLength) { int additionalBases = extensionLength - extension.length(); QByteArray n; n.fill('N', additionalBases); extension = n + extension; } return extension + getSequence(); } } QByteArray DeBruijnNode::getUpstreamSequence(int upstreamSequenceLength) const { std::vector upstreamNodes = getUpstreamNodes(); QByteArray bestUpstreamNodeSequence; for (size_t i = 0; i < upstreamNodes.size(); ++i) { DeBruijnNode * upstreamNode = upstreamNodes[i]; QByteArray upstreamNodeFullSequence = upstreamNode->getSequence(); QByteArray upstreamNodeSequence; //If the upstream node has enough sequence, great! if (upstreamNodeFullSequence.length() >= upstreamSequenceLength) upstreamNodeSequence = upstreamNodeFullSequence.right(upstreamSequenceLength); //If the upstream node does not have enough sequence, then we need to //look even further upstream. else upstreamNodeSequence = upstreamNode->getUpstreamSequence(upstreamSequenceLength - upstreamNodeFullSequence.length()) + upstreamNodeFullSequence; //If we now have enough sequence, then we can return it. if (upstreamNodeSequence.length() == upstreamSequenceLength) return upstreamNodeSequence; //If we don't have enough sequence, then we need to try the next //upstream node. If our current one is the best so far, save that in //case no complete sequence is found. if (upstreamNodeSequence.length() > bestUpstreamNodeSequence.length()) bestUpstreamNodeSequence = upstreamNodeSequence; } //If the code got here, that means that not enough upstream sequence was //found in any path! Return what we have managed to get so far. return bestUpstreamNodeSequence; } int DeBruijnNode::getFullLength() const { if (g_assemblyGraph->m_graphFileType != LAST_GRAPH) return getLength(); else return getLength() + g_assemblyGraph->m_kmer - 1; } int DeBruijnNode::getLengthWithoutTrailingOverlap() const { int length = getLength(); std::vector leavingEdges = getLeavingEdges(); if (leavingEdges.size() == 0) return length; int maxOverlap = 0; for (size_t i = 0; i < leavingEdges.size(); ++i) { int overlap = leavingEdges[i]->getOverlap(); if (overlap > maxOverlap) maxOverlap = overlap; } length -= maxOverlap; if (length < 0) length = 0; return length; } QString DeBruijnNode::getNodeNameForFasta(bool sign) const { QString nodeNameForFasta; nodeNameForFasta += "NODE_"; if (sign) nodeNameForFasta += getName(); else nodeNameForFasta += getNameWithoutSign(); nodeNameForFasta += "_length_"; nodeNameForFasta += QByteArray::number(getLength()); nodeNameForFasta += "_cov_"; nodeNameForFasta += QByteArray::number(getDepth()); return nodeNameForFasta; } //This function recursively labels all nodes as drawn that are within a //certain distance of this node. Whichever node called this will //definitely be drawn, so that one is excluded from the recursive call. void DeBruijnNode::labelNeighbouringNodesAsDrawn(int nodeDistance, DeBruijnNode * callingNode) { if (m_highestDistanceInNeighbourSearch > nodeDistance) return; m_highestDistanceInNeighbourSearch = nodeDistance; if (nodeDistance == 0) return; DeBruijnNode * otherNode; for (size_t i = 0; i < m_edges.size(); ++i) { otherNode = m_edges[i]->getOtherNode(this); if (otherNode == callingNode) continue; if (g_settings->doubleMode) otherNode->m_drawn = true; else //single mode { if (otherNode->isPositiveNode()) otherNode->m_drawn = true; else otherNode->getReverseComplement()->m_drawn = true; } otherNode->labelNeighbouringNodesAsDrawn(nodeDistance-1, this); } } std::vector DeBruijnNode::getBlastHitPartsForThisNode(double scaledNodeLength) const { std::vector returnVector; for (size_t i = 0; i < m_blastHits.size(); ++i) { std::vector hitParts = m_blastHits[i]->getBlastHitParts(false, scaledNodeLength); returnVector.insert(returnVector.end(), hitParts.begin(), hitParts.end()); } return returnVector; } std::vector DeBruijnNode::getBlastHitPartsForThisNodeOrReverseComplement(double scaledNodeLength) const { const DeBruijnNode * positiveNode = this; const DeBruijnNode * negativeNode = getReverseComplement(); if (isNegativeNode()) std::swap(positiveNode, negativeNode); //Look for blast hit parts on both the positive and the negative node, //since hits were previously filtered such that startPos < endPos, //hence we need to look at both positive and negative nodes to recover all hits. std::vector returnVector; for (size_t i = 0; i < positiveNode->m_blastHits.size(); ++i) { std::vector hitParts = positiveNode->m_blastHits[i]->getBlastHitParts(false, scaledNodeLength); returnVector.insert(returnVector.end(), hitParts.begin(), hitParts.end()); } for (size_t i = 0; i < negativeNode->m_blastHits.size(); ++i) { std::vector hitParts = negativeNode->m_blastHits[i]->getBlastHitParts(true, scaledNodeLength); returnVector.insert(returnVector.end(), hitParts.begin(), hitParts.end()); } return returnVector; } bool DeBruijnNode::isPositiveNode() const { QChar lastChar = m_name.at(m_name.length() - 1); return lastChar == '+'; } bool DeBruijnNode::isNegativeNode() const { QChar lastChar = m_name.at(m_name.length() - 1); return lastChar == '-'; } //This function checks to see if the passed node leads into //this node. If so, it returns the connecting edge. If not, //it returns a null pointer. DeBruijnEdge * DeBruijnNode::doesNodeLeadIn(DeBruijnNode * node) const { for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; if (edge->getStartingNode() == node && edge->getEndingNode() == this) return edge; } return 0; } //This function checks to see if the passed node leads away from //this node. If so, it returns the connecting edge. If not, //it returns a null pointer. DeBruijnEdge * DeBruijnNode::doesNodeLeadAway(DeBruijnNode * node) const { for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; if (edge->getStartingNode() == this && edge->getEndingNode() == node) return edge; } return 0; } bool DeBruijnNode::isNodeConnected(DeBruijnNode * node) const { for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; if (edge->getStartingNode() == node || edge->getEndingNode() == node) return true; } return false; } std::vector DeBruijnNode::getEnteringEdges() const { std::vector returnVector; for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; if (this == edge->getEndingNode()) returnVector.push_back(edge); } return returnVector; } std::vector DeBruijnNode::getLeavingEdges() const { std::vector returnVector; for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; if (this == edge->getStartingNode()) returnVector.push_back(edge); } return returnVector; } std::vector DeBruijnNode::getDownstreamNodes() const { std::vector leavingEdges = getLeavingEdges(); std::vector returnVector; for (size_t i = 0; i < leavingEdges.size(); ++i) returnVector.push_back(leavingEdges[i]->getEndingNode()); return returnVector; } std::vector DeBruijnNode::getUpstreamNodes() const { std::vector enteringEdges = getEnteringEdges(); std::vector returnVector; for (size_t i = 0; i < enteringEdges.size(); ++i) returnVector.push_back(enteringEdges[i]->getStartingNode()); return returnVector; } double DeBruijnNode::getNodeLengthPerMegabase() const { if (g_settings->nodeLengthMode == AUTO_NODE_LENGTH) return g_settings->autoNodeLengthPerMegabase; else return g_settings->manualNodeLengthPerMegabase; } bool DeBruijnNode::isInDepthRange(double min, double max) const { return m_depth >= min && m_depth <= max; } bool DeBruijnNode::sequenceIsMissing() const { return m_sequence == "*" || (m_sequence == "" && m_length > 0); } QByteArray DeBruijnNode::getSequence() const { if (sequenceIsMissing() && g_assemblyGraph->m_sequencesLoadedFromFasta == NOT_TRIED) g_assemblyGraph->attemptToLoadSequencesFromFasta(); //If the sequence is still missing, return a string of Ns equal to the //sequence length. if (sequenceIsMissing()) return QByteArray(m_length, 'N'); else return m_sequence; } //If the node has an edge which leads to itself (creating a loop), this function //will return it. Otherwise, it returns 0. DeBruijnEdge * DeBruijnNode::getSelfLoopingEdge() const { for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; if (edge->getStartingNode() == this && edge->getEndingNode() == this) return edge; } return 0; } //This function returns either 0, 1 or 2. A node with connections on both ends //(i.e. has both incoming and outgoing edges) returns 0. A node with no edges //returns 2. A node with either incoming or outgoing edges returns 1. int DeBruijnNode::getDeadEndCount() const { if (m_edges.size() == 0) return 2; std::vector enteringEdges = getEnteringEdges(); std::vector leavingEdges = getLeavingEdges(); if (enteringEdges.size() > 0 && leavingEdges.size() > 0) return 0; else return 1; } //This function returns all of the positive nodes that this node (or its //reverse complement) are connected to. std::vector DeBruijnNode::getAllConnectedPositiveNodes() const { QSet connectedPositiveNodesSet; for (size_t i = 0; i < m_edges.size(); ++i) { DeBruijnEdge * edge = m_edges[i]; DeBruijnNode * connectedNode = edge->getOtherNode(this); if (connectedNode->isNegativeNode()) connectedNode = connectedNode->getReverseComplement(); connectedPositiveNodesSet.insert(connectedNode); } std::vector connectedPositiveNodesVector; QSetIterator i(connectedPositiveNodesSet); while (i.hasNext()) connectedPositiveNodesVector.push_back(i.next()); return connectedPositiveNodesVector; } void DeBruijnNode::setCustomLabel(QString newLabel) { newLabel.replace("\t", " "); m_customLabel = newLabel; } QStringList DeBruijnNode::getCustomLabelForDisplay() const { QStringList customLabelLines; if (!getCustomLabel().isEmpty()) { QStringList labelLines = getCustomLabel().split("\\n"); for (int i = 0; i < labelLines.size(); ++i) customLabelLines << labelLines[i]; } if (!g_settings->doubleMode && !m_reverseComplement->getCustomLabel().isEmpty()) { QStringList labelLines2 = m_reverseComplement->getCustomLabel().split("\n"); for (int i = 0; i < labelLines2.size(); ++i) customLabelLines << labelLines2[i]; } return customLabelLines; } QColor DeBruijnNode::getCustomColourForDisplay() const { if (hasCustomColour()) return getCustomColour(); if (!g_settings->doubleMode && m_reverseComplement->hasCustomColour()) return m_reverseComplement->getCustomColour(); return g_settings->defaultCustomNodeColour; }