ui: Improved horizontal graph layout

* extend horizontal edges to origin column width
* leave horizontal gap in the middle of the left and right buckets for routing edges
* move left and right buckets vertically so that edges enter and leave horizontally
* removed third pivoting for graph edges, all edges enter and leave relative to the node center now
This commit is contained in:
Eberhard Graether
2018-04-10 01:45:56 +02:00
parent 535f7c1a9c
commit 20f87b084f
10 changed files with 194 additions and 68 deletions
@@ -55,7 +55,7 @@ const DummyNode::BundledNodesSet& Bucket::getNodes() const
return m_nodes;
}
void Bucket::preLayout(Vec2i viewSize)
void Bucket::preLayout(Vec2i viewSize, bool addVerticalOffset)
{
int cols = (viewSize.y > 0 ? (m_height / viewSize.y) : 0) + 1;
@@ -66,37 +66,137 @@ void Bucket::preLayout(Vec2i viewSize)
m_height = 0;
std::vector<int> colWidths;
std::vector<int> colHeights;
std::vector<std::vector<DummyNode*>> nodesInCol;
nodesInCol.push_back({ });
for (const std::shared_ptr<DummyNode>& node : m_nodes)
{
if (y > height)
{
colHeights.push_back(y - GraphViewStyle::s_gridCellPadding);
colWidths.push_back(width);
y = 0;
x += GraphViewStyle::toGridOffset(width + 45);
width = 0;
nodesInCol.push_back({ });
}
node->position.x = x;
node->position.y = y;
nodesInCol.back().push_back(node.get());
y += GraphViewStyle::toGridSize(node->size.y) + GraphViewStyle::s_gridCellPadding;
width = (node->size.x > width ? node->size.x : width);
m_height = (y > m_height ? y : m_height);
}
if (y > height)
colHeights.push_back(y - GraphViewStyle::s_gridCellPadding);
colWidths.push_back(width);
m_width = x + width;
m_height -= GraphViewStyle::s_gridCellPadding;
for (size_t i = 0; i < nodesInCol.size(); i++)
{
for (DummyNode* node : nodesInCol[i])
{
y = 0;
x += GraphViewStyle::toGridOffset(width + 30);
width = 0;
node->columnSize.x = colWidths[i];
node->columnSize.y = colHeights[i];
}
}
m_width = x + width;
if (!addVerticalOffset || nodesInCol.size() < 2)
{
return;
}
std::vector<DummyNode*> aboveNodes;
std::vector<DummyNode*> belowNodes;
// align each column vertically and leave a gap in the middle where edges can pass through
// NOTE: m_height is not gonna be correct after this, but stays unchanged to allow correct positioning next to
// active node.
for (size_t i = 0; i < nodesInCol.size(); i++)
{
int offset = 0;
bool hasOffset = false;
int mid = colHeights[i] / 2;
for (DummyNode* node : nodesInCol[i])
{
if (hasOffset)
{
belowNodes.push_back(node);
}
else if (nodesInCol[i].size() == 1)
{
offset -= (node->size.y + GraphViewStyle::s_gridCellPadding) / 2;
aboveNodes.push_back(node);
}
else if (node->position.y < mid && node->position.y + node->size.y > mid)
{
if (mid - node->position.y < (node->position.y + node->size.y) - mid)
{
offset = mid - node->position.y + GraphViewStyle::s_gridCellPadding / 2;
belowNodes.push_back(node);
}
else
{
offset = mid - (node->position.y + node->size.y) - GraphViewStyle::s_gridCellPadding / 2;
aboveNodes.push_back(node);
}
hasOffset = true;
}
else if (node->position.y + node->size.y < mid &&
mid < node->position.y + node->size.y + GraphViewStyle::s_gridCellPadding)
{
offset = mid - (node->position.y + node->size.y + GraphViewStyle::s_gridCellPadding / 2);
aboveNodes.push_back(node);
hasOffset = true;
}
else
{
aboveNodes.push_back(node);
}
}
offset += (m_height - colHeights[i]) / 2;
for (DummyNode* node : nodesInCol[i])
{
node->position.y() += offset;
}
}
int nodeOffset = GraphViewStyle::s_gridCellPadding + GraphViewStyle::s_gridCellSize;
for (DummyNode* node : aboveNodes)
{
node->position.y() -= nodeOffset;
}
for (DummyNode* node : belowNodes)
{
node->position.y() += nodeOffset;
}
}
void Bucket::layout(int x, int y, int width, int height)
{
int cx = GraphViewStyle::toGridOffset(x + (width - m_width) / 2);
int cy = GraphViewStyle::toGridOffset(y + (height - m_height) / 2);
if (!m_nodes.size())
{
return;
}
Vec2i offset = Vec2i(x + (width - m_width) / 2, y + (height - m_height) / 2);
offset = GraphViewStyle::alignOnRaster((*m_nodes.begin())->position + offset) - (*m_nodes.begin())->position;
for (const std::shared_ptr<DummyNode>& node : m_nodes)
{
node->position.x = node->position.x + cx;
node->position.y = node->position.y + cy;
node->position += offset;
}
}
@@ -126,6 +226,7 @@ void BucketLayouter::createBuckets(
{
addNode(node);
activeNodeAdded = true;
m_activeParentNode = node.get();
}
}
@@ -205,7 +306,7 @@ void BucketLayouter::layoutBuckets(bool addVerticalOffset)
{
Bucket* bucket = &m_buckets[j][i];
bucket->preLayout(m_viewSize);
bucket->preLayout(m_viewSize, i != 0);
std::map<int, int>::iterator wt = widths.find(i);
if (wt == widths.end() || wt->second < bucket->getWidth())
@@ -221,6 +322,13 @@ void BucketLayouter::layoutBuckets(bool addVerticalOffset)
}
}
int verticalOffset = 0;
if (m_activeParentNode)
{
Vec4i rect = m_activeParentNode->getActiveSubNodeRect();
verticalOffset = (rect.y + rect.w - m_activeParentNode->size.y) / 2;
}
int y = 0;
for (int j = m_j1; j <= m_j2; j++)
{
@@ -239,7 +347,12 @@ void BucketLayouter::layoutBuckets(bool addVerticalOffset)
// move every second bucket in a row slighly lower to avoid edges over nodes
else if (addVerticalOffset && std::abs(i) % 2 == 1)
{
yOff += GraphViewStyle::toGridGap(10);
yOff = GraphViewStyle::toGridGap(10);
}
// align buckets left and right of center to be vertically centered next to the active node
else if (j == 0 && i != 0 && verticalOffset != 0)
{
yOff = verticalOffset;
}
bucket->layout(x, y + yOff, widths[i], heights[j]);
@@ -290,7 +403,7 @@ std::shared_ptr<DummyNode> BucketLayouter::findTopMostDummyNodeRecursive(
void BucketLayouter::addNode(std::shared_ptr<DummyNode> node)
{
Bucket* bucket = getBucket(node->layoutBucket.x, node->layoutBucket.y);
Bucket* bucket = getBucket(0, 0);
bucket->addNode(node);
}
@@ -23,7 +23,7 @@ public:
void addNode(std::shared_ptr<DummyNode> node);
const DummyNode::BundledNodesSet& getNodes() const;
void preLayout(Vec2i viewSize);
void preLayout(Vec2i viewSize, bool addVerticalOffset);
void layout(int x, int y, int width, int height);
int i;
@@ -65,6 +65,8 @@ private:
int m_j1;
int m_i2;
int m_j2;
DummyNode* m_activeParentNode = nullptr;
};
#endif // BUCKET_LAYOUTER_H
@@ -2,6 +2,7 @@
#define DUMMY_NODE_H
#include "utility/math/Vector2.h"
#include "utility/math/Vector4.h"
#include "utility/types.h"
#include "utility/utility.h"
#include "utility/utilityString.h"
@@ -103,7 +104,6 @@ public:
, accessKind(ACCESS_NONE)
, invisibleSubNodeCount(0)
, bundleId(0)
, layoutBucket(0, 0)
, bundledNodeCount(0)
, bundledNodeType(NodeType::NODE_SYMBOL)
, qualifierName(NAME_DELIMITER_UNKNOWN)
@@ -184,6 +184,27 @@ public:
return false;
}
Vec4i getActiveSubNodeRect(Vec2i pos = Vec2i()) const
{
pos += position;
if (active)
{
return Vec4i(pos.x, pos.y, pos.x + size.x, pos.y + size.y);
}
for (const std::shared_ptr<DummyNode>& node : subNodes)
{
Vec4i rect = node->getActiveSubNodeRect(pos);
if (rect.w() > 0)
{
return rect;
}
}
return Vec4i();
}
size_t getActiveSubNodeCount() const
{
size_t count = 0;
@@ -444,7 +465,7 @@ public:
Id bundleId;
// Layout
Vec2i layoutBucket;
Vec2i columnSize;
// BundleNode
BundledNodesSet bundledNodes;
+17 -27
View File
@@ -628,8 +628,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(
style.originOffset.x = 17;
style.targetOffset.x = 17;
style.originOffset.y = -1;
style.targetOffset.y = 1;
style.originOffset.y = 1;
style.targetOffset.y = -1;
style.color = getEdgeColor(utility::encodeToUtf8(Edge::getUnderscoredTypeString(type)), isActive || isFocused);
@@ -647,8 +647,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(
style.zValue = isActive ? 1 : -5;
break;
case Edge::EDGE_CALL:
style.originOffset.y = 1;
style.targetOffset.y = -1;
style.originOffset.y = 3;
style.targetOffset.y = -3;
style.verticalOffset = 4;
if (isTrailEdge && isActive)
@@ -659,8 +659,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(
}
break;
case Edge::EDGE_USAGE:
style.originOffset.y = 3;
style.targetOffset.y = -3;
style.originOffset.y = 5;
style.targetOffset.y = -5;
style.verticalOffset = 6;
break;
case Edge::EDGE_INHERITANCE:
@@ -669,8 +669,8 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(
style.arrowClosed = true;
style.originOffset.x = 7;
style.targetOffset.x = 34;
style.originOffset.y = -15;
style.targetOffset.y = 15;
style.originOffset.y = 10;
style.targetOffset.y = -10;
style.verticalOffset = 0;
style.cornerRadius = 7;
style.zValue = isActive ? 2 : -3;
@@ -688,6 +688,11 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(
style.arrowLength = 10;
style.arrowWidth = 13;
style.arrowClosed = true;
case Edge::EDGE_TEMPLATE_ARGUMENT:
case Edge::EDGE_TYPE_ARGUMENT:
case Edge::EDGE_TEMPLATE_DEFAULT_ARGUMENT:
style.originOffset.y = 5;
style.targetOffset.y = -5;
break;
case Edge::EDGE_INCLUDE:
@@ -701,38 +706,23 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(
int GraphViewStyle::toGridOffset(int x)
{
if (x < 1)
if (x > 0)
{
return 0;
return std::ceil(x / double(s_gridCellPadding + s_gridCellSize)) * (s_gridCellPadding + s_gridCellSize);
}
int r = 0;
while (r < x - 1)
else
{
r += s_gridCellPadding + s_gridCellSize;
return std::floor(x / double(s_gridCellPadding + s_gridCellSize)) * (s_gridCellPadding + s_gridCellSize);
}
return r;
}
int GraphViewStyle::toGridSize(int x)
{
if (x < 1)
{
return 0;
}
return s_gridCellSize + toGridOffset(x - s_gridCellSize);
}
int GraphViewStyle::toGridGap(int x)
{
if (x < 1)
{
return 0;
}
return s_gridCellPadding + toGridOffset(x - s_gridCellPadding);
}