Completed
Pull Request — develop (#75)
by
unknown
01:05
created

forcegraph.js ➔ ... ➔ d3.distance   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
nc 2
dl 0
loc 6
rs 9.4285
nop 1
1
define(['d3', 'helper'], function (d3, helper) {
2
  'use strict';
3
4
  return function (config, linkScale, sidebar, router) {
5
    var self = this;
6
    var el;
7
    var canvas;
8
    var ctx;
9
    var force;
10
    var forceLink;
11
12
    var scale = 1;
13
    var translateX = 0;
14
    var translateY = 0;
15
16
    var intNodes = [];
17
    var dictNodes = {};
18
    var intLinks = [];
19
20
    var highlight;
21
22
    const nonUplinkColor = '#F2E3C6';
23
    const locationColor = '#00ff00';
24
    const noLocationColor = '#0000ff';
25
    const clientColor = 'rgba(230, 50, 75, 1.0)';
26
    const cableColor = '#50B0F0';
27
    const highlightColor = 'rgba(255, 255, 255, 0.2)';
28
29
    const NODE_RADIUS = 15;
30
    const LINE_RADIUS = 12;
31
32
    function resizeCanvas() {
33
      var r = window.devicePixelRatio;
34
      canvas.width = el.offsetWidth * r;
35
      canvas.height = el.offsetHeight * r;
36
      canvas.style.width = el.offsetWidth + 'px';
37
      canvas.style.height = el.offsetHeight + 'px';
38
      ctx.setTransform(1, 0, 0, 1, 0, 0);
39
      ctx.scale(r, r);
40
    }
41
42
    function onClick() {
43
      if (d3.event.defaultPrevented) {
44
        return;
45
      }
46
47
      var e = translateXY(d3.event);
48
49
      var n = intNodes.filter(function (d) {
50
        return distancePoint(e, d) < NODE_RADIUS;
51
      });
52
53
      if (n.length > 0) {
54
        router.node(n[0].o.node)();
55
        return;
56
      }
57
58
      var links = intLinks
59
        /* Disable Clickable VPN
60
        .filter(function (d) {
61
          return d.o.type !== 'fastd' && d.o.type !== 'L2TP';
62
        })
63
        */
64
        .filter(function (d) {
65
          return distanceLink(e, d.source, d.target) < LINE_RADIUS;
66
        });
67
68
      if (links.length > 0) {
69
        router.link(links[0].o)();
70
      }
71
    }
72
73
    function translateXY(d) {
74
      return {
75
        x: (d.x - translateX) / scale,
76
        y: (d.y - translateY) / scale
77
      };
78
    }
79
80
    function distance(a, b) {
81
      return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
82
    }
83
84
    function distancePoint(a, b) {
85
      return Math.sqrt(distance(a, b));
86
    }
87
88
    function distanceLink(p, a, b) {
89
      /* http://stackoverflow.com/questions/849211 */
90
      var l2 = distance(a, b);
91
      if (l2 === 0) {
92
        return distance(p, a);
93
      }
94
      var t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;
95
      if (t < 0) {
96
        return distance(p, a);
97
      }
98
      if (t > 1) {
99
        return distance(p, b);
100
      }
101
      return distancePoint(p, {
102
        x: a.x + t * (b.x - a.x),
103
        y: a.y + t * (b.y - a.y)
104
      });
105
    }
106
107
    function drawLink(d) {
108
      ctx.beginPath();
109
      ctx.moveTo(d.source.x, d.source.y);
110
      var to = [d.target.x, d.target.y];
111
      if (highlight && highlight.type === 'link' && d.o === highlight.o) {
112
        ctx.lineTo(to[0], to[1]);
113
        ctx.strokeStyle = highlightColor;
114
        ctx.lineWidth = LINE_RADIUS * 2;
115
        ctx.lineCap = 'round';
116
        ctx.stroke();
117
        to = [d.source.x, d.source.y];
118
      }
119
      ctx.lineTo(to[0], to[1]);
120
      ctx.strokeStyle = d.o.type === 'Kabel' ? cableColor : d.color;
121
      if (d.o.type === 'fastd' || d.o.type === 'L2TP') {
122
        ctx.globalAlpha = 0.2;
123
        ctx.lineWidth = 1.5;
124
      } else {
125
        ctx.globalAlpha = 0.8;
126
        ctx.lineWidth = 2.5;
127
      }
128
      ctx.stroke();
129
    }
130
131
    function drawNode(d) {
132
      ctx.beginPath();
133
      if (highlight && highlight.type === 'node' && d.o.node === highlight.o) {
134
        ctx.arc(d.x, d.y, NODE_RADIUS * 1.5, 0, 2 * Math.PI);
135
        ctx.fillStyle = highlightColor;
136
        ctx.fill();
137
        ctx.beginPath();
138
      }
139
      ctx.moveTo(d.x + 3, d.y);
140
      ctx.arc(d.x, d.y, 6, 0, 2 * Math.PI);
141
      ctx.fillStyle = noLocationColor;
142
      if (d.o.node && d.o.node.nodeinfo && d.o.node.nodeinfo.location) {
143
        ctx.fillStyle = locationColor;
144
      }
145
      ctx.strokeStyle = nonUplinkColor;
146
      ctx.lineWidth = 5;
147
      ctx.globalAlpha = 1;
148
      ctx.fill();
149
      ctx.stroke();
150
      if (scale > 1) {
151
        ctx.beginPath();
152
        helper.positionClients(ctx, d, Math.PI, d.o.node.statistics.clients, 15);
153
        ctx.fillStyle = clientColor;
154
        ctx.fill();
155
        ctx.beginPath();
156
        var name = d.o.node_id;
157
        if (d.o.node && d.o.node.nodeinfo) {
158
          name = d.o.node.nodeinfo.hostname;
159
        }
160
        ctx.textAlign = 'center';
161
        ctx.fillStyle = '#fff';
162
        ctx.fillText(name, d.x, d.y + 20);
163
      }
164
    }
165
166
    function redraw() {
167
      ctx.save();
168
      ctx.clearRect(0, 0, canvas.width, canvas.height);
169
      ctx.translate(translateX, translateY);
170
      ctx.scale(scale, scale);
171
172
      intLinks.forEach(drawLink);
173
      intNodes.forEach(drawNode);
174
175
      ctx.restore();
176
    }
177
178
    el = document.createElement('div');
179
    el.classList.add('graph');
180
181
    forceLink = d3.forceLink()
182
     .distance(function (d) {
183
       if (d.o.type === 'fastd' || d.o.type === 'L2TP') {
184
         return 0;
185
       }
186
       return 75;
187
     })
188
     .strength(function (d) {
189
       if (d.o.type === 'fastd' || d.o.type === 'L2TP') {
190
         return 0.02;
191
       }
192
       return Math.max(0.5, 1 / d.o.tq);
193
     });
194
195
    var zoom = d3.zoom()
196
         .scaleExtent([1 / 3, 3])
197
         .on('zoom', function () {
198
           scale = d3.event.transform.k;
199
           translateX = d3.event.transform.x;
200
           translateY = d3.event.transform.y;
201
           redraw();
202
         });
203
204
205
    force = d3.forceSimulation()
206
      .force('link', forceLink)
207
      .force('charge', d3.forceManyBody())
208
      .on('tick', redraw);
209
210
    var drag = d3.drag()
211
      .subject(function () {
212
        if (!d3.event.active) {
213
          force.alphaTarget(0.1).restart();
214
        }
215
        var mouse = translateXY(d3.event);
216
        var n = intNodes.filter(function (node) {
217
          return distancePoint(mouse, node) < NODE_RADIUS;
218
        });
219
220
        if (n.length > 0) {
221
          return n[0];
222
        }
223
        return undefined;
224
      })
225
      .on('drag', function () {
226
        d3.event.subject.x = d3.event.x;
227
        d3.event.subject.y = d3.event.y;
228
      })
229
      .on('end', function () {
230
        if (!d3.event.active) {
231
          force.alphaTarget(0);
232
        }
233
      });
234
235
    canvas = d3.select(el)
236
      .append('canvas')
237
      .on('click', onClick)
238
      .call(drag)
239
      .call(zoom)
240
      .node();
241
242
    ctx = canvas.getContext('2d');
243
244
    window.addEventListener('resize', function () {
245
      resizeCanvas();
246
      redraw();
247
    });
248
249
    self.setData = function setData(data) {
250
      intNodes = data.graph.nodes.map(function (d) {
251
        var e;
252
        if (d.id in dictNodes) {
253
          e = dictNodes[d.id];
254
        } else {
255
          e = {};
256
          dictNodes[d.id] = e;
257
        }
258
259
        e.o = d;
260
261
        return e;
262
      });
263
264
      intLinks = data.graph.links.map(function (d) {
265
        var e = {};
266
        e.o = d;
267
        e.source = dictNodes[d.source.id];
268
        e.target = dictNodes[d.target.id];
269
        e.color = linkScale(d.tq).hex();
270
271
        return e;
272
      });
273
274
      force.nodes(intNodes);
275
      forceLink.links(intLinks);
276
277
      force.restart();
278
      resizeCanvas();
279
    };
280
281
    self.resetView = function resetView() {
282
      highlight = null;
283
      redraw();
284
    };
285
286
    self.gotoNode = function gotoNode(d) {
287
      highlight = { type: 'node', o: d };
288
      redraw();
289
    };
290
291
    self.gotoLink = function gotoLink(d) {
292
      highlight = { type: 'link', o: d };
293
      redraw();
294
    };
295
296
    self.destroy = function destroy() {
297
      force.stop();
298
      canvas.remove();
299
      force = null;
300
301
      if (el.parentNode) {
302
        el.parentNode.removeChild(el);
303
      }
304
    };
305
306
    self.render = function render(d) {
307
      d.appendChild(el);
308
      resizeCanvas();
309
    };
310
311
    return self;
312
  };
313
});
314