ui: Multi-column layout within access nodes if no child node has edges

* use multiple columns for access nodes if no child has edges
* try to layout close to a square
* layout neighboring accesses to the width of the largest
* increase threshold to hide children on parent activation to 100
* use square layouting in inheritance trees as well
This commit is contained in:
Eberhard Graether
2019-04-16 16:24:38 +02:00
parent 92192659d9
commit 4dd032dcff
6 changed files with 157 additions and 49 deletions
@@ -845,18 +845,12 @@ bool GraphController::setActive(const std::vector<Id>& activeTokenIds, bool show
bool isInheritance = edge->data->isType(Edge::EDGE_INHERITANCE);
if (from && to && !edge->hidden &&
(showAllEdges || noActive || from->active || to->active || edge->active || isInheritance))
(showAllEdges || noActive || from->active || to->active || edge->active || isInheritance) &&
!(to->active && edge->data->isType(Edge::EDGE_TYPE_USAGE) && to->data->isParentOf(from->data))) // Don't show type use edges to active parent
{
edge->visible = true;
from->connected = true;
to->connected = true;
// Don't show children of active node with a type use edge to the parent
if (to->active && edge->data->isType(Edge::EDGE_TYPE_USAGE) && to->data->isParentOf(from->data))
{
from->connected = false;
to->connected = false;
}
}
else
{
@@ -1729,7 +1723,7 @@ void GraphController::groupTrailNodes(GroupType groupType)
std::shared_ptr<DummyNode> groupNode = std::make_shared<DummyNode>(DummyNode::DUMMY_GROUP);
groupNode->visible = true;
groupNode->groupType = groupType;
groupNode->groupLayout = GroupLayout::SKEWED;
groupNode->groupLayout = GroupLayout::SQUARE;
// Use token Id of first node and make first 2 bits 1
groupNode->tokenId = ~(~Id(0) >> 2) + node.nodeId;
@@ -1825,7 +1819,7 @@ void GraphController::layoutNesting()
for (const std::shared_ptr<DummyNode>& node : m_dummyNodes)
{
layoutNestingRecursive(node.get());
layoutNestingRecursive(node.get(), -1);
}
for (const std::shared_ptr<DummyNode>& node : m_dummyNodes)
@@ -1868,11 +1862,11 @@ void GraphController::extendEqualFunctionNames(const std::vector<std::shared_ptr
}
}
void GraphController::layoutNestingRecursive(DummyNode* node) const
Vec4i GraphController::layoutNestingRecursive(DummyNode* node, int maxWidth) const
{
if (!node->visible)
{
return;
return Vec4i(0, 0, 0, 0);
}
GraphViewStyle::NodeMargins margins;
@@ -1904,7 +1898,7 @@ void GraphController::layoutNestingRecursive(DummyNode* node) const
}
else if (node->isQualifierNode())
{
return;
return Vec4i(0, 0, 0, 0);
}
else if (node->isTextNode())
{
@@ -1940,6 +1934,8 @@ void GraphController::layoutNestingRecursive(DummyNode* node) const
width += margins.iconWidth;
width = std::max(width, margins.minWidth);
int maxAccessWidth = 0;
for (const std::shared_ptr<DummyNode>& subNode : node->subNodes)
{
if (!subNode->visible)
@@ -1953,47 +1949,75 @@ void GraphController::layoutNestingRecursive(DummyNode* node) const
continue;
}
layoutNestingRecursive(subNode.get());
Vec4i rect = layoutNestingRecursive(subNode.get(), maxWidth);
if (subNode->isExpandToggleNode())
{
width += margins.spacingX + subNode->size.x;
}
}
if (node->isGroupNode())
{
Vec2i viewSize = getView()->getViewSize();
switch (node->groupLayout)
else if (subNode->isAccessNode())
{
case GroupLayout::LIST:
viewSize.x = viewSize.x - 150; // prevent horizontal scroll
ListLayouter::layoutMultiColumn(viewSize, &node->subNodes);
break;
case GroupLayout::SKEWED:
ListLayouter::layoutSkewed(&node->subNodes, margins.spacingX, margins.spacingY, viewSize.x() * 1.5);
break;
case GroupLayout::BUCKET:
if (node->hasActiveSubNode() || !m_activeNodeIds.size() /* aggregations */)
{
BucketLayouter grid(viewSize);
grid.createBuckets(node->subNodes, m_dummyEdges);
grid.layoutBuckets(m_activeNodeIds.size());
node->subNodes = grid.getSortedNodes();
}
else
{
ListLayouter::layoutColumn(&node->subNodes, margins.spacingY);
}
break;
maxAccessWidth = std::max(maxAccessWidth, rect.z());
}
}
else
if (maxAccessWidth > 0)
{
ListLayouter::layoutColumn(&node->subNodes, margins.spacingY);
for (const std::shared_ptr<DummyNode>& subNode : node->subNodes)
{
if (!subNode->visible || !subNode->isAccessNode())
{
continue;
}
layoutNestingRecursive(subNode.get(), maxAccessWidth);
}
}
if (node->subNodes.size())
{
if (node->isGroupNode())
{
Vec2i viewSize = getView()->getViewSize();
switch (node->groupLayout)
{
case GroupLayout::LIST:
viewSize.x = viewSize.x - 150; // prevent horizontal scroll
ListLayouter::layoutMultiColumn(viewSize, &node->subNodes);
break;
case GroupLayout::SKEWED:
ListLayouter::layoutSkewed(&node->subNodes, margins.spacingX, margins.spacingY, viewSize.x() * 1.5);
break;
case GroupLayout::BUCKET:
if (node->hasActiveSubNode() || !m_activeNodeIds.size() /* aggregations */)
{
BucketLayouter grid(viewSize);
grid.createBuckets(node->subNodes, m_dummyEdges);
grid.layoutBuckets(m_activeNodeIds.size());
node->subNodes = grid.getSortedNodes();
}
else
{
ListLayouter::layoutColumn(&node->subNodes, margins.spacingY);
}
break;
case GroupLayout::SQUARE:
ListLayouter::layoutSquare(&node->subNodes, -1);
break;
}
}
else if (node->isAccessNode() && !node->hasConnectedSubNode())
{
ListLayouter::layoutSquare(&node->subNodes, maxWidth);
}
else
{
ListLayouter::layoutColumn(&node->subNodes, margins.spacingY);
}
}
Vec2i size = ListLayouter::offsetNodes(
@@ -2022,6 +2046,8 @@ void GraphController::layoutNestingRecursive(DummyNode* node) const
subNode->position.y = 6;
}
}
return ListLayouter::boundingRect(node->subNodes);
}
void GraphController::addExpandToggleNode(DummyNode* node) const
@@ -120,7 +120,7 @@ private:
void layoutNesting();
void extendEqualFunctionNames(const std::vector<std::shared_ptr<DummyNode>>& nodes) const;
void layoutNestingRecursive(DummyNode* node) const;
Vec4i layoutNestingRecursive(DummyNode* node, int maxWidth) const;
void addExpandToggleNode(DummyNode* node) const;
void layoutToGrid(DummyNode* node) const;
@@ -128,6 +128,85 @@ void ListLayouter::layoutMultiColumn(Vec2i viewSize, std::vector<std::shared_ptr
}
}
void ListLayouter::layoutSquare(std::vector<std::shared_ptr<DummyNode>>* nodes, int maxWidth)
{
int gapX = GraphViewStyle::s_gridCellSize + 2 * GraphViewStyle::s_gridCellPadding;
int gapY = GraphViewStyle::s_gridCellPadding;
std::vector<std::shared_ptr<DummyNode>> visibleNodes;
for (auto node : *nodes)
{
if (node->getsLayouted())
{
visibleNodes.push_back(node);
}
}
int totalHeight = 0;
for (size_t i = 0; i < visibleNodes.size(); i++)
{
totalHeight += visibleNodes[i]->size.y() + gapY;
}
int diff = -1;
size_t cols = 1;
for (size_t i = cols; i < 100; i++)
{
if (layoutSquareInternal(visibleNodes, Vec2i(maxWidth, totalHeight * i / 100), Vec2i(gapX, gapY)))
{
Vec4i rect = boundingRect(visibleNodes);
int newDiff = rect.z() * rect.w() + (rect.z() - rect.w()) * (rect.z() - rect.w()) / 4;
if (maxWidth >= 0)
{
newDiff = rect.w();
}
if (diff < 0 || newDiff <= diff)
{
diff = newDiff;
cols = i;
}
}
}
layoutSquareInternal(visibleNodes, Vec2i(maxWidth, totalHeight * cols / 100), Vec2i(gapX, gapY));
}
bool ListLayouter::layoutSquareInternal(
std::vector<std::shared_ptr<DummyNode>>& visibleNodes, const Vec2i& maxSize, const Vec2i& gap)
{
int x = 0;
int y = 0;
int width = 0;
for (std::shared_ptr<DummyNode> node : visibleNodes)
{
node->position.x() = x;
node->position.y() = y;
y += node->size.y() + gap.y();
width = std::max(width, node->size.x());
if (maxSize.x > 0 && x + width > maxSize.x)
{
return false;
}
if (y >= maxSize.y)
{
y = 0;
x += width + gap.x();
width = 0;
}
}
return true;
}
void ListLayouter::layoutSkewed(std::vector<std::shared_ptr<DummyNode>>* nodes, int gapX, int gapY, int maxWidth)
{
std::vector<std::shared_ptr<DummyNode>> visibleNodes;
@@ -16,6 +16,7 @@ public:
static void layoutColumn(std::vector<std::shared_ptr<DummyNode>>* nodes, int gap);
static void layoutMultiColumn(Vec2i viewSize, std::vector<std::shared_ptr<DummyNode>>* nodes);
static void layoutSquare(std::vector<std::shared_ptr<DummyNode>>* nodes, int maxWidth);
static void layoutSkewed(std::vector<std::shared_ptr<DummyNode>>* nodes, int gapX, int gapY, int maxWidth);
static Vec4i boundingRect(const std::vector<std::shared_ptr<DummyNode>>& nodes);
@@ -23,6 +24,7 @@ public:
private:
static void layoutSimple(std::vector<std::shared_ptr<DummyNode>>* nodes, int gapX, int gapY, bool horizontal);
static bool layoutSquareInternal(std::vector<std::shared_ptr<DummyNode>>& visibleNodes, const Vec2i& maxSize, const Vec2i& gap);
};
#endif // LIST_LAYOUTER_H
+2 -1
View File
@@ -20,7 +20,8 @@ enum class GroupLayout
{
LIST,
SKEWED,
BUCKET
BUCKET,
SQUARE
};
#endif // GROUP_TYPE_H
+2 -2
View File
@@ -1046,8 +1046,8 @@ std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(
{
m_hierarchyCache.addFirstChildIdsForNodeId(elementId, &nodeIds, &edgeIds);
// don't expand active node if it has more than 20 child nodes
if (nodeIds.size() > 20 && nodeType.isCollapsible())
// don't expand active node if it has too many child nodes
if (nodeIds.size() > 100 && nodeType.isCollapsible())
{
nodeIds.clear();
}