rewritten using elkjs
This commit is contained in:
Generated
+7
-40
@@ -8,8 +8,7 @@
|
||||
"name": "resource-graph-sim",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"ngraph.forcelayout": "^3.3.1",
|
||||
"ngraph.graph": "^20.1.2",
|
||||
"elkjs": "^0.12.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
@@ -1326,6 +1325,12 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/elkjs": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.12.0.tgz",
|
||||
"integrity": "sha512-YZcKynxVxYoKIOEpywEPwCFdg+BTbxQRNf3pbwdDCvc8O3kQD8bmIwSxKU1eOTVc4Xo+VG9Te+575mlfvOrhEQ==",
|
||||
"license": "EPL-2.0 OR GPL-3.0-or-later"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
@@ -1480,44 +1485,6 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ngraph.events": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.4.0.tgz",
|
||||
"integrity": "sha512-NeDGI4DSyjBNBRtA86222JoYietsmCXbs8CEB0dZ51Xeh4lhVl1y3wpWLumczvnha8sFQIW4E0vvVWwgmX2mGw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ngraph.forcelayout": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.forcelayout/-/ngraph.forcelayout-3.3.1.tgz",
|
||||
"integrity": "sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"ngraph.events": "^1.0.0",
|
||||
"ngraph.merge": "^1.0.0",
|
||||
"ngraph.random": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ngraph.graph": {
|
||||
"version": "20.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-20.1.2.tgz",
|
||||
"integrity": "sha512-W/G3GBR3Y5UxMLHTUCPP9v+pbtpzwuAEIqP5oZV+9IwgxAIEZwh+Foc60iPc1idlnK7Zxu0p3puxAyNmDvBd0Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"ngraph.events": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ngraph.merge": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.merge/-/ngraph.merge-1.0.0.tgz",
|
||||
"integrity": "sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ngraph.random": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-1.2.0.tgz",
|
||||
"integrity": "sha512-4EUeAGbB2HWX9njd6bP6tciN6ByJfoaAvmVL9QTaZSeXrW46eNGA9GajiXiPBbvFqxUWFkEbyo6x5qsACUuVfA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.54",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz",
|
||||
|
||||
+1
-2
@@ -9,8 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ngraph.forcelayout": "^3.3.1",
|
||||
"ngraph.graph": "^20.1.2",
|
||||
"elkjs": "^0.12.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Selection } from '../App';
|
||||
import NodeCard from './NodeCard';
|
||||
import { Game } from '../types';
|
||||
import { build2dNodes, Node2D } from '../ui/graph';
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { NodeLine } from './NodeLine';
|
||||
|
||||
interface Props {
|
||||
@@ -33,10 +33,16 @@ export const GameCanvas = function ({
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const nodes2d = useMemo(
|
||||
() => build2dNodes(game.nodes, { width: size.width, height: size.height, padding: 40 }),
|
||||
[game.nodes, size.width, size.height],
|
||||
);
|
||||
const [nodes2d, setNodes2d] = useState<Node2D[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
build2dNodes(game.nodes, { width: size.width, height: size.height, padding: 40 })
|
||||
.then((result) => {
|
||||
if (!cancelled) setNodes2d(result);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [game.nodes, size.width, size.height]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const worldEl = canvasRef.current;
|
||||
|
||||
@@ -41,7 +41,7 @@ const NodeCard = forwardRef<HTMLDivElement, Props>(function NodeCard({ node2d, s
|
||||
}
|
||||
<div className="node__links">
|
||||
{
|
||||
info.links.map((link) => <div>{link.target}/{link.resource}</div>)
|
||||
info.links.map((link) => <div key={`${link.target}/${link.resource}`}>{link.target}/{link.resource}</div>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+37
-66
@@ -1,8 +1,10 @@
|
||||
import createGraph, { Graph } from "ngraph.graph";
|
||||
import createLayout from "ngraph.forcelayout";
|
||||
import ELK, { ElkNode } from "elkjs/lib/elk.bundled.js";
|
||||
import { Node } from "../types";
|
||||
|
||||
const ITERATION_COUNT = 10;
|
||||
const elk = new ELK();
|
||||
|
||||
const NODE_WIDTH = 100;
|
||||
const NODE_HEIGHT = 90;
|
||||
|
||||
export type Node2D = {
|
||||
id: Node['id'];
|
||||
@@ -10,70 +12,39 @@ export type Node2D = {
|
||||
node: Node;
|
||||
}
|
||||
|
||||
export function build2dNodes(nodes: Node[], area: { width: number, height: number, padding: number }): Node2D[] {
|
||||
const graph = createGraph();
|
||||
for (const node of nodes) {
|
||||
graph.addNode(node.behaviour.id, { node });
|
||||
}
|
||||
for (const node of nodes) {
|
||||
for (const link of node.behaviour.getDisplayInfo().links)
|
||||
graph.addLink(node.behaviour.id, link.target);
|
||||
}
|
||||
return buildGraph2dNodes(graph, area);
|
||||
}
|
||||
export async function build2dNodes(nodes: Node[], area: { width: number, height: number, padding: number }): Promise<Node2D[]> {
|
||||
const nodesById = new Map(nodes.map((node) => [node.behaviour.id, node]));
|
||||
|
||||
function buildGraph2dNodes(graph: Graph, area: { width: number, height: number, padding: number }): Node2D[] {
|
||||
const layout = createLayout(
|
||||
graph,
|
||||
{
|
||||
springLength: 300,
|
||||
// springTransform: (link, spring) => {
|
||||
// const from = graph.getNode(link.fromId).data.radius;
|
||||
// const to = graph.getNode(link.toId).data.radius;
|
||||
// spring.length = from + to + 10; // отступ 10px
|
||||
// },
|
||||
const graph: ElkNode = {
|
||||
id: "root",
|
||||
layoutOptions: {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "RIGHT",
|
||||
"elk.spacing.nodeNode": "60",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "100",
|
||||
},
|
||||
);
|
||||
children: nodes.map((node) => ({
|
||||
id: node.behaviour.id,
|
||||
width: NODE_WIDTH,
|
||||
height: NODE_HEIGHT,
|
||||
})),
|
||||
edges: nodes.flatMap((node) =>
|
||||
node.behaviour.getDisplayInfo().links.map((link, idx) => ({
|
||||
id: `${node.behaviour.id}->${link.target}.${idx}`,
|
||||
sources: [node.behaviour.id],
|
||||
targets: [link.target],
|
||||
}))
|
||||
),
|
||||
};
|
||||
|
||||
function clampToBounds() {
|
||||
const minX = area.padding;
|
||||
const maxX = area.width - area.padding;
|
||||
const minY = area.padding;
|
||||
const maxY = area.height - area.padding;
|
||||
layout.forEachBody((body) => {
|
||||
if (body.pos.x < minX) { body.pos.x = minX; body.velocity.x = 0; }
|
||||
if (body.pos.x > maxX) { body.pos.x = maxX; body.velocity.x = 0; }
|
||||
if (body.pos.y < minY) { body.pos.y = minY; body.velocity.y = 0; }
|
||||
if (body.pos.y > maxY) { body.pos.y = maxY; body.velocity.y = 0; }
|
||||
});
|
||||
}
|
||||
|
||||
for (var i = 0; i < ITERATION_COUNT; ++i) {
|
||||
layout.step();
|
||||
// clampToBounds();
|
||||
}
|
||||
|
||||
// const rect = layout.getGraphRect();
|
||||
// const W = area.width, H = 600, PADDING = 40;
|
||||
|
||||
// const scaleX = (area.width - area.padding * 2) / (rect.x2 - rect.x1);
|
||||
// const scaleY = (area.height - area.padding * 2) / (rect.y2 - rect.y1);
|
||||
// const scale = Math.min(scaleX, scaleY); // сохраняем пропорции
|
||||
|
||||
// function toCanvas(pos: { x: number; y: number }) {
|
||||
// return {
|
||||
// x: area.padding + (pos.x - rect.x1) * scale,
|
||||
// y: area.padding + (pos.y - rect.y1) * scale,
|
||||
// };
|
||||
// }
|
||||
|
||||
const nodes: Node2D[] = [];
|
||||
layout.forEachBody((body, nodeId) => {
|
||||
nodes.push({
|
||||
id: nodeId.toString(),
|
||||
position: { x: body.pos.x + area.width / 2, y: body.pos.y + area.height / 2 },
|
||||
node: graph.getNode(nodeId)?.data.node,
|
||||
});
|
||||
});
|
||||
return nodes;
|
||||
const layout = await elk.layout(graph);
|
||||
|
||||
return (layout.children ?? []).map((child) => ({
|
||||
id: child.id,
|
||||
position: {
|
||||
x: (child.x ?? 0) + area.padding,
|
||||
y: (child.y ?? 0) + area.padding,
|
||||
},
|
||||
node: nodesById.get(child.id)!,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user