cpp_dependency_graph 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +3 -0
  4. data/.vscode/launch.json +79 -0
  5. data/.vscode/tasks.json +12 -0
  6. data/CODE_OF_CONDUCT.md +46 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE +21 -0
  9. data/README.md +84 -0
  10. data/Rakefile +9 -0
  11. data/TODO.md +38 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/cpp_dependency_graph.gemspec +44 -0
  15. data/examples/leveldb_overall.png +0 -0
  16. data/examples/leveldb_overall.svg +215 -0
  17. data/examples/leveldb_overall_d3.png +0 -0
  18. data/examples/leveldb_overall_d3.svg +1 -0
  19. data/examples/rethinkdb_queue_component.png +0 -0
  20. data/examples/rethinkdb_queue_component.svg +234 -0
  21. data/examples/rethinkdb_queue_component_d3.png +0 -0
  22. data/examples/rethinkdb_queue_component_d3.svg +1 -0
  23. data/examples/rethinkdb_queue_include.png +0 -0
  24. data/examples/rethinkdb_queue_include.svg +211 -0
  25. data/examples/rethinkdb_queue_include_d3.png +0 -0
  26. data/examples/rethinkdb_queue_include_d3.svg +1 -0
  27. data/examples/rocksdb_overall_d3.svg +1 -0
  28. data/examples/rocksdb_table_component.png +0 -0
  29. data/exe/cpp_dependency_graph +87 -0
  30. data/lib/cpp_dependency_graph/bidirectional_hash.rb +30 -0
  31. data/lib/cpp_dependency_graph/component_link.rb +37 -0
  32. data/lib/cpp_dependency_graph/cycle_detector.rb +25 -0
  33. data/lib/cpp_dependency_graph/cyclic_link.rb +30 -0
  34. data/lib/cpp_dependency_graph/dependency_graph.rb +49 -0
  35. data/lib/cpp_dependency_graph/graph_to_html_converter.rb +7 -0
  36. data/lib/cpp_dependency_graph/graph_visualiser.rb +59 -0
  37. data/lib/cpp_dependency_graph/include_dependency_graph.rb +24 -0
  38. data/lib/cpp_dependency_graph/project.rb +66 -0
  39. data/lib/cpp_dependency_graph/source_component.rb +37 -0
  40. data/lib/cpp_dependency_graph/source_file.rb +51 -0
  41. data/lib/cpp_dependency_graph/tsortable_hash.rb +12 -0
  42. data/lib/cpp_dependency_graph/version.rb +5 -0
  43. data/lib/cpp_dependency_graph.rb +48 -0
  44. data/views/index.html.template +990 -0
  45. metadata +258 -0
@@ -0,0 +1,990 @@
1
+ <!DOCTYPE html>
2
+ <meta charset="utf-8">
3
+
4
+ <head>
5
+ <style>
6
+ .node.filtered {
7
+ fill-opacity: 0.3;
8
+ stroke-opacity: 0.3;
9
+ }
10
+
11
+ .structNode.filtered {
12
+ fill-opacity: 0.3;
13
+ stroke-opacity: 0.3;
14
+ }
15
+
16
+ text.filtered {
17
+ fill-opacity: 0;
18
+ stroke-opacity: 0;
19
+ }
20
+
21
+ .link.filtered {
22
+ stroke: #ddd;
23
+ fill-opacity: 0.1;
24
+ stroke-opacity: 0.1;
25
+ }
26
+
27
+ .link.dependency {
28
+ stroke: #900;
29
+ fill: #900;
30
+ pointer-events: none;
31
+ }
32
+
33
+ .link.dependants {
34
+ stroke: #090;
35
+ fill: #090;
36
+ pointer-events: none;
37
+ }
38
+
39
+ .node.skipped {
40
+ fill-opacity: 0.0;
41
+ stroke-opacity: 0.0;
42
+ }
43
+
44
+ text.skipped {
45
+ fill-opacity: 0;
46
+ stroke-opacity: 0;
47
+ }
48
+
49
+ .structNode.skipped {
50
+ fill-opacity: 0.0;
51
+ stroke-opacity: 0.0;
52
+ }
53
+
54
+
55
+ .link.skipped {
56
+ stroke: #ddd;
57
+ fill-opacity: 0.0;
58
+ stroke-opacity: 0.0;
59
+ }
60
+
61
+ .node {
62
+ stroke: #000;
63
+ stroke-width: 0.5px;
64
+ }
65
+
66
+ .structNode {
67
+ stroke: #000;
68
+ stroke-width: 0.5px;
69
+ }
70
+
71
+ .link {
72
+ stroke: #999;
73
+ stroke-opacity: .6;
74
+ fill: none;
75
+ stroke-width: 1.5px;
76
+ pointer-events: none;
77
+ }
78
+
79
+ .marker#default {
80
+ stroke: #999;
81
+ fill: #999;
82
+ pointer-events: none;
83
+ }
84
+
85
+ .marker#dependency {
86
+ stroke: #900;
87
+ fill: #900;
88
+ pointer-events: none;
89
+ }
90
+
91
+ .marker#dependants {
92
+ stroke: #090;
93
+ fill: #090;
94
+ pointer-events: none;
95
+ }
96
+
97
+ body {
98
+ margin: 0px;
99
+ padding: 0px;
100
+ }
101
+
102
+ html {
103
+ overflow: hidden;
104
+ }
105
+
106
+ text {
107
+ stroke: #000;
108
+ stroke-width: 0.5px;
109
+ text-anchor: middle;
110
+ font: 10px sans-serif;
111
+ font-weight: normal;
112
+ font-style: normal;
113
+ pointer-events: none;
114
+ }
115
+
116
+ svg text {
117
+ -webkit-user-select: none;
118
+ -moz-user-select: none;
119
+ -ms-user-select: none;
120
+ user-select: none;
121
+ cursor: default;
122
+ }
123
+
124
+ svg text::selection {
125
+ background: none;
126
+ }
127
+
128
+ form {
129
+ position: absolute;
130
+ right: 10px;
131
+ top: 60px;
132
+ }
133
+
134
+ #simple-menu {
135
+ position: absolute;
136
+ right: 10px;
137
+ top: 10px;
138
+ }
139
+ </style>
140
+ </head>
141
+
142
+ <body>
143
+
144
+ <script type="text/javascript" src="https://unpkg.com/jquery@3.3.1/dist/jquery.min.js"></script>
145
+ <script type="text/javascript" src="https://unpkg.com/underscore@1.8.3/underscore-min.js"></script>
146
+ <script type="text/javascript" src="https://unpkg.com/d3@4.7.4/build/d3.min.js"></script>
147
+ <script type="text/javascript" src="https://cdn.rawgit.com/edeno/d3-save-svg/gh-pages/assets/d3-save-svg.min.js"></script>
148
+
149
+ <!-- ================================================= -->
150
+ <!-- ===========ACTUAL HTML ================ -->
151
+ <!-- ================================================= -->
152
+
153
+ <form id="form">
154
+ <label>
155
+ <input type="range" name="circle_size" min="1" max="50" value="15" /> Circle size</label>
156
+ <br>
157
+ <label>
158
+ <input type="range" name="charge_multiplier" min="1" max="500" value="100" /> Charge multiplier</label>
159
+ <br>
160
+ <label>
161
+ <input type="range" name="link_strength" min="0.1" max="100" value="7" /> Link strength</label>
162
+ <br>
163
+ <label>
164
+ <input type="checkbox" name="show_texts_near_circles" /> Show names</label>
165
+ <br>
166
+ <input id="search_input" placeholder="Type regexp to filter nodes" style="width:100%%;">
167
+ <br>
168
+ </form>
169
+
170
+ <button id="export">Export as SVG</button>
171
+
172
+ <div id="chart">
173
+ <!-- Here the SVG will be placed-->
174
+ </div>
175
+
176
+ <script type='text/javascript'>
177
+ var dependencies = {
178
+ links: %{dependency_links},
179
+ objects: { }
180
+ };
181
+ </script>
182
+ <script type="text/javascript">
183
+ // ===================================================
184
+ // =============== SELECTING_NODE =============
185
+ // ===================================================
186
+
187
+ let graph_actions = {
188
+ create: function (svg, dvgraph) {
189
+
190
+ return {
191
+ selectedIdx: -1,
192
+ selectedType: "normal",
193
+ svg: svg,
194
+ selectedObject: {},
195
+ dvgraph: dvgraph,
196
+
197
+ deselect_node: function (d) {
198
+ this._unlockNode(d);
199
+ this.selectedIdx = -1;
200
+ this.selectedObject = {};
201
+
202
+ this.svg.selectAll('.node, .structNode')
203
+ .each(function (node) {
204
+ node.filtered = false
205
+ })
206
+ .classed('filtered', false)
207
+ .transition();
208
+
209
+ this.svg.selectAll('path, text')
210
+ .classed('filtered', false)
211
+ .transition();
212
+
213
+
214
+ this.svg.selectAll('.link')
215
+ .attr("marker-end", "url(#default)")
216
+ .classed('filtered', false)
217
+ .classed('dependency', false)
218
+ .classed('dependants', false)
219
+ .transition();
220
+ },
221
+
222
+ deselect_selected_node: function () {
223
+ this.deselect_node(this.selectedObject)
224
+ },
225
+
226
+ _lockNode: function (node) {
227
+ node.fixed = true;
228
+ node.fx = node.x;
229
+ node.fy = node.y;
230
+ },
231
+
232
+ _unlockNode: function (node) {
233
+ delete node.fixed;
234
+ node.fx = null;
235
+ node.fy = null;
236
+ },
237
+
238
+ _selectAndLockNode: function (node, type) {
239
+ this._unlockNode(this.selectedObject);
240
+ this.selectedIdx = node.idx;
241
+ this.selectedObject = node;
242
+ this.selectedType = type;
243
+ this._lockNode(this.selectedObject);
244
+ },
245
+
246
+ _deselectNodeIfNeeded: function (node, type) {
247
+ if (node.idx === this.selectedIdx && this.selectedType === type) {
248
+ this.deselect_node(node);
249
+ return true;
250
+ }
251
+ return false;
252
+ },
253
+
254
+ _fadeOutAllNodesAndLinks: function () {
255
+ // Fade out all circles
256
+ this.svg.selectAll('.node, .structNode')
257
+ .classed('filtered', true)
258
+ .each(function (node) {
259
+ node.filtered = true;
260
+ node.neighbours = false;
261
+ }).transition();
262
+
263
+ this.svg.selectAll('text')
264
+ .classed('filtered', true)
265
+ .transition();
266
+
267
+ this.svg.selectAll('.link')
268
+ .classed('dependency', false)
269
+ .classed('dependants', false)
270
+ .transition()
271
+ .attr("marker-end", "");
272
+
273
+ },
274
+
275
+ _highlightNodesWithIndexes: function (indexesArray) {
276
+ this.svg.selectAll('.node, .structNode, text')
277
+ .filter((node) => indexesArray.indexOf(node.index) > -1)
278
+ .classed('filtered', false)
279
+ .each((node) => {
280
+ node.filtered = false;
281
+ node.neighbours = true;
282
+ })
283
+ .transition();
284
+ },
285
+
286
+ _isDependencyLink: (node, link) => (link.source.index === node.index),
287
+ _nodeExistsInLink: (node, link) => (link.source.index === node.index || link.target.index === node.index),
288
+ _oppositeNodeOfLink: (node, link) => (link.source.index === node.index ? link.target : link.target.index === node.index ? link.source : null),
289
+
290
+ _highlightLinksFromRootWithNodesIndexes: function (root, nodeNeighbors, maxLevel) {
291
+ this.svg.selectAll('.link')
292
+ .filter((link) => nodeNeighbors.indexOf(link.source.index) > -1)
293
+ .classed('filtered', false)
294
+ .classed('dependency', (l) => this._nodeExistsInLink(root, l) && this._isDependencyLink(root, l))
295
+ .classed('dependants', (l) => this._nodeExistsInLink(root, l) && !this._isDependencyLink(root, l))
296
+ .attr("marker-end", (l) => this._nodeExistsInLink(root, l) ? (this._isDependencyLink(root, l) ? "url(#dependency)" : "url(#dependants)") : (maxLevel == 1 ? "" : "url(#default)"))
297
+ .transition();
298
+ },
299
+
300
+ selectNodesStartingFromNode: function (node, maxLevel = 100) {
301
+ if (this._deselectNodeIfNeeded(node, "level" + maxLevel)) {
302
+ return
303
+ }
304
+ this._selectAndLockNode(node, "level" + maxLevel);
305
+
306
+ let neighborIndexes =
307
+ this.dvgraph.nodesStartingFromNode(node, { max_level: maxLevel, use_backward_search: maxLevel == 1 })
308
+ .map((n) => n.index);
309
+
310
+ this._fadeOutAllNodesAndLinks();
311
+ this._highlightNodesWithIndexes(neighborIndexes);
312
+ this._highlightLinksFromRootWithNodesIndexes(node, neighborIndexes, maxLevel);
313
+ }
314
+
315
+ };
316
+ }
317
+ };
318
+
319
+
320
+ </script>
321
+ <script type="text/javascript">
322
+ // ===================================================
323
+ // =============== PARSING ===========================
324
+ // ===================================================
325
+ // Input
326
+ // { links : [ {source: sourceName, dest : destName} * ] }
327
+ // Output:
328
+ let objcdv = {
329
+ version: "0.0.1",
330
+ _createGraph: function (_objects) {
331
+ return {
332
+ nodes: [],
333
+ links: [],
334
+ nodesSet: {},
335
+ objects: setDefaultValue(_objects, []),
336
+
337
+ addLink: function (link) {
338
+
339
+ var source_node = this.getNode(link.source);
340
+ source_node.source++;
341
+
342
+ var dest_node = this.getNode(link.dest);
343
+ dest_node.dest++;
344
+
345
+ this.links.push({
346
+ // d3 js properties
347
+ source: source_node.idx,
348
+ target: dest_node.idx,
349
+
350
+ // Additional link information
351
+ sourceNode: source_node,
352
+ targetNode: dest_node
353
+ })
354
+ },
355
+
356
+ getNode: function (nodeName) {
357
+ var node = this.nodesSet[nodeName];
358
+ if (node == null) {
359
+ var idx = Object.keys(this.nodesSet).length;
360
+ let object = setDefaultValue(this.objects[nodeName], {})
361
+ this.nodesSet[nodeName] = node = { idx: idx, name: nodeName, source: 1, dest: 0, type: object.type };
362
+ }
363
+ return node
364
+ },
365
+
366
+ updateNodes: function (f) {
367
+ _.values(this.nodesSet).forEach(f)
368
+ },
369
+
370
+ d3jsGraph: function () {
371
+ // Sorting up nodes, since, in some cases they aren't returned in correct number
372
+ var nodes = _.values(this.nodesSet).slice(0).sort((a, b) => a.idx - b.idx);
373
+ return { nodes: nodes, links: this.links };
374
+ },
375
+
376
+ nodesStartingFromNode: function (node, { max_level = 100, use_backward_search = false, use_forward_search = true } = {}) {
377
+ // Figure out the neighboring node id's with brute strength because the graph is small
378
+ var neighbours = {};
379
+ neighbours[node.index] = node;
380
+
381
+ var nodesToCheck = [node.index];
382
+ let current_level = 0;
383
+ while (Object.keys(nodesToCheck).length != 0) {
384
+ var forwardNeighbours = [];
385
+ var backwardNeighbours = [];
386
+
387
+ let tmpNeighbours = {};
388
+ if (use_forward_search) {
389
+ forwardNeighbours = this.links
390
+ .filter((link) => link.source.index in neighbours)
391
+ .filter((link) => !(link.target.index in neighbours))
392
+ .map((link) => {
393
+ tmpNeighbours[link.target.index] = link.target;
394
+ return link.target.index;
395
+ });
396
+ }
397
+ if (use_backward_search) {
398
+ backwardNeighbours = this.links
399
+ .filter((link) => link.target.index in neighbours)
400
+ .filter((link) => !(link.source.index in neighbours))
401
+ .map((link) => {
402
+ tmpNeighbours[link.source.index] = link.source;
403
+ return link.source.index;
404
+ });
405
+ }
406
+
407
+ _.extend(neighbours, tmpNeighbours);
408
+
409
+
410
+ nodesToCheck = forwardNeighbours.concat(backwardNeighbours);
411
+ console.log("Nodes to check" + nodesToCheck);
412
+
413
+ // Skip if we reached max level
414
+ current_level++;
415
+ if (current_level == max_level) {
416
+ console.log("Reached max at level" + current_level);
417
+ break;
418
+ }
419
+ }
420
+ return _.values(neighbours);
421
+
422
+ }
423
+
424
+ };
425
+
426
+ },
427
+ _createPrefixes: function () {
428
+ return {
429
+ _prefixesDistr: {},
430
+
431
+ _sortedPrefixes: null,
432
+
433
+ addName: function (name) {
434
+ this._sortedPrefixes = null;
435
+
436
+ var prefix = name.substring(0, 2);
437
+ if (!(prefix in this._prefixesDistr)) {
438
+ this._prefixesDistr[prefix] = 1;
439
+ } else {
440
+ this._prefixesDistr[prefix]++;
441
+ }
442
+ },
443
+
444
+ prefixIndexForName: function (name) {
445
+ var sortedPrefixes = this._getSortedPrefixes();
446
+ var prefix = name.substring(0, 2);
447
+ return _.indexOf(sortedPrefixes, prefix)
448
+ },
449
+
450
+ _getSortedPrefixes: function () {
451
+ if (this._sortedPrefixes == null) {
452
+ this._sortedPrefixes = _.map(this._prefixesDistr, (v, k) => ({ "key": k, "value": v }))
453
+ .sort((a, b) => b.value - a.value)
454
+ .map(o => o.key)
455
+ }
456
+ return this._sortedPrefixes
457
+ }
458
+ };
459
+ },
460
+
461
+
462
+ parse_dependencies_graph: function (dependencies) {
463
+
464
+ var graph = this._createGraph(dependencies.objects);
465
+ var prefixes = this._createPrefixes();
466
+
467
+ dependencies.links
468
+ .filter(link => link.source != link.dest)
469
+ .forEach(link => {
470
+ graph.addLink(link);
471
+
472
+ prefixes.addName(link.source);
473
+ prefixes.addName(link.dest);
474
+ });
475
+
476
+ // Make sure all nodes are present, even if they aren't connected
477
+ if (dependencies.objects != null) {
478
+ for (p in dependencies.objects) {
479
+ graph.getNode(p)
480
+ }
481
+ }
482
+
483
+ graph.updateNodes((node) => {
484
+ node.weight = node.source;
485
+ node.group = prefixes.prefixIndexForName(node.name) + 1
486
+ });
487
+
488
+ return graph
489
+
490
+ }
491
+
492
+ };
493
+
494
+
495
+ function setDefaultValue(value, defaultValue) {
496
+ return (value === undefined) ? defaultValue : value;
497
+ }
498
+
499
+ </script>
500
+ <script type="text/javascript">
501
+ let dvconfig = {
502
+ create: function () {
503
+ return {
504
+ default_link_distance: 10,
505
+
506
+ // How far can we change default_link_distance?
507
+ // 0 - I don't care
508
+ // 0.5 - Change it as you want, but it's preferrable to have default_link_distance
509
+ // 1 - One does not change default_link_distance
510
+ default_link_strength: 0.7,
511
+
512
+ // Should I comment this?
513
+ default_circle_radius: 15,
514
+
515
+ // you can set it to true, but this will not help to understanf what's going on
516
+ show_texts_near_circles: false,
517
+
518
+ default_max_texts_length: 100,
519
+
520
+ charge_multiplier: 200
521
+ }
522
+ }
523
+ };
524
+
525
+ </script>
526
+ <script type="text/javascript">
527
+ let dvvisualizer = {
528
+ version: "0.0.1",
529
+
530
+ create: function (_svg, _config, _d3graph) {
531
+ var visualizer = {};
532
+ Object.assign(visualizer, {
533
+ config: _config,
534
+ svg: _svg,
535
+ d3graph: _d3graph,
536
+ simulation: null,
537
+ color: null,
538
+
539
+ _link: null, // d3 selection of all links
540
+ _node: null, // d3 selection of all nodes (non-struct)
541
+ _textNode: null, // d3 selection of all text nodes
542
+ _structNode: null, // d3 selection of all struct nodes
543
+ objectNodes: null, // d3 selection for struct and other nodes
544
+ allNodes: null, // d3 selection for all Possible nodes
545
+
546
+ updateMarkers: function (size) {
547
+ function viewBox(x, y, w, h) { return [x + "", y + "", w + "", h + ""].join(" ") }
548
+ function moveTo(x, y) { return "M" + x + "," + y }
549
+ function lineTo(x, y) { return "L" + x + "," + y }
550
+
551
+ function arrow(size) {
552
+ return [
553
+ moveTo(0, -size),
554
+ lineTo(size * 2, 0),
555
+ lineTo(0, size),
556
+ ].join("")
557
+ }
558
+
559
+ svg.selectAll("marker")
560
+ .transition()
561
+ .attr("viewBox", viewBox(0, -size, size * 2, size * 2))
562
+ .attr("refX", size * 2)
563
+ .attr("refY", 0)
564
+ .attr("markerWidth", size * 2)
565
+ .attr("markerHeight", size * 2);
566
+
567
+ svg.selectAll("marker path")
568
+ .transition()
569
+ .attr("d", arrow(size));
570
+ },
571
+
572
+ _setupMarkers: function (size) {
573
+
574
+ svg.append("defs").selectAll("marker")
575
+ .data(["default", "dependency", "dependants"])
576
+ .enter().append("marker")
577
+ .attr("id", (d) => d)
578
+ .attr("orient", "auto")
579
+ .attr("class", "marker")
580
+ .append("path");
581
+
582
+ this.updateMarkers(size);
583
+ },
584
+
585
+ _setupLinks: function () {
586
+ svg.append("g").selectAll("path")
587
+ .data(this.d3graph.links)
588
+ .enter().append("path")
589
+ .attr("class", "link")
590
+ .attr("marker-end", "url(#default)")
591
+ .style("stroke-width", (d) => d);
592
+
593
+ this._link = svg.selectAll("path.link")
594
+ },
595
+
596
+ _d3graphAllNodes: function () { return this.d3graph.nodes },
597
+ _d3graphNodes: function (type) { return this.d3graph.nodes.filter(node => node.type === type) },
598
+ _d3graphNodesSkipped: function (type) { return this.d3graph.nodes.filter(node => node.type !== type) },
599
+
600
+ _setupNodes: function () {
601
+
602
+ svg.append("g").selectAll(".node")
603
+ .data(this._d3graphNodesSkipped("struct"))
604
+ .enter()
605
+ .append("circle")
606
+ .attr("class", "node")
607
+ .attr("r", this._radius)
608
+ .style("stroke-dasharray", d => d.type === "protocol" ? [5, 5] : "") // TODO: Move to styling
609
+ .style("stroke-width", d => d.type === "protocol" ? 5 : 1); // TODO: Move to styling
610
+
611
+ svg.append("g").selectAll(".structNode")
612
+ .data(this._d3graphNodes("struct"))
613
+ .enter()
614
+ .append("polygon")
615
+ .attr("class", "structNode")
616
+ .attr("points", this._structurePoints)
617
+ .style("stroke-width", 1);
618
+
619
+
620
+ this.objectNodes = svg.selectAll('.node, .structNode');
621
+ // Setting up source/ dest and coloring
622
+
623
+ this.objectNodes
624
+ .style("fill", d => this.color(d.group))
625
+ .attr("source", d => d.source)
626
+ .attr("dest", d => d.dest);
627
+
628
+ this._node = svg.selectAll('.node');
629
+ this._structNode = svg.selectAll('.structNode');
630
+
631
+ }.bind(visualizer),
632
+
633
+ _radius: function (node) {
634
+ return config.default_circle_radius + config.default_circle_radius * node.source / 10;
635
+ },
636
+
637
+ _setupSimulation: function () {
638
+
639
+ this.simulation = d3.forceSimulation(d3.values(d3graph.nodes))
640
+ .force("x", d3.forceX())
641
+ .force("y", d3.forceY())
642
+ .force("center", d3.forceCenter(x / 2, y / 2)) // TODO Move to somewhere else?
643
+ .force("charge", d3.forceManyBody().strength(this._chargeStrength))
644
+ .force("link", d3.forceLink(d3graph.links)
645
+ .distance(this._linkDistance)
646
+ .strength(this._linkStrength)
647
+ )
648
+ .on("tick", this._ticked);
649
+
650
+ },
651
+
652
+ _linkDistance: function (link) {
653
+ if (link.source.filtered || link.target.filtered) {
654
+ return 500;
655
+ }
656
+ return this._radius(link.source) + this._radius(link.target) + this.config.default_link_distance;
657
+ }.bind(visualizer),
658
+
659
+ _linkStrength: function (link) {
660
+ if (link.source.filtered || link.target.filtered) {
661
+ return 0.01;
662
+ }
663
+ return config.default_link_strength;
664
+ },
665
+
666
+ _chargeStrength: function (node) {
667
+ if (node.filtered) {
668
+ return -0.01;
669
+ }
670
+ return -node.weight * config.charge_multiplier;
671
+ },
672
+
673
+ _structurePoints: function (d) {
674
+ let r = this._radius(d);
675
+ let pts = [
676
+ { x: -r, y: 0 },
677
+ { x: -r * 0.707, y: -r * 0.707 },
678
+ { x: 0, y: -r },
679
+ { x: r * 0.707, y: -r * 0.707 },
680
+ { x: r, y: 0 },
681
+ { x: r * 0.707, y: r * 0.707 },
682
+ { x: 0, y: r },
683
+ { x: -r * 0.707, y: r * 0.707 },
684
+ ];
685
+
686
+ return pts.map(p => p.x + "," + p.y).join(" ")
687
+ }.bind(visualizer),
688
+
689
+ updateRadiuses: function (value) {
690
+
691
+ this._node.transition().attr("r", this._radius);
692
+ this._structNode.transition().attr("points", this._structurePoints);
693
+
694
+ this.updateMarkers(value / 3);
695
+ this.simulation.alphaTarget(0.3).restart()
696
+ },
697
+
698
+ reapply_charge_and_links: function () {
699
+ this.reapply_charge();
700
+ this.reapply_links_strength()
701
+ },
702
+
703
+ reapply_charge: function (value) {
704
+ config.charge_multiplier = setDefaultValue(value, config.charge_multiplier);
705
+ this.simulation.force("charge", d3.forceManyBody().strength(this._chargeStrength));
706
+ this.simulation.alphaTarget(0.3).restart()
707
+ },
708
+
709
+ updateTextVisibility: function (visible) {
710
+ this.config.show_texts_near_circles = visible;
711
+ this._textNode.attr("visibility", visible ? "visible" : "hidden");
712
+ this.simulation.alphaTarget(0.3).restart()
713
+ },
714
+
715
+ reapply_links_strength: function (linkStrength) {
716
+ config.default_link_strength = setDefaultValue(linkStrength, config.default_link_strength);
717
+ this.simulation.force("link", d3.forceLink(d3graph.links)
718
+ .distance(this._linkDistance)
719
+ .strength(this._linkStrength)
720
+ );
721
+ this.simulation.alphaTarget(0.3).restart()
722
+ },
723
+
724
+ updateCenter: function (x, y) {
725
+ this.simulation.force("center", d3.forceCenter(x / 2, y / 2))
726
+ },
727
+
728
+ _setupDragging: function () {
729
+ let dragstarted = function (d) {
730
+ if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
731
+ d.fx = d.x;
732
+ d.fy = d.y;
733
+ }.bind(visualizer);
734
+
735
+ let dragged = function (d) {
736
+ d.fx = d3.event.x;
737
+ d.fy = d3.event.y;
738
+ }.bind(visualizer);
739
+
740
+ let dragended = function (d) {
741
+ if (!d3.event.active) this.simulation.alphaTarget(0);
742
+ if (!d.fixed) {
743
+ d.fx = null;
744
+ d.fy = null;
745
+ }
746
+ }.bind(visualizer);
747
+
748
+ this.objectNodes
749
+ .call(d3.drag()
750
+ .on("start", dragstarted)
751
+ .on("drag", dragged)
752
+ .on("end", dragended));
753
+ },
754
+
755
+ _setupTexts: function () {
756
+ svg.append("g").selectAll("text")
757
+ .data(this.simulation.nodes())
758
+ .enter()
759
+ .append("text")
760
+ .attr("visibility", "hidden")
761
+ .text(d => d.name.substring(0, this.config.default_max_texts_length));
762
+
763
+ this._textNode = svg.selectAll("text");
764
+ },
765
+
766
+ _link_line: function (d) {
767
+ const dx = d.target.x - d.source.x,
768
+ dy = d.target.y - d.source.y,
769
+ dr = Math.sqrt(dx * dx + dy * dy);
770
+
771
+ if (dr === 0) { return "M0,0L0,0" }
772
+
773
+ const rsource = this._radius(d.sourceNode) / dr;
774
+ const rdest = this._radius(d.targetNode) / dr;
775
+ const startX = d.source.x + dx * rsource;
776
+ const startY = d.source.y + dy * rsource;
777
+
778
+ const endX = d.target.x - dx * rdest;
779
+ const endY = d.target.y - dy * rdest;
780
+ return "M" + startX + "," + startY + "L" + endX + "," + endY;
781
+ }.bind(visualizer),
782
+
783
+ setupZoom: function (container) {
784
+ const w = window,
785
+ d = document,
786
+ e = d.documentElement,
787
+ g = d.getElementsByTagName('body')[0],
788
+ x = w.innerWidth || e.clientWidth || g.clientWidth,
789
+ y = w.innerHeight || e.clientHeight || g.clientHeight;
790
+
791
+ const zoom = d3.zoom()
792
+ .on("zoom", function () { svg.attr("transform", d3.event.transform) });
793
+
794
+ container.append("rect")
795
+ .attr("width", x)
796
+ .attr("height", y)
797
+ .style("fill", "none")
798
+ .style("pointer-events", "all")
799
+ .lower()
800
+ .call(zoom);
801
+ },
802
+
803
+ _transform: function (d) {
804
+ return "translate(" + d.x + "," + d.y + ")";
805
+ },
806
+
807
+
808
+ _ticked: function () {
809
+ this._link.attr("d", this._link_line);
810
+ this._node.attr("transform", this._transform);
811
+ this._structNode.attr("transform", this._transform);
812
+ if (config.show_texts_near_circles) {
813
+ this._textNode.attr("transform", this._transform);
814
+ }
815
+ }.bind(visualizer),
816
+
817
+ _setupColors: function () {
818
+ // https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
819
+ this.color = d3.scaleOrdinal(d3.schemeCategory10);
820
+ },
821
+
822
+ _setupAllNodes: function () {
823
+ this.allNodes = svg.select(".node, .structNode, text")
824
+ },
825
+ initialize: function () {
826
+ this._setupColors();
827
+ this._setupMarkers(this.config.default_circle_radius / 3);
828
+ this._setupLinks();
829
+ this._setupNodes();
830
+ this._setupSimulation();
831
+ this._setupTexts();
832
+ this._setupDragging();
833
+ this._setupAllNodes();
834
+ }
835
+ });
836
+ return visualizer
837
+
838
+ }
839
+ };
840
+
841
+
842
+ function setDefaultValue(value, defaultValue) {
843
+ return (value === undefined) ? defaultValue : value;
844
+ }
845
+ </script>
846
+ <script>
847
+
848
+ // ===================================================
849
+ // =============== CONFIGURABLE PARAMS ==============
850
+ // ===================================================
851
+
852
+ let config = dvconfig.create();
853
+
854
+ const dvgraph = objcdv.parse_dependencies_graph(dependencies);
855
+ const d3graph = dvgraph.d3jsGraph();
856
+
857
+ var w = window,
858
+ d = document,
859
+ e = d.documentElement,
860
+ g = d.getElementsByTagName('body')[0],
861
+ x = w.innerWidth || e.clientWidth || g.clientWidth,
862
+ y = w.innerHeight || e.clientHeight || g.clientHeight;
863
+
864
+ // ===================================================
865
+ // =============== http://d3js.org/ Magic ===========
866
+ // ===================================================
867
+
868
+ const container = d3.select("#chart").append("svg")
869
+ .attr("width", x)
870
+ .attr("height", y)
871
+ .style("overflow", "hidden");
872
+
873
+ const svg = container.append('g');
874
+ const actions = graph_actions.create(svg, dvgraph);
875
+ let visualizer = dvvisualizer.create(svg, config, d3graph);
876
+ visualizer.initialize();
877
+ visualizer.setupZoom(container);
878
+
879
+ // ===================================================
880
+ // =============== NODES SETUP ==================
881
+ // ===================================================
882
+
883
+ // Handling pressing
884
+ visualizer.objectNodes
885
+ .on("click", d => {
886
+ if (d3.event.defaultPrevented) { return }
887
+ actions.selectNodesStartingFromNode(d, 1);
888
+ visualizer.reapply_charge_and_links()
889
+ })
890
+ .on("contextmenu", d => {
891
+ if (d3.event.defaultPrevented) { return }
892
+ // Don't actually show context menu
893
+ d3.event.preventDefault();
894
+
895
+ actions.selectNodesStartingFromNode(d);
896
+ visualizer.reapply_charge_and_links()
897
+ });
898
+
899
+
900
+ /*
901
+ Window resize update
902
+ */
903
+ w.onresize = () => {
904
+ x = w.innerWidth || e.clientWidth || g.clientWidth;
905
+ y = w.innerHeight || e.clientHeight || g.clientHeight;
906
+
907
+ container.attr("width", Math.ceil(x)).attr("height", Math.ceil(y));
908
+ visualizer.updateCenter(x / 2, y / 2);
909
+ };
910
+ </script>
911
+
912
+ <script>
913
+ // ===================================================
914
+ // =============== INPUTS HANDLING ==============
915
+ // ===================================================
916
+ d3.selectAll("input").on("change", function change() {
917
+
918
+ if (this.name === "circle_size") {
919
+ config.default_circle_radius = parseInt(this.value);
920
+ visualizer.updateRadiuses(parseInt(this.value));
921
+ }
922
+
923
+ if (this.name === "charge_multiplier") {
924
+ let chargeMultiplier = parseInt(this.value);
925
+ visualizer.reapply_charge(chargeMultiplier)
926
+ }
927
+
928
+ if (this.name === "link_strength") {
929
+ let linkStrength = parseInt(this.value) / 10;
930
+ visualizer.reapply_links_strength(linkStrength)
931
+ }
932
+
933
+ if (this.name === "show_texts_near_circles") {
934
+ visualizer.updateTextVisibility(this.checked)
935
+ }
936
+ });
937
+ </script>
938
+
939
+ <script>
940
+ // ===================================================
941
+ // =============== LIVE FILTERING ==============
942
+ // ===================================================
943
+
944
+ function live_filter_graph(regexp, classname, invert) {
945
+ classname = setDefaultValue(classname, "filtered");
946
+ invert = setDefaultValue(invert, false);
947
+
948
+ const re = new RegExp(regexp, "i");
949
+ visualizer.allNodes
950
+ .classed(classname, node => {
951
+ let filtered = !node.name.match(re);
952
+ filtered = invert ? !filtered : filtered;
953
+ node.filtered = filtered;
954
+ node.neighbours = !filtered;
955
+ return filtered;
956
+ })
957
+ .transition();
958
+
959
+ svg.selectAll('.link')
960
+ .classed(classname, l => {
961
+ let filtered = !(l.sourceNode.name.match(re) && l.targetNode.name.match(re));
962
+ filtered = invert ? !filtered : filtered;
963
+ return filtered;
964
+ })
965
+ .attr("marker-end", l => {
966
+ let filtered = !(l.sourceNode.name.match(re) && l.targetNode.name.match(re));
967
+ filtered = invert ? !filtered : filtered;
968
+ return filtered ? "" : "url(#default)"
969
+ })
970
+ .transition()
971
+ }
972
+
973
+ d3.select("#search_input").on("input", function () {
974
+ // Filter all items
975
+ console.log("Input changed to" + this.value);
976
+ actions.deselect_selected_node();
977
+
978
+ if (this.value && this.value.length) {
979
+ live_filter_graph(this.value, "filtered");
980
+ }
981
+ visualizer.reapply_charge_and_links();
982
+ });
983
+
984
+ d3.select('#export').on('click', function() {
985
+ var config = {
986
+ filename: 'deps',
987
+ }
988
+ d3_save_svg.save(d3.select('svg').node(), config);
989
+ });
990
+ </script>