/usr/share/grafana/public/app/plugins/panel/nodeGraph
import { GeomGraph, GeomEdge, GeomNode, Point, CurveFactory, SugiyamaLayoutSettings, LayerDirectionEnum, layoutGeomGraph, } from '@msagl/core'; import { parseDot } from '@msagl/parser'; /** * Use d3 force layout to lay the nodes in a sensible way. This function modifies the nodes adding the x,y positions * and also fills in node references in edges instead of node ids. */ export function layout(nodes, edges) { const { mappedEdges, DOTToIdMap } = createMappings(nodes, edges); const dot = graphToDOT(mappedEdges, DOTToIdMap); const graph = parseDot(dot); const geomGraph = new GeomGraph(graph); for (const e of graph.deepEdges) { new GeomEdge(e); } for (const n of graph.nodesBreadthFirst) { const gn = new GeomNode(n); gn.boundaryCurve = CurveFactory.mkCircle(50, new Point(0, 0)); } geomGraph.layoutSettings = new SugiyamaLayoutSettings(); geomGraph.layoutSettings.layerDirection = LayerDirectionEnum.LR; geomGraph.layoutSettings.LayerSeparation = 60; geomGraph.layoutSettings.commonSettings.NodeSeparation = 40; layoutGeomGraph(geomGraph); const nodesMap = {}; for (const node of geomGraph.nodesBreadthFirst) { nodesMap[DOTToIdMap[node.id]] = { obj: node, }; } for (const node of nodes) { nodesMap[node.id] = { ...nodesMap[node.id], datum: { ...node, x: nodesMap[node.id].obj.center.x, y: nodesMap[node.id].obj.center.y, }, }; } const edgesMapped = edges.map((e) => { return { ...e, source: nodesMap[e.source].datum, target: nodesMap[e.target].datum, }; }); // This section checks if there are separate disjointed subgraphs. If so it groups nodes for each and then aligns // each subgraph, so it starts on a single vertical line. Otherwise, they are laid out randomly from left to right. const subgraphs = []; for (const e of edgesMapped) { const sourceGraph = subgraphs.find((g) => g.nodes.has(e.source)); const targetGraph = subgraphs.find((g) => g.nodes.has(e.target)); if (sourceGraph && targetGraph) { // if the node sets are not the same we merge them if (sourceGraph !== targetGraph) { targetGraph.nodes.forEach(sourceGraph.nodes.add, sourceGraph.nodes); subgraphs.splice(subgraphs.indexOf(targetGraph), 1); sourceGraph.top = Math.min(sourceGraph.top, targetGraph.top); sourceGraph.bottom = Math.max(sourceGraph.bottom, targetGraph.bottom); sourceGraph.left = Math.min(sourceGraph.left, targetGraph.left); sourceGraph.right = Math.max(sourceGraph.right, targetGraph.right); } // if the sets are the same nothing to do. } else if (sourceGraph) { sourceGraph.nodes.add(e.target); sourceGraph.top = Math.min(sourceGraph.top, e.target.y); sourceGraph.bottom = Math.max(sourceGraph.bottom, e.target.y); sourceGraph.left = Math.min(sourceGraph.left, e.target.x); sourceGraph.right = Math.max(sourceGraph.right, e.target.x); } else if (targetGraph) { targetGraph.nodes.add(e.source); targetGraph.top = Math.min(targetGraph.top, e.source.y); targetGraph.bottom = Math.max(targetGraph.bottom, e.source.y); targetGraph.left = Math.min(targetGraph.left, e.source.x); targetGraph.right = Math.max(targetGraph.right, e.source.x); } else { // we don't have these nodes subgraphs.push({ top: Math.min(e.source.y, e.target.y), bottom: Math.max(e.source.y, e.target.y), left: Math.min(e.source.x, e.target.x), right: Math.max(e.source.x, e.target.x), nodes: new Set([e.source, e.target]), }); } } let top = 0; let left = 0; for (const g of subgraphs) { if (top === 0) { top = g.bottom + 200; left = g.left; } else { const topDiff = top - g.top; const leftDiff = left - g.left; for (const n of g.nodes) { n.x += leftDiff; n.y += topDiff; } top += g.bottom - g.top + 200; } } const finalNodes = Object.values(nodesMap).map((v) => v.datum); centerNodes(finalNodes); return [finalNodes, edgesMapped]; } // We create mapping because the DOT language we use later to create the graph doesn't support arbitrary IDs. So we // map our IDs to just an index of the node so the IDs are safe for the DOT parser and also create and inverse mapping // for quick lookup. function createMappings(nodes, edges) { // Edges where the source and target IDs are the indexes we use for layout const mappedEdges = []; // Key is an ID of the node and value is new ID which is just iteration index const idToDOTMap = {}; // Key is an iteration index and value is actual ID of the node const DOTToIdMap = {}; let index = 0; for (const node of nodes) { idToDOTMap[node.id] = index.toString(10); DOTToIdMap[index.toString(10)] = node.id; index++; } for (const edge of edges) { mappedEdges.push({ source: idToDOTMap[edge.source], target: idToDOTMap[edge.target] }); } return { mappedEdges, DOTToIdMap, idToDOTMap, }; } function graphToDOT(edges, nodeIDsMap) { let dot = ` digraph G { rankdir="LR"; TBbalance="min" `; for (const edge of edges) { dot += edge.source + '->' + edge.target + ' ' + '[ minlen=3 ]\n'; } dot += nodesDOT(nodeIDsMap); dot += '}'; return dot; } function nodesDOT(nodeIdsMap) { let dot = ''; for (const node of Object.keys(nodeIdsMap)) { dot += node + ' [fixedsize=true, width=1.2, height=1.7] \n'; } return dot; } /** * Makes sure that the center of the graph based on its bound is in 0, 0 coordinates. * Modifies the nodes directly. */ function centerNodes(nodes) { const bounds = graphBounds(nodes); for (let node of nodes) { node.x = node.x - bounds.center.x; node.y = node.y - bounds.center.y; } } /** * Get bounds of the graph meaning the extent of the nodes in all directions. */ function graphBounds(nodes) { if (nodes.length === 0) { return { top: 0, right: 0, bottom: 0, left: 0, center: { x: 0, y: 0 } }; } const bounds = nodes.reduce( (acc, node) => { if (node.x > acc.right) { acc.right = node.x; } if (node.x < acc.left) { acc.left = node.x; } if (node.y > acc.bottom) { acc.bottom = node.y; } if (node.y < acc.top) { acc.top = node.y; } return acc; }, { top: Infinity, right: -Infinity, bottom: -Infinity, left: Infinity } ); const y = bounds.top + (bounds.bottom - bounds.top) / 2; const x = bounds.left + (bounds.right - bounds.left) / 2; return { ...bounds, center: { x, y, }, }; }
.
Edit
..
Edit
Edge.tsx
Edit
EdgeArrowMarker.tsx
Edit
EdgeLabel.tsx
Edit
Legend.test.tsx
Edit
Legend.tsx
Edit
Marker.tsx
Edit
Node.test.tsx
Edit
Node.tsx
Edit
NodeGraph.test.tsx
Edit
NodeGraph.tsx
Edit
NodeGraphPanel.tsx
Edit
README.md
Edit
ViewControls.tsx
Edit
createLayoutWorker.ts
Edit
editor
Edit
forceLayout.js
Edit
img
Edit
layeredLayout.js
Edit
layeredLayout.test.ts
Edit
layeredLayout.worker.js
Edit
layout.test.ts
Edit
layout.ts
Edit
layout.worker.js
Edit
module.tsx
Edit
panelcfg.cue
Edit
panelcfg.gen.ts
Edit
plugin.json
Edit
suggestions.ts
Edit
types.ts
Edit
useCategorizeFrames.ts
Edit
useContextMenu.tsx
Edit
useFocusPositionOnLayout.ts
Edit
useHighlight.ts
Edit
useNodeLimit.ts
Edit
usePanning.ts
Edit
useZoom.ts
Edit
utils.test.ts
Edit
utils.ts
Edit