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 +15 -1
- package/dist/total-diagram.js +5 -0
- package/examples/1_basic.html +10 -1
- package/examples/2_node.html +235 -0
- package/examples/3_link.html +319 -0
- package/manager-links.js +21 -0
- package/manager-nodes.js +22 -1
- package/package.json +1 -1
- package/render-link.js +4 -4
- package/render-node.js +7 -19
- package/total-diagram-logo.png +0 -0
- package/total-diagram-showcase.png +0 -0
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.
|
|
11
|
+
v0.9.6
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
[](https://github.com/dariuszdawidowski/total-diagram/actions/workflows/build.yml)
|
|
15
15
|
[](https://www.npmjs.com/package/total-diagram)
|
|
16
|
+
[](https://www.npmjs.com/package/total-diagram)
|
|
16
17
|
[](./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"}}
|
package/examples/1_basic.html
CHANGED
|
@@ -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"
|
|
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
|
-
|
|
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
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.
|
|
25
|
+
this.start.links.add(this);
|
|
26
26
|
|
|
27
27
|
// End Node
|
|
28
28
|
this.end = args.end;
|
|
29
|
-
this.end.
|
|
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.
|
|
47
|
-
if (this.end) this.end.
|
|
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
|
|
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
|
*/
|
package/total-diagram-logo.png
CHANGED
|
Binary file
|
|
Binary file
|