total-diagram 0.9.6 → 0.9.7
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/LICENSE +1 -1
- package/README.md +3 -2
- package/cypress/e2e/fundamentals.cy.js +10 -0
- package/cypress/fixtures/example.json +5 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/e2e.js +20 -0
- package/cypress.config.js +9 -0
- package/dist/total-diagram.js +3 -2
- package/examples/1_basic.html +1 -0
- package/examples/2_node.html +1 -0
- package/examples/3_link.html +1 -0
- package/examples/4_navigation.html +349 -0
- package/examples/5_traversal.html +263 -0
- package/examples/e2e.html +193 -0
- package/gremlin.js +107 -0
- package/package.json +6 -4
- package/render-node.js +15 -4
- package/render.js +63 -4
- package/total-diagram.js.ejs +1 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ 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.7
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
[](https://github.com/dariuszdawidowski/total-diagram/actions/workflows/build.yml)
|
|
@@ -55,4 +55,5 @@ https://unpkg.com/total-diagram@latest/dist/total-diagram.js
|
|
|
55
55
|
|
|
56
56
|
# Authors
|
|
57
57
|
|
|
58
|
-
Dariusz Dawidowski
|
|
58
|
+
Dariusz Dawidowski\
|
|
59
|
+
Jagoda Dawidowska
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
describe('Fundamentals test', () => {
|
|
2
|
+
it('passes', () => {
|
|
3
|
+
// Open website
|
|
4
|
+
cy.visit('http://127.0.0.1:8080/examples/e2e.html')
|
|
5
|
+
// Two Nodes
|
|
6
|
+
cy.get('.total-diagram-node.node').should('have.length', 2)
|
|
7
|
+
// One links
|
|
8
|
+
cy.get('.link').should('have.length', 1)
|
|
9
|
+
})
|
|
10
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// ***********************************************
|
|
2
|
+
// This example commands.js shows you how to
|
|
3
|
+
// create various custom commands and overwrite
|
|
4
|
+
// existing commands.
|
|
5
|
+
//
|
|
6
|
+
// For more comprehensive examples of custom
|
|
7
|
+
// commands please read more here:
|
|
8
|
+
// https://on.cypress.io/custom-commands
|
|
9
|
+
// ***********************************************
|
|
10
|
+
//
|
|
11
|
+
//
|
|
12
|
+
// -- This is a parent command --
|
|
13
|
+
// Cypress.Commands.add('login', (email, password) => { ... })
|
|
14
|
+
//
|
|
15
|
+
//
|
|
16
|
+
// -- This is a child command --
|
|
17
|
+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
|
18
|
+
//
|
|
19
|
+
//
|
|
20
|
+
// -- This is a dual command --
|
|
21
|
+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
|
22
|
+
//
|
|
23
|
+
//
|
|
24
|
+
// -- This will overwrite an existing command --
|
|
25
|
+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ***********************************************************
|
|
2
|
+
// This example support/e2e.js is processed and
|
|
3
|
+
// loaded automatically before your test files.
|
|
4
|
+
//
|
|
5
|
+
// This is a great place to put global configuration and
|
|
6
|
+
// behavior that modifies Cypress.
|
|
7
|
+
//
|
|
8
|
+
// You can change the location of this file or turn off
|
|
9
|
+
// automatically serving support files with the
|
|
10
|
+
// 'supportFile' configuration option.
|
|
11
|
+
//
|
|
12
|
+
// You can read more here:
|
|
13
|
+
// https://on.cypress.io/configuration
|
|
14
|
+
// ***********************************************************
|
|
15
|
+
|
|
16
|
+
// Import commands.js using ES2015 syntax:
|
|
17
|
+
import './commands'
|
|
18
|
+
|
|
19
|
+
// Alternatively you can use CommonJS syntax:
|
|
20
|
+
// require('./commands')
|
package/dist/total-diagram.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
class
|
|
1
|
+
class AnonymousTraversalSource{constructor(r){this.traversalSourceClass=r}static traversal(r){return new AnonymousTraversalSource(r||GraphTraversalSource)}withLists(r,s){return new this.traversalSourceClass(r,s)}}class GraphTraversalSource{constructor(r,s){this.vertices=r,this.edges=s}V(r=null){return new GraphTraversal(this.vertices,r)}E(r=null){return new GraphTraversal(this.edges,r)}}class GraphTraversal{constructor(r,s=null){if(this.result=null,null==s)this.result=r;else if("object"==typeof s){let t=s;for(;t.parentNode;)"classList"in t&&t.classList.contains("total-diagram-node")&&(this.result=r.find((r=>r.id==t.dataset.id))),t=t.parentNode}else"function"==typeof node?this.result=r.filter((r=>r instanceof node)):"string"==typeof s&&(this.result=r.find((r=>r.id==s)))}hasNext(){return null!=this.result&&"Array"!=this.result.constructor.name}next(){return this.result}toList(){return"Array"!=this.result.constructor.name?[this.result]:this.result}}
|
|
2
|
+
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=>{-1===this.links.list.indexOf(t)&&this.links.list.push(t)},del:t=>{const s=this.links.list.indexOf(t);-1!==s&&this.links.list.splice(s,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((s=>s.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.w<this.transform.wmin&&(this.transform.w=this.transform.wmin),this.transform.w>this.transform.wmax&&(this.transform.w=this.transform.wmax),this.transform.h<this.transform.hmin&&(this.transform.h=this.transform.hmin),this.transform.h>this.transform.hmax&&(this.transform.h=this.transform.hmax),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,s=null){null!==s&&(this.element.style[t]=s)}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
3
|
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
4
|
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
5
|
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
|
|
6
|
+
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;const s=AnonymousTraversalSource.traversal;this.g=s().withLists(this.nodes.list,this.links.list)}pan(t,s){this.offset.x+=t,this.offset.y+=s,this.update()}zoom(t,s,i,e){let h=this.offset.z;this.offset.z=Math.max(.1,Math.min(3,this.offset.z-i/e*this.offset.z)),h=this.offset.z/h;const o=this.board.getBoundingClientRect(),n=o.width/2+o.left-t,r=o.height/2+o.top-s;this.offset.x+=n*h-n,this.offset.y+=r*h-r,this.update()}pinchZoom(t,s,i,e,h){let o=this.offset.z;this.offset.z=Math.max(.1,Math.min(3,this.offset.z*h)),o=this.offset.z/o;const n=this.board.getBoundingClientRect(),r=n.width/2+n.left-(t+i)/2,f=n.height/2+n.top-(s+e)/2;this.offset.x+=r*o-r,this.offset.y+=f*o-f,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",i="hard",e=null){"smooth"==i&&(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/(e.height+e.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()}focusBounds(){const t=this.getBounds(this.nodes.get("*").filter((t=>t.parent==this.nodes.parent))),s={x:this.size.width/t.width,y:this.size.height/t.height,avg:function(){return(this.x+this.y)/2}};this.offset.z=t.isZero()?1:Math.min(Math.max(s.avg(),.3),1),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()}getBounds(t){const s={left:1/0,right:-1/0,top:1/0,bottom:-1/0,x:0,y:0,width:0,height:0,isZero:function(){for(let t in this)if("isZero"!==t&&Math.abs(this[t])>Number.EPSILON)return!1;return!0}};return t.forEach((t=>{s.left=Math.min(t.transform.x-t.transform.w/2,s.left),s.top=Math.min(t.transform.y-t.transform.h/2,s.top),s.right=Math.max(t.transform.x+t.transform.w/2,s.right),s.bottom=Math.max(t.transform.y+t.transform.h/2,s.bottom)})),s.width=s.right-s.left,s.height=s.bottom-s.top,s.x=s.left+s.width/2,s.y=s.top+s.height/2,s}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
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
</div>
|
|
73
73
|
|
|
74
74
|
<!-- Total Diagram library -->
|
|
75
|
+
<script type="text/javascript" src="../gremlin.js"></script>
|
|
75
76
|
<script type="text/javascript" src="../render-node.js"></script>
|
|
76
77
|
<script type="text/javascript" src="../render-link.js"></script>
|
|
77
78
|
<script type="text/javascript" src="../manager-nodes.js"></script>
|
package/examples/2_node.html
CHANGED
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
</div>
|
|
106
106
|
|
|
107
107
|
<!-- Total Diagram library -->
|
|
108
|
+
<script type="text/javascript" src="../gremlin.js"></script>
|
|
108
109
|
<script type="text/javascript" src="../render-node.js"></script>
|
|
109
110
|
<script type="text/javascript" src="../render-link.js"></script>
|
|
110
111
|
<script type="text/javascript" src="../manager-nodes.js"></script>
|
package/examples/3_link.html
CHANGED
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
</div>
|
|
115
115
|
|
|
116
116
|
<!-- Total Diagram library -->
|
|
117
|
+
<script type="text/javascript" src="../gremlin.js"></script>
|
|
117
118
|
<script type="text/javascript" src="../render-node.js"></script>
|
|
118
119
|
<script type="text/javascript" src="../render-link.js"></script>
|
|
119
120
|
<script type="text/javascript" src="../manager-nodes.js"></script>
|
|
@@ -0,0 +1,349 @@
|
|
|
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>4: Navigation demo</h3>
|
|
113
|
+
<p>How to navigate (pan and zoom) using mouse.</p>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Total Diagram library -->
|
|
117
|
+
<script type="text/javascript" src="../gremlin.js"></script>
|
|
118
|
+
<script type="text/javascript" src="../render-node.js"></script>
|
|
119
|
+
<script type="text/javascript" src="../render-link.js"></script>
|
|
120
|
+
<script type="text/javascript" src="../manager-nodes.js"></script>
|
|
121
|
+
<script type="text/javascript" src="../manager-links.js"></script>
|
|
122
|
+
<script type="text/javascript" src="../render.js"></script>
|
|
123
|
+
|
|
124
|
+
<script>
|
|
125
|
+
|
|
126
|
+
// Socket definition
|
|
127
|
+
|
|
128
|
+
class SocketPoint {
|
|
129
|
+
|
|
130
|
+
constructor(args) {
|
|
131
|
+
|
|
132
|
+
// Store parent
|
|
133
|
+
this.parent = args.parent;
|
|
134
|
+
|
|
135
|
+
// Main element
|
|
136
|
+
this.element = document.createElement('div');
|
|
137
|
+
this.element.classList.add('socket');
|
|
138
|
+
args.parent.element.append(this.element);
|
|
139
|
+
|
|
140
|
+
// Position
|
|
141
|
+
this.position = args.pos;
|
|
142
|
+
if ('top' in args.pos) this.element.style.top = args.pos.top + 'px';
|
|
143
|
+
if ('left' in args.pos) this.element.style.left = args.pos.left + 'px';
|
|
144
|
+
if ('right' in args.pos) this.element.style.right = args.pos.right + 'px';
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getWorldPosition() {
|
|
149
|
+
let x = 0;
|
|
150
|
+
let y = this.parent.transform.y - (this.parent.transform.h / 2) + this.position.top + 5;
|
|
151
|
+
if ('left' in this.position) {
|
|
152
|
+
x = this.parent.transform.x - (this.parent.transform.w / 2) + this.position.left + 5;
|
|
153
|
+
}
|
|
154
|
+
else if ('right' in this.position) {
|
|
155
|
+
x = this.parent.transform.x + (this.parent.transform.w / 2) + this.position.right + 5;
|
|
156
|
+
}
|
|
157
|
+
return {x, y};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// More advanced node definition
|
|
163
|
+
|
|
164
|
+
class NodeMoreAdvanced extends TotalDiagramNode {
|
|
165
|
+
|
|
166
|
+
constructor(args) {
|
|
167
|
+
super(args);
|
|
168
|
+
|
|
169
|
+
// Class
|
|
170
|
+
this.element.classList.add('node');
|
|
171
|
+
|
|
172
|
+
// Title bar
|
|
173
|
+
this.titlebar = document.createElement('div');
|
|
174
|
+
this.titlebar.classList.add('titlebar');
|
|
175
|
+
this.titlebar.innerText = args.title || 'Node';
|
|
176
|
+
this.element.append(this.titlebar);
|
|
177
|
+
|
|
178
|
+
// Header
|
|
179
|
+
this.header = document.createElement('div');
|
|
180
|
+
this.header.classList.add('header');
|
|
181
|
+
this.header.innerText = args.header || 'Header';
|
|
182
|
+
this.element.append(this.header);
|
|
183
|
+
|
|
184
|
+
// Options
|
|
185
|
+
if ('options' in args) {
|
|
186
|
+
args.options.forEach(text => {
|
|
187
|
+
const option = document.createElement('div');
|
|
188
|
+
option.classList.add('option');
|
|
189
|
+
option.innerHTML = text;
|
|
190
|
+
this.element.append(option);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Sockets
|
|
195
|
+
this.socketIn = new SocketPoint({
|
|
196
|
+
parent: this,
|
|
197
|
+
pos: {left: -5, top: 11}
|
|
198
|
+
});
|
|
199
|
+
this.socketOut = new SocketPoint({
|
|
200
|
+
parent: this,
|
|
201
|
+
pos: {right: -5, top: 11}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Size
|
|
205
|
+
this.setSize({width: 200, height: 300});
|
|
206
|
+
|
|
207
|
+
// Update
|
|
208
|
+
this.update();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Bezier curve link definition
|
|
213
|
+
|
|
214
|
+
class LinkBezier extends TotalDiagramLink {
|
|
215
|
+
|
|
216
|
+
constructor(args) {
|
|
217
|
+
super(args);
|
|
218
|
+
|
|
219
|
+
// SVG DOM element
|
|
220
|
+
this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
221
|
+
this.element.appendChild(this.path);
|
|
222
|
+
|
|
223
|
+
// Curvature
|
|
224
|
+
this.curvature = 0.5;
|
|
225
|
+
|
|
226
|
+
// Update
|
|
227
|
+
this.update();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
update() {
|
|
231
|
+
|
|
232
|
+
// Start & end position
|
|
233
|
+
const startPos = this.start.socketOut.getWorldPosition();
|
|
234
|
+
const endPos = this.end.socketIn.getWorldPosition();
|
|
235
|
+
let x1 = 0;
|
|
236
|
+
let y1 = 0;
|
|
237
|
+
let x2 = 0;
|
|
238
|
+
let y2 = 0;
|
|
239
|
+
|
|
240
|
+
// Left -> right
|
|
241
|
+
if (this.start.transform.x <= this.end.transform.x) {
|
|
242
|
+
x1 = startPos.x;
|
|
243
|
+
y1 = startPos.y;
|
|
244
|
+
x2 = endPos.x;
|
|
245
|
+
y2 = endPos.y;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Right -> left
|
|
249
|
+
else {
|
|
250
|
+
x1 = endPos.x;
|
|
251
|
+
y1 = endPos.y;
|
|
252
|
+
x2 = startPos.x;
|
|
253
|
+
y2 = startPos.y;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Calculate & render
|
|
257
|
+
const hx1 = x1 + Math.abs(x2 - x1) * this.curvature;
|
|
258
|
+
const hx2 = x2 - Math.abs(x2 - x1) * this.curvature;
|
|
259
|
+
const angle = Math.atan2(y2 - y1, ((x2 + hx2) / 2) - ((x1 + hx1) / 2));
|
|
260
|
+
this.path.setAttribute('d', `M ${x1} ${y1} C ${hx1} ${y1} ${hx2} ${y2} ${x2} ${y2}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
function initEvents(render) {
|
|
267
|
+
const container = document.querySelector('#container');
|
|
268
|
+
let state = 0;
|
|
269
|
+
|
|
270
|
+
container.addEventListener('mousedown', (event) => {
|
|
271
|
+
state = 1;
|
|
272
|
+
} );
|
|
273
|
+
|
|
274
|
+
container.addEventListener('mousemove', (event) => {
|
|
275
|
+
|
|
276
|
+
if (state == 1) {
|
|
277
|
+
render.pan(event.movementX, event.movementY);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
container.addEventListener('mouseup', (event) => {
|
|
282
|
+
state = 0;
|
|
283
|
+
} );
|
|
284
|
+
|
|
285
|
+
container.addEventListener('mousewheel', (event) => {
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
render.zoom(event.pageX, event.pageY, event.deltaY, 1000);
|
|
288
|
+
} );
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Start
|
|
293
|
+
|
|
294
|
+
window.onload = function() {
|
|
295
|
+
|
|
296
|
+
// Create managers
|
|
297
|
+
const render = new TotalDiagramRenderHTML5({
|
|
298
|
+
container: document.getElementById('container'),
|
|
299
|
+
nodes: new TotalDiagramNodesManager(),
|
|
300
|
+
links: new TotalDiagramLinksManager()
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
initEvents(render);
|
|
304
|
+
|
|
305
|
+
// Create first node
|
|
306
|
+
const node1 = new NodeMoreAdvanced({
|
|
307
|
+
title: 'Node #1',
|
|
308
|
+
header: 'Options for node 1:',
|
|
309
|
+
options: [
|
|
310
|
+
'<b>Option 1:</b> Hello',
|
|
311
|
+
'<b>Option 2:</b> World',
|
|
312
|
+
'<b>Option 3:</b> Lorem',
|
|
313
|
+
'<b>Option 4:</b> Ipsum'
|
|
314
|
+
],
|
|
315
|
+
x: -200,
|
|
316
|
+
y: -50
|
|
317
|
+
});
|
|
318
|
+
render.nodes.add(node1);
|
|
319
|
+
|
|
320
|
+
// Create second node
|
|
321
|
+
const node2 = new NodeMoreAdvanced({
|
|
322
|
+
title: 'Node #2',
|
|
323
|
+
header: 'Options for node 2:',
|
|
324
|
+
options: [
|
|
325
|
+
'<b>Option 1:</b> Hello',
|
|
326
|
+
'<b>Option 2:</b> World',
|
|
327
|
+
'<b>Option 3:</b> Lorem',
|
|
328
|
+
'<b>Option 4:</b> Ipsum'
|
|
329
|
+
],
|
|
330
|
+
x: 200,
|
|
331
|
+
y: 50
|
|
332
|
+
});
|
|
333
|
+
render.nodes.add(node2);
|
|
334
|
+
|
|
335
|
+
// Create link between them
|
|
336
|
+
const link1 = new LinkBezier({
|
|
337
|
+
start: node1,
|
|
338
|
+
end: node2
|
|
339
|
+
});
|
|
340
|
+
render.links.add(link1);
|
|
341
|
+
|
|
342
|
+
// Move to view central 0,0
|
|
343
|
+
render.center();
|
|
344
|
+
|
|
345
|
+
};
|
|
346
|
+
</script>
|
|
347
|
+
|
|
348
|
+
</body>
|
|
349
|
+
</html>
|
|
@@ -0,0 +1,263 @@
|
|
|
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
|
+
.panel {
|
|
45
|
+
text-align: center;
|
|
46
|
+
color: white;
|
|
47
|
+
margin: 30px auto;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.node {
|
|
51
|
+
display: flex;
|
|
52
|
+
position: absolute;
|
|
53
|
+
color: #552603;
|
|
54
|
+
background-color: #e88f56;
|
|
55
|
+
border: 2px solid transparent;
|
|
56
|
+
border-radius: 50%;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
font-size: 14px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.node.selected {
|
|
63
|
+
color: #fbfbfb;;
|
|
64
|
+
background-color: #eeab81;
|
|
65
|
+
border: 2px solid #ffc758;
|
|
66
|
+
box-shadow: 0 0 20px white;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.link {
|
|
70
|
+
position: absolute;
|
|
71
|
+
left: 0;
|
|
72
|
+
top: 0;
|
|
73
|
+
overflow: visible;
|
|
74
|
+
z-index: -1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.link line {
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
fill: #fff;
|
|
80
|
+
stroke: #fff;
|
|
81
|
+
stroke-width: 2;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
</style>
|
|
85
|
+
</head>
|
|
86
|
+
<body>
|
|
87
|
+
<!-- Main diagram container -->
|
|
88
|
+
<div id="container">
|
|
89
|
+
<h3>5: Traversal demo</h3>
|
|
90
|
+
<p>How to use graph travesal query language system to select nodes.</p>
|
|
91
|
+
<div class="panel">
|
|
92
|
+
Select mode <select id="mode">
|
|
93
|
+
<option>Single</option>
|
|
94
|
+
<option>All</option>
|
|
95
|
+
<option>Closest siblings</option>
|
|
96
|
+
<option>Up to tree</option>
|
|
97
|
+
<option>Down to tree</option>
|
|
98
|
+
</select> and click any node below
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Total Diagram library -->
|
|
103
|
+
<script type="text/javascript" src="../gremlin.js"></script>
|
|
104
|
+
<script type="text/javascript" src="../render-node.js"></script>
|
|
105
|
+
<script type="text/javascript" src="../render-link.js"></script>
|
|
106
|
+
<script type="text/javascript" src="../manager-nodes.js"></script>
|
|
107
|
+
<script type="text/javascript" src="../manager-links.js"></script>
|
|
108
|
+
<script type="text/javascript" src="../render.js"></script>
|
|
109
|
+
|
|
110
|
+
<script>
|
|
111
|
+
|
|
112
|
+
// Default node definition
|
|
113
|
+
|
|
114
|
+
class NodeBall extends TotalDiagramNode {
|
|
115
|
+
|
|
116
|
+
constructor(args) {
|
|
117
|
+
super(args);
|
|
118
|
+
|
|
119
|
+
// Class
|
|
120
|
+
this.element.classList.add('node');
|
|
121
|
+
|
|
122
|
+
// Size
|
|
123
|
+
this.setSize({width: 30, height: 30});
|
|
124
|
+
|
|
125
|
+
// Inner text
|
|
126
|
+
if ('text' in args) this.element.innerHTML = args.text;
|
|
127
|
+
|
|
128
|
+
// Update
|
|
129
|
+
this.update();
|
|
130
|
+
|
|
131
|
+
// On click event
|
|
132
|
+
if ('callback' in args) this.element.addEventListener('click', () => { args.callback(this.id) });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Default link definition
|
|
137
|
+
|
|
138
|
+
class LinkLine extends TotalDiagramLink {
|
|
139
|
+
|
|
140
|
+
constructor(args) {
|
|
141
|
+
super(args);
|
|
142
|
+
|
|
143
|
+
// SVG DOM element
|
|
144
|
+
this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
145
|
+
this.element.appendChild(this.line);
|
|
146
|
+
|
|
147
|
+
// Update
|
|
148
|
+
this.update();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
update() {
|
|
152
|
+
this.line.setAttribute('x1', this.start.transform.x);
|
|
153
|
+
this.line.setAttribute('y1', this.start.transform.y);
|
|
154
|
+
this.line.setAttribute('x2', this.end.transform.x);
|
|
155
|
+
this.line.setAttribute('y2', this.end.transform.y);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Main class
|
|
161
|
+
|
|
162
|
+
class Main {
|
|
163
|
+
|
|
164
|
+
constructor() {
|
|
165
|
+
|
|
166
|
+
// Create managers
|
|
167
|
+
this.render = new TotalDiagramRenderHTML5({
|
|
168
|
+
container: document.getElementById('container'),
|
|
169
|
+
nodes: new TotalDiagramNodesManager(),
|
|
170
|
+
links: new TotalDiagramLinksManager()
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Create random diagram
|
|
174
|
+
this.spawnNodes();
|
|
175
|
+
|
|
176
|
+
// Move to view central 0,0
|
|
177
|
+
this.render.center();
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Spawn
|
|
182
|
+
|
|
183
|
+
spawnNodes() {
|
|
184
|
+
|
|
185
|
+
// Random spawn nodes
|
|
186
|
+
for (let i = 0; i < 20; i++) {
|
|
187
|
+
|
|
188
|
+
const node = new NodeBall({
|
|
189
|
+
x: this.randomRangeInt(-400, 400),
|
|
190
|
+
y: this.randomRangeInt(-400, 200),
|
|
191
|
+
text: this.randomLetter(),
|
|
192
|
+
callback: this.nodeClicked.bind(this)
|
|
193
|
+
});
|
|
194
|
+
this.render.nodes.add(node);
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Random links
|
|
199
|
+
for (let i = 0; i < 20; i++) {
|
|
200
|
+
const node1 = this.render.nodes.list[this.randomRangeInt(0, this.render.nodes.list.length - 1)];
|
|
201
|
+
const node2 = this.render.nodes.list[this.randomRangeInt(0, this.render.nodes.list.length - 1)];
|
|
202
|
+
if (node1.id == node2.id) continue;
|
|
203
|
+
const link = new LinkLine({
|
|
204
|
+
start: node1,
|
|
205
|
+
end: node2
|
|
206
|
+
});
|
|
207
|
+
this.render.links.add(link);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Utils
|
|
213
|
+
|
|
214
|
+
randomRangeInt(min, max) {
|
|
215
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
randomLetter() {
|
|
219
|
+
const randomIndex = Math.floor(Math.random() * 26);
|
|
220
|
+
const randomLetter = String.fromCharCode(65 + randomIndex);
|
|
221
|
+
return randomLetter;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Render nodes
|
|
225
|
+
|
|
226
|
+
nodesClear() {
|
|
227
|
+
document.querySelectorAll('.node').forEach(node => {
|
|
228
|
+
node.classList.remove('selected');
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
nodeClicked(id) {
|
|
233
|
+
this.nodesClear();
|
|
234
|
+
|
|
235
|
+
const mode = document.querySelector('#mode');
|
|
236
|
+
switch (mode.value) {
|
|
237
|
+
case 'Single':
|
|
238
|
+
const node = this.render.g.V(id).next();
|
|
239
|
+
if (node) node.element.classList.add('selected');
|
|
240
|
+
break;
|
|
241
|
+
case 'All':
|
|
242
|
+
for (const node of this.render.g.V().toList()) {
|
|
243
|
+
node.element.classList.add('selected');
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Start
|
|
253
|
+
|
|
254
|
+
window.onload = function() {
|
|
255
|
+
|
|
256
|
+
const main = new Main();
|
|
257
|
+
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
</script>
|
|
261
|
+
|
|
262
|
+
</body>
|
|
263
|
+
</html>
|
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
position: absolute;
|
|
47
|
+
background-color: #e88f56;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.link {
|
|
51
|
+
position: absolute;
|
|
52
|
+
left: 0;
|
|
53
|
+
top: 0;
|
|
54
|
+
overflow: visible;
|
|
55
|
+
z-index: -1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.link line {
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
fill: #fff;
|
|
61
|
+
stroke: #fff;
|
|
62
|
+
stroke-width: 2;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<!-- Main diagram container -->
|
|
69
|
+
<div id="container">
|
|
70
|
+
<h3>E2E test</h3>
|
|
71
|
+
<p>Testing sandbox for Cypress</p>
|
|
72
|
+
<p>
|
|
73
|
+
<button id="addNodeButton">add node</button>
|
|
74
|
+
<button id="delNodeButton">delete node</button>
|
|
75
|
+
<button id="addLinkButton">add link</button>
|
|
76
|
+
<button id="delLinkButton">delete link</button>
|
|
77
|
+
</p>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Total Diagram library -->
|
|
81
|
+
<script type="text/javascript" src="../gremlin.js"></script>
|
|
82
|
+
<script type="text/javascript" src="../render-node.js"></script>
|
|
83
|
+
<script type="text/javascript" src="../render-link.js"></script>
|
|
84
|
+
<script type="text/javascript" src="../manager-nodes.js"></script>
|
|
85
|
+
<script type="text/javascript" src="../manager-links.js"></script>
|
|
86
|
+
<script type="text/javascript" src="../render.js"></script>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
|
|
90
|
+
// Default node definition
|
|
91
|
+
|
|
92
|
+
class NodeSquare extends TotalDiagramNode {
|
|
93
|
+
|
|
94
|
+
constructor(args) {
|
|
95
|
+
super(args);
|
|
96
|
+
|
|
97
|
+
// Class
|
|
98
|
+
this.element.classList.add('node');
|
|
99
|
+
|
|
100
|
+
// Size
|
|
101
|
+
this.setSize({width: 50, height: 50});
|
|
102
|
+
|
|
103
|
+
// Update
|
|
104
|
+
this.update();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Default link definition
|
|
109
|
+
|
|
110
|
+
class LinkLine extends TotalDiagramLink {
|
|
111
|
+
|
|
112
|
+
constructor(args) {
|
|
113
|
+
super(args);
|
|
114
|
+
|
|
115
|
+
// SVG DOM element
|
|
116
|
+
this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
117
|
+
this.element.appendChild(this.line);
|
|
118
|
+
|
|
119
|
+
// Update
|
|
120
|
+
this.update();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
update() {
|
|
124
|
+
this.line.setAttribute('x1', this.start.transform.x);
|
|
125
|
+
this.line.setAttribute('y1', this.start.transform.y);
|
|
126
|
+
this.line.setAttribute('x2', this.end.transform.x);
|
|
127
|
+
this.line.setAttribute('y2', this.end.transform.y);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Start
|
|
133
|
+
|
|
134
|
+
window.onload = function() {
|
|
135
|
+
|
|
136
|
+
// Create managers
|
|
137
|
+
const render = new TotalDiagramRenderHTML5({
|
|
138
|
+
container: document.getElementById('container'),
|
|
139
|
+
nodes: new TotalDiagramNodesManager(),
|
|
140
|
+
links: new TotalDiagramLinksManager()
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// // Create first node
|
|
144
|
+
// const node1 = new NodeSquare({
|
|
145
|
+
// x: -150,
|
|
146
|
+
// y: -150
|
|
147
|
+
// });
|
|
148
|
+
// render.nodes.add(node1);
|
|
149
|
+
//
|
|
150
|
+
// // Create second node
|
|
151
|
+
// const node2 = new NodeSquare({
|
|
152
|
+
// x: 150,
|
|
153
|
+
// y: 150
|
|
154
|
+
// });
|
|
155
|
+
// render.nodes.add(node2);
|
|
156
|
+
//
|
|
157
|
+
// // Create link between them
|
|
158
|
+
// const link1 = new LinkLine({
|
|
159
|
+
// start: node1,
|
|
160
|
+
// end: node2
|
|
161
|
+
// });
|
|
162
|
+
// render.links.add(link1);
|
|
163
|
+
|
|
164
|
+
const addNodeButton = document.querySelector("#addNodeButton");
|
|
165
|
+
const delNodeButton = document.querySelector("#delNodeButton");
|
|
166
|
+
const addLinkButton = document.querySelector("#addLinkButton");
|
|
167
|
+
const delLinkButton = document.querySelector("#delLinkButton");
|
|
168
|
+
|
|
169
|
+
addNodeButton.addEventListener('click', () => {
|
|
170
|
+
|
|
171
|
+
console.log("add new node");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
delNodeButton.addEventListener('click', () => {
|
|
175
|
+
console.log("del old node");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
addLinkButton.addEventListener('click', () => {
|
|
179
|
+
console.log("add new lnk");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
delLinkButton.addEventListener('click', () => {
|
|
183
|
+
console.log("del old lnk");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Move to view central 0,0
|
|
187
|
+
render.center();
|
|
188
|
+
|
|
189
|
+
};
|
|
190
|
+
</script>
|
|
191
|
+
|
|
192
|
+
</body>
|
|
193
|
+
</html>
|
package/gremlin.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/***************************************************************************************************
|
|
2
|
+
* . . *
|
|
3
|
+
* / \__***__/ \ Gremlin Light (ultra lightweight version) *
|
|
4
|
+
* ( I / \ I ) Graph travesal query language system *
|
|
5
|
+
* \_| O.O |_/ Syntax inspired by Apache TinkerPop™ Gremlin Query Language *
|
|
6
|
+
* \ ___ / MIT License *
|
|
7
|
+
* \_V___/ Copyright (c) 2023 Dariusz Dawidowski *
|
|
8
|
+
* *
|
|
9
|
+
**************************************************************************************************/
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
Example:
|
|
13
|
+
|
|
14
|
+
const traversal = AnonymousTraversalSource.traversal;
|
|
15
|
+
const g = traversal().withLists(render.nodes.list, render.links.list);
|
|
16
|
+
const allVertices = g.V().toList();
|
|
17
|
+
console.log(allVertices);
|
|
18
|
+
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// https://github.com/apache/tinkerpop/blob/master/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/anonymous-traversal.js
|
|
23
|
+
|
|
24
|
+
class AnonymousTraversalSource {
|
|
25
|
+
|
|
26
|
+
constructor(traversalSourceClass) {
|
|
27
|
+
this.traversalSourceClass = traversalSourceClass;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static traversal(traversalSourceClass) {
|
|
31
|
+
return new AnonymousTraversalSource(traversalSourceClass || GraphTraversalSource);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
withLists(vertices, edges) {
|
|
35
|
+
return new this.traversalSourceClass(vertices, edges);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
// https://tinkerpop.apache.org/javadocs/current/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.html
|
|
42
|
+
|
|
43
|
+
class GraphTraversalSource {
|
|
44
|
+
|
|
45
|
+
constructor(vertices, edges) {
|
|
46
|
+
this.vertices = vertices;
|
|
47
|
+
this.edges = edges;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
V(query = null) {
|
|
51
|
+
return new GraphTraversal(this.vertices, query);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
E(query = null) {
|
|
55
|
+
return new GraphTraversal(this.edges, query);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
// https://tinkerpop.apache.org/javadocs/current/full/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html
|
|
62
|
+
|
|
63
|
+
class GraphTraversal {
|
|
64
|
+
|
|
65
|
+
constructor(list, query = null) {
|
|
66
|
+
|
|
67
|
+
// Query result list
|
|
68
|
+
this.result = null;
|
|
69
|
+
|
|
70
|
+
// All
|
|
71
|
+
if (query == null) this.result = list;
|
|
72
|
+
|
|
73
|
+
// Find one node by giving DOM element
|
|
74
|
+
else if (typeof(query) == 'object') {
|
|
75
|
+
// Traverse DOM
|
|
76
|
+
let target = query;
|
|
77
|
+
while (target.parentNode) {
|
|
78
|
+
if ('classList' in target && target.classList.contains('total-diagram-node')) {
|
|
79
|
+
// Found
|
|
80
|
+
this.result = list.find(n => n.id == target.dataset.id);
|
|
81
|
+
}
|
|
82
|
+
target = target.parentNode;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Find by giving class type
|
|
87
|
+
else if (typeof(node) == 'function') this.result = list.filter(n => n instanceof node);
|
|
88
|
+
|
|
89
|
+
// Find by ID string
|
|
90
|
+
else if (typeof(query) == 'string') this.result = list.find(n => n.id == query);
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
hasNext() {
|
|
95
|
+
return (this.result != null && this.result.constructor.name != 'Array');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
next() {
|
|
99
|
+
return this.result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
toList() {
|
|
103
|
+
if (this.result.constructor.name != 'Array') return [this.result];
|
|
104
|
+
return this.result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "total-diagram",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "Simple, powerful, extensible and fast JavaScript/ES8 diagram renderer for web browsers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build:bundle": "node build.js",
|
|
8
8
|
"build": "npm-run-all build:bundle",
|
|
9
|
-
"start": "npm-run-all build"
|
|
9
|
+
"start": "npm-run-all build",
|
|
10
|
+
"test": "cypress open"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
12
|
-
"
|
|
13
|
-
"fs": "^0.0.1-security",
|
|
13
|
+
"cypress": "^13.0.0",
|
|
14
14
|
"ejs": "^3.1.9",
|
|
15
|
+
"fs": "^0.0.1-security",
|
|
16
|
+
"npm-run-all": "^4.1.5",
|
|
15
17
|
"terser": "^5.17.3"
|
|
16
18
|
}
|
|
17
19
|
}
|
package/render-node.js
CHANGED
|
@@ -65,11 +65,14 @@ class TotalDiagramNode {
|
|
|
65
65
|
// Links
|
|
66
66
|
this.links = {
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// List of links [TotalDiagramLink, ...]
|
|
69
|
+
list: [],
|
|
69
70
|
|
|
70
71
|
// Assign to link
|
|
71
72
|
add: (link) => {
|
|
72
|
-
|
|
73
|
+
// Maybe already added during blossoming
|
|
74
|
+
const index = this.links.list.indexOf(link);
|
|
75
|
+
if (index === -1) this.links.list.push(link);
|
|
73
76
|
},
|
|
74
77
|
|
|
75
78
|
// Unassign from a link
|
|
@@ -151,8 +154,16 @@ class TotalDiagramNode {
|
|
|
151
154
|
if ('minHeight' in size) this.transform.hmin = size.minHeight;
|
|
152
155
|
if ('maxWidth' in size) this.transform.wmax = size.maxWidth;
|
|
153
156
|
if ('maxHeight' in size) this.transform.hmax = size.maxHeight;
|
|
154
|
-
if (
|
|
155
|
-
|
|
157
|
+
if (this.transform.w < this.transform.wmin) this.transform.w = this.transform.wmin;
|
|
158
|
+
if (this.transform.w > this.transform.wmax) this.transform.w = this.transform.wmax;
|
|
159
|
+
if (this.transform.h < this.transform.hmin) this.transform.h = this.transform.hmin;
|
|
160
|
+
if (this.transform.h > this.transform.hmax) this.transform.h = this.transform.hmax;
|
|
161
|
+
if ('border' in size) {
|
|
162
|
+
this.transform.border = size.border;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.transform.border = parseInt(getComputedStyle(this.element, null).getPropertyValue('border-left-width').replace('px', '')) || 0;
|
|
166
|
+
}
|
|
156
167
|
this.setOrigin();
|
|
157
168
|
this.element.style.width = this.transform.w + 'px';
|
|
158
169
|
this.element.style.height = this.transform.h + 'px';
|
package/render.js
CHANGED
|
@@ -30,11 +30,11 @@ class TotalDiagramRenderHTML5 {
|
|
|
30
30
|
this.board.style.overflow = 'visible';
|
|
31
31
|
this.container.appendChild(this.board);
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// Render area window dimensions
|
|
34
34
|
this.size = this.container.getBoundingClientRect();
|
|
35
35
|
this.size.center = {x: this.size.width / 2, y: this.size.height / 2};
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
// Render area window margins
|
|
38
38
|
this.margin = {
|
|
39
39
|
left: this.size.left - document.documentElement.scrollLeft,
|
|
40
40
|
top: this.size.top - document.documentElement.scrollTop,
|
|
@@ -60,6 +60,11 @@ class TotalDiagramRenderHTML5 {
|
|
|
60
60
|
// Link
|
|
61
61
|
this.links = args.links;
|
|
62
62
|
this.links.render = this;
|
|
63
|
+
|
|
64
|
+
// Gremlin
|
|
65
|
+
const traversal = AnonymousTraversalSource.traversal;
|
|
66
|
+
this.g = traversal().withLists(this.nodes.list, this.links.list);
|
|
67
|
+
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
|
@@ -84,7 +89,7 @@ class TotalDiagramRenderHTML5 {
|
|
|
84
89
|
|
|
85
90
|
zoom(x, y, deltaZ, factorZ) {
|
|
86
91
|
let deltaZoom = this.offset.z;
|
|
87
|
-
this.offset.z = (this.offset.z - (deltaZ / factorZ) * this.offset.z)
|
|
92
|
+
this.offset.z = Math.max(0.1, Math.min(3.0, this.offset.z - (deltaZ / factorZ) * this.offset.z));
|
|
88
93
|
deltaZoom = this.offset.z / deltaZoom;
|
|
89
94
|
|
|
90
95
|
const boundingRect = this.board.getBoundingClientRect();
|
|
@@ -107,7 +112,7 @@ class TotalDiagramRenderHTML5 {
|
|
|
107
112
|
|
|
108
113
|
pinchZoom(x1, y1, x2, y2, deltaZ) {
|
|
109
114
|
let deltaZoom = this.offset.z;
|
|
110
|
-
this.offset.z = (this.offset.z * deltaZ)
|
|
115
|
+
this.offset.z = Math.max(0.1,Math.min(3.0, this.offset.z * deltaZ));
|
|
111
116
|
deltaZoom = this.offset.z / deltaZoom;
|
|
112
117
|
|
|
113
118
|
const boundingRect = this.board.getBoundingClientRect();
|
|
@@ -177,6 +182,60 @@ class TotalDiagramRenderHTML5 {
|
|
|
177
182
|
this.update();
|
|
178
183
|
}
|
|
179
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Focus on content
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
focusBounds() {
|
|
190
|
+
const bbox = this.getBounds(this.nodes.get('*').filter(node => node.parent == this.nodes.parent));
|
|
191
|
+
const scale = {
|
|
192
|
+
x: this.size.width / bbox.width,
|
|
193
|
+
y: this.size.height / bbox.height,
|
|
194
|
+
avg: function() {
|
|
195
|
+
return (this.x + this.y) / 2;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
this.offset.z = bbox.isZero() ? 1 : Math.min(Math.max(scale.avg(), 0.3), 1.0);
|
|
199
|
+
this.offset.x = (-bbox.x * this.offset.z) + this.size.center.x;
|
|
200
|
+
this.offset.y = (-bbox.y * this.offset.z) + this.size.center.y;
|
|
201
|
+
this.update();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Calculate bound box
|
|
206
|
+
* nodes: all given nodes
|
|
207
|
+
*/
|
|
208
|
+
|
|
209
|
+
getBounds(nodes) {
|
|
210
|
+
const bbox = {
|
|
211
|
+
left: Infinity, // -x world coords
|
|
212
|
+
right: -Infinity, // +x world coords
|
|
213
|
+
top: Infinity, // -y world coords
|
|
214
|
+
bottom: -Infinity, // +y world coords
|
|
215
|
+
x: 0, // center x (world coords)
|
|
216
|
+
y: 0, // center y (world coords)
|
|
217
|
+
width: 0, // size x
|
|
218
|
+
height: 0, // size y
|
|
219
|
+
isZero: function() {
|
|
220
|
+
for (let key in this) {
|
|
221
|
+
if (key !== 'isZero' && Math.abs(this[key]) > Number.EPSILON) return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
nodes.forEach(node => {
|
|
227
|
+
bbox.left = Math.min(node.transform.x - (node.transform.w / 2), bbox.left);
|
|
228
|
+
bbox.top = Math.min(node.transform.y - (node.transform.h / 2), bbox.top);
|
|
229
|
+
bbox.right = Math.max(node.transform.x + (node.transform.w / 2), bbox.right);
|
|
230
|
+
bbox.bottom = Math.max(node.transform.y + (node.transform.h / 2), bbox.bottom);
|
|
231
|
+
});
|
|
232
|
+
bbox.width = bbox.right - bbox.left;
|
|
233
|
+
bbox.height = bbox.bottom - bbox.top;
|
|
234
|
+
bbox.x = bbox.left + (bbox.width / 2);
|
|
235
|
+
bbox.y = bbox.top + (bbox.height / 2);
|
|
236
|
+
return bbox;
|
|
237
|
+
}
|
|
238
|
+
|
|
180
239
|
/**
|
|
181
240
|
* Delete all nodes and links and clear DOM
|
|
182
241
|
*/
|
package/total-diagram.js.ejs
CHANGED