ui: switch Graph to bucket grid layouting
This change introduces bucket grid layouting, which sorts the nodes into buckets based on how they are connected. Each bucket is then layouted individually before they are arranged next to each other. Edges go from left to right, except for inheritance edges that go from bottom to top.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "component/controller/helper/DummyEdge.h"
|
||||
#include "component/controller/helper/DummyNode.h"
|
||||
#include "component/controller/helper/GraphLayouter.h"
|
||||
#include "component/controller/helper/GraphPostprocessor.h"
|
||||
#include "component/view/GraphView.h"
|
||||
#include "component/view/GraphViewStyle.h"
|
||||
@@ -88,8 +89,7 @@ void GraphController::handleMessage(MessageGraphNodeBundleSplit* message)
|
||||
setActiveAndVisibility(utility::concat(m_activeNodeIds, m_activeEdgeIds));
|
||||
|
||||
layoutNesting();
|
||||
GraphLayouter::layoutSpectralPrototype(m_dummyNodes, m_dummyEdges);
|
||||
GraphPostprocessor::doPostprocessing(m_dummyNodes);
|
||||
layoutGraph();
|
||||
|
||||
buildGraph(message);
|
||||
}
|
||||
@@ -102,9 +102,9 @@ void GraphController::handleMessage(MessageGraphNodeExpand* message)
|
||||
node->expanded = message->expand;
|
||||
|
||||
setActiveAndVisibility(utility::concat(m_activeNodeIds, m_activeEdgeIds));
|
||||
layoutNesting();
|
||||
|
||||
GraphPostprocessor::doPostprocessing(m_dummyNodes);
|
||||
layoutNesting();
|
||||
layoutGraph();
|
||||
|
||||
buildGraph(message);
|
||||
}
|
||||
@@ -179,8 +179,7 @@ void GraphController::createDummyGraphForTokenIds(const std::vector<Id>& tokenId
|
||||
bundleNodes();
|
||||
|
||||
layoutNesting();
|
||||
GraphLayouter::layoutSpectralPrototype(m_dummyNodes, m_dummyEdges);
|
||||
GraphPostprocessor::doPostprocessing(m_dummyNodes);
|
||||
layoutGraph();
|
||||
|
||||
m_graph = graph;
|
||||
}
|
||||
@@ -867,6 +866,14 @@ void GraphController::layoutToGrid(DummyNode& node) const
|
||||
}
|
||||
}
|
||||
|
||||
void GraphController::layoutGraph()
|
||||
{
|
||||
// GraphLayouter::layoutSpectralPrototype(m_dummyNodes, m_dummyEdges);
|
||||
// GraphPostprocessor::doPostprocessing(m_dummyNodes);
|
||||
|
||||
GraphLayouter::layoutBucket(m_dummyNodes, m_dummyEdges, getView()->getViewSize());
|
||||
}
|
||||
|
||||
DummyNode* GraphController::findDummyNodeRecursive(std::vector<DummyNode>& nodes, Id tokenId) const
|
||||
{
|
||||
for (DummyNode& node : nodes)
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "utility/messaging/type/MessageGraphNodeMove.h"
|
||||
|
||||
#include "component/controller/Controller.h"
|
||||
#include "component/controller/GraphLayouter.h"
|
||||
#include "component/view/GraphView.h"
|
||||
#include "data/graph/token_component/TokenComponentAccess.h"
|
||||
#include "data/graph/token_component/TokenComponentAggregation.h"
|
||||
@@ -72,6 +71,8 @@ private:
|
||||
void addExpandToggleNode(DummyNode& node) const;
|
||||
void layoutToGrid(DummyNode& node) const;
|
||||
|
||||
void layoutGraph();
|
||||
|
||||
DummyNode* findDummyNodeRecursive(std::vector<DummyNode>& nodes, Id tokenId) const;
|
||||
DummyNode* findTopLevelDummyNodeRecursive(std::vector<DummyNode>& nodes, Id tokenId) const;
|
||||
DummyNode* findDummyNodeAccessRecursive(std::vector<DummyNode>& nodes, Id parentId, TokenComponentAccess::AccessType type) const;
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
#include "component/controller/helper/BucketGrid.h"
|
||||
|
||||
#include "component/controller/helper/DummyEdge.h"
|
||||
#include "component/controller/helper/DummyNode.h"
|
||||
#include "component/view/GraphViewStyle.h"
|
||||
|
||||
Bucket::Bucket()
|
||||
: i(0)
|
||||
, j(0)
|
||||
, m_width(0)
|
||||
, m_height(0)
|
||||
{
|
||||
}
|
||||
|
||||
Bucket::Bucket(int i, int j)
|
||||
: i(i)
|
||||
, j(j)
|
||||
, m_width(0)
|
||||
, m_height(0)
|
||||
{
|
||||
}
|
||||
|
||||
int Bucket::getWidth() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
int Bucket::getHeight() const
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
bool Bucket::hasNode(DummyNode* node) const
|
||||
{
|
||||
for (DummyNode* n : m_nodes)
|
||||
{
|
||||
if (node == n)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Bucket::addNode(DummyNode* node)
|
||||
{
|
||||
m_nodes.push_back(node);
|
||||
|
||||
m_width = (node->size.x > m_width ? node->size.x : m_width);
|
||||
m_height += node->size.y + GraphViewStyle::toGridGap(10);
|
||||
}
|
||||
|
||||
void Bucket::preLayout(Vec2i viewSize)
|
||||
{
|
||||
int cols = m_height / viewSize.y + 1;
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int height = m_height / cols;
|
||||
int width = 0;
|
||||
|
||||
m_height = 0;
|
||||
|
||||
for (DummyNode* node : m_nodes)
|
||||
{
|
||||
node->position.x = x;
|
||||
node->position.y = y;
|
||||
|
||||
y += node->size.y + GraphViewStyle::toGridGap(10);
|
||||
|
||||
width = (node->size.x > width ? node->size.x : width);
|
||||
m_height = (y > m_height ? y : m_height);
|
||||
|
||||
if (y > height)
|
||||
{
|
||||
y = 0;
|
||||
|
||||
x += width + GraphViewStyle::toGridGap(25);
|
||||
width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
m_width = x + width;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
for (DummyNode* node : m_nodes)
|
||||
{
|
||||
node->position.x = node->position.x + cx;
|
||||
node->position.y = node->position.y + cy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BucketGrid::layout(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges, Vec2i viewSize)
|
||||
{
|
||||
BucketGrid grid(viewSize);
|
||||
grid.createBuckets(nodes, edges);
|
||||
grid.layoutBuckets();
|
||||
}
|
||||
|
||||
BucketGrid::BucketGrid(Vec2i viewSize)
|
||||
: m_viewSize(viewSize)
|
||||
, m_i1(0)
|
||||
, m_j1(0)
|
||||
, m_i2(0)
|
||||
, m_j2(0)
|
||||
{
|
||||
m_buckets[0][0] = Bucket(0, 0);
|
||||
}
|
||||
|
||||
void BucketGrid::createBuckets(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges)
|
||||
{
|
||||
if (!nodes.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool activeNodeAdded = false;
|
||||
for (DummyNode& node : nodes)
|
||||
{
|
||||
if (node.hasActiveSubNode())
|
||||
{
|
||||
addNode(&node);
|
||||
activeNodeAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!activeNodeAdded)
|
||||
{
|
||||
addNode(&nodes[0]);
|
||||
}
|
||||
|
||||
std::vector<const DummyEdge*> remainingEdges;
|
||||
for (const DummyEdge& edge : edges)
|
||||
{
|
||||
remainingEdges.push_back(&edge);
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
while (remainingEdges.size())
|
||||
{
|
||||
const DummyEdge* edge = remainingEdges[i];
|
||||
|
||||
DummyNode* owner = findTopMostDummyNodeRecursive(nodes, edge->ownerId);
|
||||
DummyNode* target = findTopMostDummyNodeRecursive(nodes, edge->targetId);
|
||||
|
||||
bool removeEdge = false;
|
||||
if (!owner || !target)
|
||||
{
|
||||
removeEdge = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool horizontal = edge->data ? !edge->data->isType(Edge::EDGE_INHERITANCE) : true;
|
||||
removeEdge = addNode(owner, target, horizontal);
|
||||
}
|
||||
|
||||
if (removeEdge)
|
||||
{
|
||||
remainingEdges.erase(remainingEdges.begin() + i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == remainingEdges.size())
|
||||
{
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BucketGrid::layoutBuckets()
|
||||
{
|
||||
std::map<int, int> widths;
|
||||
std::map<int, int> heights;
|
||||
|
||||
for (int j = m_j1; j <= m_j2; j++)
|
||||
{
|
||||
for (int i = m_i1; i <= m_i2; i++)
|
||||
{
|
||||
Bucket* bucket = &m_buckets[j][i];
|
||||
|
||||
bucket->preLayout(m_viewSize);
|
||||
|
||||
std::map<int, int>::iterator wt = widths.find(i);
|
||||
if (wt == widths.end() || wt->second < bucket->getWidth())
|
||||
{
|
||||
widths[i] = bucket->getWidth();
|
||||
}
|
||||
|
||||
std::map<int, int>::iterator ht = heights.find(j);
|
||||
if (ht == heights.end() || ht->second < bucket->getHeight())
|
||||
{
|
||||
heights[j] = bucket->getHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int y = 0;
|
||||
for (int j = m_j1; j <= m_j2; j++)
|
||||
{
|
||||
int x = 0;
|
||||
for (int i = m_i1; i <= m_i2; i++)
|
||||
{
|
||||
Bucket* bucket = &m_buckets[j][i];
|
||||
|
||||
bucket->layout(x, y, widths[i], heights[j]);
|
||||
|
||||
x += widths[i] + GraphViewStyle::toGridGap(85);
|
||||
}
|
||||
|
||||
y += heights[j] + GraphViewStyle::toGridGap(45);
|
||||
}
|
||||
}
|
||||
|
||||
DummyNode* BucketGrid::findTopMostDummyNodeRecursive(std::vector<DummyNode>& nodes, Id tokenId, DummyNode* top)
|
||||
{
|
||||
for (DummyNode& node : nodes)
|
||||
{
|
||||
DummyNode* t = (top ? top : &node);
|
||||
|
||||
if (node.visible && node.tokenId == tokenId)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
DummyNode* result = findTopMostDummyNodeRecursive(node.subNodes, tokenId, t);
|
||||
if (result != nullptr)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BucketGrid::addNode(DummyNode* node)
|
||||
{
|
||||
Bucket* bucket = getBucket(0, 0);
|
||||
bucket->addNode(node);
|
||||
}
|
||||
|
||||
bool BucketGrid::addNode(DummyNode* owner, DummyNode* target, bool horizontal)
|
||||
{
|
||||
Bucket* ownerBucket = getBucket(owner);
|
||||
Bucket* targetBucket = getBucket(target);
|
||||
|
||||
if (!ownerBucket && !targetBucket)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (ownerBucket && targetBucket)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ownerBucket)
|
||||
{
|
||||
int i = horizontal ? ownerBucket->i + 1 : ownerBucket->i;
|
||||
int j = horizontal ? ownerBucket->j : ownerBucket->j - 1;
|
||||
|
||||
Bucket* bucket = getBucket(i, j);
|
||||
bucket->addNode(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = horizontal ? targetBucket->i - 1 : targetBucket->i;
|
||||
int j = horizontal ? targetBucket->j : targetBucket->j + 1;
|
||||
|
||||
Bucket* bucket = getBucket(i, j);
|
||||
bucket->addNode(owner);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bucket* BucketGrid::getBucket(int i, int j)
|
||||
{
|
||||
bool newColumn = false;
|
||||
bool newRow = false;
|
||||
|
||||
if (i == m_i1 - 1)
|
||||
{
|
||||
m_i1 = i;
|
||||
newColumn = true;
|
||||
}
|
||||
else if (i == m_i2 + 1)
|
||||
{
|
||||
m_i2 = i;
|
||||
newColumn = true;
|
||||
}
|
||||
|
||||
if (newColumn)
|
||||
{
|
||||
for (int cj = m_j1; cj <= m_j2; cj++)
|
||||
{
|
||||
m_buckets[cj][i] = Bucket(i, cj);
|
||||
}
|
||||
}
|
||||
|
||||
if (j == m_j1 - 1)
|
||||
{
|
||||
m_j1 = j;
|
||||
newRow = true;
|
||||
}
|
||||
else if (j == m_j2 + 1)
|
||||
{
|
||||
m_j2 = j;
|
||||
newRow = true;
|
||||
}
|
||||
|
||||
if (newRow)
|
||||
{
|
||||
for (int ci = m_i1; ci <= m_i2; ci++)
|
||||
{
|
||||
m_buckets[j][ci] = Bucket(ci, j);
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= m_i1 && i <= m_i2 && j >= m_j1 && j <= m_j2)
|
||||
{
|
||||
return &m_buckets[j][i];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Bucket* BucketGrid::getBucket(DummyNode* node)
|
||||
{
|
||||
for (int j = m_j1; j <= m_j2; j++)
|
||||
{
|
||||
for (int i = m_i1; i <= m_i2; i++)
|
||||
{
|
||||
Bucket* bucket = &m_buckets[j][i];
|
||||
|
||||
if (bucket->hasNode(node))
|
||||
{
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef BUCKET_GRID_H
|
||||
#define BUCKET_GRID_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "utility/math/Vector2.h"
|
||||
#include "utility/types.h"
|
||||
|
||||
struct DummyEdge;
|
||||
struct DummyNode;
|
||||
|
||||
class Bucket
|
||||
{
|
||||
public:
|
||||
Bucket();
|
||||
Bucket(int i, int j);
|
||||
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
|
||||
bool hasNode(DummyNode* node) const;
|
||||
void addNode(DummyNode* node);
|
||||
|
||||
void preLayout(Vec2i viewSize);
|
||||
void layout(int x, int y, int width, int height);
|
||||
|
||||
int i;
|
||||
int j;
|
||||
|
||||
private:
|
||||
int m_width;
|
||||
int m_height;
|
||||
|
||||
std::vector<DummyNode*> m_nodes;
|
||||
};
|
||||
|
||||
|
||||
class BucketGrid
|
||||
{
|
||||
public:
|
||||
static void layout(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges, Vec2i viewSize);
|
||||
|
||||
private:
|
||||
BucketGrid(Vec2i viewSize);
|
||||
|
||||
void createBuckets(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges);
|
||||
void layoutBuckets();
|
||||
|
||||
DummyNode* findTopMostDummyNodeRecursive(std::vector<DummyNode>& nodes, Id tokenId, DummyNode* top = nullptr);
|
||||
|
||||
void addNode(DummyNode* node);
|
||||
bool addNode(DummyNode* owner, DummyNode* target, bool horizontal);
|
||||
|
||||
Bucket* getBucket(int i, int j);
|
||||
Bucket* getBucket(DummyNode* node);
|
||||
|
||||
Vec2i m_viewSize;
|
||||
std::map<int, std::map<int, Bucket>> m_buckets;
|
||||
|
||||
int m_i1;
|
||||
int m_j1;
|
||||
int m_i2;
|
||||
int m_j2;
|
||||
};
|
||||
|
||||
#endif // BUCKET_GRID_H
|
||||
+10
-4
@@ -1,13 +1,13 @@
|
||||
#include "component/controller/GraphLayouter.h"
|
||||
#include "component/controller/helper/GraphLayouter.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
#include "utility/math/MatrixDynamicBase.h"
|
||||
|
||||
#include "component/controller/helper/BucketGrid.h"
|
||||
#include "component/controller/helper/DummyEdge.h"
|
||||
#include "component/controller/helper/DummyNode.h"
|
||||
#include "data/graph/Node.h"
|
||||
|
||||
// for prototyping, remove when done
|
||||
#include "Eigen/Dense"
|
||||
@@ -64,6 +64,11 @@ void GraphLayouter::layoutSimpleRing(std::vector<DummyNode>& nodes)
|
||||
}
|
||||
}
|
||||
|
||||
void GraphLayouter::layoutBucket(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges, Vec2i viewSize)
|
||||
{
|
||||
BucketGrid::layout(nodes, edges, viewSize);
|
||||
}
|
||||
|
||||
void GraphLayouter::layoutSpectralPrototype(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges)
|
||||
{
|
||||
if (nodes.size() < 2)
|
||||
@@ -153,7 +158,8 @@ void GraphLayouter::layoutSpectralPrototype(std::vector<DummyNode>& nodes, const
|
||||
}
|
||||
}
|
||||
|
||||
MatrixDynamicBase<int> GraphLayouter::buildLaplacianMatrix(const std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges)
|
||||
MatrixDynamicBase<int> GraphLayouter::buildLaplacianMatrix(
|
||||
const std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges)
|
||||
{
|
||||
MatrixDynamicBase<int> matrix(nodes.size(), nodes.size());
|
||||
|
||||
+7
-5
@@ -3,8 +3,9 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
template<class T>
|
||||
class MatrixDynamicBase;
|
||||
#include "utility/math/MatrixDynamicBase.h"
|
||||
#include "utility/math/Vector2.h"
|
||||
#include "utility/types.h"
|
||||
|
||||
struct DummyEdge;
|
||||
struct DummyNode;
|
||||
@@ -12,15 +13,16 @@ struct DummyNode;
|
||||
class GraphLayouter
|
||||
{
|
||||
public:
|
||||
typedef void (*LayoutFunction)(std::vector<DummyNode>&);
|
||||
|
||||
static void layoutSimpleRaster(std::vector<DummyNode>& nodes);
|
||||
static void layoutSimpleRing(std::vector<DummyNode>& nodes);
|
||||
|
||||
static void layoutBucket(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges, Vec2i viewSize);
|
||||
|
||||
static void layoutSpectralPrototype(std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges);
|
||||
|
||||
private:
|
||||
static MatrixDynamicBase<int> buildLaplacianMatrix(const std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges);
|
||||
static MatrixDynamicBase<int> buildLaplacianMatrix(
|
||||
const std::vector<DummyNode>& nodes, const std::vector<DummyEdge>& edges);
|
||||
};
|
||||
|
||||
#endif // GRAPH_LAYOUTER_H
|
||||
Reference in New Issue
Block a user