-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgraph.js
More file actions
1720 lines (1389 loc) · 53.6 KB
/
graph.js
File metadata and controls
1720 lines (1389 loc) · 53.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
console.log('CANVE javascript started')
var width
var height
var presentationSVGWidth
var presentationSVGHeight
function windowSizeAdapter() {
width = window.innerWidth
height = window.innerHeight
console.log('viewport dimensions: ' + width + ' x ' + height)
//
// Chrome may add an extra pixel beyond the screen dimension on either axis, upon zoom,
// which may directly require either one of the axis scroll bars, that in turn will
// by definition reduce the viewport, thus forcing the complementary scroll bar
// becoming necessary as well, thus cascading into both scroll bars being
// necessarily visible, wasting a lot of viewport space and attention for just one pixel.
//
// We bypass all that by using one pixel less than what the viewport size initially is -
// in both axis dimensions.
//
presentationSVGWidth = width -1
presentationSVGHeight = height -1
presentationSVG.attr('width', presentationSVGWidth)
.attr('height', presentationSVGHeight)
forceLayout.size([presentationSVGWidth, presentationSVGHeight])
}
var sphereFontSize = 12 // implying pixel size
var interactionState = {
longStablePressEnd: false,
ctrlDown: false,
searchDialogEnabled: false
}
var awesompleteContainerDiv = document.getElementById("awesompleteContainer")
function searchDialogDisable() {
document.getElementById('inputBar').value = ''
interactionState.searchDialogEnabled = false
awesompleteContainerDiv.style.visibility = 'hidden'
}
function searchDialogEnable() {
interactionState.searchDialogEnabled = true
awesompleteContainerDiv.style.visibility = 'visible'
}
function isAlphaNumeric(keyCode) {
return ((keyCode >= 65 && keyCode <= 90) ||
(keyCode >= 97 && keyCode <= 122) ||
(keyCode >= 48 && keyCode <= 57))
}
document.onkeypress = function(evt) {
console.log(evt.keyCode)
if (isAlphaNumeric(evt.keyCode)) {
interactionState.searchDialogEnabled = true
searchDialogEnable()
document.getElementById('inputBar').focus()
}
function getSelectedNodes() {
return displayGraph.nodes().filter(function(nodeId) {
return displayGraph.node(nodeId).selectStatus === 'selected'
})
}
if (evt.keyCode == 43) { // plus key
getSelectedNodes().forEach(function(nodeId) {
addNodeNeighbors(displayGraph, nodeId, 1)
})
updateForceLayout(displayGraph)
}
if (evt.keyCode == 45) { // minus key
getSelectedNodes().forEach(function(nodeId) {
removeNodeFromDisplay(nodeId)
})
updateForceLayout(displayGraph, true)
}
}
document.onkeydown = function(evt) {
if (evt.keyCode == 17) {
interactionState.ctrlDown = true
}
if (evt.keyCode == 27) { // the escape key
if (interactionState.searchDialogEnabled)
searchDialogDisable()
}
}
document.onkeyup = function(evt) {
if (evt.keyCode == 17) {
interactionState.ctrlDown = false
}
}
// create svg for working out dimensions necessary for rendering labels' text
var hiddenSVG = d3.select('body').append('svg:svg').attr('width', 0).attr('height', 0)
var svgText = hiddenSVG.append('svg:text')
.attr('y', -500)
.attr('x', -500)
.style('font-size', sphereFontSize)
var presentationSVG = d3.select('body').append('svg:svg').style('position', 'aboslute').style('z-index', 0)
initForceLayout()
windowSizeAdapter()
function experimentalFishEyeIntegration() {
// Note: this feels a little jerky, maybe tweening is required
// Note: does not play well with the force layout ticks, but
// should be easy to reconcile the two by merging
// this logic into the main rendering function, to
// rely on the fisheye values directly there.
presentationSVG.on("mousemove", function() {
fisheye.focus(d3.mouse(this))
d3DisplayNodes.each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 4.5; });
d3DisplayLinks.attr("points", function(d) {
var source = d.source.fisheye.x + "," + d.source.fisheye.y + " "
var mid = (d.source.fisheye.x + d.target.fisheye.x)/2 + "," + (d.source.fisheye.y + d.target.fisheye.y)/2 + " "
var target = d.target.fisheye.x + "," + d.target.fisheye.y
return source + mid + target
})
})
}
var fisheye = d3.fisheye.circular()
.radius(100)
.distortion(5);
function setSvgDefs() {
var svgDefSection = presentationSVG.append("svg:defs")
// arrow-head svg definition
function setUsesShapeDef(length, ratio) {
var shortEdgeLength = length * ratio
var path = 'M0,0' +
'L0,' + shortEdgeLength +
'L' + length + ',' + (shortEdgeLength/2) +
'L0,0'
svgDefSection.selectAll("marker")
.data(["arrow"])
.enter().append("svg:marker")
.attr("id", "arrow")
.attr("refX", 0)
.attr("refY", shortEdgeLength/2)
.attr("markerWidth", length)
.attr("markerHeight", shortEdgeLength)
.attr("markerUnits", "userSpaceOnUse")
//.attr("markerUnits", "strokeWidth")
.attr("orient", "auto")
.append("svg:path")
.attr("d", path)
.style("fill", d3.rgb('green'))
}; setUsesShapeDef(10, 0.5)
function setMyRadialGradientDef() {
var gradientDef = svgDefSection
.selectAll("MyRadialGradientDef").data(["MyRadialGradientDef"]).enter().append("svg:radialGradient")
.attr("id", "MyRadialGradientDef")
gradientDef
.append("svg:stop")
.attr('offset', '30%')
.attr('stop-color', 'green')
gradientDef
.append("svg:stop")
.attr('offset', '90%')
.attr('stop-color', d3.rgb('blue').brighter(1))
}; setMyRadialGradientDef()
function setMyLinearGradientDef() {
var gradientDef = svgDefSection
.selectAll("MyLinearGradientDef").data(["MyLinearGradientDef"]).enter().append("svg:linearGradient")
.attr("id", "MyLinearGradientDef")
gradientDef
.attr('x1', '0')
.attr('y1', '1')
.attr('x2', '0')
.attr('y2', '0')
gradientDef
.append("svg:stop")
.attr('offset', '50%')
.attr('stop-color', 'white')
gradientDef
.append("svg:stop")
.attr('offset', '100%')
.attr('stop-color', 'red')
}; setMyLinearGradientDef()
}; setSvgDefs()
/*
function setExtedsShape(length, ratio) {
var shortEdgeLength = length * ratio
var path = 'M0,0' +
'L0,' + shortEdgeLength +
'L' + length + ',' + (shortEdgeLength/2) +
'L0,0'
presentationSVG.append("svg:defs").selectAll("marker")
.data(["nonDash"])
.enter().append("svg:marker")
.attr("id", "nonDash")
.attr("refX", length)
.attr("refY", shortEdgeLength/2)
.attr("markerWidth", length)
.attr("markerHeight", shortEdgeLength)
.attr("markerUnits", "userSpaceOnUse")
//.attr("markerUnits", "strokeWidth")
.attr("orient", "auto")
.append("svg:path")
.attr("d", path)
.style("fill", d3.rgb('green'))
}; setExtedsShape(10, 0.5)
*/
// arrow-head svg definition
var globalGraph = new dagre.graphlib.Graph({ multigraph: true});
function formattedText(node) {
function splitByLengthAndCamelOrWord(text) {
function isUpperCase(char) {
return (char >= 'A' && char <= 'Z') // is this locale safe?
}
for (i = 0; i < text.length; i++)
{
if (i > 0)
if ((!isUpperCase(text.charAt(i-1)) && isUpperCase(text.charAt(i))) || // camel case transition
text.charAt(i-1) == ' ') // new word
if (i > 3)
return [text.slice(0, i)].concat(splitByLengthAndCamelOrWord(text.slice(i)))
if (i == text.length-1) return [text]
}
}
//var text = [node.kind]
var text = []
var splitName = splitByLengthAndCamelOrWord(node.displayName)
splitName.forEach(function(line) {
text.push(line)
})
return text
}
function calcBBox(node) {
svgText.selectAll('tspan').remove()
formattedText(node).forEach(function(line) {
svgText.append('tspan')
.attr("text-anchor", "middle")
.attr('x', 0)
.attr('dy', '1.2em')
.text(line)
})
return svgText.node().getBBox()
}
function adjustNames(node) {
if (node.kind == 'anonymous class' && node.name == '$anon') {
node.name = 'unnamed class'
node.displayName = node.name
}
if (node.kind == 'value' && node.name.indexOf('qual$') == 0) {
node.name = 'unnamed value'
node.displayName = node.name
}
if (node.kind == 'constructor' && node.name == '<init>') {
node.name = 'constructor'
node.displayName = node.name
}
if (node.kind == 'method' && node.name.indexOf('<init>$default$') == 0) {
node.name = 'default argument'
node.displayName = node.name
}
if (node.kind == 'value' && node.name.indexOf('x0$') == 0) { // a block argument
node.name = 'a block argument'
node.displayName = node.name
}
if (node.kind == 'lazy value') { // because showing laziness seems a little over of scope...
node.kind = 'value'
}
}
function loadNodes(callback){
console.log('loading nodes')
d3.csv('canve-data/nodes', function(err, inputNodes) {
if (err) console.error(err)
else {
console.log('raw input nodes: '); console.dir (inputNodes)
inputNodes.forEach(function(node) {
adjustNames(node)
if (node.displayName === undefined)
node.displayName = node.kind + ' ' + node.name
bbox = calcBBox(node)
globalGraph.setNode(node.id, { name: node.name,
kind: node.kind,
displayName: node.displayName,
notSynthetic: node.notSynthetic,
definition: node.definition,
textBbox: bbox })
})
console.log('nodes: '); console.dir(globalGraph.nodes())
console.log('loading sources, this may take a while...');
//console.log('skipping preemptive source loading')
callback()
getSources(function(){})
}
})
}
function ownerShipNormalize(edge){
// make an 'owned by' edge equivalent to a 'declares member' edge
// the nature of the real-world difference will be sorted out by using this
// code, but as it currently stands they are considered just the same here.
// in the end, this will be handled in the Scala code itself
if (edge.edgeKind == 'owned by') {
t = edge.id1; edge.id1 = edge.id2; edge.id2 = t; // swap edge's direction
edge.edgeKind = 'declares member'
}
}
function loadEdges(callback){
console.log('loading edges')
d3.csv('canve-data/edges', function(err, inputEdges) {
if (err) console.error(err)
else {
console.log('input edges: '); console.dir(inputEdges)
inputEdges.forEach(function(edge) {
ownerShipNormalize(edge)
globalGraph.setEdge(edge.id1, edge.id2, { edgeKind: edge.edgeKind });
})
console.log('edges: '); console.dir(globalGraph.edges())
inputEdges.forEach(function(edge) {
if (globalGraph.edge({v:edge.id1, w:edge.id2}) === undefined)
console.warn('input edge ' + edge + ' failed to initialize as a graphlib edge')
})
callback()
}
})
}
function getSources(callback) {
sourceMap = {}
// filter down the nodes that are defined in the project itself rather than
// imported from outside, as only for these nodes, their source should be fetched
var projectNodes = globalGraph.nodes().filter(function(nodeId) {
return (globalGraph.node(nodeId).definition === 'project')
})
// asynchronously fetch the sources for those nodes
var asyncPending = 0
var sources = 0
projectNodes.forEach(function(nodeId) {
asyncPending += 1
d3.text('canve-data/' + 'node-source-' + nodeId, function(err, nodeSource) {
if (err) console.error(err)
else {
sourceMap[nodeId] = nodeSource
sources += 1
}
asyncPending -= 1
})
})
// check source fetching progress status every interval
var interval = window.setInterval(function() {
console.log(asyncPending + ' sources still pending loading')
if (asyncPending == 0) {
clearInterval(interval)
if (sources == projectNodes.length)
console.log('done fetching all sources')
else
console.warn('failed fetching some sources')
callback()
}
}, 300) // the interval length in ms
}
function initRadii() {
function radiusByEdges(nodeId) {
return Math.log(globalGraph.nodeEdges(nodeId).length * 250)
}
globalGraph.nodes().forEach(function(nodeId) {
globalGraph.node(nodeId).collapsedRadius = radiusByEdges(nodeId)
globalGraph.node(nodeId).radius = globalGraph.node(nodeId).collapsedRadius
})
}
function onDataLoaded(callback) {
console.log('data loading done')
applyGraphFilters()
debugListSpecialNodes() // show what special nodes still slip through the filters
console.log('data filters applied')
initRadii()
displayGraph = new dagre.graphlib.Graph({ multigraph: true}); // directed graph, allowing multiple edges between two nodes
displayGraph.setGraph({})
window.onresize = function() {
windowSizeAdapter()
rewarmForceLayout()
}
initAwesomplete()
//fireGraphDisplay(87570)
//fireGraphDisplay(35478)
//fireGraphDisplay(8464)
//fireGraphDisplay(8250)
//getUnusedTypes(globalGraph).forEach(fireGraphDisplay)
unusedTypes = getUnusedTypes(globalGraph)
console.log(unusedTypes.length + ' unused project types detected:')
console.log(unusedTypes)
fireGraphDisplay(unusedTypes[0])
}
// return the node at the other end of a given node's edge - the node's "peer node" on the edge
function getPeerNode(graph, nodeId, edge) {
if (nodeId == graph.edge(edge).v) return edge.w
else return edge.v
}
// recursive removal of nodes owned by a given node,
// along with the ownership edges connecting them
function removeOwned(nodeId, graph) {
for (edge of graph.nodeEdges(nodeId)) {
if (edge.w != nodeId) // avoid infinitely going back to parent every time
if (graph.edge(edge).edgeKind == 'declares member') {
var owned = edge.w
console.log('removing ' + owned)
removeOwned(owned, graph)
graph.removeNode(owned)
graph.removeEdge(edge)
}
}
}
packageExcludeList = [
{
description: 'scala core',
chain: ['scala']
},
{
description: 'java core',
chain: ['java', 'lang']
}
]
function filterByChain(chain, graph) {
function trim(nodeId) {
console.log('trimming ownership chain starting at: ' + chain.join('.') + ' (' + nodeId + ')')
removeOwned(nodeId, graph)
}
function findUniqueByName(nodeName) {
var nodeIds = getNodesByName(nodeName, graph)
if (nodeIds.length != 1) {
console.warn ('could not uniquely identify requested node, ' + nodeName + ' : ' + nodeName.length + ' root nodes found, whereas only one is expected!')
return undefined
}
return nodeIds[0]
}
var nodeId = findUniqueByName('<root>')
if (nodeId === undefined) return false
var match = true
for (var chainPos = 0; chainPos < chain.length && match == true; chainPos++) {
chainNodeName = chain[chainPos]
match = false
for (edge of graph.nodeEdges(nodeId)) {
if (graph.edge(edge).edgeKind == 'declares member') {
nodeId = edge.w
if (graph.node(nodeId).name == chainNodeName) {
match = true
break
}
}
}
}
if (match == true)
trim(nodeId)
}
function removeWithAllEdges(graph, nodeId) {
graph.nodeEdges(nodeId).forEach(function(edge) { graph.removeEdge(edge)})
graph.removeNode(nodeId)
}
// return direct callers of a node
function directUsers(graph, nodeId) {
var callers = graph.nodeEdges(nodeId).filter(function(edge) {
return (edge.w == nodeId &&
graph.edge(edge).edgeKind === 'uses')
})
return callers
}
// this may ultimately go to a separate output file for easy audit and/or test enablement
function logInputGraphPreprocessing(text) {
console.log(text)
}
//
// filter out non-informative nodes from the global graph
//
function applyGraphFilters() {
// filter away everything in certain external packages, other than their usage itself
// made in the project's code
function filterExternalPackageChains() {
nodesBefore = globalGraph.nodes().length
edgesBefore = globalGraph.edges().length
for (exclusion of packageExcludeList) {
filterByChain(exclusion.chain, globalGraph)
}
nodesAfter = globalGraph.nodes().length
edgesAfter = globalGraph.edges().length
console.log('filtered out nodes belonging to packages ' + packageExcludeList.map(function(l){ return l.chain.join('.')}).join(', ') +
', accounting for ' + parseInt((1-(nodesAfter/nodesBefore))*100) + '% of nodes and ' +
parseInt((1-(edgesAfter/edgesBefore))*100) + '% of links.')
}
// this function's functionality is in process of being
// superseded by directly using symbol properties arriving
// from the compiler. If anything surviving that transition
// still makes sense collapsing out of the graph,
// that collapsing can be reinstated.
function variousFilters() {
globalGraph.nodes().forEach(function(nodeId) {
var node = globalGraph.node(nodeId)
// The compiler creates default anonymous methods for copying the arguments passed
// to a case class. They do not convey any useful information, hence filtered.
if (node.kind == 'method' && node.name.indexOf('copy$default') == 0) {
logInputGraphPreprocessing('removing case class default copier ' + node.name + ' (and its edge)')
removeWithAllEdges(globalGraph, nodeId)
}
// similar to the above, it appears these just apply the default copy methods
if (node.kind == 'method' && node.name.indexOf('apply$default') == 0) {
logInputGraphPreprocessing('removing default applier ' + node.name + ' (and its edge)')
removeWithAllEdges(globalGraph, nodeId)
}
// redundant method definition created for some traits
if (node.kind == 'method' && node.name === '$init$') {
logInputGraphPreprocessing('removing redundant trait init method ' + node.name + ' (and its edge)')
removeWithAllEdges(globalGraph, nodeId)
}
//
if (node.kind == 'value' && node.name.indexOf('x$') == 0) // unnamed value
if (directUsers(globalGraph, nodeId).length == 0) { // that is not used
logInputGraphPreprocessing('removing unnamed and unused value ' + node.name + ' (and its edges)')
removeWithAllEdges(globalGraph, nodeId)
}
})
}
function hasNonSyntheticUsers(graph, nodeId) {
for (edge of graph.nodeEdges(nodeId)) {
if (edge.w == nodeId) {
edgeKind = graph.edge(edge).edgeKind
if (edgeKind == 'extends' ||
edgeKind == 'is of type' ||
edgeKind == 'uses') {
// arriving here, the edge peer uses our node being inspected
if (graph.node(edge.v).notSynthetic == true)
return true
else
return hasNonSyntheticUsers(graph, edge.v)
}
}
}
return false
}
function filterUnusedSynthetics() {
globalGraph.nodes().forEach(function(nodeId) {
var node = globalGraph.node(nodeId)
if (node.notSynthetic == "false")
if (!hasNonSyntheticUsers(globalGraph, nodeId)) {
logInputGraphPreprocessing('removing compiler-synthetic entity not being used: ' + node.displayName + ' (' + nodeId +'), and its edges')
console.log(node)
removeWithAllEdges(globalGraph, nodeId)
}
})
}
filterExternalPackageChains()
//variousFilters()
collapseValRepresentationPairs()
filterUnusedSynthetics()
}
// Collapses all val pairs that represent a single val each.
//
// Rational: the compiler will generate two vals for each val found
// in the code being compiled. Here we collapse them,
// as the duplication seems not to add any
// informative value.
//
// Note that it is better to keep this as a separate function,
// in case inter-relationships between such pairs emerge
// in further testing.
//
function collapseValRepresentationPairs() {
function getValRepresentationPairs() {
var valRepresentationPairs = []
globalGraph.edges().forEach(function(edge) {
if (globalGraph.node(edge.v).name == globalGraph.node(edge.w).name)
if (globalGraph.node(edge.v).kind == 'value' && globalGraph.node(edge.w).kind == 'value')
if (edge.w - edge.v == 1) // is this really a requirement?
if (globalGraph.edge(edge).edgeKind == 'uses')
valRepresentationPairs.push(edge)
})
a = valRepresentationPairs
return valRepresentationPairs
}
getValRepresentationPairs().forEach(function(edge){
logInputGraphPreprocessing('deduplicating value representation pair ' +
edge.v + ' -> ' + edge.w + ' by removing ' + edge.w + ' alltogether')
removeWithAllEdges(globalGraph, edge.w)
})
}
function passiveEdgeKindVoice(edgeKind) {
if (edgeKind == 'declares member') return 'declared by'
if (edgeKind == 'extends') return 'extended by'
if (edgeKind == 'is of type') return 'instantiated as'
if (edgeKind == 'uses') return 'used by'
}
function describe(graph, edge) {
edgeKind = graph.edge(edge).edgeKind
return edgeKind + ' ' +
graph.node(edge.w).displayName
}
function describeReversed(graph, edge) {
edgeKind = graph.edge(edge).edgeKind
return passiveEdgeKindVoice(edgeKind) + ' ' +
graph.node(edge.v).displayName
}
function logNodeNeighbors(graph, nodeId) {
console.log('')
console.log(graph.node(nodeId).displayName + ':')
graph.nodeEdges(nodeId).forEach(function(edge) {
edgeKind = graph.edge(edge).edgeKind
if (nodeId == edge.v)
console.log(describe(graph,edge))
if (nodeId == edge.w)
console.log(describeReversed(graph, edge))
})
console.log('')
}
function debugListSpecialNodes() {
globalGraph.edges().forEach(ownerShipNormalize)
globalGraph.nodes().forEach(function(nodeId){
if (globalGraph.node(nodeId).name.indexOf('$') > 0) console.log(globalGraph.node(nodeId).name)
})
}
function fetchData(callback) {
// callback-hell-style flow control for all data loading
loadNodes(function(){loadEdges(onDataLoaded)})
}
fetchData()
function getNodesByName(searchNodeName, graph) {
var found = graph.nodes().filter(function(id) {
return graph.node(id).name == searchNodeName
})
return found
}
function getOnwershipChain(id) {
var chain = []
function getNodeOwnershipChain(id) {
// look for ownership edges
globalGraph.nodeEdges(id).forEach(function(edge) {
if (globalGraph.edge(edge).edgeKind == 'declares member') {
if (edge.w == id) {
var owner = edge.v
chain.push(owner)
getNodeOwnershipChain(owner)
}
}
})
}
getNodeOwnershipChain(id)
}
// recompute and adjust the node rim's style,
// based on the intersection of two state properties.
// (we currently leave the transition duration to the caller, as this
// function currently doesn't deal with the source state only the target state).
function adjustedNodeRimVisualization(node, transitionDuration) {
var color
var width
// matrix for computing the appropriate style
if (node.selectStatus == 'selected' && node.highlightStatus == 'highlighted')
{ color = d3.rgb('red').darker(1); width = 4; }
if (node.selectStatus == 'selected' && node.highlightStatus == 'unhighlighted')
{ color = d3.rgb('red').darker(1); width = 2 }
if (node.selectStatus == 'unselected' && node.highlightStatus == 'highlighted')
{ color = 'orange'; width = 2 }
if (node.selectStatus == 'unselected' && node.highlightStatus == 'unhighlighted')
{ color = '#fff'; width = 1 }
if (transitionDuration === undefined) transitionDuration = 0
// apply the style
var selector = '#node' + node.id
presentationCircle = presentationSVG.select(selector).select(".circle")
presentationCircle
.transition('nodeHighlighting').duration(transitionDuration)
.style('stroke', color)
.style('stroke-width', width)
}
function toggleHighlightState(nodeId, targetState) {
var node = displayGraph.node(nodeId)
//console.log(node.selectStatus)
//if (node.selectStatus == 'selected') return
if (targetState == 'highlight') {
node.highlightStatus = 'highlighted'
adjustedNodeRimVisualization(node, 200)
}
if (targetState == 'unhighlight') {
node.highlightStatus = 'unhighlighted'
adjustedNodeRimVisualization(node, 500)
}
}
// return node on the other side of its supplied edge
function edgePeer(nodeId, edge) {
if (nodeId != edge.v && nodeId != edge.w) throw "invalid call to edgePeer"
return nodeId == edge.v ? edge.w : edge.v
}
// removes a given node from the display graph,
// along any of its connected nodes that aren't
// connected to any other nodes - never used
function removeNodeFromDisplayFancy(id) {
var nodePeers = displayGraph.nodeEdges(id).map(function(edge) { return edgePeer(id, edge) })
displayGraph.removeWithAllEdges(id)
nodePeers.forEach(function(id) {
if (displayGraph.nodeEdges(id).filter(function(edge) { return edge.v != edge.w }) == 0)
displayGraph.remove(id)
})
}
// remove all unconnected nodes from the display
function removeAllUnconnectedFromDisplay() {
displayGraph.nodes().forEach(function(id) {
if (displayGraph.nodeEdges(id).filter(function(edge) { return edge.v != edge.w }) == 0)
displayGraph.removeNode(id)
})
updateForceLayout(displayGraph)
}
function removeNodeFromDisplay(nodeId) {
removeWithAllEdges(displayGraph, nodeId)
}
function addNodeToDisplay(id) {
if (displayGraph.node(id) === undefined) {
var node = globalGraph.node(id)
node.id = id
node.expandStatus = 'collapsed'
node.selectStatus = 'unselected'
node.highlightStatus = 'unhighlighted'
displayGraph.setNode(id, node)
addNeighborLinksToDisplay(id)
}
}
//
// adds node's links to all other nodes already on the display.
//
function addNeighborLinksToDisplay(id) {
globalGraph.nodeEdges(id).forEach(function(edge) {
if (edge.v == id && displayGraph.hasNode(edge.w) ||
edge.w == id && displayGraph.hasNode(edge.v))
displayGraph.setEdge(edge, globalGraph.edge(edge))
})
}
// add node neighbors to display graph
function addNodeNeighbors(graph, id, degree) {
if (degree == 0) return
globalGraph.nodeEdges(id).forEach(function(edge) {
addNodeToDisplay(edge.v)
addNodeToDisplay(edge.w)
graph.setEdge(edge.v, edge.w, globalGraph.edge(edge.v, edge.w))
if (edge.v != id) addNodeNeighbors(graph, edge.v, degree - 1)
if (edge.w != id) addNodeNeighbors(graph, edge.w, degree - 1)
})
}
function addNodeEnv(id, degree) {
// this is a naive implementation meant for very small values of degree.
// for any humbly large degree, this needs to be re-implemented for efficient Big O(V,fembelish),
// as the current one is very naive in that sense.
addNodeToDisplay(id)
addNodeNeighbors(displayGraph, id, degree)
return displayGraph
}
function makeHierarchyChain(nodeId) {
var hierarchyNode = { name: displayGraph.node(nodeId).name }
var children = []
displayGraph.edges(nodeId).forEach(function(edge) {
if (displayGraph.edge(edge).edgeKind == 'declares member' && edge.v == nodeId) {
//console.log(edge)
children.push(makeHierarchyChain(edge.w))
}
})
if (children.length > 0) hierarchyNode['children'] = children
return hierarchyNode
}
// compute a circle pack layout for a given hierarchy
function computeCirclePack(hierarchy) {
var pack = d3.layout.pack()
.size([100, 100])
.padding(2)
.value(function(d) { return 20 })
pack(hierarchy)
var nodes = pack.nodes(hierarchy)
var links = pack.links(nodes)
}
// compute circle graph
function fireGraphDisplay(nodeId) {
addNodeEnv(nodeId, 1)
// this creates a dagre initial layout that is unfortunately
// not bound to the window's viewport but may
// be much much larger.
//dagre.layout(displayGraph)
//console.log('dagre layout dimensions: ' + displayGraph.graph().width + ', ' + displayGraph.graph().height)
console.log(displayGraph)
console.log('nodes: ' + displayGraph.nodes().length + ', ' + 'edges: ' + displayGraph.edges().length)
console.log('layout computed')
// computeCirclePack(dispyChain(nodeId)) // we don't do anything with it right now
// do the following both whether the node was already on the display or not
var node = displayGraph.node(nodeId)
var selector = '#node' + nodeId
presentationSVG.select(selector).select(".circle")
.transition('nodeHighlighting').duration(500).style('stroke', 'orange').style('stroke-width', 6)
.each('end', function() {
adjustedNodeRimVisualization(node, 2000)
})
updateForceLayout(displayGraph)
if (node.expandStatus === 'collapsed') expandNode(node)
}
function initAwesomplete() {
'use strict'
//getFirstResultEnv("signature")
let nodes = globalGraph.nodes().map(function(id) {
let node = { id: id,
data: globalGraph.node(id) }
//return node.name + ' ' + '(' + id + ')'
return node
})
var inputBar = document.getElementById('inputBar')
new Awesomplete(inputBar, {
minChars: 1,
maxItems: 100,
list: nodes,
item: function (node, input) {
let suggestedElem = document.createElement('li')
suggestedElem.appendChild(document.createTextNode(node.data.displayName + ' (' + node.id + ')'))
return suggestedElem
},
filter: function (node, input) {
return node.data.name.toLowerCase().indexOf(input.toLowerCase()) > -1 ||
node.id === input
},
sort: function compare(a, b) {
if (a.data.name < b.data.name) return -1
if (a.data.name > b.data.name) return 1