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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020-2023 Dariusz Dawidowski
3
+ Copyright (c) 2020-2024 Dariusz Dawidowski
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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.6
11
+ v0.9.7
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)
@@ -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,5 @@
1
+ {
2
+ "name": "Using fixtures to represent data",
3
+ "email": "hello@cypress.io",
4
+ "body": "Fixtures are a great way to mock data for responses to routes"
5
+ }
@@ -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')
@@ -0,0 +1,9 @@
1
+ const { defineConfig } = require("cypress");
2
+
3
+ module.exports = defineConfig({
4
+ e2e: {
5
+ setupNodeEvents(on, config) {
6
+ // implement node event listeners here
7
+ },
8
+ },
9
+ });
@@ -1,5 +1,6 @@
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)`}}
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,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"}}
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"}}
@@ -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>
@@ -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>
@@ -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.6",
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
- "npm-run-all": "^4.1.5",
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
- list: [], // [TotalDiagramLink, ...]
68
+ // List of links [TotalDiagramLink, ...]
69
+ list: [],
69
70
 
70
71
  // Assign to link
71
72
  add: (link) => {
72
- this.links.list.push(link);
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 ('border' in size) this.transform.border = size.border;
155
- else this.transform.border = parseInt(getComputedStyle(this.element, null).getPropertyValue('border-left-width').replace('px', '')) || 0;
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
- // Board dimensions
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
- // Board margins
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).clamp(0.1, 3.0);
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).clamp(0.1, 3.0);
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
  */
@@ -1,3 +1,4 @@
1
+ <%- await minjs('gremlin.js') %>
1
2
  <%- await minjs('render-node.js') %>
2
3
  <%- await minjs('manager-nodes.js') %>
3
4
  <%- await minjs('render-link.js') %>