Completed
Pull Request — develop (#124)
by
unknown
01:11
created

forcegraph.js ➔ ... ➔ ???   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
nc 1
dl 0
loc 4
rs 10
nop 0
1
define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegraph/draw'],
2
  function (d3Selection, d3Force, d3Zoom, d3Drag, math, draw) {
3
    'use strict';
4
5
    return function (config, linkScale, sidebar, router) {
6
      var self = this;
7
      var el;
8
      var canvas;
9
      var ctx;
10
      var force;
11
      var forceLink;
12
      var animate = false;
13
14
      var transform = d3Zoom.zoomIdentity;
15
      var intNodes = [];
16
      var dictNodes = {};
17
      var intLinks = [];
18
19
      var NODE_RADIUS_DRAG = 10;
20
      var NODE_RADIUS_SELECT = 15;
21
      var LINK_RADIUS_SELECT = 12;
22
23
      var ZOOM_MIN = 1 / 8;
24
      var ZOOM_MAX = 3;
25
26
      var FORCE_ALPHA = 0.3;
27
28
      draw.setTransform(transform);
29
30
      function sleep(time) {
31
        return new Promise((resolve) => setTimeout(resolve, time));
32
      }
33
34
      function resizeCanvas() {
35
        canvas.width = el.offsetWidth;
36
        canvas.height = el.offsetHeight;
37
        draw.setMaxArea(canvas.width, canvas.height);
38
      }
39
40
      function moveTo(x, y) {
41
        transform.x = (canvas.width + sidebar()) / 2 - x * transform.k;
42
        transform.y = canvas.height / 2 - y * transform.k;
43
      }
44
45
      function onClick() {
46
        if (d3Selection.event.defaultPrevented) {
47
          return;
48
        }
49
50
        var e = transform.invert([d3Selection.event.clientX, d3Selection.event.clientY]);
51
        var n = force.find(e[0], e[1], NODE_RADIUS_SELECT);
52
53
        if (n !== undefined) {
54
          router.node(n.o.node)();
55
          return;
56
        }
57
58
        e = { x: e[0], y: e[1] };
59
60
61
        var closedLink;
62
        var radius = LINK_RADIUS_SELECT;
63
        intLinks
64
          .forEach(function (d) {
65
            var distance = math.distanceLink(e, d.source, d.target);
66
            if (distance < radius) {
67
              closedLink = d;
68
              radius = distance;
69
            }
70
          });
71
72
        if (closedLink !== undefined) {
73
          router.link(closedLink.o)();
74
        }
75
      }
76
77
      function redraw() {
78
        ctx.save();
79
        ctx.clearRect(0, 0, canvas.width, canvas.height);
80
        ctx.translate(transform.x, transform.y);
81
        ctx.scale(transform.k, transform.k);
82
83
        intLinks.forEach(draw.drawLink);
84
        intNodes.forEach(draw.drawNode);
85
86
        ctx.restore();
87
      }
88
89
      el = document.createElement('div');
90
      el.classList.add('graph');
91
92
      forceLink = d3Force.forceLink()
93
        .distance(function (d) {
94
          if (d.o.vpn) {
95
            return 0;
96
          }
97
          return 75;
98
        })
99
        .strength(function (d) {
100
          if (d.o.vpn) {
101
            return 0.02;
102
          }
103
          return Math.max(0.5, 1 / d.o.tq);
104
        });
105
106
      var zoom = d3Zoom.zoom()
107
        .scaleExtent([ZOOM_MIN, ZOOM_MAX])
108
        .on('zoom', function () {
109
          transform = d3Selection.event.transform;
110
          draw.setTransform(transform);
111
          redraw();
112
        });
113
114
      force = d3Force.forceSimulation()
115
        .force('link', forceLink)
116
        .force('charge', d3Force.forceManyBody())
117
        .force('x', d3Force.forceX().strength(0.02))
118
        .force('y', d3Force.forceY().strength(0.02))
119
        .force('collide', d3Force.forceCollide())
120
        .on('tick', redraw)
121
        .alphaDecay(0.01)
122
        .velocityDecay(0.1);
123
124
      var drag = d3Drag.drag()
125
        .subject(function () {
126
          var e = transform.invert([d3Selection.event.x, d3Selection.event.y]);
127
          var n = force.find(e[0], e[1], NODE_RADIUS_DRAG);
128
129
          if (n !== undefined) {
130
            n.x = d3Selection.event.x;
131
            n.y = d3Selection.event.y;
132
            return n;
133
          }
134
          return undefined;
135
        })
136
        .on('start', function () {
137
          if (!d3Selection.event.active) {
138
            force.alphaTarget(FORCE_ALPHA).restart();
139
          }
140
          d3Selection.event.subject.fx = transform.invertX(d3Selection.event.subject.x);
141
          d3Selection.event.subject.fy = transform.invertY(d3Selection.event.subject.y);
142
        })
143
        .on('drag', function () {
144
          d3Selection.event.subject.fx = transform.invertX(d3Selection.event.x);
145
          d3Selection.event.subject.fy = transform.invertY(d3Selection.event.y);
146
        })
147
        .on('end', function () {
148
          if (!d3Selection.event.active) {
149
            force.alphaTarget(0);
150
          }
151
          d3Selection.event.subject.fx = null;
152
          d3Selection.event.subject.fy = null;
153
        });
154
155
      canvas = d3Selection.select(el)
156
        .append('canvas')
157
        .on('click', onClick)
158
        .call(drag)
159
        .call(zoom)
160
        .node();
161
162
      ctx = canvas.getContext('2d');
163
      draw.setCTX(ctx);
164
165
      window.addEventListener('resize', function () {
166
        resizeCanvas();
167
        redraw();
168
      });
169
170
      self.setData = function setData(data) {
171
        intNodes = data.graph.nodes.map(function (d) {
172
          var e;
173
          if (d.id in dictNodes) {
174
            e = dictNodes[d.id];
175
          } else {
176
            e = {};
177
            dictNodes[d.id] = e;
178
          }
179
180
          e.o = d;
181
182
          return e;
183
        });
184
185
        intLinks = data.graph.links.map(function (d) {
186
          var e = {};
187
          e.o = d;
188
          e.source = dictNodes[d.source.id];
189
          e.target = dictNodes[d.target.id];
190
          e.color = linkScale(1 / d.tq);
191
192
          return e;
193
        });
194
195
        force.nodes(intNodes);
196
        forceLink.links(intLinks);
197
198
        force.alpha(1).restart();
199
        force.alphaTarget(1);
200
        resizeCanvas();
201
        sleep(1000).then(() => {
202
          force.alphaTarget(0);
203
          animate = true;
204
        });
205
      };
206
207
      self.resetView = function resetView() {
208
        draw.setHighlight(null);
209
        transform.k = (ZOOM_MIN + 1) / 2;
210
        moveTo(0, 0);
211
        redraw();
212
      };
213
214
      self.gotoNode = function gotoNode(d) {
215
        function getNode() {
216
          for (var i = 0; i < intNodes.length; i++) {
217
            var n = intNodes[i];
218
            if (n.o.node.nodeinfo.node_id !== d.nodeinfo.node_id) {
219
              continue;
220
            }
221
            draw.setHighlight({ type: 'node', o: n.o.node });
222
            transform.k = (ZOOM_MAX + 1) / 2;
223
            moveTo(n.x, n.y);
224
            break;
225
          }
226
          redraw();
227
        }
228
        if (!animate) {
229
          sleep(1500).then(getNode);
230
        } else {
231
          getNode();
232
        }
233
      };
234
235
      self.gotoLink = function gotoLink(d) {
236
        function getLink() {
237
          draw.setHighlight({ type: 'link', o: d });
238
          for (var i = 0; i < intLinks.length; i++) {
239
            var l = intLinks[i];
240
            if (l.o !== d) {
241
              continue;
242
            }
243
            moveTo((l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2);
244
            break;
245
          }
246
          redraw();
247
        }
248
        if (!animate) {
249
          sleep(1500).then(getLink);
250
        } else {
251
          getLink();
252
        }
253
      };
254
255
      self.destroy = function destroy() {
256
        force.stop();
257
        canvas.remove();
258
        force = null;
259
260
        if (el.parentNode) {
261
          el.parentNode.removeChild(el);
262
        }
263
      };
264
265
      self.render = function render(d) {
266
        d.appendChild(el);
267
        resizeCanvas();
268
      };
269
270
      return self;
271
    };
272
  });
273