QtNodes Documentation

repository·master·Indexed 25 days ago

https://github.com/paceholder/nodeeditor

A Qt-based library for creating node editors supporting general graph visualization and dataflow programming using a Model-View architecture. It provides core classes like AbstractGraphModel and DataFlowGraphModel for graph logic, and BasicGraphicsScene and DataFlowGraphicsScene for visual representation. The library includes tools for custom node painting, geometry, styling, and a comprehensive undo/redo system via command classes.

Tokens
27K
Snippets
85
Records
120
Agent score
86%

What's inside QtNodes

  1. Overview of QtNodes

    master
    QtNodes is a general-purpose Qt-based library for developing Node Editors. It supports both simple graph visualization/editing and the Dataflow programming paradigm. The library uses a Model-View approach where the graph structure is defined by a class derived from AbstractGraphModel. It supports a "headless" mode, allowing you to manipulate the graph model without attaching it to a QGraphicsScene or QGraphicsView.
  2. Understand the QtNodes Model-View Architecture

    master

    QtNodes uses a Model-View architecture to separate graph data from its visual representation. This separation allows you to run graph logic in headless mode (without a GUI), maintain multiple views of the same data, and test graph logic independently of the UI.

    • Model: An AbstractGraph (e.g., AbstractGraphModel or DataFlowGraphModel) that holds your data like nodes, connections, and positions.
    • View: A BasicGraphicsScene (inheriting from QGraphicsScene) and a GraphicsView (inheriting from QGraphicsView) that visualize the model.
  3. QtNodes Example Gallery Overview

    master

    The following examples demonstrate specific features of QtNodes:

    • Calculator (examples/calculator/): Full data flow application with number sources, operators, and display. Demonstrates embedded widgets, save/load, and menu integration.
    • Simple Graph Model (examples/simple_graph_model/): Minimal custom graph model implementation by subclassing AbstractGraphModel and using BasicGraphicsScene.
    • Styles (examples/styles/): Custom styling with different color schemes and visual effects.
    • Connection Colors (examples/connection_colors/): Data-type-based connection coloring where each data type has its own color.
    • Vertical Layout (examples/vertical_layout/): Top-to-bottom node arrangement with ports on top and bottom edges.
    • Dynamic Ports (examples/dynamic_ports/): Adding and removing ports at runtime using the two-phase port modification API.
    • Resizable Images (examples/resizable_images/): Nodes with embedded image widgets that can be resized by dragging.
    • Lock Nodes & Connections (examples/lock_nodes_and_connections/): Preventing node movement and connection detachment.
    • Node Validation (examples/node_validation/): Demonstrating NodeValidationState and NodeProcessingStatus for error/warning states and processing indicators.
    • Custom Painter (examples/custom_painter/): Custom node and connection rendering with gradients and arrows.
    • Text (examples/text/): Simple text propagation between nodes.
  4. Build QtNodes with CMake

    master

    You can configure the build using CMake. Use the USE_QT6 flag to switch between Qt5 and Qt6, and BUILD_SHARED_LIBS to toggle between shared and static libraries.

    To build with Qt6:

    mkdir build && cd build && cmake .. -DUSE_QT6=on

    To build with Qt5:

    mkdir build && cd build && cmake .. -DUSE_QT6=off

    To build a static library:

    cmake .. -DBUILD_SHARED_LIBS=off

    To disable testing (if Catch2 is not installed):

    cmake .. -DBUILD_TESTING=OFF
    mkdir build && cd build && cmake .. -DUSE_QT6=on
  5. Use data-defined colors for connections

    master

    To color connections based on the data type they carry, follow these two steps:

    1. Enable UseDataDefinedColors in your global ConnectionStyle.
    2. Define a color in your NodeDataType implementation.

    When a connection is made between ports of a specific data type, the connection line will use the color defined in that type's type() method.

    // 1. Enable data-defined colors globally
    ConnectionStyle::setConnectionStyle(R"({
        "ConnectionStyle": {
            "UseDataDefinedColors": true
        }
    })");
    
    // 2. Define the color in your NodeDataType
    NodeDataType NumberData::type() const
    {
        return NodeDataType{
            "number",           // id
            "Number",           // name
            QColor(0, 128, 255) // color for connections
        };
    }
  6. Implement serialization for Undo/Redo node deletion

    master

    To ensure that deleted nodes can be restored via the undo system, your model must implement saveNode() and loadNode(). These methods allow the undo framework to serialize the node's state before deletion and reconstruct it during an undo operation.

    QJsonObject MyModel::saveNode(NodeId nodeId) const override
    {
        // Save all data needed to recreate this node
        QJsonObject json;
        json["id"] = static_cast<qint64>(nodeId);
    
        QPointF pos = nodeData(nodeId, NodeRole::Position).toPointF();
        json["position"] = QJsonObject{{"x", pos.x()}, {"y", pos.y()}};
    
        // Save your custom data too
        json["internal-data"] = getNodeInternalData(nodeId);
    
        return json;
    }
    
    void MyModel::loadNode(QJsonObject const& json) override
    {
        // Recreate node from saved data
        NodeId nodeId = static_cast<NodeId>(json["id"].toInt());
        _nextId = std::max(_nextId, nodeId + 1);
    
        _nodes.insert(nodeId);
        emit nodeCreated(nodeId);
    
        // Restore position
        auto pos = json["position"].toObject();
        setNodeData(nodeId, NodeRole::Position,
                    QPointF(pos["x"].toDouble(), pos["y"].toDouble()));
    
        // Restore custom data
        restoreNodeInternalData(nodeId, json["internal-data"].toObject());
    }
  7. Implement the Data Flow Model

    master

    The data flow model automates data propagation between nodes. When a node's output changes, connected nodes automatically receive the new data. The system consists of three components:

    1. DataFlowGraphModel: Manages nodes and routes data.
    2. NodeDelegateModel: Contains your specific node logic (one class per node type).
    3. NodeDelegateModelRegistry: A factory used to create node instances.

    To implement a data flow application, you must define a custom NodeData type, create a NodeDelegateModel for your node logic, and register these nodes in a NodeDelegateModelRegistry before passing the registry to a DataFlowGraphModel.

    #include <QtNodes/NodeData>
    #include <QtNodes/NodeDelegateModel>
    #include <QtNodes/NodeDelegateModelRegistry>
    
    // 1. Define data type
    class NumberData : public QtNodes::NodeData {
    public:
        NumberData(double value = 0.0) : _value(value) {}
        QtNodes::NodeDataType type() const override { return {"number", "Number"}; }
        double value() const { return _value; }
    private:
        double _value;
    };
    
    // 2. Create node delegate
    class AdditionNode : public QtNodes::NodeDelegateModel {
        Q_OBJECT
    public:
        QString caption() const override { return "Add"; }
        QString name() const override { return "Addition"; }
        unsigned int nPorts(PortType type) const override { return type == PortType::In ? 2 : 1; }
        NodeDataType dataType(PortType, PortIndex) const override { return NumberData{}.type(); }
        void setInData(std::shared_ptr<NodeData> data, PortIndex port) override;
        std::shared_ptr<NodeData> outData(PortIndex) override;
        QWidget* embeddedWidget() override { return nullptr; }
    private:
        std::shared_ptr<NumberData> _input1, _input2, _result;
    };
    
    // 3. Register and create model
    auto registry = std::make_shared<NodeDelegateModelRegistry>();
    registry->registerModel<NumberSourceNode>("Sources");
    registry->registerModel<AdditionNode>("Operators");
    registry->registerModel<DisplayNode>("Outputs");
    
    DataFlowGraphModel model(registry);
    DataFlowGraphicsScene scene(model);
  8. Visualize a graph model using BasicGraphicsScene and GraphicsView

    master

    To display your graph model in a window, follow these steps:

    1. Instantiate your AbstractGraphModel implementation.
    2. Create a QtNodes::BasicGraphicsScene passing your model to the constructor.
    3. Create a QtNodes::GraphicsView passing the scene to the constructor.
    4. Configure and show the view.
    #include <QApplication>
    #include <QtNodes/GraphicsView>
    #include <QtNodes/BasicGraphicsScene>
    #include "SimpleGraphModel.hpp"
    
    int main(int argc, char* argv[])
    {
        QApplication app(argc, argv);
    
        // 1. Create the graph model
        SimpleGraphModel model;
    
        // 2. Create a scene that visualizes the model
        auto* scene = new QtNodes::BasicGraphicsScene(model);
    
        // 3. Create a view to display the scene
        QtNodes::GraphicsView view(scene);
        view.setWindowTitle("My First Node Graph");
        view.resize(800, 600);
        view.show();
    
        return app.exec();
    }
  9. Build and run QtNodes tests

    master

    The QtNodes library uses the Catch2 testing framework. You can build and execute the test suite from your build directory using make commands.

    To build the tests, use make test_nodes. To run the entire test suite, execute the test_nodes binary located in the bin/ directory.

    # Build tests
    make test_nodes
    
    # Run all tests
    ./bin/test_nodes
  10. Choose an approach for graph implementation

    master

    QtNodes provides two distinct approaches depending on whether you need simple visualization or active data processing:

    1. Simple Graph Visualization: Use AbstractGraphModel and BasicGraphicsScene. This approach is best when you want to manage all graph data yourself and focus on visualization or custom graph logic.
    2. Data Flow Processing: Use DataFlowGraphModel and DataFlowGraphicsScene. This approach is best for visual programming, calculators, or pipelines, as the library manages node delegates and data routing automatically.
  11. Register Node Delegates in v3.x

    master

    When registering models in the NodeDelegateModelRegistry, you must now provide a category string. In v2, registerModel<T>() did not require this argument.

    auto registry = std::make_shared<NodeDelegateModelRegistry>();
    registry->registerModel<MyNode>("Category");  // Category now required