-
Notifications
You must be signed in to change notification settings - Fork 0
/
force-directed-graph.js
189 lines (170 loc) · 5.4 KB
/
force-directed-graph.js
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
"use strict";
var margin = {top: 0, right: 0, bottom: 0, left: 0};
//
// extra_XY to account for side-margins + top div's -> avoid scrollbars
//
const extra_height = 120;
const extra_width = 20;
var width = globalThis.innerWidth - extra_width;
var height = globalThis.innerHeight - extra_height;
// FDG params
const Friction = 0.9;
const Charge = -100;
const LinkStrength = 2.0;
const Theta = 0.3;
const Gravity = 0.40;
const LinkLen = width/50;
// Bad* vars are there to highlight users with a different group
// among file-sharers (group == 3 in datobj.json)
// Links style
var LinkColor = "#333333";
var BadLinkColor = "#ff0000";
var LinkWidth = 2;
var BadLinkWidth = 6;
// Nodes style
var FileNodeSize = 9;
var UserNodeSize = 12;
var BadUserNodeSize = 16;
var FileNodeColor = "#00aaff";
var UserNodeColor = "#000000";
var BadUserNodeColor = "#ff0000";
var UndefUserNodeColor = "#ff00ff";
var bad_app = function(d) {
// A specific app: <int> value in the data
return !(typeof d.app === 'undefined');
};
var app_width = function(d) {
if (bad_app(d)) { return BadLinkWidth; };
return LinkWidth;
};
var app_color = function(d) {
if (bad_app(d)) { return BadLinkColor; };
return LinkColor;
};
var force = d3.layout.force()
.size([width, height])
//
// Official docs:
// https://github.com/d3/d3-force/tree/v3.0.0
// https://d3-wiki.readthedocs.io/zh_CN/master/Force-Layout/
// https://en.wikipedia.org/wiki/Verlet_integration
//
// Default settings:
// size 1×1
// linkStrength 1
// friction 0.9
// distance 20
// charge -30
// gravity 0.1
// theta 0.8
//
// The default nodes and links are the empty array, and when the layout
// is started, the internal alpha cooling parameter is set to 0.1
//
// A few of the parameters are explained here:
// charge:
// (Negative) charge determines repulsion
// Effect is opposite of gravity (between two nodes)
// linkStrength:
// pulls two negative charges closer together
// 0.1 keeps them far apart
// friction:
// Makes the graph movement stiffer & more resistant to change
// when disturbed. If too high, may fail to converge!
// gravity:
// Global gravity force: pulls everything towards the center
// - If set to 1.0, everything becomes one big concentrated blob
// - If set to 0.0, blobs of nodes remain detached / outside the canvas
// default is 0.1
.charge(Charge)
.linkDistance(LinkLen)
.linkStrength(LinkStrength)
.friction(Friction)
.chargeDistance(width/2.0)
.theta(Theta)
.gravity(Gravity)
;
// Add the SVG canvas
var canvasDiv = document.getElementById('canvas');
// define the div for tooltips
var tooltip = d3.select('#canvas')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
function tooltip_text(d) {
if (d.group == 3) {
// A special group
return `${d.name}</br>(possible extfiltration)`;
}
return d.name;
}
var svg = d3.select('#canvas')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform',
'translate(' + margin.left + ',' + margin.top + ')')
;
d3.json('datobj.json', function(error, graph) {
if (error) {
alert(error);
throw error;
}
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll('.link')
.data(graph.links)
.enter().append('line')
.attr('class', 'link')
.style('stroke', function(d) { return app_color(d) })
.style('stroke-width', function(d) { return app_width(d) })
.style('fill', function(d) { return app_color(d) });
var node = svg.selectAll('.node')
.data(graph.nodes)
.enter().append('circle')
// .attr('class', 'node')
.attr('class', function(d) {
if (d.group == 1) return 'user';
if (d.group == 2) return 'file';
if (d.group == 3) return 'extuser';
return 'unknown';
})
.attr('r', function(d) {
if (d.group == 1) return UserNodeSize;
if (d.group == 2) return FileNodeSize;
if (d.group == 3) return BadUserNodeSize;
return BadUserNodeSize*2;
})
.on('mouseover', function(d) {
tooltip.transition()
.delay(0)
.duration(0)
.style('opacity', 1.0)
tooltip.html(tooltip_text(d))
.style('left', (d3.event.pageX + 14) + 'px')
.style('top', (d3.event.pageY - 30) + 'px')
;
})
.on('mouseout', function(d) {
tooltip.transition()
.delay(0)
.duration(0)
.style('opacity', 0.0)
;
})
.call(force.drag)
;
force.on('tick', function() {
link.attr('x1', function(d) { return d.source.x; })
.attr('y1', function(d) { return d.source.y; })
.attr('x2', function(d) { return d.target.x; })
.attr('y2', function(d) { return d.target.y; })
;
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
;
});
});