diff --git a/examples/graph/elk-layout.bat b/examples/graph/elk-layout.bat new file mode 100644 index 0000000000..5727905690 --- /dev/null +++ b/examples/graph/elk-layout.bat @@ -0,0 +1,5 @@ +@echo off +set main=./examples/graph/elk-layout.js +cd .. +cd .. +npm run dev \ No newline at end of file diff --git a/examples/graph/elk-layout.js b/examples/graph/elk-layout.js new file mode 100644 index 0000000000..8023a05da2 --- /dev/null +++ b/examples/graph/elk-layout.js @@ -0,0 +1,100 @@ +import phaser from 'phaser/src/phaser.js'; +import GraphPlugin from '../../plugins/graph-plugin.js'; +import RandomPlacePlugin from '../../plugins/randomplace-plugin.js'; +import MoveToPlugin from '../../plugins/moveto-plugin.js'; + +const COLOR_MAIN = 0x7986cb; +const COLOR_LIGHT = 0xaab6fe; +const COLOR_DARK = 0x49599a; + +const GetRandomItem = Phaser.Utils.Array.GetRandom; +const DistanceBetween = Phaser.Math.Distance.Between; +const RemoveItem = Phaser.Utils.Array.Remove; + +class Demo extends Phaser.Scene { + constructor() { + super({ + key: 'examples' + }) + } + + preload() { } + + create() { + var nodeA = CreateNode(this); + var nodeB = CreateNode(this); + var nodeC = CreateNode(this); + var nodeD = CreateNode(this); + var edgeAB = CreateEdge(this); + var edgeAC = CreateEdge(this); + var edgeBD = CreateEdge(this); + var edgeCD = CreateEdge(this); + + var graph = this.rexGraph.add.graph() + .addNodes([nodeA, nodeB, nodeC, nodeD]) + .addEdge(edgeAB, nodeA, nodeB) + .addEdge(edgeAC, nodeA, nodeC) + .addEdge(edgeBD, nodeB, nodeD) + .addEdge(edgeCD, nodeC, nodeD) + + graph.on('layout.edge', function (edgeGameObject, path) { + var startPoint = path[0]; + var endPoint = path[path.length - 1]; + edgeGameObject + .setPosition(startPoint.x, startPoint.y) + .setTo(0, 0, endPoint.x - startPoint.x, endPoint.y - startPoint.y) + }); + + this.rexGraph.ELKLayout(graph) + .once('layout.complete', function () { + console.log('layout.complete') + }) + + } + + update() { + } +} + +var CreateNode = function (scene) { + return scene.add.rectangle(0, 0, 100, 100).setStrokeStyle(3, 0x00ffff) +} + +var CreateEdge = function (scene) { + return scene.add.line(0, 0, 0, 0, 0, 0, 0xff0000).setLineWidth(2).setOrigin(0) +} + +var config = { + type: Phaser.AUTO, + parent: 'phaser-example', + width: 800, + height: 600, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + scene: Demo, + plugins: { + global: [ + { + key: 'rexRandomPlace', + plugin: RandomPlacePlugin, + start: true + }, + { + key: 'rexMoveTo', + plugin: MoveToPlugin, + start: true + } + ], + scene: [ + { + key: 'rexGraph', + plugin: GraphPlugin, + mapping: 'rexGraph' + } + ] + } +}; + +var game = new Phaser.Game(config); \ No newline at end of file diff --git a/plugins/graph-components.js b/plugins/graph-components.js new file mode 100644 index 0000000000..e97f630a90 --- /dev/null +++ b/plugins/graph-components.js @@ -0,0 +1,7 @@ +import Graph from './graph/graph/Graph.js'; +import ELKLayout from './graph/layout/elkjs/Layout.js'; + +export { + Graph, + ELKLayout +} \ No newline at end of file diff --git a/plugins/graph-plugin.js b/plugins/graph-plugin.js index af5dc2eb86..38fdb59129 100644 --- a/plugins/graph-plugin.js +++ b/plugins/graph-plugin.js @@ -1,6 +1,7 @@ import ObjectFactory from './graph/ObjectFactory.js'; import GraphFactory from './graph/graph/Factory.js'; +import ELKLayout from './graph/layout/elkjs/Layout.js'; class GraphPlugin extends Phaser.Plugins.ScenePlugin { constructor(scene, pluginManager) { @@ -18,6 +19,11 @@ class GraphPlugin extends Phaser.Plugins.ScenePlugin { this.add.destroy(); super.destroy(); } + + ELKLayout(graph, config) { + ELKLayout(graph, config); + return graph + } } export default GraphPlugin; diff --git a/plugins/graph/layout/elkjs/BuildGraphData.js b/plugins/graph/layout/elkjs/BuildGraphData.js index 4de1c26846..088a2039cd 100644 --- a/plugins/graph/layout/elkjs/BuildGraphData.js +++ b/plugins/graph/layout/elkjs/BuildGraphData.js @@ -1,24 +1,44 @@ import UIDToObj from '../../graphitem/UIDToObj.js'; -import { GetTopLeft } from '../../../utils/bounds/GetBounds.js'; var BuildGraphData = function (graph, config) { var nodes = []; + var nodeGameObjectMap = {}; graph.graph.forEachNode(function (uid) { var nodeGameObject = UIDToObj(uid); if (!nodeGameObject) { return; } - var nodeData = { id: uid, gameObject: nodeGameObject }; - GetTopLeft(nodeGameObject, nodeData); - nodeData.width = nodeGameObject.displayWidth; - nodeData.height = nodeGameObject.displayHeight; + var nodeData = { + gameObject: nodeGameObject, + id: uid, + width: nodeGameObject.displayWidth, + height: nodeGameObject.displayHeight + }; nodes.push(nodeData); + + nodeGameObjectMap[uid] = nodeGameObject; }) var edges = []; graph.graph.forEachEdge(function (uid, attributes, sourceUID, targetUID) { - edges.puth({ id: uid, source: sourceUID, target: targetUID }); + var sourceGameObject = nodeGameObjectMap[sourceUID]; + var targetGameObject = nodeGameObjectMap[targetUID]; + + if (!sourceGameObject || !targetGameObject) { + return; + } + var edgeGameObject = UIDToObj(uid); + if (!edgeGameObject) { + return; + } + var edgeData = { + gameObject: edgeGameObject, + sourceGameObject: sourceGameObject, + targetGameObject: targetGameObject, + id: uid, source: sourceUID, target: targetUID, + }; + edges.push(edgeData); }) return { diff --git a/plugins/graph/layout/elkjs/GetPath.js b/plugins/graph/layout/elkjs/GetPath.js new file mode 100644 index 0000000000..a929f2da8a --- /dev/null +++ b/plugins/graph/layout/elkjs/GetPath.js @@ -0,0 +1,19 @@ +var GetPath = function (edgeData) { + var result = []; + + var pathData = edgeData.sections[0]; + + result.push(pathData.startPoint); + + if (pathData.bendPoints) { + pathData.bendPoints.forEach(function (point) { + result.push(point); + }) + } + + result.push(pathData.endPoint); + + return result; +} + +export default GetPath; \ No newline at end of file diff --git a/plugins/graph/layout/elkjs/Layout.js b/plugins/graph/layout/elkjs/Layout.js index 1db25cb4d3..0cb74515ad 100644 --- a/plugins/graph/layout/elkjs/Layout.js +++ b/plugins/graph/layout/elkjs/Layout.js @@ -2,11 +2,15 @@ import ELK from '../../../utils/elkjs/elk.bundled.js'; import BuildGraphData from './BuildGraphData.js'; import PlaceGameObjects from './PlaceGameObjects.js'; -var Layout = async function (graph, config) { - var elk = new ELK(); +var Layout = function (graph, config) { var graphData = BuildGraphData(graph, config); - graphData = await elk.layout(graphData); - PlaceGameObjects(graphData, graph); + + var elk = new ELK(); + elk.layout(graphData) + .then(function (graphData) { + PlaceGameObjects(graph, graphData, config); + graph.emit('layout.complete'); + }) } export default Layout; \ No newline at end of file diff --git a/plugins/graph/layout/elkjs/PlaceGameObjects.js b/plugins/graph/layout/elkjs/PlaceGameObjects.js index d9dc4958b5..b3775ae194 100644 --- a/plugins/graph/layout/elkjs/PlaceGameObjects.js +++ b/plugins/graph/layout/elkjs/PlaceGameObjects.js @@ -1,13 +1,18 @@ import AlignIn from '../../../utils/actions/AlignIn.js'; +import GetPath from './GetPath.js'; const ALIGN_CENTER = Phaser.Display.Align.CENTER; -var PlaceGameObjects = function (graphData, graph) { +var PlaceGameObjects = function (graph, graphData, config) { graphData.children.forEach(function (nodeData) { AlignIn(nodeData.gameObject, nodeData.x, nodeData.y, nodeData.width, nodeData.height, ALIGN_CENTER); + graph.emit('layout.node', nodeData.gameObject); }) - // TODO: edge? + graphData.edges.forEach(function (edgeData) { + var path = GetPath(edgeData); + graph.emit('layout.edge', edgeData.gameObject, path, edgeData.sourceGameObject, edgeData.targetGameObject); + }) } export default PlaceGameObjects; \ No newline at end of file