total-diagram 0.9.4 → 0.9.6

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.
package/README.md CHANGED
@@ -8,11 +8,12 @@ Total Diagram
8
8
  Simple, powerful, extensible and fast JavaScript/ES8 diagram renderer for web browsers.
9
9
  </p>
10
10
  <p align="center">
11
- v0.9.4
11
+ v0.9.6
12
12
  </p>
13
13
 
14
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
15
  [![npm](https://img.shields.io/npm/v/total-diagram)](https://www.npmjs.com/package/total-diagram)
16
+ [![NPM Downloads](https://img.shields.io/npm/dm/total-diagram)](https://www.npmjs.com/package/total-diagram)
16
17
  [![license](https://img.shields.io/github/license/dariuszdawidowski/total-diagram?color=9cf)](./LICENSE)
17
18
 
18
19
  # About
@@ -20,8 +21,21 @@ v0.9.4
20
21
  A library for rendering diagrams consisting of nodes and links.
21
22
  Designed for simplicity, it can be the basis for creating a diagramming application or data representation on a website.
22
23
 
24
+ <img src="https://raw.githubusercontent.com/dariuszdawidowski/total-diagram/main/total-diagram-showcase.png" alt="" />
25
+
26
+ # Quick Start
27
+
23
28
  For more details look into 'examples/' directory. You can find self-explanatory tutorials there.
24
29
 
30
+ # Philosophy behind the library
31
+
32
+ Does the world need yet another library for displaying diagrams?
33
+ I tried most of them and the problem I encountered was that I couldn't realize my idea because it was inconsistent with the vision of the creators of the library.
34
+ The bigger and more complicated the library becomes, the less flexible it is proportionally.
35
+ The basis of this project is very clean and simple, no need to complicate it.
36
+ The first thing you should do is analyze all the files in the 'examples' directory, which are a kind of tutorial on how to build your own diagramming system based on this solution.
37
+ If you are looking for an example of building a larger system based on this library, see the project https://github.com/dariuszdawidowski/metaviz-editor.
38
+
25
39
  # Features
26
40
 
27
41
  - Vanilla JavaScript/ES8
@@ -0,0 +1,5 @@
1
+ class TotalDiagramNode{constructor(t={}){this.id="id"in t?t.id:crypto.randomUUID(),this.name="name"in t?t.name:null,this.transform={x:"x"in t?t.x:0,y:"y"in t?t.y:0,border:0,zindex:"zindex"in t?t.zindex:0,ox:0,oy:0,w:0,h:0,wmin:64,hmin:64,wmax:1024,hmax:1024,clear:()=>{this.transform.x=0,this.transform.y=0,this.transform.zindex=0,this.element.style.zIndex=0}},this.links={list:[],add:t=>{this.links.list.push(t)},del:t=>{const i=this.links.list.indexOf(t);-1!==i&&this.links.list.splice(i,1)},get:t=>"*"==t?this.links.list:"in"==t?this.links.list.filter((t=>t.end.id==this.id)):"out"==t?this.links.list.filter((t=>t.start.id==this.id)):"string"==typeof t?this.links.list.find((i=>i.id==t)):null,update:()=>{this.links.list.forEach((t=>t.update()))}},this.element=document.createElement("div"),this.element.classList.add("total-diagram-node"),this.element.style.zIndex=this.transform.zindex,this.element.dataset.id=this.id}destructor(){}awake(){}start(){}setSize(t){this.transform.w=t.width,this.transform.h=t.height,"minWidth"in t&&(this.transform.wmin=t.minWidth),"minHeight"in t&&(this.transform.hmin=t.minHeight),"maxWidth"in t&&(this.transform.wmax=t.maxWidth),"maxHeight"in t&&(this.transform.hmax=t.maxHeight),this.transform.border="border"in t?t.border:parseInt(getComputedStyle(this.element,null).getPropertyValue("border-left-width").replace("px",""))||0,this.setOrigin(),this.element.style.width=this.transform.w+"px",this.element.style.height=this.transform.h+"px"}getSize(){return{width:this.transform.w,height:this.transform.h,minWidth:this.transform.wmin,minHeight:this.transform.hmin,maxWidth:this.transform.wmax,maxHeight:this.transform.hmax}}setOrigin(){this.transform.ox=this.transform.w/2,this.transform.oy=this.transform.h/2}setPosition(t){this.transform.x=t.x,this.transform.y=t.y}getPosition(){return{x:this.transform.x,y:this.transform.y}}addPosition(t){this.transform.x+=t.x,this.transform.y+=t.y}subPosition(t){this.transform.x-=t.x,this.transform.y-=t.y}setStyle(t,i=null){null!==i&&(this.element.style[t]=i)}getStyle(t=null){return null!==t?this.element.style[t]:this.element.style}transparent(t){this.element.style.opacity=100==t?null:t/100}animated(t=!0){this.element.style.transition=t?"transform 0.5s ease-in-out":null}serialize(){return{id:this.id,type:this.constructor.name,x:this.transform.x,y:this.transform.y,w:this.transform.w,h:this.transform.h,zindex:this.transform.zindex}}setSortingZ(t){this.transform.zindex=t,this.element.style.zIndex=t}update(){this.element.style.transform=`translate(${this.transform.x-this.transform.ox}px, ${this.transform.y-this.transform.oy}px)`}}
2
+ class TotalDiagramNodesManager{constructor(){this.render=null,this.list=[]}add(t){this.list.push(t),this.render.board.append(t.element),t.awake();const e=new CustomEvent("broadcast:addnode",{detail:t});this.render.container.dispatchEvent(e)}del(t){if("*"!=t){const e=t.links.get("*");for(;e.length;){const t=e.pop();this.render.links.del(t)}t.destructor(),t.element.remove();const s=this.list.indexOf(t);-1!==s&&this.list.splice(s,1);const n=new CustomEvent("broadcast:delnode",{detail:t});this.render.container.dispatchEvent(n)}else if("*"==t){this.list.forEach((t=>{t.destructor(),t.element.remove()})),this.list.length=0;const t=new CustomEvent("broadcast:delnodes",{detail:"*"});this.render.container.dispatchEvent(t)}}get(t){if("*"==t)return this.list;if(null!=t&&"object"==typeof t){let e=t;for(;e.parentNode;){if("classList"in e&&e.classList.contains("total-diagram-node"))return this.get(e.dataset.id);e=e.parentNode}}else{if("function"==typeof t)return this.list.filter((e=>e instanceof t));if("string"==typeof t)return this.list.find((e=>e.id==t))}return null}}
3
+ class TotalDiagramLink{constructor(t){this.id="id"in t?t.id:crypto.randomUUID(),this.start=t.start,this.start.links.add(this),this.end=t.end,this.end.links.add(this),this.element=document.createElementNS("http://www.w3.org/2000/svg","svg"),this.element.classList.add("link"),this.element.dataset.id=this.id}destructor(){this.start&&this.start.links.del(this),this.end&&this.end.links.del(this)}serialize(){return{id:this.id,type:this.constructor.name,start:this.start.id,end:this.end.id}}transparent(t){this.element.style.opacity=100==t?null:t/100}update(){}}
4
+ class TotalDiagramLinksManager{constructor(){this.render=null,this.list=[]}add(t){if(!t.start||!t.end)return null;this.list.push(t),this.render.board.append(t.element);const e=new CustomEvent("broadcast:addlink",{detail:t});this.render.container.dispatchEvent(e)}del(t){if("*"!=t){t.destructor(),t.element.remove();const e=this.list.indexOf(t);-1!==e&&this.list.splice(e,1);const n=new CustomEvent("broadcast:dellink",{detail:t});this.render.container.dispatchEvent(n)}else{this.list.forEach((t=>{t.destructor(),t.element.remove()})),this.list.length=0;const t=new CustomEvent("broadcast:delnodes",{detail:"*"});this.render.container.dispatchEvent(t)}}get(t,e=null){if("*"==t)return this.list;if(e){for(const n of t.links.get("*"))for(const t of e.links.get("*"))if(n.id==t.id)return n;return null}return"string"==typeof t?this.list.find((e=>e.element.dataset.id==t)):"function"==typeof t?this.list.filter((e=>e instanceof t)):"object"==typeof t&&"dataset"in t?this.get(t.dataset.id):null}}
5
+ class TotalDiagramRenderHTML5{constructor(t){this.container=t.container,this.board=document.createElement("div"),this.board.id="total-diagram-attach",this.board.style.transformOrigin="0px 0px",this.board.style.width=0,this.board.style.height=0,this.board.style.overflow="visible",this.container.appendChild(this.board),this.size=this.container.getBoundingClientRect(),this.size.center={x:this.size.width/2,y:this.size.height/2},this.margin={left:this.size.left-document.documentElement.scrollLeft,top:this.size.top-document.documentElement.scrollTop},this.offset={x:0,y:0,z:1},window.addEventListener("resize",(()=>{this.size=this.container.getBoundingClientRect(),this.size.center={x:this.size.width/2,y:this.size.height/2}})),this.nodes=t.nodes,this.nodes.render=this,this.links=t.links,this.links.render=this}pan(t,s){this.offset.x+=t,this.offset.y+=s,this.update()}zoom(t,s,e,i){let h=this.offset.z;this.offset.z=(this.offset.z-e/i*this.offset.z).clamp(.1,3),h=this.offset.z/h;const o=this.board.getBoundingClientRect(),n=o.width/2+o.left-t,f=o.height/2+o.top-s;this.offset.x+=n*h-n,this.offset.y+=f*h-f,this.update()}pinchZoom(t,s,e,i,h){let o=this.offset.z;this.offset.z=(this.offset.z*h).clamp(.1,3),o=this.offset.z/o;const n=this.board.getBoundingClientRect(),f=n.width/2+n.left-(t+e)/2,r=n.height/2+n.top-(s+i)/2;this.offset.x+=f*o-f,this.offset.y+=r*o-r,this.update()}screen2World(t){return{x:Math.round((t.x-this.offset.x)/this.offset.z-this.margin.left),y:Math.round((t.y-this.offset.y)/this.offset.z-this.margin.top)}}world2Screen(t){return{x:Math.round(this.offset.x+t.x*this.offset.z-this.margin.left),y:Math.round(this.offset.y+t.y*this.offset.z-this.margin.top)}}center(t={x:0,y:0},s="none",e="hard",i=null){"smooth"==e&&(this.board.style.transition="transform 1s",setInterval((()=>{this.board.style.removeProperty("transition")}),1e3)),"reset"==s?this.offset.z=1:"focus"==s&&(this.offset.z=this.size.height/(i.height+i.margin)),this.offset.x=-t.x*this.offset.z+this.size.center.x,this.offset.y=-t.y*this.offset.z+this.size.center.y,this.update()}centerZoom(){this.offset.z=1,this.update()}clear(){this.board.innerHTML="",this.links.del("*"),this.nodes.del("*")}update(){this.board.style.transform=`translate(${this.offset.x}px, ${this.offset.y}px) scale(${this.offset.z})`}redraw(){this.container.style.display="none";this.container.offsetHeight;this.container.style.display="block"}}
@@ -33,6 +33,12 @@
33
33
  user-select: none;
34
34
  touch-action: none;
35
35
  overflow: hidden;
36
+ font-family: Arial, sans-serif;
37
+ }
38
+
39
+ h3, p {
40
+ color: white;
41
+ text-align: center;
36
42
  }
37
43
 
38
44
  .node {
@@ -60,7 +66,10 @@
60
66
  </head>
61
67
  <body>
62
68
  <!-- Main diagram container -->
63
- <div id="container"></div>
69
+ <div id="container">
70
+ <h3>1: Basic demo</h3>
71
+ <p>How to create two nodes and link them.</p>
72
+ </div>
64
73
 
65
74
  <!-- Total Diagram library -->
66
75
  <script type="text/javascript" src="../render-node.js"></script>
@@ -0,0 +1,235 @@
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
+ font-family: Arial, sans-serif;
37
+ }
38
+
39
+ h3, p {
40
+ color: white;
41
+ text-align: center;
42
+ }
43
+
44
+ .node {
45
+ display: flex;
46
+ flex-direction: column;
47
+ position: absolute;
48
+ background-color: white;
49
+ border-radius: 8px;
50
+ border-bottom: 10px solid #f19331;
51
+ }
52
+
53
+ .node .titlebar {
54
+ color: white;
55
+ background-color: #f19331;
56
+ width: 100%;
57
+ height: 35px;
58
+ line-height: 35px;
59
+ text-align: center;
60
+ font-size: 16px;
61
+ font-weight: bold;
62
+ border-radius: 8px 8px 0 0;
63
+ }
64
+
65
+ .node .header {
66
+ color: #222;
67
+ text-align: left;
68
+ font-size: 14px;
69
+ margin: 12px 16px;
70
+ font-weight: bold;
71
+ }
72
+
73
+ .node .option {
74
+ color: #222;
75
+ background-color: #fff5f5;
76
+ text-align: left;
77
+ font-size: 14px;
78
+ margin: 8px 16px 0 16px;
79
+ padding: 10px;
80
+ border-radius: 8px;
81
+ }
82
+
83
+ .link {
84
+ position: absolute;
85
+ left: 0;
86
+ top: 0;
87
+ overflow: visible;
88
+ z-index: -1;
89
+ }
90
+
91
+ .link line {
92
+ pointer-events: none;
93
+ fill: #e88f56;
94
+ stroke: #e88f56;
95
+ stroke-width: 2;
96
+ }
97
+
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <!-- Main diagram container -->
102
+ <div id="container">
103
+ <h3>2: Node demo</h3>
104
+ <p>How to make and style custom nodes.</p>
105
+ </div>
106
+
107
+ <!-- Total Diagram library -->
108
+ <script type="text/javascript" src="../render-node.js"></script>
109
+ <script type="text/javascript" src="../render-link.js"></script>
110
+ <script type="text/javascript" src="../manager-nodes.js"></script>
111
+ <script type="text/javascript" src="../manager-links.js"></script>
112
+ <script type="text/javascript" src="../render.js"></script>
113
+
114
+ <script>
115
+
116
+ // More advanced node definition
117
+
118
+ class NodeMoreAdvanced extends TotalDiagramNode {
119
+
120
+ constructor(args) {
121
+ super(args);
122
+
123
+ // Class
124
+ this.element.classList.add('node');
125
+
126
+ // Title bar
127
+ this.titlebar = document.createElement('div');
128
+ this.titlebar.classList.add('titlebar');
129
+ this.titlebar.innerText = args.title || 'Node';
130
+ this.element.append(this.titlebar);
131
+
132
+ // Header
133
+ this.header = document.createElement('div');
134
+ this.header.classList.add('header');
135
+ this.header.innerText = args.header || 'Header';
136
+ this.element.append(this.header);
137
+
138
+ // Options
139
+ if ('options' in args) {
140
+ args.options.forEach(text => {
141
+ const option = document.createElement('div');
142
+ option.classList.add('option');
143
+ option.innerHTML = text;
144
+ this.element.append(option);
145
+ });
146
+ }
147
+
148
+ // Size
149
+ this.setSize({width: 200, height: 300});
150
+
151
+ // Update
152
+ this.update();
153
+ }
154
+ }
155
+
156
+ // Default link definition
157
+
158
+ class LinkLine extends TotalDiagramLink {
159
+
160
+ constructor(args) {
161
+ super(args);
162
+
163
+ // SVG DOM element
164
+ this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
165
+ this.element.appendChild(this.line);
166
+
167
+ // Update
168
+ this.update();
169
+ }
170
+
171
+ update() {
172
+ this.line.setAttribute('x1', this.start.transform.x);
173
+ this.line.setAttribute('y1', this.start.transform.y);
174
+ this.line.setAttribute('x2', this.end.transform.x);
175
+ this.line.setAttribute('y2', this.end.transform.y);
176
+ }
177
+
178
+ }
179
+
180
+ // Start
181
+
182
+ window.onload = function() {
183
+
184
+ // Create managers
185
+ const render = new TotalDiagramRenderHTML5({
186
+ container: document.getElementById('container'),
187
+ nodes: new TotalDiagramNodesManager(),
188
+ links: new TotalDiagramLinksManager()
189
+ });
190
+
191
+ // Create first node
192
+ const node1 = new NodeMoreAdvanced({
193
+ title: 'Node #1',
194
+ header: 'Options for node 1:',
195
+ options: [
196
+ '<b>Option 1:</b> Hello',
197
+ '<b>Option 2:</b> World',
198
+ '<b>Option 3:</b> Lorem',
199
+ '<b>Option 4:</b> Ipsum'
200
+ ],
201
+ x: -200,
202
+ y: -50
203
+ });
204
+ render.nodes.add(node1);
205
+
206
+ // Create second node
207
+ const node2 = new NodeMoreAdvanced({
208
+ title: 'Node #2',
209
+ header: 'Options for node 2:',
210
+ options: [
211
+ '<b>Option 1:</b> Hello',
212
+ '<b>Option 2:</b> World',
213
+ '<b>Option 3:</b> Lorem',
214
+ '<b>Option 4:</b> Ipsum'
215
+ ],
216
+ x: 200,
217
+ y: 50
218
+ });
219
+ render.nodes.add(node2);
220
+
221
+ // Create link between them
222
+ const link1 = new LinkLine({
223
+ start: node1,
224
+ end: node2
225
+ });
226
+ render.links.add(link1);
227
+
228
+ // Move to view central 0,0
229
+ render.center();
230
+
231
+ };
232
+ </script>
233
+
234
+ </body>
235
+ </html>
@@ -0,0 +1,319 @@
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
+ font-family: Arial, sans-serif;
37
+ }
38
+
39
+ h3, p {
40
+ color: white;
41
+ text-align: center;
42
+ }
43
+
44
+ .node {
45
+ display: flex;
46
+ flex-direction: column;
47
+ position: absolute;
48
+ background-color: white;
49
+ border-radius: 8px;
50
+ border-bottom: 10px solid #f19331;
51
+ }
52
+
53
+ .node .titlebar {
54
+ color: white;
55
+ background-color: #f19331;
56
+ width: 100%;
57
+ height: 35px;
58
+ line-height: 35px;
59
+ text-align: center;
60
+ font-size: 16px;
61
+ font-weight: bold;
62
+ border-radius: 8px 8px 0 0;
63
+ }
64
+
65
+ .node .header {
66
+ color: #222;
67
+ text-align: left;
68
+ font-size: 14px;
69
+ margin: 12px 16px;
70
+ font-weight: bold;
71
+ }
72
+
73
+ .node .option {
74
+ color: #222;
75
+ background-color: #fff5f5;
76
+ text-align: left;
77
+ font-size: 14px;
78
+ margin: 8px 16px 0 16px;
79
+ padding: 10px;
80
+ border-radius: 8px;
81
+ }
82
+
83
+ .socket {
84
+ position: absolute;
85
+ width: 8px;
86
+ height: 8px;
87
+ background-color: #fff5f5;
88
+ border: 2px solid #f19331;
89
+ border-radius: 50%;
90
+ }
91
+
92
+ .link {
93
+ position: absolute;
94
+ left: 0;
95
+ top: 0;
96
+ overflow: visible;
97
+ z-index: -1;
98
+ }
99
+
100
+ .link path {
101
+ pointer-events: none;
102
+ fill: none;
103
+ stroke: #e88f56;
104
+ stroke-width: 2;
105
+ }
106
+
107
+ </style>
108
+ </head>
109
+ <body>
110
+ <!-- Main diagram container -->
111
+ <div id="container">
112
+ <h3>3: Link demo</h3>
113
+ <p>How to make custom and stylized link connected to node's socket.</p>
114
+ </div>
115
+
116
+ <!-- Total Diagram library -->
117
+ <script type="text/javascript" src="../render-node.js"></script>
118
+ <script type="text/javascript" src="../render-link.js"></script>
119
+ <script type="text/javascript" src="../manager-nodes.js"></script>
120
+ <script type="text/javascript" src="../manager-links.js"></script>
121
+ <script type="text/javascript" src="../render.js"></script>
122
+
123
+ <script>
124
+
125
+ // Socket definition
126
+
127
+ class SocketPoint {
128
+
129
+ constructor(args) {
130
+
131
+ // Store parent
132
+ this.parent = args.parent;
133
+
134
+ // Main element
135
+ this.element = document.createElement('div');
136
+ this.element.classList.add('socket');
137
+ args.parent.element.append(this.element);
138
+
139
+ // Position
140
+ this.position = args.pos;
141
+ if ('top' in args.pos) this.element.style.top = args.pos.top + 'px';
142
+ if ('left' in args.pos) this.element.style.left = args.pos.left + 'px';
143
+ if ('right' in args.pos) this.element.style.right = args.pos.right + 'px';
144
+
145
+ }
146
+
147
+ getWorldPosition() {
148
+ let x = 0;
149
+ let y = this.parent.transform.y - (this.parent.transform.h / 2) + this.position.top + 5;
150
+ if ('left' in this.position) {
151
+ x = this.parent.transform.x - (this.parent.transform.w / 2) + this.position.left + 5;
152
+ }
153
+ else if ('right' in this.position) {
154
+ x = this.parent.transform.x + (this.parent.transform.w / 2) + this.position.right + 5;
155
+ }
156
+ return {x, y};
157
+ }
158
+
159
+ }
160
+
161
+ // More advanced node definition
162
+
163
+ class NodeMoreAdvanced extends TotalDiagramNode {
164
+
165
+ constructor(args) {
166
+ super(args);
167
+
168
+ // Class
169
+ this.element.classList.add('node');
170
+
171
+ // Title bar
172
+ this.titlebar = document.createElement('div');
173
+ this.titlebar.classList.add('titlebar');
174
+ this.titlebar.innerText = args.title || 'Node';
175
+ this.element.append(this.titlebar);
176
+
177
+ // Header
178
+ this.header = document.createElement('div');
179
+ this.header.classList.add('header');
180
+ this.header.innerText = args.header || 'Header';
181
+ this.element.append(this.header);
182
+
183
+ // Options
184
+ if ('options' in args) {
185
+ args.options.forEach(text => {
186
+ const option = document.createElement('div');
187
+ option.classList.add('option');
188
+ option.innerHTML = text;
189
+ this.element.append(option);
190
+ });
191
+ }
192
+
193
+ // Sockets
194
+ this.socketIn = new SocketPoint({
195
+ parent: this,
196
+ pos: {left: -5, top: 11}
197
+ });
198
+ this.socketOut = new SocketPoint({
199
+ parent: this,
200
+ pos: {right: -5, top: 11}
201
+ });
202
+
203
+ // Size
204
+ this.setSize({width: 200, height: 300});
205
+
206
+ // Update
207
+ this.update();
208
+ }
209
+ }
210
+
211
+ // Bezier curve link definition
212
+
213
+ class LinkBezier extends TotalDiagramLink {
214
+
215
+ constructor(args) {
216
+ super(args);
217
+
218
+ // SVG DOM element
219
+ this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
220
+ this.element.appendChild(this.path);
221
+
222
+ // Curvature
223
+ this.curvature = 0.5;
224
+
225
+ // Update
226
+ this.update();
227
+ }
228
+
229
+ update() {
230
+
231
+ // Start & end position
232
+ const startPos = this.start.socketOut.getWorldPosition();
233
+ const endPos = this.end.socketIn.getWorldPosition();
234
+ let x1 = 0;
235
+ let y1 = 0;
236
+ let x2 = 0;
237
+ let y2 = 0;
238
+
239
+ // Left -> right
240
+ if (this.start.transform.x <= this.end.transform.x) {
241
+ x1 = startPos.x;
242
+ y1 = startPos.y;
243
+ x2 = endPos.x;
244
+ y2 = endPos.y;
245
+ }
246
+
247
+ // Right -> left
248
+ else {
249
+ x1 = endPos.x;
250
+ y1 = endPos.y;
251
+ x2 = startPos.x;
252
+ y2 = startPos.y;
253
+ }
254
+
255
+ // Calculate & render
256
+ const hx1 = x1 + Math.abs(x2 - x1) * this.curvature;
257
+ const hx2 = x2 - Math.abs(x2 - x1) * this.curvature;
258
+ const angle = Math.atan2(y2 - y1, ((x2 + hx2) / 2) - ((x1 + hx1) / 2));
259
+ this.path.setAttribute('d', `M ${x1} ${y1} C ${hx1} ${y1} ${hx2} ${y2} ${x2} ${y2}`);
260
+ }
261
+
262
+ }
263
+
264
+ // Start
265
+
266
+ window.onload = function() {
267
+
268
+ // Create managers
269
+ const render = new TotalDiagramRenderHTML5({
270
+ container: document.getElementById('container'),
271
+ nodes: new TotalDiagramNodesManager(),
272
+ links: new TotalDiagramLinksManager()
273
+ });
274
+
275
+ // Create first node
276
+ const node1 = new NodeMoreAdvanced({
277
+ title: 'Node #1',
278
+ header: 'Options for node 1:',
279
+ options: [
280
+ '<b>Option 1:</b> Hello',
281
+ '<b>Option 2:</b> World',
282
+ '<b>Option 3:</b> Lorem',
283
+ '<b>Option 4:</b> Ipsum'
284
+ ],
285
+ x: -200,
286
+ y: -50
287
+ });
288
+ render.nodes.add(node1);
289
+
290
+ // Create second node
291
+ const node2 = new NodeMoreAdvanced({
292
+ title: 'Node #2',
293
+ header: 'Options for node 2:',
294
+ options: [
295
+ '<b>Option 1:</b> Hello',
296
+ '<b>Option 2:</b> World',
297
+ '<b>Option 3:</b> Lorem',
298
+ '<b>Option 4:</b> Ipsum'
299
+ ],
300
+ x: 200,
301
+ y: 50
302
+ });
303
+ render.nodes.add(node2);
304
+
305
+ // Create link between them
306
+ const link1 = new LinkBezier({
307
+ start: node1,
308
+ end: node2
309
+ });
310
+ render.links.add(link1);
311
+
312
+ // Move to view central 0,0
313
+ render.center();
314
+
315
+ };
316
+ </script>
317
+
318
+ </body>
319
+ </html>
package/manager-links.js CHANGED
@@ -39,6 +39,10 @@ class TotalDiagramLinksManager {
39
39
  // Add to DOM
40
40
  this.render.board.append(link.element);
41
41
 
42
+ // Broadcast creation event
43
+ const event = new CustomEvent('broadcast:addlink', { detail: link });
44
+ this.render.container.dispatchEvent(event);
45
+
42
46
  }
43
47
 
44
48
  /**
@@ -58,11 +62,28 @@ class TotalDiagramLinksManager {
58
62
  // Remove from list
59
63
  const index = this.list.indexOf(link);
60
64
  if (index !== -1) this.list.splice(index, 1);
65
+
66
+ // Broadcast delete event
67
+ const event = new CustomEvent('broadcast:dellink', { detail: link });
68
+ this.render.container.dispatchEvent(event);
61
69
  }
62
70
 
63
71
  // Remove all links
64
72
  else {
73
+
74
+ // All list
75
+ this.list.forEach(l => {
76
+ l.destructor();
77
+ l.element.remove();
78
+ });
79
+
80
+ // Delete
65
81
  this.list.length = 0;
82
+
83
+ // Broadcast delete event
84
+ const event = new CustomEvent('broadcast:delnodes', { detail: '*' });
85
+ this.render.container.dispatchEvent(event);
86
+
66
87
  }
67
88
 
68
89
  }
package/manager-nodes.js CHANGED
@@ -39,6 +39,10 @@ class TotalDiagramNodesManager {
39
39
  // Call node awake function sice it's added into DOM tree
40
40
  node.awake();
41
41
 
42
+ // Broadcast creation event
43
+ const event = new CustomEvent('broadcast:addnode', { detail: node });
44
+ this.render.container.dispatchEvent(event);
45
+
42
46
  }
43
47
 
44
48
  /**
@@ -51,7 +55,7 @@ class TotalDiagramNodesManager {
51
55
  if (node != '*') {
52
56
 
53
57
  // First delete associated links
54
- let linksToDelete = node.links.get('*');
58
+ const linksToDelete = node.links.get('*');
55
59
  while (linksToDelete.length) {
56
60
  const link = linksToDelete.pop();
57
61
  this.render.links.del(link);
@@ -66,11 +70,28 @@ class TotalDiagramNodesManager {
66
70
  // Remove from list
67
71
  const index = this.list.indexOf(node);
68
72
  if (index !== -1) this.list.splice(index, 1);
73
+
74
+ // Broadcast delete event
75
+ const event = new CustomEvent('broadcast:delnode', { detail: node });
76
+ this.render.container.dispatchEvent(event);
69
77
  }
70
78
 
71
79
  // Clear all on the list
72
80
  else if (node == '*') {
81
+
82
+ // All list
83
+ this.list.forEach(n => {
84
+ n.destructor();
85
+ n.element.remove();
86
+ });
87
+
88
+ // Delete
73
89
  this.list.length = 0;
90
+
91
+ // Broadcast delete event
92
+ const event = new CustomEvent('broadcast:delnodes', { detail: '*' });
93
+ this.render.container.dispatchEvent(event);
94
+
74
95
  }
75
96
 
76
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-diagram",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Simple, powerful, extensible and fast JavaScript/ES8 diagram renderer for web browsers.",
5
5
  "license": "MIT",
6
6
  "scripts": {
package/render-link.js CHANGED
@@ -22,11 +22,11 @@ class TotalDiagramLink {
22
22
 
23
23
  // Start Node
24
24
  this.start = args.start;
25
- this.start.addLink(this);
25
+ this.start.links.add(this);
26
26
 
27
27
  // End Node
28
28
  this.end = args.end;
29
- this.end.addLink(this);
29
+ this.end.links.add(this);
30
30
 
31
31
  // SVG DOM element
32
32
  this.element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
@@ -43,8 +43,8 @@ class TotalDiagramLink {
43
43
 
44
44
  destructor() {
45
45
  // Remove references in related nodes
46
- if (this.start) this.start.delLink(this);
47
- if (this.end) this.end.delLink(this);
46
+ if (this.start) this.start.links.del(this);
47
+ if (this.end) this.end.links.del(this);
48
48
  }
49
49
 
50
50
  /**
package/render-node.js CHANGED
@@ -78,12 +78,18 @@ class TotalDiagramNode {
78
78
  if (index !== -1) this.links.list.splice(index, 1);
79
79
  },
80
80
 
81
- // Get assigned link
81
+ // Get connected link(s)
82
82
  get: (id) => {
83
83
 
84
84
  // All links
85
85
  if (id == '*') return this.links.list;
86
86
 
87
+ // Input links
88
+ else if (id == 'in') return this.links.list.filter(l => l.end.id == this.id);
89
+
90
+ // Output links
91
+ else if (id == 'out') return this.links.list.filter(l => l.start.id == this.id);
92
+
87
93
  // Find one link by ID
88
94
  else if (typeof(id) == 'string') {
89
95
  return this.links.list.find(l => l.id == id);
@@ -212,24 +218,6 @@ class TotalDiagramNode {
212
218
  this.transform.y -= transform.y;
213
219
  }
214
220
 
215
- /**
216
- * Add link
217
- * link: TotalDiagramLink object
218
- */
219
-
220
- addLink(link) {
221
- this.links.add(link);
222
- }
223
-
224
- /**
225
- * Delete link
226
- * link: TotalDiagramLink object
227
- */
228
-
229
- delLink(link) {
230
- this.links.del(link);
231
- }
232
-
233
221
  /**
234
222
  * Style
235
223
  */
Binary file
Binary file