From ce4dc7546c18d3c5586a0c6baddc3888a2b40d9a Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Thu, 8 Feb 2024 18:35:03 +0800 Subject: [PATCH] feat(examples): add dot layout example update update --- examples/dot/.gitignore | 3 + examples/dot/README.md | 22 +++ examples/dot/css/dot.css | 8 + examples/dot/index.html | 13 ++ examples/dot/package.json | 35 ++++ examples/dot/src/index.js | 314 +++++++++++++++++++++++++++++++++ examples/dot/webpack.config.js | 29 +++ 7 files changed, 424 insertions(+) create mode 100644 examples/dot/.gitignore create mode 100644 examples/dot/README.md create mode 100644 examples/dot/css/dot.css create mode 100644 examples/dot/index.html create mode 100644 examples/dot/package.json create mode 100644 examples/dot/src/index.js create mode 100644 examples/dot/webpack.config.js diff --git a/examples/dot/.gitignore b/examples/dot/.gitignore new file mode 100644 index 000000000..69c575d17 --- /dev/null +++ b/examples/dot/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +node_modules/ diff --git a/examples/dot/README.md b/examples/dot/README.md new file mode 100644 index 000000000..2a003e042 --- /dev/null +++ b/examples/dot/README.md @@ -0,0 +1,22 @@ +# JointJS List Demo + +## Setup + +Use Yarn to run this demo. + +You need to build *JointJS* first. Navigate to the root folder and run: +```bash +yarn install +yarn run build +``` + +Navigate to this directory, then run: +```bash +yarn start +``` + +## License + +The *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE). + +Copyright © 2013-2024 client IO diff --git a/examples/dot/css/dot.css b/examples/dot/css/dot.css new file mode 100644 index 000000000..400324d85 --- /dev/null +++ b/examples/dot/css/dot.css @@ -0,0 +1,8 @@ +#paper-container { + position: absolute; + right: 0; + top: 0; + left: 0; + bottom: 0; + overflow: scroll; + } diff --git a/examples/dot/index.html b/examples/dot/index.html new file mode 100644 index 000000000..5cdc5ba6e --- /dev/null +++ b/examples/dot/index.html @@ -0,0 +1,13 @@ + + + + + + + Fault Tree Analysis | JointJS + + +
+ + + diff --git a/examples/dot/package.json b/examples/dot/package.json new file mode 100644 index 000000000..363e9e4af --- /dev/null +++ b/examples/dot/package.json @@ -0,0 +1,35 @@ +{ + "name": "@joint/demo-graphviz-dot-layout", + "version": "4.0.1", + "main": "src/index.js", + "homepage": "https://jointjs.com", + "author": { + "name": "client IO", + "url": "https://client.io" + }, + "license": "MPL-2.0", + "private": true, + "installConfig": { + "hoistingLimits": "workspaces" + }, + "scripts": { + "start": "webpack-dev-server", + "tsc": "tsc" + }, + "dependencies": { + "@hpcc-js/wasm": "^2.15.3", + "@joint/core": "workspace:^" + }, + "devDependencies": { + "css-loader": "3.5.3", + "style-loader": "1.2.1", + "webpack": "^5.61.0", + "webpack-cli": "^4.8.0", + "webpack-dev-server": "^4.2.1" + }, + "volta": { + "node": "16.18.1", + "npm": "8.19.2", + "yarn": "3.4.1" + } +} diff --git a/examples/dot/src/index.js b/examples/dot/src/index.js new file mode 100644 index 000000000..fe90f93bb --- /dev/null +++ b/examples/dot/src/index.js @@ -0,0 +1,314 @@ +import { dia, elementTools, shapes as defaultShapes, util, V, g } from '@joint/core'; +import { Graphviz } from "@hpcc-js/wasm/graphviz"; + + import '../css/dot.css' + +const graph = new dia.Graph({}, { cellNamespace: defaultShapes }); + +const paper = new dia.Paper({ + width: '100%', + height: '100%', + model: graph, + // defaultConnectionPoint: { name: 'boundary', args: { offset: 20 }}, + // defaultConnector: { + // name: 'straight', + // args: { cornerType: 'line', cornerRadius: 10 }, + // }, + // defaultRouter: { name: 'orthogonal' }, + defaultConnector: { name: 'curve' }, + defaultConnectionPoint: { name: 'anchor' }, + async: true, + // interactive: false, + frozen: true, + cellViewNamespace: defaultShapes, + background: { color: '#131e29' } +}); + +document.getElementById('paper-container').appendChild(paper.el); + +const diagram = { + 'objects': [ + { + 'id': 'pm', + 'data': { + 'nodes': [ + { + 'node_id': 0, + }, + { + 'node_id': 6, + }, + { + 'node_id': 8, + }, + { + 'node_id': 5, + }, + { + 'node_id': 3, + }, + { + 'node_id': 9, + }, + { + 'node_id': 1, + }, + { + 'node_id': 4, + }, + { + 'node_id': 7, + }, + { + 'node_id': 11, + }, + { + 'node_id': 10, + }, + { + 'node_id': 2, + }, + { + 'node_id': 12, + } + ], + 'edges': [ + { + 'node_id_from': 5, + 'node_id_to': 1, + }, + { + 'node_id_from': 5, + 'node_id_to': 3, + }, + { + 'node_id_from': 5, + 'node_id_to': 5, + }, + { + 'node_id_from': 5, + 'node_id_to': 9, + }, + { + 'node_id_from': 9, + 'node_id_to': 3, + }, + { + 'node_id_from': 9, + 'node_id_to': 4, + }, + { + 'node_id_from': 10, + 'node_id_to': 2, + }, + { + 'node_id_from': 8, + 'node_id_to': 5, + }, + { + 'node_id_from': 8, + 'node_id_to': 8, + }, + { + 'node_id_from': 1, + 'node_id_to': 2, + }, + { + 'node_id_from': 3, + 'node_id_to': 4, + }, + { + 'node_id_from': 3, + 'node_id_to': 9, + }, + { + 'node_id_from': 6, + 'node_id_to': 8, + }, + { + 'node_id_from': 11, + 'node_id_to': 12, + }, + { + 'node_id_from': 11, + 'node_id_to': 7, + }, + { + 'node_id_from': 11, + 'node_id_to': 10, + }, + { + 'node_id_from': 0, + 'node_id_to': 6, + }, + { + 'node_id_from': 2, + 'node_id_to': 12, + }, + { + 'node_id_from': 7, + 'node_id_to': 10, + }, + { + 'node_id_from': 7, + 'node_id_to': 11, + }, + { + 'node_id_from': 4, + 'node_id_to': 7, + }, + { + 'node_id_from': 4, + 'node_id_to': 11, + } + ] + } + } + ] +}; + +const cells = []; +diagram.objects[0].data.nodes.forEach((node) => { + cells.push({ + id: `${node.node_id}`, + type: 'standard.Ellipse', + size: { width: 32, height: 32 }, + // position: { x: Math.random() * 800, y: Math.random() * 800 }, + attrs: { + // label: { text: node.node_name }, + label: { text: `${node.node_id}` }, + body: { fill: '#3498db', stroke: '#2980b9' } + }, + z: 2 + }); +}); + +diagram.objects[0].data.edges.forEach((edge) => { + cells.push({ + type: 'standard.Link', + source: { id: `${edge.node_id_from}` }, + target: { id: `${edge.node_id_to}` }, + z: -1, + + attrs: { + line: { + stroke: 'white', + targetMarker: { + 'type': 'path', + 'd': 'M 6 -3 -2 0 6 3 z' + } + } + } + }); +}); + +const variants = ['0', '6', '8', '5', '9', '4', '11']; + +graph.fromJSON({ cells: cells }); + +Graphviz.load().then(graphviz => { + const links = graph.getLinks().map(link => { + const source = link.source().id.replace(/-/g, '_'); + const target = link.target().id.replace(/-/g, '_'); + console.log(source, target); + return `${source} -> ${target};`; + }); + + const elements = graph.getElements().map(element => element.id.replace(/-/g, '_')); + const elementAttributes = '' && elements.map(v => `${v} [width=2 height=2];`).join(' '); + const dot = `digraph G { rankdir=LR; node [fixedsize=true width=0.5 height=0.5] edge [arrowhead=none] ${links.join(' ')} { rank=same; ${variants.join(', ')} } }`; + console.log(dot) + + // const dot = `digraph G { rankdir=TB; ordering="in"; ${variants.map(v => `${v} [group=1];`).join(' ')} ${links.join(' ')} }`; + + const result = JSON.parse(graphviz.layout(dot, 'json', 'dot', { yInvert: true })); + + result.objects.forEach(obj => { + const id = obj.name.replace(/_/g, '-'); + const element = graph.getCell(id); + if (!element) { + return; + } + if (variants.includes(id)) { + element.attr('body/stroke', '#e74c3c'); + } + const [x,y] = obj.pos.split(',').map(Number); + const { width, height } = element.size(); + element.position(x - width / 2, y - height / 2); + }); + + graph.getLinks().forEach((link, index) => { + + + const rawPoints = result.edges[index]._draw_[1].points.map(([x,y]) => { return { x, y } }); + + let points = rawPoints.slice(); + + function showPoints(points) { + points.forEach((point, index) => { + V('circle').attr({ r: 3, fill: 'red', cx: point.x, cy: point.y }).appendTo(paper.viewport); + }); + } + + // points = points.filter((_, index) => index === 0 || ((index + 1) % 3 === 0)); + + // points = points.filter((_, index) => index === 0); + + // showPoints(points); + + // x1,c1, c2a, x2, c2b, c3, x3 + // 0 1 2 3 0 1 2 3 + // find first point, the last point and points in between + // const first = points[0]; + // const last = points[points.length - 1]; + // const middle = points.slice(1, points.length - 1); + + // showPoints([first]); + + const [first, second] = rawPoints; + const [beforeLast, last] = rawPoints.slice(-2); + + const sourceCenter = link.getSourceCell().getBBox().center(); + const targetCenter = link.getTargetCell().getBBox().center(); + + const sourceVector = new g.Point(second).difference(first); + const targetVector = new g.Point(beforeLast).difference(last); + + const { x: dx1, y: dy1 } = new g.Point(first).difference(link.getSourceCell().getBBox().center()); + const { x: dx2, y: dy2 } = new g.Point(last).difference(link.getTargetCell().getBBox().center()); + + link.prop({ + source: { anchor: { name: 'modelCenter', args: { dx: dx1, dy: dy1 }}}, + target: { anchor: { name: 'modelCenter', args: { dx: dx2, dy: dy2 }}} + }); + + link.connector('curve', { + sourceTangent: sourceVector, + targetTangent: targetVector, + }); + + // showPoints([beforeLast, last]); + // showPoints([rawPoints[0], rawPoints[1]]); + + const vertices = rawPoints.filter((_, index) => ((index + 1) % 3 === 0)); + if (link.hasLoop()) { + // showPoints(rawPoints); + link.vertices([rawPoints[3]]) + } + // link.set('vertices', vertices.slice(0, -1)); + // console.log(vertices.slice(0, -1)) + }); + + // graph.getLinks()[1].attr('line/stroke', '#e74c3c'); + + // console.log(result); + + paper.transformToFitContent({ + padding: 15, + contentArea: graph.getBBox(), + horizontalAlign: 'middle', + }); + + paper.unfreeze(); + +}); diff --git a/examples/dot/webpack.config.js b/examples/dot/webpack.config.js new file mode 100644 index 000000000..20663a5d3 --- /dev/null +++ b/examples/dot/webpack.config.js @@ -0,0 +1,29 @@ +const path = require('path'); + +module.exports = { + resolve: { + extensions: ['.js'] + }, + entry: './src/index.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + publicPath: '/dist/' + }, + mode: 'development', + module: { + rules: [ + { + test: /\.css$/, + sideEffects: true, + use: ['style-loader', 'css-loader'], + } + ] + }, + devServer: { + static: { + directory: __dirname, + }, + compress: true + }, +};