import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 /** A component displaying a Graph (nodes, attributes and edges). */ Item { id: root property variant graph: null property variant selectedNode: null property int nodeWidth: 140 property int nodeHeight: 40 property int gridSpacing: 10 property var _attributeToDelegate: ({}) // signals signal workspaceMoved() signal workspaceClicked() clip: true // Activate multisampling for edges antialiasing layer.enabled: true layer.samples: 8 MouseArea { id: mouseArea anchors.fill: parent property double factor: 1.15 hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton drag.threshold: 0 onWheel: { var zoomFactor = wheel.angleDelta.y > 0 ? factor : 1/factor if(Math.min(draggable.width*draggable.scale*zoomFactor, draggable.height*draggable.scale*zoomFactor) < 10) return var point = mapToItem(draggable, wheel.x, wheel.y) draggable.x += (1-zoomFactor) * point.x * draggable.scale draggable.y += (1-zoomFactor) * point.y * draggable.scale draggable.scale *= zoomFactor draggable.scale = draggable.scale.toFixed(2) workspaceMoved() } onPressed: { if(mouse.button & Qt.MiddleButton) drag.target = draggable // start drag } onReleased: { drag.target = undefined // stop drag workspaceClicked() } onPositionChanged: { if(drag.active) workspaceMoved() } Item { id: draggable transformOrigin: Item.TopLeft width: 1000 height: 1000 // Edges Repeater { id: edgesRepeater // delay edges loading after nodes (edges needs attribute pins to be created) model: nodeRepeater.loaded ? root.graph.edges : undefined delegate: Edge { property var src: root._attributeToDelegate[edge.src] property var dst: root._attributeToDelegate[edge.dst] property var srcAnchor: src.nodeItem.mapFromItem(src, src.edgeAnchorPos.x, src.edgeAnchorPos.y) property var dstAnchor: dst.nodeItem.mapFromItem(dst, dst.edgeAnchorPos.x, dst.edgeAnchorPos.y) edge: object point1x: src.nodeItem.x + srcAnchor.x point1y: src.nodeItem.y + srcAnchor.y point2x: dst.nodeItem.x + dstAnchor.x point2y: dst.nodeItem.y + dstAnchor.y onPressed: { if(event.button == Qt.RightButton) _reconstruction.removeEdge(edge) } } } // Nodes Repeater { id: nodeRepeater model: root.graph.nodes property bool loaded: count === model.count onLoadedChanged: if(loaded) { doAutoLayout() } delegate: Node { node: object width: root.nodeWidth height: Math.max(root.nodeHeight, implicitHeight) radius: 1 border.color: root.selectedNode == node ? Qt.darker(color, 1.8) : Qt.darker(color, 1.1) onAttributePinCreated: root._attributeToDelegate[attribute] = pin onPressed: { root.selectedNode = object } Behavior on x { NumberAnimation {} } Behavior on y { NumberAnimation {} } } } } } Row { anchors.bottom: parent.bottom Button { text: "Fit" onClicked: root.fit() z: 10 } Button { text: "AutoLayout" onClicked: root.doAutoLayout() z: 10 } } // Fit graph to fill root function fit() { // compute bounding box var first = nodeRepeater.itemAt(0) var bbox = Qt.rect(first.x, first.y, 1, 1) for(var i=0; i