Skip to content

Commit

Permalink
#228 Draft a RandomLayout to initialze random node positions.
Browse files Browse the repository at this point in the history
Signed-off-by: cneben <benoit@destrat.io>
  • Loading branch information
cneben committed Aug 15, 2024
1 parent 66c5bfc commit d1c9570
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 4 deletions.
13 changes: 12 additions & 1 deletion samples/layouts/layouts.qml
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,16 @@ ApplicationWindow {
graph.insertEdge(n12, n122)
//graph.insertEdge(n121, n1211)
graph.insertEdge(n13, n131)

randomLayout.layout(n1);
}
Qan.OrgTreeLayout {
id: orgTreeLayout
}
Qan.RandomLayout {
id: randomLayout
layoutRect: Qt.rect(100, 100, 1000, 1000)
}
} // Qan.Graph
Menu { // Context menu demonstration
id: contextMenu
Expand All @@ -105,14 +111,19 @@ ApplicationWindow {
anchors.top: parent.top
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
width: 420
width: 470
height: 50
padding: 2
RowLayout {
anchors.fill: parent
Label {
text: "Apply OrgTree:"
}
Button {
text: 'Random'
Material.roundedScale: Material.SmallScale
onClicked: randomLayout.layout(graphView.treeRoot)
}
Button {
text: 'Mixed'
Material.roundedScale: Material.SmallScale
Expand Down
1 change: 1 addition & 0 deletions src/QuickQanava.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ struct QuickQanava {
qmlRegisterType<qan::RightResizer>("QuickQanava", 2, 0, "RightResizer");
qmlRegisterType<qan::BottomResizer>("QuickQanava", 2, 0, "BottomResizer");

qmlRegisterType<qan::RandomLayout>("QuickQanava", 2, 0, "RandomLayout");
qmlRegisterType<qan::OrgTreeLayout>("QuickQanava", 2, 0, "OrgTreeLayout");
} // initialize()
};
Expand Down
2 changes: 1 addition & 1 deletion src/qanGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -1073,7 +1073,7 @@ class Graph : public gtpo::graph<QQuickItem, qan::Node, qan::Group, qan::Edge>
*/
std::vector<const qan::Node*> collectDfs(const qan::Node& node, bool collectGroup = false) const noexcept;

//! \copydoc collectDfs()
//! Collect all out nodes of \c nodes using DFS, return an unordered set of subnodes (nodes in node are _not_ in returned set).
auto collectSubNodes(const QVector<qan::Node*> nodes, bool collectGroup = false) const noexcept -> std::unordered_set<const qan::Node*>;

private:
Expand Down
52 changes: 52 additions & 0 deletions src/qanTreeLayouts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,58 @@ void NaiveTreeLayout::layout(qan::Node* root) noexcept
//-----------------------------------------------------------------------------


/* OrgTreeLayout Object Management *///----------------------------------------
RandomLayout::RandomLayout(QObject* parent) noexcept :
QObject{parent}
{
}
RandomLayout::~RandomLayout() { }

bool RandomLayout::setLayoutRect(QRectF layoutRect) noexcept
{
_layoutRect = layoutRect;
emit layoutRectChanged();
return true;
}
const QRectF RandomLayout::getLayoutRect() const noexcept { return _layoutRect; }

void RandomLayout::layout(qan::Node& root) noexcept
{
// In nodes, out nodes, adjacent nodes ?
const auto graph = root.getGraph();
if (graph == nullptr)
return;
if (root.getItem() == nullptr)
return;

// Generate a 1000x1000 layout rect centered on root if the user has not specified one
const auto rootPosition = root.getItem()->position();
const auto layoutRect = _layoutRect.isEmpty() ? QRectF{rootPosition.x() - 500, rootPosition.y() - 500, 1000., 1000.} :
_layoutRect;

auto outNodes = graph->collectSubNodes(QVector<qan::Node*>{&root}, false);
outNodes.insert(&root);
for (auto n : outNodes) {
auto node = const_cast<qan::Node*>(n);
if (node->getItem() == nullptr)
continue;
const auto nodeBr = node->getItem()->boundingRect();
qreal maxX = layoutRect.width() - nodeBr.width();
qreal maxY = layoutRect.height() - nodeBr.height();
// Generate and set random x and y positions within available area
node->getItem()->setX(QRandomGenerator::global()->bounded(maxX) + layoutRect.left());
node->getItem()->setY(QRandomGenerator::global()->bounded(maxY) + layoutRect.top());
}
}

void RandomLayout::layout(qan::Node* root) noexcept
{
if (root != nullptr)
layout(*root);
}
//-----------------------------------------------------------------------------


/* OrgTreeLayout Object Management *///----------------------------------------
OrgTreeLayout::OrgTreeLayout(QObject* parent) noexcept :
QObject{parent}
Expand Down
54 changes: 52 additions & 2 deletions src/qanTreeLayouts.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,57 @@ class NaiveTreeLayout : public QObject
};


/*! \brief
/*! \brief Layout nodes randomly inside a bounding rect.
* \nosubgrouping
*/
class RandomLayout : public QObject
{
Q_OBJECT
/*! \name RandomLayout Object Management *///-----------------------------
//@{
public:
explicit RandomLayout(QObject* parent = nullptr) noexcept;
virtual ~RandomLayout() override;
RandomLayout(const RandomLayout&) = delete;
RandomLayout& operator=(const RandomLayout&) = delete;
RandomLayout(RandomLayout&&) = delete;
RandomLayout& operator=(RandomLayout&&) = delete;

public:
//! \copydoc getLayoutRect()
Q_PROPERTY(QRectF layoutRect READ getLayoutRect WRITE setLayoutRect NOTIFY layoutRectChanged FINAL)
//! \copydoc getLayoutRect()
bool setLayoutRect(QRectF layoutRect) noexcept;
//! \copydoc getLayoutRect()
const QRectF getLayoutRect() const noexcept;
protected:
//! \copydoc getLayoutRect()
QRectF _layoutRect = QRectF{};
signals:
//! \copydoc getLayoutRect()
void layoutRectChanged();

public:
/*! \brief Apply a random layout fitting nodes positions inside \c layoutRect.
* If \c layoutRect is empty, generate a 1000x1000 default rect around \c root position.
*/
void layout(qan::Node& root) noexcept;

//! QML invokable version of layout().
Q_INVOKABLE void layout(qan::Node* root) noexcept;
//@}
//-------------------------------------------------------------------------
};


/*! \brief Org chart naive recursive variant of Reingold-Tilford algorithm with shifting.
*
* This algorithm layout tree in an "Org chart" fashion using no space optimization,
* respecting node ordering and working for n-ary trees.
*
* \note This layout does not enforces that the input graph is a tree, laying out
* a non-tree graph might lead to infinite recursion.
*
* \nosubgrouping
*/
class OrgTreeLayout : public QObject
Expand Down Expand Up @@ -107,7 +157,7 @@ class OrgTreeLayout : public QObject
//! \copydoc LayoutOrientation
Q_PROPERTY(LayoutOrientation layoutOrientation READ getLayoutOrientation WRITE setLayoutOrientation NOTIFY layoutOrientationChanged FINAL)
//! \copydoc LayoutOrientation
virtual bool setLayoutOrientation(LayoutOrientation layoutOrientation) noexcept;
bool setLayoutOrientation(LayoutOrientation layoutOrientation) noexcept;
//! \copydoc LayoutOrientation
LayoutOrientation getLayoutOrientation() noexcept;
//! \copydoc LayoutOrientation
Expand Down

0 comments on commit d1c9570

Please sign in to comment.