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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ QtLineItemBase::QtLineItemBase(QGraphicsItem* parent)
|
||||
, m_onBack(false)
|
||||
, m_earlyBend(false)
|
||||
, m_route(ROUTE_ANY)
|
||||
, m_pivot(PIVOT_THIRD)
|
||||
{
|
||||
this->setAcceptHoverEvents(true);
|
||||
}
|
||||
@@ -49,11 +48,6 @@ void QtLineItemBase::setRoute(Route route)
|
||||
m_route = route;
|
||||
}
|
||||
|
||||
void QtLineItemBase::setPivot(Pivot pivot)
|
||||
{
|
||||
m_pivot = pivot;
|
||||
}
|
||||
|
||||
void QtLineItemBase::setOnFront(bool front)
|
||||
{
|
||||
m_onFront = front;
|
||||
@@ -406,7 +400,7 @@ void QtLineItemBase::drawArrow(const QPolygon& poly, QPainterPath* path, QPainte
|
||||
|
||||
void QtLineItemBase::getPivotPoints(Vec2f* p, const Vec4i& in, const Vec4i& out, int offset, bool target) const
|
||||
{
|
||||
float f = m_pivot == PIVOT_THIRD ? (target ? 2 / 3.f : 1 / 3.f) : 1 / 2.f;
|
||||
float f = 1 / 2.f;
|
||||
|
||||
p[0] = Vec2f(in.x + (in.z - in.x) * f + offset, out.y);
|
||||
p[2] = Vec2f(in.x + (in.z - in.x) * f + offset, out.w);
|
||||
|
||||
@@ -18,12 +18,6 @@ public:
|
||||
ROUTE_VERTICAL
|
||||
};
|
||||
|
||||
enum Pivot
|
||||
{
|
||||
PIVOT_THIRD,
|
||||
PIVOT_MIDDLE
|
||||
};
|
||||
|
||||
QtLineItemBase(QGraphicsItem* parent);
|
||||
virtual ~QtLineItemBase();
|
||||
|
||||
@@ -34,7 +28,6 @@ public:
|
||||
size_t weight, bool showArrow);
|
||||
|
||||
void setRoute(Route route);
|
||||
void setPivot(Pivot pivot);
|
||||
|
||||
void setOnFront(bool front);
|
||||
void setOnBack(bool back);
|
||||
@@ -57,7 +50,6 @@ protected:
|
||||
bool m_earlyBend;
|
||||
|
||||
Route m_route;
|
||||
Pivot m_pivot;
|
||||
|
||||
private:
|
||||
Vec4i m_ownerRect;
|
||||
|
||||
@@ -987,6 +987,7 @@ QtGraphNode* QtGraphView::createNodeRecursive(
|
||||
|
||||
newNode->setPosition(node->position);
|
||||
newNode->setSize(node->size);
|
||||
newNode->setColumnSize(node->columnSize);
|
||||
newNode->setIsActive(node->active);
|
||||
newNode->setMultipleActive(multipleActive);
|
||||
|
||||
|
||||
@@ -133,7 +133,6 @@ void QtGraphEdge::updateLine()
|
||||
QtLineItemBezier* bezier = new QtLineItemBezier(this);
|
||||
bezier->updateLine(ownerRect, rect, ownerParentRect, rect, style, m_weight, false);
|
||||
bezier->setRoute(route);
|
||||
bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE);
|
||||
|
||||
QtLineItemStraight* line = new QtLineItemStraight(this);
|
||||
if (route == QtLineItemBase::ROUTE_HORIZONTAL)
|
||||
@@ -154,7 +153,6 @@ void QtGraphEdge::updateLine()
|
||||
QtLineItemBezier* bezier = new QtLineItemBezier(this);
|
||||
bezier->updateLine(ownerRect, targetRect, ownerParentRect, targetParentRect, style, m_weight, showArrow);
|
||||
bezier->setRoute(route);
|
||||
bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE);
|
||||
|
||||
if (ownerNonGroupParent == targetNonGroupParent)
|
||||
{
|
||||
@@ -170,6 +168,11 @@ void QtGraphEdge::updateLine()
|
||||
}
|
||||
else
|
||||
{
|
||||
const Vec2i& ownerColumnSize = m_owner->getLastParent()->getColumnSize();
|
||||
const Vec2i& targetColumnSize = m_target->getLastParent()->getColumnSize();
|
||||
ownerParentRect.z = std::max(ownerParentRect.x + ownerColumnSize.x, ownerParentRect.z());
|
||||
targetParentRect.z = std::max(targetParentRect.x + targetColumnSize.x, targetParentRect.z());
|
||||
|
||||
if (!m_child)
|
||||
{
|
||||
m_child = new QtLineItemAngled(this);
|
||||
@@ -214,11 +217,6 @@ void QtGraphEdge::updateLine()
|
||||
child->setRoute(QtLineItemBase::ROUTE_HORIZONTAL);
|
||||
}
|
||||
|
||||
if (type == Edge::EDGE_AGGREGATION || type == Edge::EDGE_INHERITANCE)
|
||||
{
|
||||
child->setPivot(QtLineItemBase::PIVOT_MIDDLE);
|
||||
}
|
||||
|
||||
bool showArrow = true;
|
||||
if (type == Edge::EDGE_AGGREGATION)
|
||||
{
|
||||
|
||||
@@ -109,6 +109,7 @@ bool QtGraphNode::setPosition(const Vec2i& position)
|
||||
if (offset.x != 0 || offset.y != 0)
|
||||
{
|
||||
this->moveBy(offset.x, offset.y);
|
||||
setColumnSize(Vec2i());
|
||||
notifyEdgesAfterMove();
|
||||
return true;
|
||||
}
|
||||
@@ -116,7 +117,7 @@ bool QtGraphNode::setPosition(const Vec2i& position)
|
||||
return false;
|
||||
}
|
||||
|
||||
Vec2i QtGraphNode::getSize() const
|
||||
const Vec2i& QtGraphNode::getSize() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
@@ -130,6 +131,16 @@ void QtGraphNode::setSize(const Vec2i& size)
|
||||
m_undefinedRect->setRect(1, 1, size.x - 2, size.y - 2);
|
||||
}
|
||||
|
||||
const Vec2i& QtGraphNode::getColumnSize() const
|
||||
{
|
||||
return m_columnSize;
|
||||
}
|
||||
|
||||
void QtGraphNode::setColumnSize(const Vec2i& size)
|
||||
{
|
||||
m_columnSize = size;
|
||||
}
|
||||
|
||||
QSize QtGraphNode::size() const
|
||||
{
|
||||
return QSize(m_size.x, m_size.y);
|
||||
|
||||
@@ -46,9 +46,12 @@ public:
|
||||
Vec2i getPosition() const;
|
||||
virtual bool setPosition(const Vec2i& position);
|
||||
|
||||
Vec2i getSize() const;
|
||||
const Vec2i& getSize() const;
|
||||
void setSize(const Vec2i& size);
|
||||
|
||||
const Vec2i& getColumnSize() const;
|
||||
void setColumnSize(const Vec2i& size);
|
||||
|
||||
QSize size() const;
|
||||
void setSize(const QSize& size);
|
||||
|
||||
@@ -124,6 +127,7 @@ protected:
|
||||
QGraphicsPixmapItem* m_icon = nullptr;
|
||||
|
||||
Vec2i m_size;
|
||||
Vec2i m_columnSize;
|
||||
|
||||
bool m_isActive = false;
|
||||
bool m_multipleActive = false;
|
||||
|
||||
Reference in New Issue
Block a user