Graph Models
The graph model is the core of your application. It stores nodes, connections,
and all associated data. This guide covers implementing your own model by
subclassing AbstractGraphModel.
Tip
If you want automatic data propagation between nodes, see Data Flow Model instead. Use a custom graph model when you need full control over graph logic.
AbstractGraphModel Overview
Your model must implement these pure virtual methods:
class MyGraphModel : public QtNodes::AbstractGraphModel
{
public:
// ID generation
NodeId newNodeId() override;
// Node queries
std::unordered_set<NodeId> allNodeIds() const override;
bool nodeExists(NodeId) const override;
QVariant nodeData(NodeId, NodeRole) const override;
bool setNodeData(NodeId, NodeRole, QVariant) override;
// Connection queries
std::unordered_set<ConnectionId> allConnectionIds(NodeId) const override;
std::unordered_set<ConnectionId> connections(NodeId, PortType, PortIndex) const override;
bool connectionExists(ConnectionId) const override;
bool connectionPossible(ConnectionId) const override;
// Mutations
NodeId addNode(QString nodeType) override;
void addConnection(ConnectionId) override;
bool deleteNode(NodeId) override;
bool deleteConnection(ConnectionId) override;
// Port queries
QVariant portData(NodeId, PortType, PortIndex, PortRole) const override;
bool setPortData(NodeId, PortType, PortIndex, QVariant, PortRole) override;
};
Implementing Node Management
ID Generation
Generate unique IDs. A simple counter works:
NodeId MyGraphModel::newNodeId()
{
return _nextId++;
}
Adding Nodes
Store the node and emit the signal:
NodeId MyGraphModel::addNode(QString nodeType)
{
NodeId id = newNodeId();
_nodes.insert(id);
_nodeTypes[id] = nodeType;
_nodePositions[id] = QPointF(0, 0);
emit nodeCreated(id); // Required!
return id;
}
Deleting Nodes
Remove connections first, then the node:
bool MyGraphModel::deleteNode(NodeId nodeId)
{
if (!nodeExists(nodeId))
return false;
// Remove all connections involving this node
for (auto& conn : allConnectionIds(nodeId)) {
deleteConnection(conn);
}
_nodes.erase(nodeId);
emit nodeDeleted(nodeId); // Required!
return true;
}
NodeRole Reference
Implement nodeData() and setNodeData() to provide node information:
Role |
Type |
Description |
|---|---|---|
|
|
Node type identifier (e.g., “AddNode”, “ImageFilter”) |
|
|
Position on the canvas |
|
|
Size hint for embedded widgets |
|
|
Display name shown on the node |
|
|
Whether to show the caption |
|
|
Per-node style overrides (JSON) |
|
|
Custom data for serialization |
|
|
Number of input ports |
|
|
Number of output ports |
|
|
Embedded widget (or nullptr) |
|
|
Current validation state |
|
|
Current processing status |
Example implementation:
QVariant MyGraphModel::nodeData(NodeId nodeId, NodeRole role) const
{
switch (role) {
case NodeRole::Type:
return _nodeTypes.value(nodeId);
case NodeRole::Position:
return _nodePositions.value(nodeId);
case NodeRole::Caption:
return QString("Node %1").arg(nodeId);
case NodeRole::InPortCount:
return 2u; // All nodes have 2 inputs
case NodeRole::OutPortCount:
return 1u; // All nodes have 1 output
default:
return {};
}
}
Implementing Connections
Connection Queries
Return connections filtered by node and port:
std::unordered_set<ConnectionId>
MyGraphModel::connections(NodeId nodeId, PortType portType, PortIndex portIndex) const
{
std::unordered_set<ConnectionId> result;
for (const auto& conn : _connections) {
if (portType == PortType::In &&
conn.inNodeId == nodeId &&
conn.inPortIndex == portIndex) {
result.insert(conn);
}
else if (portType == PortType::Out &&
conn.outNodeId == nodeId &&
conn.outPortIndex == portIndex) {
result.insert(conn);
}
}
return result;
}
Connection Validation
Control what connections are allowed:
bool MyGraphModel::connectionPossible(ConnectionId conn) const
{
// Nodes must exist
if (!nodeExists(conn.inNodeId) || !nodeExists(conn.outNodeId))
return false;
// No self-connections
if (conn.inNodeId == conn.outNodeId)
return false;
// No duplicate connections
if (connectionExists(conn))
return false;
// Custom logic: check port compatibility, etc.
return true;
}
PortRole Reference
Implement portData() for port-specific information:
Role |
Type |
Description |
|---|---|---|
|
|
The actual data at this port |
|
|
Type descriptor for compatibility checks |
|
|
|
|
|
Port label text |
|
|
Whether to show the label |
Required Signals
Your model must emit these signals at the appropriate times:
// After adding a node
emit nodeCreated(nodeId);
// After removing a node
emit nodeDeleted(nodeId);
// After node data changes (caption, style, etc.)
emit nodeUpdated(nodeId);
// After position changes specifically
emit nodePositionUpdated(nodeId);
// After adding a connection
emit connectionCreated(connectionId);
// After removing a connection
emit connectionDeleted(connectionId);
Warning
Forgetting to emit signals will cause the view to become out of sync with your model.
Serialization Support
Override saveNode() and loadNode() to support save/load:
QJsonObject MyGraphModel::saveNode(NodeId nodeId) const
{
QJsonObject json;
json["id"] = static_cast<int>(nodeId);
json["type"] = _nodeTypes[nodeId];
QJsonObject pos;
pos["x"] = _nodePositions[nodeId].x();
pos["y"] = _nodePositions[nodeId].y();
json["position"] = pos;
return json;
}
See Serialization for complete save/load implementation.
Complete Example
See examples/simple_graph_model/ for a complete, working implementation
of a custom graph model.
See also
Data Flow Model – For automatic data propagation
QtNodes Class Reference – Full API reference