total-diagram 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout repo
14
+ uses: actions/checkout@v3
15
+
16
+ - name: Use Node.js
17
+ uses: actions/setup-node@v3
18
+ with:
19
+ node-version: "16.x"
20
+
21
+ - name: Install
22
+ run: npm install
23
+
24
+ - name: Build
25
+ run: npm run build --if-present
@@ -0,0 +1,29 @@
1
+ name: Deploy
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ deploy:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Checkout repo
12
+ uses: actions/checkout@v3
13
+
14
+ - name: Use Node.js
15
+ uses: actions/setup-node@v3
16
+ with:
17
+ node-version: "16.x"
18
+ registry-url: 'https://registry.npmjs.org'
19
+
20
+ - name: Install
21
+ run: npm install
22
+
23
+ - name: Build
24
+ run: npm run build --if-present
25
+
26
+ - name: Publish
27
+ run: npm publish
28
+ env:
29
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2023 Dariusz Dawidowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/dariuszdawidowski/total-diagram/main/total-diagram-logo.png">
3
+ </p>
4
+ <h1 align="center">
5
+ Total Diagram
6
+ </h1>
7
+ <p align="center">
8
+ Simple, powerful, extensible and fast JavaScript/ES8 diagram renderer for web browsers.
9
+ <p>
10
+ <p align="center">
11
+ v 0.9.2
12
+ <p>
13
+
14
+ [![build](https://github.com/dariuszdawidowski/total-diagram/actions/workflows/build.yml/badge.svg)](https://github.com/dariuszdawidowski/total-diagram/actions/workflows/build.yml)
15
+ [![license](https://img.shields.io/github/license/dariuszdawidowski/total-diagram?color=9cf)](./LICENSE)
16
+
17
+ # About
18
+
19
+ A library for rendering diagrams consisting of nodes and links.
20
+ Designed for simplicity, it can be the basis for creating a diagramming application or data representation on a website.
21
+ For more details look into self-explanatory demo.html.
22
+
23
+ # Features
24
+
25
+ - Vanilla JavaScript/ES8
26
+ - No dependencies
27
+
28
+ # Build minified bundle file
29
+
30
+ ```bash
31
+ npm install
32
+ npm run build
33
+ ```
34
+ Note: This is browser-centric vanilla JavaScript library, npm is only used to minify and bundle files into one.
package/build.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Build script
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const ejs = require('ejs');
7
+ const { readFile } = require('fs').promises;
8
+ const { minify } = require('terser');
9
+
10
+ const minjs = async (filePath) => {
11
+ try {
12
+ const inputCode = await readFile(filePath, 'utf8');
13
+ const minifiedCode = (await minify(inputCode)).code;
14
+ return minifiedCode;
15
+ }
16
+ catch (error) {
17
+ console.error(`Error minifying js ${filePath}:`, error);
18
+ return null;
19
+ }
20
+ };
21
+
22
+ ejs.render(fs.readFileSync('total-diagram.js.ejs', 'utf8'), { minjs }, { async: true })
23
+ .then(output => fs.writeFileSync('total-diagram.js', output, 'utf8'));
package/demo.html ADDED
@@ -0,0 +1,155 @@
1
+ <!doctype html>
2
+
3
+ <html>
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Total Diagram Demo</title>
7
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
8
+ <meta http-equiv="Pragma" content="no-cache">
9
+ <meta http-equiv="Expires" content="0">
10
+ <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
11
+ <style>
12
+
13
+ html, body {
14
+ width: 100%;
15
+ height: 100%;
16
+ margin: 0;
17
+ }
18
+
19
+ #container {
20
+ position: relative;
21
+ margin: 0 auto;
22
+ display: block;
23
+ background-color: #060531;
24
+ width: 100%;
25
+ min-width: 640px;
26
+ height: 100%;
27
+ min-height: 480px;
28
+ -webkit-touch-callout: none;
29
+ -webkit-user-select: none;
30
+ -khtml-user-select: none;
31
+ -moz-user-select: none;
32
+ -ms-user-select: none;
33
+ user-select: none;
34
+ touch-action: none;
35
+ overflow: hidden;
36
+ }
37
+
38
+ .node {
39
+ display: flex;
40
+ position: absolute;
41
+ background-color: #e88f56;
42
+ }
43
+
44
+ .link {
45
+ position: absolute;
46
+ left: 0;
47
+ top: 0;
48
+ overflow: visible;
49
+ z-index: -1;
50
+ }
51
+
52
+ .link line {
53
+ pointer-events: none;
54
+ fill: #fff;
55
+ stroke: #fff;
56
+ stroke-width: 2;
57
+ }
58
+
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <!-- Main diagram container -->
63
+ <div id="container"></div>
64
+
65
+ <!-- Total Diagram library -->
66
+ <script type="text/javascript" src="node.js"></script>
67
+ <script type="text/javascript" src="link.js"></script>
68
+ <script type="text/javascript" src="nodes.js"></script>
69
+ <script type="text/javascript" src="links.js"></script>
70
+ <script type="text/javascript" src="render.js"></script>
71
+
72
+ <script>
73
+
74
+ // Default node definition
75
+
76
+ class NodeSquare extends TotalDiagramNode {
77
+
78
+ constructor(args) {
79
+ super(args);
80
+
81
+ // Class
82
+ this.element.classList.add('node');
83
+
84
+ // Size
85
+ this.setSize({width: 50, height: 50});
86
+
87
+ // Update
88
+ this.update();
89
+ }
90
+ }
91
+
92
+ // Default link definition
93
+
94
+ class LinkLine extends TotalDiagramLink {
95
+
96
+ constructor(args) {
97
+ super(args);
98
+
99
+ // SVG DOM element
100
+ this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
101
+ this.element.appendChild(this.line);
102
+
103
+ // Update
104
+ this.update();
105
+ }
106
+
107
+ update() {
108
+ this.line.setAttribute('x1', this.start.transform.x);
109
+ this.line.setAttribute('y1', this.start.transform.y);
110
+ this.line.setAttribute('x2', this.end.transform.x);
111
+ this.line.setAttribute('y2', this.end.transform.y);
112
+ }
113
+
114
+ }
115
+
116
+ // Start
117
+
118
+ window.onload = function() {
119
+
120
+ // Create managers
121
+ const render = new TotalDiagramRenderHTML5({
122
+ container: document.getElementById('container'),
123
+ nodes: new TotalDiagramNodesManager(),
124
+ links: new TotalDiagramLinksManager()
125
+ });
126
+
127
+ // Create first node
128
+ const node1 = new NodeSquare({
129
+ x: -150,
130
+ y: -150
131
+ });
132
+ render.nodes.add(node1);
133
+
134
+ // Create second node
135
+ const node2 = new NodeSquare({
136
+ x: 150,
137
+ y: 150
138
+ });
139
+ render.nodes.add(node2);
140
+
141
+ // Create link between them
142
+ const link1 = new LinkLine({
143
+ start: node1,
144
+ end: node2
145
+ });
146
+ render.links.add(link1);
147
+
148
+ // Move to view central 0,0
149
+ render.center();
150
+
151
+ };
152
+ </script>
153
+
154
+ </body>
155
+ </html>
package/link.js ADDED
@@ -0,0 +1,82 @@
1
+ /***************************************************************************************************
2
+ * *
3
+ * Total Diagram Link *
4
+ * \___________________u.. Base class for diagram link *
5
+ * ( ________________ .-` MIT License *
6
+ * '' '' Copyright (c) 2020-2023 Dariusz Dawidowski *
7
+ * *
8
+ **************************************************************************************************/
9
+
10
+ class TotalDiagramLink {
11
+
12
+ /**
13
+ * Constructor
14
+ * @param args.start <TotalDiagramNode>: Start node
15
+ * @param args.end <TotalDiagramNode>: End node
16
+ */
17
+
18
+ constructor(args) {
19
+
20
+ // ID
21
+ this.id = 'id' in args ? args.id : crypto.randomUUID();
22
+
23
+ // Start Node
24
+ this.start = args.start;
25
+ this.start.addLink(this);
26
+
27
+ // End Node
28
+ this.end = args.end;
29
+ this.end.addLink(this);
30
+
31
+ // SVG DOM element
32
+ this.element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
33
+ this.element.classList.add('link');
34
+
35
+ // Copy ID onto DOM element
36
+ this.element.dataset.id = this.id;
37
+
38
+ }
39
+
40
+ /**
41
+ * Destructor
42
+ */
43
+
44
+ destructor() {
45
+ // Remove references in related nodes
46
+ if (this.start) this.start.delLink(this);
47
+ if (this.end) this.end.delLink(this);
48
+ }
49
+
50
+ /**
51
+ * Serialization
52
+ */
53
+
54
+ serialize() {
55
+ return {
56
+ 'id': this.id,
57
+ 'type': this.constructor.name,
58
+ 'start': this.start.id,
59
+ 'end': this.end.id,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Make link transparent
65
+ */
66
+
67
+ transparent(percent) {
68
+ if (percent == 100)
69
+ this.element.style.opacity = null;
70
+ else
71
+ this.element.style.opacity = percent / 100;
72
+ }
73
+
74
+ /**
75
+ * Update position
76
+ */
77
+
78
+ update() {
79
+ /*** Overload ***/
80
+ }
81
+
82
+ }
package/links.js ADDED
@@ -0,0 +1,103 @@
1
+ /***************************************************************************************************
2
+ * *
3
+ * ___ ___ Total Diagram Links Manager *
4
+ * (o,o) (-,-) Create, delete and find links between nodes on the board. *
5
+ * /)''') ('''(\ MIT License *
6
+ * -----m-m-----m-m----- Copyright (c) 2020-2023 Dariusz Dawidowski *
7
+ * *
8
+ **************************************************************************************************/
9
+
10
+ class TotalDiagramLinksManager {
11
+
12
+ /**
13
+ * Constructor
14
+ */
15
+
16
+ constructor() {
17
+
18
+ // Main manager reference
19
+ this.render = null;
20
+
21
+ // Array for all links
22
+ this.list = [];
23
+
24
+ }
25
+
26
+ /**
27
+ * Add a link between two nodes
28
+ * @param link <TotalDiagramLink>: Link object
29
+ */
30
+
31
+ add(link) {
32
+
33
+ // Check if nodes exists
34
+ if (!link.start || !link.end) return null;
35
+
36
+ // Store link
37
+ this.list.push(link);
38
+
39
+ // Add to DOM
40
+ this.render.board.append(link.element);
41
+
42
+ }
43
+
44
+ /**
45
+ * Del link from board
46
+ * @param link <TotalDiagramLink>: Link object
47
+ */
48
+
49
+ del(link) {
50
+
51
+ if (link != '*') {
52
+ // Local link's destructor
53
+ link.destructor();
54
+
55
+ // Remove from DOM
56
+ link.element.remove()
57
+
58
+ // Remove from list
59
+ const index = this.list.indexOf(link);
60
+ if (index !== -1) this.list.splice(index, 1);
61
+ }
62
+
63
+ // Remove all links
64
+ else {
65
+ this.list.length = 0;
66
+ }
67
+
68
+ }
69
+
70
+ /**
71
+ * Get link(s)
72
+ */
73
+
74
+ get(link = 0, node2 = null) {
75
+
76
+ // Find link betwen two nodes
77
+ if (node2) {
78
+ for (const link1 of link.links.get()) {
79
+ for (const link2 of node2.links.get()) {
80
+ if (link1.id == link2.id) return link1;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
86
+ // All links
87
+ else if (link === 0) return this.list;
88
+
89
+ // Find one link by giving ID
90
+ else if (typeof(link) == 'string') return this.list.find(n => n.element.dataset.id == link);
91
+
92
+ // Find all links type by giving class
93
+ else if (typeof(link) == 'function') return this.list.filter(l => l instanceof link);
94
+
95
+ // Find one link by giving DOM element
96
+ else if (typeof(link) == 'object' && 'dataset' in link) return this.get(link.dataset.id);
97
+
98
+ // Not found
99
+ return null;
100
+
101
+ }
102
+
103
+ }
package/node.js ADDED
@@ -0,0 +1,297 @@
1
+ /***************************************************************************************************
2
+ * *
3
+ * /\_/\ Total Diagram Node *
4
+ * (=^o^=)/\ Base class for diagram node *
5
+ * (___)__/ MIT License *
6
+ * ~~~~~~~~~~~~~~~ Copyright (c) 2020-2023 Dariusz Dawidowski *
7
+ * *
8
+ **************************************************************************************************/
9
+
10
+ class TotalDiagramNode {
11
+
12
+ /**
13
+ * Constructor
14
+ */
15
+
16
+ constructor(args = {}) {
17
+
18
+ // ID
19
+ this.id = 'id' in args ? args.id : crypto.randomUUID();
20
+
21
+ // Human readable name
22
+ this.name = 'name' in args ? args.name : null;
23
+
24
+ // Position, origin, dimensions
25
+ this.transform = {
26
+ // Global x position
27
+ x: 'x' in args ? args.x : 0,
28
+ // Global y position
29
+ y: 'y' in args ? args.y : 0,
30
+
31
+ // Border size
32
+ border: 0,
33
+
34
+ // Z-Sorting
35
+ zindex: 'zindex' in args ? args.zindex : 0,
36
+
37
+ // Local center x position
38
+ ox: 0,
39
+ // Local center y position
40
+ oy: 0,
41
+
42
+ // Width of boundings
43
+ w: 0,
44
+ // Height of boundings
45
+ h: 0,
46
+ // Min width of boundings
47
+ wmin: 64,
48
+ // Min height of boundings
49
+ hmin: 64,
50
+ // Max width of boundings
51
+ wmax: 1024,
52
+ // Max height of boundings
53
+ hmax: 1024,
54
+
55
+ // Identity
56
+ clear: () => {
57
+ this.transform.x = 0;
58
+ this.transform.y = 0;
59
+ this.transform.zindex = 0;
60
+ this.element.style.zIndex = 0;
61
+ }
62
+
63
+ };
64
+
65
+ // Links
66
+ this.links = {
67
+
68
+ list: [], // [TotalDiagramLink, ...]
69
+
70
+ // Assign to link
71
+ add: (link) => {
72
+ this.links.list.push(link);
73
+ },
74
+
75
+ // Unassign from a link
76
+ del: (link) => {
77
+ const index = this.links.list.indexOf(link);
78
+ if (index !== -1) this.links.list.splice(index, 1);
79
+ },
80
+
81
+ // Get assigned link
82
+ get: (id = null) => {
83
+ // All links
84
+ if (id === null) return this.links.list;
85
+
86
+ // Find one link by ID
87
+ else if (typeof(id) == 'string') {
88
+ return this.links.list.find(l => l.id == id);
89
+ }
90
+
91
+ return null;
92
+ },
93
+
94
+ // Update assigned links
95
+ update: () => {
96
+ this.links.list.forEach(link => link.update());
97
+ }
98
+
99
+ };
100
+
101
+ // Node DOM element
102
+ this.element = document.createElement('div');
103
+ this.element.classList.add('total-diagram-node');
104
+ this.element.style.zIndex = this.transform.zindex;
105
+
106
+ // Set or generate ID
107
+ this.element.dataset.id = this.id;
108
+
109
+ }
110
+
111
+ /**
112
+ * Destructor
113
+ */
114
+
115
+ destructor() {
116
+ /* Overload */
117
+ }
118
+
119
+ /**
120
+ * Init after node is added into DOM tree
121
+ */
122
+
123
+ awake() {
124
+ /* Overload */
125
+ }
126
+
127
+ /**
128
+ * Init after all scene is created and updated once
129
+ */
130
+
131
+ start() {
132
+ /* Overload */
133
+ }
134
+
135
+ /**
136
+ * Size
137
+ * {width: <Number>, height: <Number>, minWidth: <Number>, minHeight: <Number>, maxWidth: <Number>, maxHeight: <Number>, border: [Number]}
138
+ */
139
+
140
+ setSize(size) {
141
+ this.transform.w = size.width;
142
+ this.transform.h = size.height;
143
+ if ('minWidth' in size) this.transform.wmin = size.minWidth;
144
+ if ('minHeight' in size) this.transform.hmin = size.minHeight;
145
+ if ('maxWidth' in size) this.transform.wmax = size.maxWidth;
146
+ if ('maxHeight' in size) this.transform.hmax = size.maxHeight;
147
+ if ('border' in size) this.transform.border = size.border;
148
+ this.setOrigin();
149
+ this.element.style.width = this.transform.w + 'px';
150
+ this.element.style.height = this.transform.h + 'px';
151
+ }
152
+
153
+ getSize() {
154
+ return {
155
+ width: this.transform.w,
156
+ height: this.transform.h,
157
+ minWidth: this.transform.wmin,
158
+ minHeight: this.transform.hmin,
159
+ maxWidth: this.transform.wmax,
160
+ maxHeight: this.transform.hmax
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Set origin (local center point of node)
166
+ * Override this to make different origin than middle point
167
+ */
168
+
169
+ setOrigin() {
170
+ this.transform.ox = this.transform.w / 2;
171
+ this.transform.oy = this.transform.h / 2;
172
+ }
173
+
174
+ /**
175
+ * Set position
176
+ * transform {x: float, y: float}
177
+ */
178
+
179
+ setPosition(transform) {
180
+ this.transform.x = transform.x;
181
+ this.transform.y = transform.y;
182
+ }
183
+
184
+ /**
185
+ * Get position
186
+ * transform {x: float, y: float}
187
+ */
188
+
189
+ getPosition() {
190
+ return {x: this.transform.x, y: this.transform.y};
191
+ }
192
+
193
+ /**
194
+ * Change position
195
+ * transform {x: float, y: float}
196
+ */
197
+
198
+ addPosition(transform) {
199
+ this.transform.x += transform.x;
200
+ this.transform.y += transform.y;
201
+ }
202
+
203
+ /**
204
+ * Change position
205
+ * transform {x: float, y: float}
206
+ */
207
+
208
+ subPosition(transform) {
209
+ this.transform.x -= transform.x;
210
+ this.transform.y -= transform.y;
211
+ }
212
+
213
+ /**
214
+ * Add link
215
+ * link: TotalDiagramLink object
216
+ */
217
+
218
+ addLink(link) {
219
+ this.links.add(link);
220
+ }
221
+
222
+ /**
223
+ * Delete link
224
+ * link: TotalDiagramLink object
225
+ */
226
+
227
+ delLink(link) {
228
+ this.links.del(link);
229
+ }
230
+
231
+ /**
232
+ * Style
233
+ */
234
+
235
+ setStyle(key, value = null) {
236
+ if (value !== null) this.element.style[key] = value;
237
+ }
238
+
239
+ getStyle(key = null) {
240
+ if (key !== null) return this.element.style[key];
241
+ else return this.element.style;
242
+ }
243
+
244
+ /**
245
+ * Make node transparent
246
+ */
247
+
248
+ transparent(percent) {
249
+ if (percent == 100)
250
+ this.element.style.opacity = null;
251
+ else
252
+ this.element.style.opacity = percent / 100;
253
+ }
254
+
255
+ /**
256
+ * Enable/disable smooth css animation
257
+ */
258
+
259
+ animated(state = true) {
260
+ this.element.style.transition = state ? 'transform 0.5s ease-in-out' : null;
261
+ }
262
+
263
+ /**
264
+ * Serialization
265
+ */
266
+
267
+ serialize() {
268
+ return {
269
+ id: this.id,
270
+ type: this.constructor.name,
271
+ x: this.transform.x,
272
+ y: this.transform.y,
273
+ w: this.transform.w,
274
+ h: this.transform.h,
275
+ zindex: this.transform.zindex
276
+ };
277
+ }
278
+
279
+ /**
280
+ * Set z-index
281
+ */
282
+
283
+ setSortingZ(zindex) {
284
+ this.transform.zindex = zindex;
285
+ this.element.style.zIndex = zindex;
286
+ }
287
+
288
+ /**
289
+ * Update (everyframe when something is changed e.g. move)
290
+ */
291
+
292
+ update() {
293
+ // Calculate position (PYQt6 doesn't support separate css translate yet)
294
+ this.element.style.transform = `translate(${this.transform.x - this.transform.ox}px, ${this.transform.y - this.transform.oy}px)`;
295
+ }
296
+
297
+ }
package/nodes.js ADDED
@@ -0,0 +1,111 @@
1
+ /***************************************************************************************************
2
+ * *
3
+ * ____(__) Total Diagram Nodes Manager *
4
+ * /| oo Create, delete and find nodes on the board. *
5
+ * * | _ (..)-* MIT License *
6
+ * v || || v Copyright (c) 2020-2023 Dariusz Dawidowski *
7
+ * '' ' ^^ ^^ ' '' *
8
+ **************************************************************************************************/
9
+
10
+ class TotalDiagramNodesManager {
11
+
12
+ /**
13
+ * Constructor
14
+ */
15
+
16
+ constructor() {
17
+
18
+ // Main manager reference
19
+ this.render = null;
20
+
21
+ // Array for all nodes
22
+ this.list = [];
23
+
24
+ }
25
+
26
+ /**
27
+ * Add node to scene
28
+ * @param node <TotalDiagramNode>: Node object
29
+ */
30
+
31
+ add(node) {
32
+
33
+ // Add to list
34
+ this.list.push(node);
35
+
36
+ // Add to DOM
37
+ this.render.board.append(node.element);
38
+
39
+ // Call node awake function sice it's added into DOM tree
40
+ node.awake();
41
+
42
+ }
43
+
44
+ /**
45
+ * Del object from scene
46
+ */
47
+
48
+ del(node) {
49
+
50
+ // Single node
51
+ if (node != '*') {
52
+
53
+ // First delete associated links on the copy of links list
54
+ let linksToDelete = node.links.get().slice();
55
+ while (linksToDelete.length) {
56
+ const link = linksToDelete.pop();
57
+ this.render.links.del(link);
58
+ }
59
+
60
+ // Local node's destructor
61
+ node.destructor();
62
+
63
+ // Remove from DOM
64
+ node.element.remove();
65
+
66
+ // Remove from list
67
+ const index = this.list.indexOf(node);
68
+ if (index !== -1) this.list.splice(index, 1);
69
+ }
70
+
71
+ // Clear all on the list
72
+ else if (node == '*') {
73
+ this.list.length = 0;
74
+ }
75
+
76
+ }
77
+
78
+ /**
79
+ * Get nodes from scene
80
+ */
81
+
82
+ get(node = 0) {
83
+
84
+ // Find one node by giving DOM element
85
+ if (node != null && typeof(node) == 'object') {
86
+ // Traverse DOM
87
+ let target = node;
88
+ while (target.parentNode) {
89
+ if ('classList' in target && target.classList.contains('total-diagram-node')) {
90
+ // Found node
91
+ return this.get(target.dataset.id);
92
+ }
93
+ target = target.parentNode;
94
+ }
95
+ }
96
+
97
+ // All nodes
98
+ else if (node === 0) return this.list;
99
+
100
+ // Find all nodes type by giving class
101
+ else if (typeof(node) == 'function') return this.list.filter(n => n instanceof node);
102
+
103
+ // Find one node by giving ID
104
+ else if (typeof(node) == 'string') return this.list.find(n => n.id == node);
105
+
106
+ // Not found
107
+ return null;
108
+
109
+ }
110
+
111
+ }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "total-diagram",
3
+ "version": "0.9.2",
4
+ "description": "Simple, powerful, extensible and fast JavaScript/ES8 diagram renderer for web browsers.",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "build:bundle": "node build.js",
8
+ "build": "npm-run-all build:bundle",
9
+ "start": "npm-run-all build"
10
+ },
11
+ "devDependencies": {
12
+ "ejs": "^3.1.9",
13
+ "fs": "^0.0.1-security",
14
+ "npm-run-all": "^4.1.5",
15
+ "terser": "^5.17.3"
16
+ }
17
+ }
package/render.js ADDED
@@ -0,0 +1,199 @@
1
+ /***************************************************************************************************
2
+ * *
3
+ * ,; ) Total Diagram Render HTML5 *
4
+ * _o_ _; ( One manager to rule them all *
5
+ * (c( )/ ____ MIT License *
6
+ * |___| \__/) Copyright (c) 2020-2023 Dariusz Dawidowski *
7
+ * *
8
+ **************************************************************************************************/
9
+
10
+ class TotalDiagramRenderHTML5 {
11
+
12
+ /**
13
+ * Constructor
14
+ * @param args.container <HTMLElement>: DOM container for rendering
15
+ * @param args.nodes <TotalDiagramNodesManager>: Nodes manager
16
+ * @param args.links <TotalDiagramLinksManager>: Links manager
17
+ */
18
+
19
+ constructor(args) {
20
+
21
+ // Container
22
+ this.container = args.container;
23
+
24
+ // Create 0x0 main object to attach nodes/links to follow transforms
25
+ this.board = document.createElement('div');
26
+ this.board.id = 'total-diagram-attach';
27
+ this.board.style.transformOrigin = '0px 0px';
28
+ this.board.style.width = 0;
29
+ this.board.style.height = 0;
30
+ this.board.style.overflow = 'visible';
31
+ this.container.appendChild(this.board);
32
+
33
+ // Board dimensions
34
+ this.size = this.container.getBoundingClientRect();
35
+ this.size.center = {x: this.size.width / 2, y: this.size.height / 2};
36
+
37
+ // Board margins
38
+ this.margin = {
39
+ left: this.size.left - document.documentElement.scrollLeft,
40
+ top: this.size.top - document.documentElement.scrollTop,
41
+ };
42
+
43
+ // Board current pan offset (x,y) and zoom (z)
44
+ this.offset = {
45
+ x: 0,
46
+ y: 0,
47
+ z: 1
48
+ };
49
+
50
+ // Window resize callback
51
+ window.addEventListener('resize', () => {
52
+ this.size = this.container.getBoundingClientRect();
53
+ this.size.center = {x: this.size.width / 2, y: this.size.height / 2};
54
+ });
55
+
56
+ // Nodes
57
+ this.nodes = args.nodes;
58
+ this.nodes.render = this;
59
+
60
+ // Link
61
+ this.links = args.links;
62
+ this.links.render = this;
63
+ }
64
+
65
+ /**
66
+ * Perform pan
67
+ */
68
+
69
+ pan(deltaX, deltaY) {
70
+ this.offset.x += deltaX;
71
+ this.offset.y += deltaY;
72
+ this.update();
73
+ }
74
+
75
+ /**
76
+ * Perform zoom
77
+ */
78
+
79
+ zoom(x, y, deltaZ, factorZ) {
80
+ let deltaZoom = this.offset.z;
81
+ this.offset.z = (this.offset.z - (deltaZ / factorZ) * this.offset.z).clamp(0.1, 3.0);
82
+ deltaZoom = this.offset.z / deltaZoom;
83
+
84
+ const boundingRect = this.board.getBoundingClientRect();
85
+ const ox = boundingRect.width / 2 + boundingRect.left - x;
86
+ const oy = boundingRect.height / 2 + boundingRect.top - y;
87
+
88
+ this.offset.x += ox * deltaZoom - ox;
89
+ this.offset.y += oy * deltaZoom - oy;
90
+ this.update();
91
+ }
92
+
93
+ /**
94
+ * Perform mobile zoom
95
+ */
96
+
97
+ pinchZoom(x1, y1, x2, y2, deltaZ) {
98
+ let deltaZoom = this.offset.z;
99
+ this.offset.z = (this.offset.z * deltaZ).clamp(0.1, 3.0);
100
+ deltaZoom = this.offset.z / deltaZoom;
101
+
102
+ const boundingRect = this.board.getBoundingClientRect();
103
+ const ox = boundingRect.width / 2 + boundingRect.left - ((x1 + x2) / 2);
104
+ const oy = boundingRect.height / 2 + boundingRect.top - ((y1 + y2) / 2);
105
+
106
+ this.offset.x += ox * deltaZoom - ox;
107
+ this.offset.y += oy * deltaZoom - oy;
108
+ this.update();
109
+ }
110
+
111
+ /**
112
+ * Convert window coordinates to world
113
+ * {x: ..., y: ...}
114
+ */
115
+
116
+ screen2World(coords) {
117
+ return {
118
+ x: Math.round( (((coords.x - this.offset.x) / this.offset.z) - this.margin.left) ),
119
+ y: Math.round( (((coords.y - this.offset.y) / this.offset.z) - this.margin.top) ),
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Convert world coordinates to window
125
+ * {x: ..., y: ...}
126
+ */
127
+
128
+ world2Screen(coords) {
129
+ return {
130
+ x: Math.round( ((this.offset.x + (coords.x * this.offset.z)) - this.margin.left) ),
131
+ y: Math.round( ((this.offset.y + (coords.y * this.offset.z)) - this.margin.top) ),
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Reset to origin
137
+ * z: 'none' (don't zoom) | 'reset': standard zoom | 'focus': zoom to node size
138
+ * animation: 'hard' | 'smooth'
139
+ * rect: {width, height: dimensions of the focus target}
140
+ */
141
+
142
+ center(coords = {x: 0, y: 0}, z = 'none', animation = 'hard', rect = null) {
143
+
144
+ // Animation
145
+ if (animation == 'smooth') {
146
+ this.board.style.transition = 'transform 1s';
147
+ setInterval(() => { this.board.style.removeProperty('transition'); }, 1000);
148
+ }
149
+
150
+ // Zoom
151
+ if (z == 'reset') this.offset.z = 1;
152
+ else if (z == 'focus') this.offset.z = ((this.size.height) / (rect.height + rect.margin));
153
+
154
+ // Render
155
+ this.offset.x = (-coords.x * this.offset.z) + this.size.center.x;
156
+ this.offset.y = (-coords.y * this.offset.z) + this.size.center.y;
157
+ this.update();
158
+ }
159
+
160
+ /**
161
+ * Reset zoom
162
+ */
163
+
164
+ centerZoom() {
165
+ this.offset.z = 1;
166
+ this.update();
167
+ }
168
+
169
+ /**
170
+ * Delete all nodes and links and clear DOM
171
+ */
172
+
173
+ clear() {
174
+ this.board.innerHTML = '';
175
+ this.links.del('*');
176
+ this.nodes.del('*');
177
+ }
178
+
179
+ /**
180
+ * Render update
181
+ */
182
+
183
+ update() {
184
+ // Calculate board's position
185
+ // Note: using 'transform' since PYQt6 doesn't support new css 'translate' and 'scale' yet
186
+ this.board.style.transform = `translate(${this.offset.x}px, ${this.offset.y}px) scale(${this.offset.z})`;
187
+ }
188
+
189
+ /**
190
+ * Hack to force redraw window
191
+ */
192
+
193
+ redraw() {
194
+ this.container.style.display = 'none';
195
+ const trick = this.container.offsetHeight;
196
+ this.container.style.display = 'block';
197
+ }
198
+
199
+ }
Binary file
@@ -0,0 +1,5 @@
1
+ <%- await minjs('node.js') %>
2
+ <%- await minjs('nodes.js') %>
3
+ <%- await minjs('link.js') %>
4
+ <%- await minjs('links.js') %>
5
+ <%- await minjs('render.js') %>