diff --git a/src/lib/component/controller/GraphController.cpp b/src/lib/component/controller/GraphController.cpp index 612b332c..246a7fc7 100644 --- a/src/lib/component/controller/GraphController.cpp +++ b/src/lib/component/controller/GraphController.cpp @@ -3,6 +3,7 @@ #include #include "utility/logging/logging.h" +#include "utility/messaging/type/MessageStatus.h" #include "utility/tracing.h" #include "utility/utility.h" #include "utility/utilityString.h" @@ -106,6 +107,8 @@ void GraphController::handleMessage(MessageActivateTrail* message) { TRACE("trail activate"); + MessageStatus("Retrieving graph data", false, true).dispatch(); + m_activeEdgeIds.clear(); std::shared_ptr graph = m_storageAccess->getGraphForTrail( @@ -142,9 +145,13 @@ void GraphController::handleMessage(MessageActivateTrail* message) } } + MessageStatus("Layouting graph", false, true).dispatch(); + TrailLayouter layout(direction); layout.layoutGraph(m_dummyNodes, m_dummyEdges, m_topLevelAncestorIds); + MessageStatus("Displaying graph", false, true).dispatch(); + buildGraph(message, true, true, false); } diff --git a/src/lib/component/controller/helper/TrailLayouter.cpp b/src/lib/component/controller/helper/TrailLayouter.cpp index db0cfc73..68d843ce 100644 --- a/src/lib/component/controller/helper/TrailLayouter.cpp +++ b/src/lib/component/controller/helper/TrailLayouter.cpp @@ -4,6 +4,7 @@ TrailLayouter::TrailLayouter(LayoutDirection dir) : m_direction(dir) + , m_rootNode(nullptr) { } @@ -14,7 +15,13 @@ void TrailLayouter::layoutGraph( { buildGraph(dummyNodes, dummyEdges, topLevelAncestorIds); - makeAcyclic(); + if (!m_rootNode) + { + return; + } + + removeDeadEnds(); + makeAcyclicRecursive(m_rootNode, std::set()); assignLongestPathLevels(); assignRemainingLevels(); @@ -44,46 +51,87 @@ void TrailLayouter::buildGraph( { dummyEdge->path.clear(); - if (dummyEdge->data && !dummyEdge->data->isType(Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE)) - { - addEdge(dummyEdge, topLevelAncestorIds); - } - } - - for (const std::shared_ptr dummyEdge : dummyEdges) - { - if (dummyEdge->data && dummyEdge->data->isType(Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE)) - { - addEdge(dummyEdge, topLevelAncestorIds); - } + addEdge(dummyEdge, topLevelAncestorIds); } } -void TrailLayouter::makeAcyclic() +void TrailLayouter::removeDeadEnds() { - std::set edgesToSwitch; - for (std::shared_ptr node : m_allNodes) - { - if (!node->incomingEdges.size()) - { - utility::append(edgesToSwitch, node->outgoingEdges); - } - } + std::set predecessors; - for (TrailEdge* edge : edgesToSwitch) - { - switchEdge(edge); - } + std::set deadEnds; + std::set loseEnds; - for (TrailNode* node : m_rootNodes) + std::deque nodes; + nodes.push_back(m_rootNode); + + while (nodes.size()) { - std::set edgesToSwitch = node->incomingEdges; - for (TrailEdge* edge : edgesToSwitch) + TrailNode* node = nodes.front(); + nodes.pop_front(); + + if (predecessors.find(node) == predecessors.end()) { - switchEdge(edge); + predecessors.insert(node); + + for (TrailEdge* edge : node->outgoingEdges) + { + if (predecessors.find(edge->target) == predecessors.end()) + { + nodes.push_back(edge->target); + } + } + + for (TrailEdge* edge : node->incomingEdges) + { + if (predecessors.find(edge->origin) == predecessors.end()) + { + loseEnds.insert(edge->origin); + } + } + + if (!node->outgoingEdges.size()) + { + deadEnds.insert(node); + } } - makeAcyclicRecursive(node, std::set()); + while (!nodes.size() && (deadEnds.size() || loseEnds.size()) && predecessors.size() < m_allNodes.size()) + { + if (deadEnds.size()) + { + TrailNode* deadEnd = *deadEnds.begin(); + deadEnds.erase(deadEnds.begin()); + + for (TrailEdge* edge : deadEnd->incomingEdges) + { + if (predecessors.find(edge->origin) == predecessors.end()) + { + nodes.push_back(edge->origin); + switchEdge(edge); + break; + } + } + } + else + { + TrailNode* loseEnd = *loseEnds.begin(); + loseEnds.erase(loseEnds.begin()); + + if (predecessors.find(loseEnd) == predecessors.end()) + { + for (TrailEdge* edge : loseEnd->outgoingEdges) + { + if (predecessors.find(edge->target) != predecessors.end()) + { + nodes.push_back(loseEnd); + switchEdge(edge); + break; + } + } + } + } + } } } @@ -112,7 +160,9 @@ void TrailLayouter::makeAcyclicRecursive(TrailNode* node, std::set p void TrailLayouter::assignLongestPathLevels() { - std::set nodes(m_rootNodes.begin(), m_rootNodes.end()); + std::set nodes; + nodes.insert(m_rootNode); + std::map predecessorNodes; int level = 0; @@ -160,16 +210,26 @@ void TrailLayouter::assignLongestPathLevels() void TrailLayouter::assignRemainingLevels() { - std::set nodes(m_rootNodes.begin(), m_rootNodes.end()); + std::multimap nodes; + nodes.emplace(m_rootNode->level, m_rootNode); + + std::set allNodes; + allNodes.insert(m_rootNode); + while (nodes.size()) { - std::set newNodes; + std::multimap newNodes; - for (TrailNode* node : nodes) + for (std::pair p : nodes) { + TrailNode* node = p.second; + for (TrailEdge* edge : node->outgoingEdges) { - newNodes.insert(edge->target); + if (allNodes.insert(edge->target).second) + { + newNodes.emplace(edge->target->level, edge->target); + } } if (node->level < 0) @@ -178,7 +238,21 @@ void TrailLayouter::assignRemainingLevels() for (TrailEdge* edge : node->incomingEdges) { - level = std::max(level, edge->origin->level + 1); + if (edge->origin->level == -1) + { + if (allNodes.insert(edge->origin).second) + { + newNodes.emplace(edge->origin->level, edge->origin); + } + + level = node->level; + newNodes.emplace(level, node); + break; + } + else + { + level = std::max(level, edge->origin->level + 1); + } } node->level = level; @@ -376,61 +450,134 @@ void TrailLayouter::layout() // process layout before highest column for (size_t i = maxHeightIndex; i > 0; i--) { - moveNodesToAveragePosition(m_nodesPerCol[i - 1]); + moveNodesToAveragePosition(m_nodesPerCol[i - 1], false); } // process layout after highest column for (size_t i = maxHeightIndex + 1; i < m_nodesPerCol.size(); i++) { - moveNodesToAveragePosition(m_nodesPerCol[i]); + moveNodesToAveragePosition(m_nodesPerCol[i], true); } // put into grid } -void TrailLayouter::moveNodesToAveragePosition(std::vector nodes) +void TrailLayouter::moveNodesToAveragePosition(std::vector nodes, bool forward) { unsigned int yIdx = horizontalLayout() ? 1 : 0; - for (size_t k = 0; k < 2; k++) + std::map> averagePositions; + for (TrailNode* node : nodes) { - for (size_t j = 0; j < nodes.size(); j++) + int sum = 0; + int count = 0; + + if ((forward && node->incomingEdges.size()) || (!forward && !node->outgoingEdges.size())) { - size_t l = k ? nodes.size() - 1 - j : j; - size_t i = l % 2 ? nodes.size() - (l + 1) / 2 : l / 2; - TrailNode* node = nodes[i]; - - int sum = 0; - int count = 0; - - for (TrailEdge* edge : node->outgoingEdges) - { - sum += edge->target->pos.getValue(yIdx) + edge->target->size.getValue(yIdx) / 2; - count++; - } - for (TrailEdge* edge : node->incomingEdges) { sum += edge->origin->pos.getValue(yIdx) + edge->origin->size.getValue(yIdx) / 2; count++; } - - if (count) + } + else + { + for (TrailEdge* edge : node->outgoingEdges) { - node->pos.setValue(yIdx, sum / count - node->size.getValue(yIdx) / 2); + sum += edge->target->pos.getValue(yIdx) + edge->target->size.getValue(yIdx) / 2; + count++; + } + } - TrailNode* above = i > 0 ? nodes[i - 1] : nullptr; - if (above && above->pos.getValue(yIdx) + above->size.getValue(yIdx) + 30 > node->pos.getValue(yIdx)) - { - node->pos.setValue(yIdx, above->pos.getValue(yIdx) + above->size.getValue(yIdx) + 30); - } + if (count) + { + averagePositions[sum / count].push_back(node); + } + } - TrailNode* below = i + 1 < nodes.size() ? nodes[i + 1] : nullptr; - if (below && below->pos.getValue(yIdx) - 30 < node->pos.getValue(yIdx) + node->size.getValue(yIdx)) + if (!averagePositions.size()) + { + return; + } + + int averagePosition = 0; + for (std::pair> p : averagePositions) + { + averagePosition += p.first; + } + averagePosition /= averagePositions.size(); + + + std::multimap distanceFromAveragePosition; + for (std::pair> p : averagePositions) + { + distanceFromAveragePosition.emplace(std::abs(averagePosition - p.first), p.first); + } + + int currentTop = averagePosition; + int currentBottom = averagePosition; + + for (std::pair p : distanceFromAveragePosition) + { + int groupAveragePosition = p.second; + std::vector nodeGroup = averagePositions.find(groupAveragePosition)->second; + + int size = -30; + for (TrailNode* node : nodeGroup) + { + size += node->size.getValue(yIdx) + 30; + } + + int top = groupAveragePosition - size / 2; + if (currentTop != currentBottom) + { + if (top < currentTop) + { + if (top + size + 30 > currentTop) { - node->pos.setValue(yIdx, below->pos.getValue(yIdx) - 30 - node->size.getValue(yIdx)); + top = currentTop - 30 - size; } } + else if (top > currentBottom) + { + if (top - 30 < currentBottom) + { + top = currentBottom + 30; + } + } + else + { + if ((currentTop + currentBottom) / 2 > top + size / 2) + { + top = currentTop - 30 - size; + } + else + { + top = currentBottom + 30; + } + } + } + + int y = top; + + for (TrailNode* node : nodeGroup) + { + node->pos.setValue(yIdx, y); + y += node->size.getValue(yIdx) + 30; + } + + if (currentTop == currentBottom) + { + currentTop = top; + currentBottom = top + size; + } + else if (top < currentTop) + { + currentTop = top; + } + else if (top + size > currentBottom) + { + currentBottom = top + size; } } } @@ -468,15 +615,21 @@ void TrailLayouter::print() std::cout << "graph: " << std::endl; for (std::shared_ptr node : m_allNodes) { - std::cout << node->id << "\t" << node->level << "\t"; - std::cout << node->incomingEdges.size() << "\t" << node->outgoingEdges.size() << "\t"; - std::cout << node->name << std::endl; + if (node->id) + { + std::cout << node->id << "\t" << node->level << "\t"; + std::cout << node->incomingEdges.size() << "\t" << node->outgoingEdges.size() << "\t"; + std::cout << node->name << std::endl; + } } std::cout << std::endl; for (std::shared_ptr edge : m_allEdges) { - std::cout << edge->id << "\t" << edge->origin->name << "\t" << edge->target->name << std::endl; + if (edge->origin->id || edge->target->id) + { + std::cout << edge->id << "\t" << edge->origin->name << "\t" << edge->target->name << std::endl; + } } std::cout << std::endl; } @@ -498,9 +651,9 @@ void TrailLayouter::addNode(const std::shared_ptr& dummyNode) m_nodesById.emplace(node->id, node.get()); } - if (dummyNode->hasActiveSubNode()) + if (!m_rootNode && dummyNode->hasActiveSubNode()) { - m_rootNodes.push_back(node.get()); + m_rootNode = node.get(); } } diff --git a/src/lib/component/controller/helper/TrailLayouter.h b/src/lib/component/controller/helper/TrailLayouter.h index 89dd6fd6..deb277dd 100644 --- a/src/lib/component/controller/helper/TrailLayouter.h +++ b/src/lib/component/controller/helper/TrailLayouter.h @@ -62,7 +62,7 @@ private: const std::vector>& dummyEdges, const std::map& topLevelAncestorIds); - void makeAcyclic(); + void removeDeadEnds(); void makeAcyclicRecursive(TrailNode* node, std::set predecessors); void assignLongestPathLevels(); @@ -73,7 +73,7 @@ private: void reduceEdgeCrossings(); void layout(); - void moveNodesToAveragePosition(std::vector nodes); + void moveNodesToAveragePosition(std::vector nodes, bool forward); void retrievePositions(const std::map& topLevelAncestorIds); void print(); @@ -91,7 +91,7 @@ private: std::vector> m_allEdges; std::map m_nodesById; - std::vector m_rootNodes; + TrailNode* m_rootNode; std::vector> m_nodesPerCol; }; diff --git a/src/lib_gui/qt/view/QtGraphView.cpp b/src/lib_gui/qt/view/QtGraphView.cpp index ddc4fc74..b37ebac8 100644 --- a/src/lib_gui/qt/view/QtGraphView.cpp +++ b/src/lib_gui/qt/view/QtGraphView.cpp @@ -20,6 +20,7 @@ #include "utility/messaging/type/MessageActivateTrail.h" #include "utility/messaging/type/MessageDeactivateEdge.h" #include "utility/messaging/type/MessageScrollGraph.h" +#include "utility/messaging/type/MessageStatus.h" #include "utility/ResourcePaths.h" #include "qt/graphics/QtGraphicsView.h" @@ -232,16 +233,23 @@ void QtGraphView::finishedTransition() void QtGraphView::clickedInEmptySpace() { - size_t activeEdgeCount = 0; + std::vector> activeEdges; for (std::shared_ptr edge : m_oldEdges) { if (edge->getIsActive()) { - activeEdgeCount++; + activeEdges.push_back(edge); } } - if (activeEdgeCount == 1) + if (m_graph && m_graph->getTrailMode() != Graph::TRAIL_NONE) + { + for (std::shared_ptr edge : activeEdges) + { + edge->setIsActive(false); + } + } + else if (activeEdges.size() == 1) { MessageDeactivateEdge(false).dispatch(); } @@ -519,6 +527,11 @@ void QtGraphView::switchToNewGraphData() view->update(); updateTrailButtons(); + + if (m_oldGraph && m_oldGraph->getTrailMode() != Graph::TRAIL_NONE) + { + MessageStatus("Finished graph display").dispatch(); + } } QtGraphicsView* QtGraphView::getView() const @@ -554,6 +567,8 @@ void QtGraphView::doRebuildGraph( QGraphicsView* view = getView(); + + // create nodes size_t activeNodeCount = 0; for (unsigned int i = 0; i < nodes.size(); i++) { @@ -574,31 +589,42 @@ void QtGraphView::doRebuildGraph( } } - Graph::TrailMode trailMode = m_graph ? m_graph->getTrailMode() : Graph::TRAIL_NONE; - if (trailMode == Graph::TRAIL_NONE) - { - QPointF center = itemsBoundingRect(m_nodes).center(); - Vec2i o = GraphViewStyle::alignOnRaster(Vec2i(center.x(), center.y())); - QPointF offset = QPointF(o.x, o.y); - m_sceneRectOffset = offset - center; - for (const std::shared_ptr& node : m_nodes) - { - node->setPos(node->pos() - offset); - } + // move graph to center + QPointF center = itemsBoundingRect(m_nodes).center(); + Vec2i o = GraphViewStyle::alignOnRaster(Vec2i(center.x(), center.y())); + QPointF offset = QPointF(o.x, o.y); + m_sceneRectOffset = offset - center; + + for (const std::shared_ptr& node : m_nodes) + { + node->setPos(node->pos() - offset); } m_edges.clear(); - std::set visibleEdgeIds; for (std::shared_ptr edge : edges) + { + for (size_t i = 0; i < edge->path.size(); i++) + { + edge->path[i].x = edge->path[i].x - offset.x(); + edge->path[i].z = edge->path[i].z - offset.x(); + edge->path[i].y = edge->path[i].y - offset.y(); + edge->path[i].w = edge->path[i].w - offset.y(); + } + } + + // create edges + Graph::TrailMode trailMode = m_graph ? m_graph->getTrailMode() : Graph::TRAIL_NONE; + std::set visibleEdgeIds; + for (const std::shared_ptr edge : edges) { if (!edge->data || !edge->data->isType(Edge::EDGE_AGGREGATION)) { createEdge(view, edge.get(), &visibleEdgeIds, trailMode); } } - for (std::shared_ptr edge : edges) + for (const std::shared_ptr edge : edges) { if (edge->data && edge->data->isType(Edge::EDGE_AGGREGATION)) { @@ -850,7 +876,7 @@ QRectF QtGraphView::getSceneRect(const std::list>& sceneRect |= rect; } - return sceneRect.adjusted(-25, -25, 25, 25).translated(m_sceneRectOffset); + return sceneRect.adjusted(-75, -75, 75, 75).translated(m_sceneRectOffset); } void QtGraphView::compareNodesRecursive( @@ -1081,11 +1107,16 @@ void QtGraphView::doFocusOut(const std::vector& tokenIds) if (node && node->isDataNode()) { node->focusOut(); + continue; + } + + for (std::shared_ptr edge : m_oldEdges) + { + if (edge->getData() && edge->getData()->getId() == tokenId) + { + edge->focusOut(); + break; + } } } - - for (std::shared_ptr edge : m_oldEdges) - { - edge->focusOut(); - } } diff --git a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp index e8be8ad3..f23ee60c 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp @@ -1,6 +1,7 @@ #include "qt/view/graphElements/QtGraphEdge.h" #include +#include #include #include "component/view/GraphViewStyle.h" @@ -39,7 +40,8 @@ QtGraphEdge::QtGraphEdge( , m_isHorizontalTrail(false) , m_mousePos(0.0f, 0.0f) , m_mouseMoved(false) - , m_willDispatchMessageFocusIn(false) + , m_willFocusIn(false) + , m_ignoreFocusIn(false) { if (m_direction == TokenComponentAggregation::DIRECTION_BACKWARD) { @@ -104,17 +106,10 @@ void QtGraphEdge::updateLine() if (m_isTrailEdge) { - if (!m_child) + for (QGraphicsItem* item : childItems()) { - m_child = new QGraphicsLineItem(this); - } - else - { - for (QGraphicsItem* item : m_child->childItems()) - { - item->hide(); - item->setParentItem(nullptr); - } + item->hide(); + item->setParentItem(nullptr); } style.originOffset.y() = 0; @@ -128,13 +123,13 @@ void QtGraphEdge::updateLine() for (const Vec4i& rect : m_path) { - QtLineItemBezier* bezier = new QtLineItemBezier(m_child); + QtLineItemBezier* bezier = new QtLineItemBezier(this); bezier->updateLine(ownerRect, rect, ownerParentRect, rect, style, m_weight, false); bezier->setRoute(route); bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE); bezier->setToolTip(toolTip); - QtLineItemStraight* line = new QtLineItemStraight(m_child); + QtLineItemStraight* line = new QtLineItemStraight(this); line->setToolTip(toolTip); if (route == QtLineItemBase::ROUTE_HORIZONTAL) { @@ -151,7 +146,7 @@ void QtGraphEdge::updateLine() bool showArrow = m_direction != TokenComponentAggregation::DIRECTION_NONE; - QtLineItemBezier* bezier = new QtLineItemBezier(m_child); + QtLineItemBezier* bezier = new QtLineItemBezier(this); bezier->updateLine( ownerRect, target->getBoundingRect(), ownerParentRect, target->getParentBoundingRect(), style, m_weight, showArrow); @@ -312,43 +307,47 @@ void QtGraphEdge::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) void QtGraphEdge::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { - if (!getData()) + if (!m_ignoreFocusIn) { - focusIn(); - return; - } - - if (!m_willDispatchMessageFocusIn) - { - QTimer::singleShot(50, this, SLOT(dispatchMessageFocusIn())); - m_willDispatchMessageFocusIn = true; + QTimer::singleShot(50, this, SLOT(doFocusIn())); + m_ignoreFocusIn = true; + m_willFocusIn = true; } } void QtGraphEdge::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { + m_willFocusIn = false; + QTimer::singleShot(100, this, SLOT(doFocusOut())); + if (!getData()) { focusOut(); return; } - if (m_willDispatchMessageFocusIn) - { - m_willDispatchMessageFocusIn = false; - return; - } - MessageFocusOut(std::vector(1, getData()->getId())).dispatch(); } -void QtGraphEdge::dispatchMessageFocusIn() +void QtGraphEdge::doFocusIn() { - if (m_willDispatchMessageFocusIn) + if (!m_willFocusIn) { - m_willDispatchMessageFocusIn = false; - MessageFocusIn(std::vector(1, getData()->getId())).dispatch(); + return; } + + if (!getData() || isTrailEdge()) + { + focusIn(); + return; + } + + MessageFocusIn(std::vector(1, getData()->getId())).dispatch(); +} + +void QtGraphEdge::doFocusOut() +{ + m_ignoreFocusIn = false; } void QtGraphEdge::setDirection(TokenComponentAggregation::Direction direction) diff --git a/src/lib_gui/qt/view/graphElements/QtGraphEdge.h b/src/lib_gui/qt/view/graphElements/QtGraphEdge.h index 6f0d2a3d..cf64ef16 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphEdge.h +++ b/src/lib_gui/qt/view/graphElements/QtGraphEdge.h @@ -60,7 +60,8 @@ protected: virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); private slots: - void dispatchMessageFocusIn(); + void doFocusIn(); + void doFocusOut(); private: const Edge* m_data; @@ -68,7 +69,7 @@ private: std::weak_ptr m_owner; std::weak_ptr m_target; - QGraphicsLineItem* m_child; + QGraphicsItem* m_child; bool m_isActive; bool m_fromActive; @@ -87,7 +88,8 @@ private: Vec2i m_mousePos; bool m_mouseMoved; - bool m_willDispatchMessageFocusIn; + bool m_willFocusIn; + bool m_ignoreFocusIn; }; #endif // QT_GRAPH_EDGE_H