├── .gitignore
├── README.md
├── dist
└── js-diagram-chart.js
├── example.png
├── example
└── index.html
├── package-lock.json
├── package.json
├── src
├── helper
│ └── index.js
└── main.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/node
3 | # Edit at https://www.gitignore.io/?templates=node
4 |
5 | ### Node ###
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # Optional npm cache directory
48 | .npm
49 |
50 | # Optional eslint cache
51 | .eslintcache
52 |
53 | # Optional REPL history
54 | .node_repl_history
55 |
56 | # Output of 'npm pack'
57 | *.tgz
58 |
59 | # Yarn Integrity file
60 | .yarn-integrity
61 |
62 | # dotenv environment variables file
63 | .env
64 | .env.test
65 |
66 | # parcel-bundler cache (https://parceljs.org/)
67 | .cache
68 |
69 | # next.js build output
70 | .next
71 |
72 | # nuxt.js build output
73 | .nuxt
74 |
75 | # vuepress build output
76 | .vuepress/dist
77 |
78 | # Serverless directories
79 | .serverless/
80 |
81 | # FuseBox cache
82 | .fusebox/
83 |
84 | # DynamoDB Local files
85 | .dynamodb/
86 |
87 | # End of https://www.gitignore.io/api/node
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-diagram-chart
2 |
3 | It is a simple diagram chart based on hierarchy, developed in Javascript!
4 |
5 | 
6 |
7 | ## How to use
8 |
9 | ```shell
10 | $ npm run build
11 | $ ./dist/js-diagram-chart.js
12 | ```
13 |
14 | ## Installation
15 |
16 | Using npm:
17 |
18 | ```shell
19 | $ npm i --save js-diagram-chart
20 | ```
21 |
22 | In Node.js:
23 |
24 | ```js
25 |
26 | let JsDiagramChart = require("js-diagram-chart").default;
27 |
28 | // Or
29 |
30 | import JsDiagramChart from "js-diagram-chart";
31 |
32 | ```
33 |
34 | ## Live Example
35 |
36 | http://antuane.github.io/js-diagram-chart/example/index.html
37 |
38 | ## Code Example
39 |
40 | Example in HTML:
41 |
42 | ```html
43 |
46 | ```
47 |
48 | Example in JS:
49 |
50 | Modules:
51 |
52 | ```js
53 |
54 | import JsDiagramChart from "js-diagram-chart";
55 |
56 | //OR
57 |
58 | const JsDiagramChart = require('js-diagram-chart').default;
59 |
60 | ```
61 |
62 | Script import:
63 |
64 | ```html
65 |
66 | ```
67 |
68 | Example Data:
69 |
70 | ```js
71 |
72 | const dataExample = {
73 | diagrams: [
74 | { id: 1, text: "DIAGRAM 01", color: "#999999", bgColor: "#330000" },
75 | { id: 2, text: "DIAGRAM 02", color: "#999999", bgColor: "#003300" },
76 | { id: 3, text: "DIAGRAM 03", color: "#999999", bgColor: "#000033" },
77 | { id: 4, text: "DIAGRAM 04", color: "#999999", bgColor: "#333300" },
78 | { id: 5, text: "DIAGRAM 05", color: "#999999", bgColor: "#003333" },
79 | { id: 6, text: "DIAGRAM 06", color: "#999999", bgColor: "#330033" },
80 | { id: 7, text: "DIAGRAM 07", color: "#999999", bgColor: "#000000" },
81 | { id: 8, text: "DIAGRAM 08", color: "#999999", bgColor: "#000000" },
82 | { id: 9, text: "DIAGRAM 09", color: "#999999", bgColor: "#000000" },
83 | { id: 10, text: "DIAGRAM 10", color: "#999999", bgColor: "#333333" }
84 | ],
85 | links: [
86 | { source: 2, parent: 1 },
87 | { source: 3, parent: 1 },
88 | { source: 4, parent: 3 },
89 | { source: 5, parent: 3 },
90 | { source: 6, parent: 2 },
91 | { source: 9, parent: 9 },
92 | { source: 9, parent: 6 }
93 | ],
94 | config: {
95 | element: "mycanvas",
96 | margin: 30,
97 | padding: 10,
98 | width: 100,
99 | height: 50,
100 | radius: 3,
101 | hiddenBg: false,
102 | arrowWidth: 8,
103 | lineWidth: 2,
104 | lineDiff: true,
105 | fontFamily: "Arial",
106 | fontSize: 12,
107 | autoSize: true,
108 | mouseEvents: true
109 | //lineColor: "#000000", // OPTIONAL
110 | }
111 | };
112 |
113 | let chart = new JsDiagramChart(dataExample);
114 |
115 | // window.addEventListener("resize", function() { chart = new JsDiagramChart(dataExample) });
116 |
117 | // OTHERS METHODS
118 |
119 | // chart.addZoom(-5);
120 | // chart.resetZoom();
121 | // chart.update(dataExample);
122 |
123 | ```
--------------------------------------------------------------------------------
/dist/js-diagram-chart.js:
--------------------------------------------------------------------------------
1 | !function(t){var i={};function n(o){if(i[o])return i[o].exports;var s=i[o]={i:o,l:!1,exports:{}};return t[o].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=t,n.c=i,n.d=function(t,i,o){n.o(t,i)||Object.defineProperty(t,i,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,i){if(1&i&&(t=n(t)),8&i)return t;if(4&i&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&i&&"string"!=typeof t)for(var s in t)n.d(o,s,function(i){return t[i]}.bind(null,s));return o},n.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(i,"a",i),i},n.o=function(t,i){return Object.prototype.hasOwnProperty.call(t,i)},n.p="",n(n.s=0)}([function(t,i,n){"use strict";function o(t,i,n,o,s,e,h){t.beginPath(),t.moveTo(i+e,n),t.lineTo(i+o-e,n),t.quadraticCurveTo(i+o,n,i+o,n+e),t.lineTo(i+o,n+s-e),t.quadraticCurveTo(i+o,n+s,i+o-e,n+s),t.lineTo(i+e,n+s),t.quadraticCurveTo(i,n+s,i,n+s-e),t.lineTo(i,n+e),t.quadraticCurveTo(i,n,i+e,n),t.closePath(),t.stroke(),h||t.fill()}function s(t,i,n,o,s,e,h){t.textAlign="center",t.textBaseline="middle",n+=s/2,o+=e/2;for(var a=i.split("\n"),c=0;cs?(r=g[f]+" ",o-=h/2):r=l}r="";for(var d=0;ds?(t.fillText(r,n,o),r=g[d]+" ",o+=h):r=m}t.fillText(r,n,o),o+=h}}function e(t,i){for(var n=0;no&&(o=l.count)):i[f].orphan||(n.push({id:i[f].y,count:1}),i[f].x=1)}this.config.linesCount=n,this.config.columnsCount=o,this.config.columnLower=s,this.diagrams=i},this.draw=function(){if(this.context.transformedPoint){var t=this.context.transformedPoint(0,0),i=this.context.transformedPoint(this.canvas.width,this.canvas.height);this.context.clearRect(t.x,t.y,i.x-t.x,i.y-t.y)}else this.context.clearRect(0,0,this.canvas.width,this.canvas.height);var n=this.config.width+2*this.config.margin;0==this.config.columnsCount&&(this.config.columnsCount=parseInt(this.canvas.width/n));var h=this.config.columnsCount*n,a=parseInt(this.canvas.width)/2-h/2;this.context.rotate(2*Math.PI);for(var c=0,r=0;r1&&(W=!0),this.context.setLineDash&&this.context.setLineDash([this.config.lineWidth,0]),this.context.strokeStyle=this.config.lineColor||this.diagrams[p].bgColor,this.context.fillStyle=this.config.lineColor||this.diagrams[p].bgColor;var C=0;b?(C=this.config.margin/2-this.config.margin/this.config.columnsCount*this.diagrams[p].x,isFinite(C)||(C=0),this.context.beginPath(),this.context.moveTo(y.left+this.config.width/2+-1*C-C,y.top+this.config.height),this.context.lineTo(y.left+this.config.width/2+-1*C-C,y.top+this.config.height+this.config.margin/2.5),this.context.lineTo(y.left+this.config.width/2+-1*C+C,y.top+this.config.height+this.config.margin/2.5),this.context.lineTo(y.left+this.config.width/2+-1*C+C,y.top+this.config.height+this.config.arrowWidth/2),this.context.stroke(),this.context.beginPath(),this.context.moveTo(y.left+this.config.width/2+-1*C+C,y.top+this.config.height),this.context.lineTo(y.left+this.config.width/2-this.config.arrowWidth/2+-1*C+C,y.top+this.config.height+this.config.arrowWidth),this.context.lineTo(y.left+this.config.width/2+this.config.arrowWidth/2+-1*C+C,y.top+this.config.height+this.config.arrowWidth),this.context.fill()):T?(this.config.lineDiff&&(C=this.config.margin/2-this.config.margin/this.config.columnsCount*this.diagrams[p].x+(this.config.margin-(this.config.arrowWidth+2*this.config.lineWidth*0))),this.context.setLineDash&&this.context.setLineDash([this.config.lineWidth,this.config.lineWidth]),this.context.beginPath(),this.context.moveTo(this.diagrams[p].left+this.config.width/2+C,this.diagrams[p].top+this.config.height/2+C),this.context.lineTo(this.diagrams[p].left+this.config.width+this.config.margin+C,this.diagrams[p].top+this.config.height/2+C),this.context.lineTo(this.diagrams[p].left+this.config.width+this.config.margin+C,y.top-this.config.margin+C),this.context.lineTo(y.left+this.config.width/2+C,y.top-this.config.margin+C),this.context.lineTo(y.left+this.config.width/2+C,y.top-this.config.arrowWidth+C),this.context.lineWidth=this.config.lineWidth,this.context.stroke(),this.context.beginPath(),this.context.moveTo(y.left+this.config.width/2+C,y.top),this.context.lineTo(y.left+this.config.width/2-this.config.arrowWidth/2+C,y.top-this.config.arrowWidth),this.context.lineTo(y.left+this.config.width/2+this.config.arrowWidth/2+C,y.top-this.config.arrowWidth),this.context.fill()):W?(this.config.lineDiff&&(C=this.config.margin/2-this.config.margin/this.config.columnsCount*this.diagrams[p].x+(this.config.margin-(this.config.arrowWidth+2*this.config.lineWidth*S))),S++,isFinite(C)||(C=0),this.context.setLineDash&&this.context.setLineDash([this.config.lineWidth,this.config.lineWidth]),this.context.beginPath(),this.context.moveTo(this.diagrams[p].left+this.config.width/2+-1*C,this.diagrams[p].top),this.context.lineTo(this.diagrams[p].left+this.config.width/2+-1*C,this.diagrams[p].top-this.config.margin+C),this.context.lineTo(y.left+this.config.width/2+-1*C,this.diagrams[p].top-this.config.margin+C),this.context.lineTo(y.left+this.config.width/2+-1*C,y.top+this.config.height+this.config.margin+C),this.context.lineTo(y.left+this.config.width/2+-1*C,y.top+this.config.height+this.config.arrowWidth/2),this.context.lineWidth=this.config.lineWidth,this.context.stroke(),this.context.beginPath(),this.context.moveTo(y.left+this.config.width/2+-1*C,y.top+this.config.height),this.context.lineTo(y.left+this.config.width/2-this.config.arrowWidth/2+-1*C,y.top+this.config.height+this.config.arrowWidth),this.context.lineTo(y.left+this.config.width/2+this.config.arrowWidth/2+-1*C,y.top+this.config.height+this.config.arrowWidth),this.context.fill()):(this.config.lineDiff&&(C=this.config.margin/2-this.config.margin/this.config.columnsCount*this.diagrams[p].x),isFinite(C)||(C=0),this.context.beginPath(),this.context.moveTo(this.diagrams[p].left+this.config.width/2+-1*C,this.diagrams[p].top),this.context.lineTo(this.diagrams[p].left+this.config.width/2+-1*C,this.diagrams[p].top-this.config.margin+C),this.context.lineTo(y.left+this.config.width/2+-1*C,y.top+this.config.height+this.config.margin+C),this.context.lineTo(y.left+this.config.width/2+-1*C,y.top+this.config.height+this.config.arrowWidth/2),this.context.lineWidth=this.config.lineWidth,this.context.stroke(),this.context.beginPath(),this.context.moveTo(y.left+this.config.width/2+-1*C,y.top+this.config.height),this.context.lineTo(y.left+this.config.width/2-this.config.arrowWidth/2+-1*C,y.top+this.config.height+this.config.arrowWidth),this.context.lineTo(y.left+this.config.width/2+this.config.arrowWidth/2+-1*C,y.top+this.config.height+this.config.arrowWidth),this.context.fill())}for(var P=0;P
2 |
3 |
4 |
5 |
6 |
7 |
12 | Antuane Solutions
13 |
22 |
23 |
24 |
25 |
26 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-diagram-chart",
3 | "description": "It is a simple prototype diagram chart based on hierarchy",
4 | "version": "1.0.0",
5 | "main": "src/main.js",
6 | "scripts": {
7 | "watch": "webpack --watch",
8 | "start": "webpack-dev-server --open",
9 | "build": "webpack --prod"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/antuane/js-diagram-chart.git"
14 | },
15 | "keywords": [
16 | "antuane",
17 | "chart",
18 | "diagram",
19 | "javascript"
20 | ],
21 | "author": "Diogenes Silveira",
22 | "license": "ISC",
23 | "bugs": {
24 | "url": "https://github.com/antuane/js-diagram-chart/issues"
25 | },
26 | "homepage": "https://github.com/antuane/js-diagram-chart/antuane-chart#readme",
27 | "devDependencies": {
28 | "@babel/core": "^7.1.0",
29 | "@babel/preset-env": "^7.1.0",
30 | "babel-loader": "^8.0.2",
31 | "clean-webpack-plugin": "^0.1.19",
32 | "webpack": "^4.19.1",
33 | "webpack-cli": "^3.1.1"
34 | },
35 | "dependencies": {
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/helper/index.js:
--------------------------------------------------------------------------------
1 |
2 | /*global window*/
3 | /*global document*/
4 |
5 | export function getRandomInt(min, max) {
6 | return Math.floor(Math.random() * (max - min)) + min;
7 | }
8 |
9 | export function roundRect(context, x, y, width, height, radius, hiddenBg) {
10 | context.beginPath();
11 | context.moveTo(x + radius, y);
12 | context.lineTo(x + width - radius, y);
13 | context.quadraticCurveTo(x + width, y, x + width, y + radius);
14 | context.lineTo(x + width, y + height - radius);
15 | context.quadraticCurveTo(
16 | x + width,
17 | y + height,
18 | x + width - radius,
19 | y + height
20 | );
21 | context.lineTo(x + radius, y + height);
22 | context.quadraticCurveTo(x, y + height, x, y + height - radius);
23 | context.lineTo(x, y + radius);
24 | context.quadraticCurveTo(x, y, x + radius, y);
25 | context.closePath();
26 | context.stroke();
27 | if (!hiddenBg) {
28 | context.fill();
29 | }
30 | }
31 |
32 | export function wrapText(context, text, x, y, width, height, lineHeight) {
33 | context.textAlign = "center";
34 | context.textBaseline = "middle";
35 | x = x + width / 2;
36 | y = y + height / 2;
37 |
38 | let cars = text.split("\n");
39 |
40 | for (let ii = 0; ii < cars.length; ii++) {
41 | let line = "";
42 | let words = cars[ii].split(" ");
43 |
44 | for (let n = 0; n < words.length; n++) {
45 | let testLine = line + words[n] + " ";
46 | let metrics = context.measureText(testLine);
47 | let testWidth = metrics.width;
48 |
49 | if (testWidth > width) {
50 | line = words[n] + " ";
51 | y -= lineHeight / 2;
52 | } else {
53 | line = testLine;
54 | }
55 | }
56 |
57 | line = "";
58 |
59 | for (let n = 0; n < words.length; n++) {
60 | let testLine = line + words[n] + " ";
61 | let metrics = context.measureText(testLine);
62 | let testWidth = metrics.width;
63 |
64 | if (testWidth > width) {
65 | context.fillText(line, x, y);
66 | line = words[n] + " ";
67 | y += lineHeight;
68 | } else {
69 | line = testLine;
70 | }
71 | }
72 |
73 | context.fillText(line, x, y);
74 | y += lineHeight;
75 | }
76 | }
77 |
78 | export function colorLuminance(hex, lum) {
79 | hex = String(hex).replace(/[^0-9a-f]/gi, "");
80 | if (hex.length < 6) {
81 | hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
82 | }
83 | lum = lum || 0;
84 | let rgb = "#",
85 | c,
86 | i;
87 | for (i = 0; i < 3; i++) {
88 | c = parseInt(hex.substr(i * 2, 2), 16);
89 | c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16);
90 | rgb += ("00" + c).substr(c.length);
91 | }
92 | return rgb;
93 | }
94 |
95 | export function cloneObj(obj) {
96 | if (obj == null || typeof obj != "object") return obj;
97 |
98 | let temp = obj.constructor();
99 |
100 | for (let key in obj) {
101 | if (obj.hasOwnProperty(key)) {
102 | temp[key] = cloneObj(obj[key]);
103 | }
104 | }
105 | return temp;
106 | }
107 |
108 | export function trackTransforms(ctx) {
109 | let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
110 | let xform = svg.createSVGMatrix();
111 | ctx.getTransform = function() {
112 | return xform;
113 | };
114 |
115 | let savedTransforms = [];
116 | let save = ctx.save;
117 | ctx.save = function() {
118 | savedTransforms.push(xform.translate(0, 0));
119 | return save.call(ctx);
120 | };
121 | let restore = ctx.restore;
122 | ctx.restore = function() {
123 | xform = savedTransforms.pop();
124 | return restore.call(ctx);
125 | };
126 |
127 | let scale = ctx.scale;
128 | ctx.scale = function(sx, sy) {
129 | xform = xform.scaleNonUniform(sx, sy);
130 | return scale.call(ctx, sx, sy);
131 | };
132 | let rotate = ctx.rotate;
133 | ctx.rotate = function(radians) {
134 | xform = xform.rotate((radians * 180) / Math.PI);
135 | return rotate.call(ctx, radians);
136 | };
137 | let translate = ctx.translate;
138 | ctx.translate = function(dx, dy) {
139 | xform = xform.translate(dx, dy);
140 | return translate.call(ctx, dx, dy);
141 | };
142 | let transform = ctx.transform;
143 | ctx.transform = function(a, b, c, d, e, f) {
144 | let m2 = svg.createSVGMatrix();
145 | m2.a = a;
146 | m2.b = b;
147 | m2.c = c;
148 | m2.d = d;
149 | m2.e = e;
150 | m2.f = f;
151 | xform = xform.multiply(m2);
152 | return transform.call(ctx, a, b, c, d, e, f);
153 | };
154 | let setTransform = ctx.setTransform;
155 | ctx.setTransform = function(a, b, c, d, e, f) {
156 | xform.a = a;
157 | xform.b = b;
158 | xform.c = c;
159 | xform.d = d;
160 | xform.e = e;
161 | xform.f = f;
162 | return setTransform.call(ctx, a, b, c, d, e, f);
163 | };
164 | let pt = svg.createSVGPoint();
165 | ctx.transformedPoint = function(x, y) {
166 | pt.x = x;
167 | pt.y = y;
168 | return pt.matrixTransform(xform.inverse());
169 | };
170 | }
171 |
172 | export function getObjById(listObj, idObj) {
173 | for (let i = 0; i < listObj.length; i++) {
174 | if (listObj[i].id == idObj) {
175 | return listObj[i];
176 | }
177 | }
178 | return null;
179 | }
180 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /*global window*/
4 | /*global document*/
5 |
6 | import { getObjById, roundRect, trackTransforms, wrapText } from "./helper";
7 |
8 | function JsDiagramChart(data) {
9 | this.init = function(data) {
10 | let _this = this;
11 | _this.canvas = document.getElementById(data.config.element);
12 |
13 | if (_this.canvas) {
14 | _this.context = _this.canvas.getContext("2d");
15 | } else {
16 | throw "Element not found";
17 | }
18 |
19 | _this.context = this.canvas.getContext("2d");
20 | _this.context.lineWidth = data.config.lineWidth;
21 |
22 | if (data.config.autoSize) {
23 | this.canvas.width = this.canvas.parentNode.clientWidth;
24 | this.canvas.height = this.canvas.parentNode.clientHeight;
25 | }
26 |
27 | trackTransforms(this.context);
28 |
29 | data.config.scaleFactor = 1.1;
30 | data.config.zoomScale = 0;
31 | data.config.moveX = 0;
32 | data.config.moveY = 0;
33 |
34 | //this.data = data;
35 | this.config = data.config;
36 | this.update(data);
37 | this.draw();
38 |
39 | if (_this.config.mouseEvents) {
40 | this.events();
41 | }
42 | };
43 |
44 | this.addZoom = function(size) {
45 | let _this = this;
46 | _this.config.zoomScale += size;
47 | let pt = _this.context.transformedPoint(
48 | _this.canvas.width / 2,
49 | _this.canvas.height / 2
50 | );
51 | this.context.translate(pt.x, pt.y);
52 | let factor = Math.pow(_this.config.scaleFactor, size);
53 | _this.context.scale(factor, factor);
54 | _this.context.translate(-pt.x, -pt.y);
55 | this.draw();
56 | };
57 |
58 | this.resetZoom = function() {
59 | let _this = this;
60 | _this.addZoom(-_this.config.zoomScale);
61 | _this.context.translate(-_this.config.moveX, -_this.config.moveY);
62 | _this.config.zoomScale = 0;
63 | _this.config.moveX = 0;
64 | _this.config.moveY = 0;
65 | };
66 |
67 | this.events = function() {
68 | let _this = this;
69 |
70 | let lastX = _this.canvas.width / 2;
71 | let lastY = _this.canvas.height / 2;
72 | let dragStart = false;
73 | let dragged = false;
74 |
75 | _this.canvas.addEventListener(
76 | "mousedown",
77 | function(evt) {
78 | document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect =
79 | "none";
80 | _this.canvas.style.cursor = "move";
81 | lastX = evt.offsetX || evt.pageX - _this.canvas.offsetLeft;
82 | lastY = evt.offsetY || evt.pageY - _this.canvas.offsetTop;
83 | dragStart = _this.context.transformedPoint(lastX, lastY);
84 | dragged = false;
85 | evt.returnValue = false;
86 | },
87 | false
88 | );
89 |
90 | _this.canvas.addEventListener(
91 | "mousemove",
92 | function(evt) {
93 | lastX = evt.offsetX || evt.pageX - _this.canvas.offsetLeft;
94 | lastY = evt.offsetY || evt.pageY - _this.canvas.offsetTop;
95 |
96 | dragged = true;
97 | if (dragStart) {
98 | let pt = _this.context.transformedPoint(lastX, lastY);
99 | _this.context.translate(pt.x - dragStart.x, pt.y - dragStart.y);
100 | _this.draw();
101 | _this.config.moveX += pt.x - dragStart.x;
102 | _this.config.moveY += pt.y - dragStart.y;
103 | }
104 | },
105 | false
106 | );
107 |
108 | _this.canvas.addEventListener(
109 | "mouseup",
110 | function(evt) {
111 | dragStart = null;
112 | _this.canvas.style.cursor = "default";
113 | //if (!dragged) zoom(evt.shiftKey ? -1 : 1 );
114 | },
115 | false
116 | );
117 |
118 | let handleScroll = function(evt) {
119 | let delta = evt.wheelDelta
120 | ? evt.wheelDelta / 300
121 | : evt.detail
122 | ? -evt.detail
123 | : 0;
124 | _this.addZoom(delta);
125 | return evt.preventDefault() && false;
126 | };
127 |
128 | _this.canvas.addEventListener("DOMMouseScroll", handleScroll, false);
129 | _this.canvas.addEventListener("mousewheel", handleScroll, false);
130 | };
131 |
132 | this.update = function(data) {
133 | // if(data){
134 | // data = this.data;
135 | // }
136 |
137 | let _this = this;
138 | //_this.data = data;
139 |
140 | let diagramList = [];
141 | let diagramLinesCount = [];
142 | let diagramColumnsCount = 0;
143 | let diagramColumnLower = 0;
144 |
145 | for (let i = 0; i < data.diagrams.length; i++) {
146 | let objectDiagram = {
147 | id: data.diagrams[i].id,
148 | x: 0,
149 | y: 0,
150 | cx: 0,
151 | cy: 0,
152 | text: data.diagrams[i].text,
153 | color: data.diagrams[i].color,
154 | bgColor: data.diagrams[i].bgColor,
155 | parents: [],
156 | children: [],
157 | virgin: true,
158 | orphan: true
159 | };
160 |
161 | for (let j = 0; j < data.links.length; j++) {
162 | if (data.links[j].source == objectDiagram.id) {
163 | objectDiagram.parents.push(data.links[j].parent);
164 | objectDiagram.orphan = false;
165 | }
166 |
167 | if (data.links[j].parent == objectDiagram.id) {
168 | objectDiagram.children.push(data.links[j].source);
169 | objectDiagram.orphan = false;
170 | }
171 | }
172 |
173 | diagramList.push(objectDiagram);
174 | }
175 |
176 | let getLinesObjects = function getLinesObjects(object, hierarchy) {
177 | if (object.virgin) {
178 | object.virgin = false;
179 | object.y = hierarchy;
180 |
181 | if (hierarchy < diagramColumnLower) {
182 | diagramColumnLower = hierarchy;
183 | }
184 |
185 | for (let i = 0; i < object.children.length; i++) {
186 | let tmpObj = getObjById(diagramList, object.children[i]);
187 | getLinesObjects(tmpObj, hierarchy + 1);
188 | }
189 |
190 | for (let i = 0; i < object.parents.length; i++) {
191 | let tmpObj = getObjById(diagramList, object.parents[i]);
192 | getLinesObjects(tmpObj, hierarchy - 1);
193 | }
194 | }
195 | };
196 |
197 | for (let i = 0; i < diagramList.length; i++) {
198 | getLinesObjects(diagramList[i], 0);
199 | }
200 |
201 | for (let i = 0; i < diagramList.length; i++) {
202 | let countObjTmp = getObjById(diagramLinesCount, diagramList[i].y);
203 | if (countObjTmp != null) {
204 | if (!diagramList[i].orphan) {
205 | countObjTmp.count++;
206 | diagramList[i].x = countObjTmp.count;
207 | if (countObjTmp.count > diagramColumnsCount) {
208 | diagramColumnsCount = countObjTmp.count;
209 | }
210 | }
211 | } else {
212 | if (!diagramList[i].orphan) {
213 | diagramLinesCount.push({ id: diagramList[i].y, count: 1 });
214 | diagramList[i].x = 1;
215 | }
216 | }
217 | }
218 |
219 | this.config.linesCount = diagramLinesCount;
220 | this.config.columnsCount = diagramColumnsCount;
221 | this.config.columnLower = diagramColumnLower;
222 | this.diagrams = diagramList;
223 | };
224 |
225 | this.draw = function() {
226 | let _this = this;
227 |
228 | if (_this.context.transformedPoint) {
229 | let p1 = _this.context.transformedPoint(0, 0);
230 | let p2 = _this.context.transformedPoint(
231 | _this.canvas.width,
232 | _this.canvas.height
233 | );
234 | _this.context.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
235 | } else {
236 | _this.context.clearRect(0, 0, _this.canvas.width, _this.canvas.height);
237 | }
238 |
239 | let diagramWithMargin = _this.config.width + 2 * _this.config.margin;
240 |
241 | if (_this.config.columnsCount == 0) {
242 | _this.config.columnsCount = parseInt(
243 | _this.canvas.width / diagramWithMargin
244 | );
245 | }
246 |
247 | let maxWidth = _this.config.columnsCount * diagramWithMargin;
248 | let marginScreen = parseInt(_this.canvas.width) / 2 - maxWidth / 2;
249 |
250 | _this.context.rotate(2 * Math.PI);
251 |
252 | let countOrphanDemand = 0;
253 | for (let i = 0; i < _this.diagrams.length; i++) {
254 | if (_this.diagrams[i].orphan) {
255 | let positionX =
256 | marginScreen +
257 | _this.config.margin +
258 | parseInt(countOrphanDemand % _this.config.columnsCount) *
259 | (_this.config.width + 2 * _this.config.margin);
260 | let positionY =
261 | _this.config.margin +
262 | parseInt(countOrphanDemand / _this.config.columnsCount) *
263 | (_this.config.height + 2 * _this.config.margin);
264 | _this.context.fillStyle = _this.diagrams[i].bgColor;
265 | _this.context.strokeStyle = _this.diagrams[i].bgColor;
266 | roundRect(
267 | _this.context,
268 | positionX,
269 | positionY,
270 | _this.config.width,
271 | _this.config.height,
272 | _this.config.radius,
273 | _this.config.hiddenBg
274 | );
275 | //
276 | let nodeText = _this.context;
277 | nodeText.fillStyle = _this.diagrams[i].color;
278 | nodeText.font = _this.config.fontSize + "px " + _this.config.fontFamily;
279 | wrapText(
280 | _this.context,
281 | _this.diagrams[i].text,
282 | positionX + _this.config.padding,
283 | positionY,
284 | _this.config.width - _this.config.padding * 2,
285 | _this.config.height,
286 | _this.config.fontSize + _this.config.fontSize * 0.2
287 | );
288 |
289 | countOrphanDemand++;
290 | }
291 | }
292 |
293 | //CALCULATE X Y NOT ORPHANS DIAGRAMS
294 |
295 | let marginTopNotOrphans =
296 | _this.config.height +
297 | 3 * _this.config.margin +
298 | parseInt((countOrphanDemand - 1) / _this.config.columnsCount) *
299 | (_this.config.height + 2 * _this.config.margin);
300 |
301 | if (isNaN(marginTopNotOrphans)) {
302 | marginTopNotOrphans = _this.config.margin;
303 | }
304 |
305 | for (let i = 0; i < _this.diagrams.length; i++) {
306 | if (!_this.diagrams[i].orphan) {
307 | let maxColumnsInLine = getObjById(
308 | _this.config.linesCount,
309 | _this.diagrams[i].y
310 | ).count;
311 | let ratio = maxWidth / maxColumnsInLine;
312 | let positionX =
313 | marginScreen +
314 | ratio * _this.diagrams[i].x +
315 | (ratio / 2 - _this.config.width / 2) -
316 | ratio;
317 | let positionY =
318 | marginTopNotOrphans +
319 | Math.abs(_this.config.columnLower) *
320 | (_this.config.height + 2 * _this.config.margin) +
321 | _this.diagrams[i].y * (_this.config.height + 2 * _this.config.margin);
322 | _this.diagrams[i].left = positionX;
323 | _this.diagrams[i].top = positionY;
324 | }
325 | }
326 |
327 | //DRAW LINES
328 |
329 | for (let i = 0; i < _this.diagrams.length; i++) {
330 | if (!_this.diagrams[i].orphan) {
331 | for (let j = 0; j < _this.diagrams[i].parents.length; j++) {
332 | let tmpObj = getObjById(_this.diagrams, _this.diagrams[i].parents[j]);
333 | let invertParent = false;
334 | let isDistant = false;
335 | let autoRef = false;
336 | let countDistant = 0;
337 | let countInvertParent = 0;
338 |
339 | if (_this.diagrams[i].id == tmpObj.id) {
340 | autoRef = true;
341 | } else if (_this.diagrams[i].y < tmpObj.y) {
342 | invertParent = true;
343 | } else if (_this.diagrams[i].y - tmpObj.y > 1) {
344 | isDistant = true;
345 | }
346 |
347 | if (_this.context.setLineDash) {
348 | _this.context.setLineDash([_this.config.lineWidth, 0]);
349 | }
350 |
351 | _this.context.strokeStyle =
352 | _this.config.lineColor || _this.diagrams[i].bgColor;
353 | _this.context.fillStyle =
354 | _this.config.lineColor || _this.diagrams[i].bgColor;
355 |
356 | let difference = 0;
357 |
358 | if (autoRef) {
359 | difference =
360 | _this.config.margin / 2 -
361 | (_this.config.margin / _this.config.columnsCount) *
362 | _this.diagrams[i].x;
363 |
364 | if (!isFinite(difference)) {
365 | difference = 0;
366 | }
367 |
368 | _this.context.beginPath();
369 | _this.context.moveTo(
370 | tmpObj.left +
371 | _this.config.width / 2 +
372 | difference * -1 -
373 | difference,
374 | tmpObj.top + _this.config.height
375 | );
376 | _this.context.lineTo(
377 | tmpObj.left +
378 | _this.config.width / 2 +
379 | difference * -1 -
380 | difference,
381 | tmpObj.top + _this.config.height + _this.config.margin / 2.5
382 | );
383 | _this.context.lineTo(
384 | tmpObj.left +
385 | _this.config.width / 2 +
386 | difference * -1 +
387 | difference,
388 | tmpObj.top + _this.config.height + _this.config.margin / 2.5
389 | );
390 | _this.context.lineTo(
391 | tmpObj.left +
392 | _this.config.width / 2 +
393 | difference * -1 +
394 | difference,
395 | tmpObj.top + _this.config.height + _this.config.arrowWidth / 2
396 | );
397 | _this.context.stroke();
398 |
399 | _this.context.beginPath();
400 | _this.context.moveTo(
401 | tmpObj.left +
402 | _this.config.width / 2 +
403 | difference * -1 +
404 | difference,
405 | tmpObj.top + _this.config.height
406 | );
407 | _this.context.lineTo(
408 | tmpObj.left +
409 | _this.config.width / 2 -
410 | _this.config.arrowWidth / 2 +
411 | difference * -1 +
412 | difference,
413 | tmpObj.top + _this.config.height + _this.config.arrowWidth
414 | );
415 | _this.context.lineTo(
416 | tmpObj.left +
417 | _this.config.width / 2 +
418 | _this.config.arrowWidth / 2 +
419 | difference * -1 +
420 | difference,
421 | tmpObj.top + _this.config.height + _this.config.arrowWidth
422 | );
423 | _this.context.fill();
424 | } else if (invertParent) {
425 | if (_this.config.lineDiff) {
426 | difference =
427 | _this.config.margin / 2 -
428 | (_this.config.margin / _this.config.columnsCount) *
429 | _this.diagrams[i].x +
430 | (_this.config.margin -
431 | (_this.config.arrowWidth +
432 | _this.config.lineWidth * 2 * countInvertParent));
433 | }
434 |
435 | if (_this.context.setLineDash) {
436 | _this.context.setLineDash([
437 | _this.config.lineWidth,
438 | _this.config.lineWidth
439 | ]);
440 | }
441 |
442 | _this.context.beginPath();
443 | _this.context.moveTo(
444 | _this.diagrams[i].left + _this.config.width / 2 + difference,
445 | _this.diagrams[i].top + _this.config.height / 2 + difference
446 | );
447 | _this.context.lineTo(
448 | _this.diagrams[i].left +
449 | _this.config.width +
450 | _this.config.margin +
451 | difference,
452 | _this.diagrams[i].top + _this.config.height / 2 + difference
453 | );
454 | _this.context.lineTo(
455 | _this.diagrams[i].left +
456 | _this.config.width +
457 | _this.config.margin +
458 | difference,
459 | tmpObj.top - _this.config.margin + difference
460 | );
461 | _this.context.lineTo(
462 | tmpObj.left + _this.config.width / 2 + difference,
463 | tmpObj.top - _this.config.margin + difference
464 | );
465 | _this.context.lineTo(
466 | tmpObj.left + _this.config.width / 2 + difference,
467 | tmpObj.top - _this.config.arrowWidth + difference
468 | );
469 | _this.context.lineWidth = _this.config.lineWidth;
470 | _this.context.stroke();
471 |
472 | _this.context.beginPath();
473 | _this.context.moveTo(
474 | tmpObj.left + _this.config.width / 2 + difference,
475 | tmpObj.top
476 | );
477 | _this.context.lineTo(
478 | tmpObj.left +
479 | _this.config.width / 2 -
480 | _this.config.arrowWidth / 2 +
481 | difference,
482 | tmpObj.top - _this.config.arrowWidth
483 | );
484 | _this.context.lineTo(
485 | tmpObj.left +
486 | _this.config.width / 2 +
487 | _this.config.arrowWidth / 2 +
488 | difference,
489 | tmpObj.top - _this.config.arrowWidth
490 | );
491 | _this.context.fill();
492 | } else if (isDistant) {
493 | if (_this.config.lineDiff) {
494 | difference =
495 | _this.config.margin / 2 -
496 | (_this.config.margin / _this.config.columnsCount) *
497 | _this.diagrams[i].x +
498 | (_this.config.margin -
499 | (_this.config.arrowWidth +
500 | _this.config.lineWidth * 2 * countDistant));
501 | }
502 |
503 | countDistant++;
504 |
505 | if (!isFinite(difference)) {
506 | difference = 0;
507 | }
508 |
509 | if (_this.context.setLineDash) {
510 | _this.context.setLineDash([
511 | _this.config.lineWidth,
512 | _this.config.lineWidth
513 | ]);
514 | }
515 |
516 | _this.context.beginPath();
517 | _this.context.moveTo(
518 | _this.diagrams[i].left + _this.config.width / 2 + difference * -1,
519 | _this.diagrams[i].top
520 | );
521 | _this.context.lineTo(
522 | _this.diagrams[i].left + _this.config.width / 2 + difference * -1,
523 | _this.diagrams[i].top - _this.config.margin + difference
524 | );
525 | _this.context.lineTo(
526 | tmpObj.left + _this.config.width / 2 + difference * -1,
527 | _this.diagrams[i].top - _this.config.margin + difference
528 | );
529 | _this.context.lineTo(
530 | tmpObj.left + _this.config.width / 2 + difference * -1,
531 | tmpObj.top +
532 | _this.config.height +
533 | _this.config.margin +
534 | difference
535 | );
536 | _this.context.lineTo(
537 | tmpObj.left + _this.config.width / 2 + difference * -1,
538 | tmpObj.top + _this.config.height + _this.config.arrowWidth / 2
539 | );
540 | _this.context.lineWidth = _this.config.lineWidth;
541 | _this.context.stroke();
542 |
543 | _this.context.beginPath();
544 | _this.context.moveTo(
545 | tmpObj.left + _this.config.width / 2 + difference * -1,
546 | tmpObj.top + _this.config.height
547 | );
548 | _this.context.lineTo(
549 | tmpObj.left +
550 | _this.config.width / 2 -
551 | _this.config.arrowWidth / 2 +
552 | difference * -1,
553 | tmpObj.top + _this.config.height + _this.config.arrowWidth
554 | );
555 | _this.context.lineTo(
556 | tmpObj.left +
557 | _this.config.width / 2 +
558 | _this.config.arrowWidth / 2 +
559 | difference * -1,
560 | tmpObj.top + _this.config.height + _this.config.arrowWidth
561 | );
562 | _this.context.fill();
563 | } else {
564 | //var
565 | if (_this.config.lineDiff) {
566 | difference =
567 | _this.config.margin / 2 -
568 | (_this.config.margin / _this.config.columnsCount) *
569 | _this.diagrams[i].x;
570 | }
571 |
572 | if (!isFinite(difference)) {
573 | difference = 0;
574 | }
575 |
576 | _this.context.beginPath();
577 | _this.context.moveTo(
578 | _this.diagrams[i].left + _this.config.width / 2 + difference * -1,
579 | _this.diagrams[i].top
580 | );
581 | _this.context.lineTo(
582 | _this.diagrams[i].left + _this.config.width / 2 + difference * -1,
583 | _this.diagrams[i].top - _this.config.margin + difference
584 | );
585 | _this.context.lineTo(
586 | tmpObj.left + _this.config.width / 2 + difference * -1,
587 | tmpObj.top +
588 | _this.config.height +
589 | _this.config.margin +
590 | difference
591 | );
592 | _this.context.lineTo(
593 | tmpObj.left + _this.config.width / 2 + difference * -1,
594 | tmpObj.top + _this.config.height + _this.config.arrowWidth / 2
595 | );
596 | _this.context.lineWidth = _this.config.lineWidth;
597 | _this.context.stroke();
598 |
599 | _this.context.beginPath();
600 | _this.context.moveTo(
601 | tmpObj.left + _this.config.width / 2 + difference * -1,
602 | tmpObj.top + _this.config.height
603 | );
604 | _this.context.lineTo(
605 | tmpObj.left +
606 | _this.config.width / 2 -
607 | _this.config.arrowWidth / 2 +
608 | difference * -1,
609 | tmpObj.top + _this.config.height + _this.config.arrowWidth
610 | );
611 | _this.context.lineTo(
612 | tmpObj.left +
613 | _this.config.width / 2 +
614 | _this.config.arrowWidth / 2 +
615 | difference * -1,
616 | tmpObj.top + _this.config.height + _this.config.arrowWidth
617 | );
618 | _this.context.fill();
619 | }
620 | }
621 | }
622 | }
623 |
624 | //DRAW NOT ORPHANS DIAGRAMS
625 |
626 | for (let i = 0; i < _this.diagrams.length; i++) {
627 | if (!_this.diagrams[i].orphan) {
628 | let positionX = _this.diagrams[i].left;
629 | let positionY = _this.diagrams[i].top;
630 | _this.context.fillStyle = _this.diagrams[i].bgColor;
631 | _this.context.strokeStyle = _this.diagrams[i].bgColor;
632 | roundRect(
633 | _this.context,
634 | positionX,
635 | positionY,
636 | _this.config.width,
637 | _this.config.height,
638 | _this.config.radius,
639 | _this.config.hiddenBg
640 | );
641 |
642 | let nodeText = _this.context;
643 | nodeText.fillStyle = _this.diagrams[i].color;
644 | nodeText.font = _this.config.fontSize + "px " + _this.config.fontFamily;
645 | wrapText(
646 | _this.context,
647 | _this.diagrams[i].text,
648 | positionX + _this.config.padding,
649 | positionY,
650 | _this.config.width - _this.config.padding * 2,
651 | _this.config.height,
652 | _this.config.fontSize + _this.config.fontSize * 0.2
653 | );
654 | }
655 | }
656 | };
657 |
658 | this.init(data);
659 | }
660 |
661 | window.JsDiagramChart = JsDiagramChart;
662 |
663 | export default JsDiagramChart;
664 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const CleanWebpackPlugin = require('clean-webpack-plugin');
4 |
5 | const srcPath = './src';
6 |
7 | module.exports = {
8 | entry: srcPath + '/main.js',
9 | output: {
10 | filename: 'js-diagram-chart.js',
11 | path: path.resolve(__dirname, 'dist'),
12 | },
13 | devtool: 'inline-source-map',
14 | module: {
15 | rules: [
16 | {
17 | test: /\.m?js$/,
18 | exclude: /(node_modules|bower_components)/,
19 | use: {
20 | loader: 'babel-loader',
21 | options: {
22 | presets: ['@babel/preset-env']
23 | }
24 | }
25 | }
26 | ]
27 | },
28 | plugins: [
29 | new CleanWebpackPlugin(["dist"]),
30 | new webpack.ProgressPlugin(),
31 | ]
32 | };
--------------------------------------------------------------------------------