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

forcegraph.js ➔ ... ➔ d3.drag.drag   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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