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

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