-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLayoutNode.js
More file actions
152 lines (132 loc) · 3.6 KB
/
LayoutNode.js
File metadata and controls
152 lines (132 loc) · 3.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
class LayoutNode {
constructor(graphNode, nodesByPos, pos) {
this.graphNode_ = graphNode;
this.nodesByPos_ = nodesByPos;
this.pos = pos;
this.groups = new Set();
this.tags = new Set();
this.affinity_ = [];
this.label = this.graphNode_.label;
this.pageRank = this.graphNode_.pageRank;
this.subgraph = this.graphNode_.subgraph;
this.nodesByPos_.set(this.pos, this);
}
resolveLinks(nodesByGraphNode) {
this.links = [];
for (let link of this.graphNode_.links) {
this.links.push({
to: nodesByGraphNode.get(link.to),
id: link.id,
label: link.label,
labelId: link.labelId,
});
}
}
setAffinity(nodesByGraphNode) {
const INF = 999999;
for (let node of nodesByGraphNode.values()) {
// Weak affinity full mesh
// Keep unassociated subgroups together
this.addAffinity(node, d => d);
// Try to stack nodes with the same label
if (node.label == this.label) {
this.addAffinity(node, (d, v) => v[0] == 0 ? 200 : 500);
}
for (let group of this.groups) {
if (!group.isType('group') && !group.isType('subgraph')) {
continue;
}
// Ensure groups do not overlap, with border
if (group.nodes.has(node)) {
continue;
}
this.addAffinity(node,
(d, v, p) => group.isContainedWithMargin(p) ? -INF : 0);
}
}
for (let link of this.links) {
// Stronger affinity for links
// Prefer to move toward the target instance
this.addAffinity(link.to, d => d <= 2 ? -INF : d * 40);
link.to.addAffinity(this, d => d <= 2 ? -INF : d * 35);
}
// Affinity within groups
for (let group of this.groups) {
if (!group.isType('group')) {
continue;
}
for (let node of group.nodes) {
this.addAffinity(node, d => d * 100);
}
}
}
addAffinity(node, distanceToWeight) {
if (this == node) {
return;
}
this.affinity_.push({
node: node,
distanceToWeight: distanceToWeight,
});
}
setTension() {
this.vec = [0, 0];
this.tension = 0;
for (let aff of this.affinity_) {
let vec = [], vecsum = 0;
for (let i of [0, 1]) {
vec[i] = aff.node.pos[i] - this.pos[i];
vecsum += Math.abs(vec[i]);
}
// Avoid calling sqrt(), since the results are used relatively
let distanceSquared = vec[0] * vec[0] + vec[1] * vec[1];
let weight = aff.distanceToWeight(distanceSquared, vec, aff.node.pos);
if (weight instanceof Array) {
for (let i of [0, 1]) {
this.vec[i] += weight[i];
this.tension += Math.abs(weight[i]);
}
} else {
for (let i of [0, 1]) {
this.vec[i] += (weight * vec[i]) / vecsum;
}
this.tension += Math.abs(weight);
}
}
}
offsetToPos(offset) {
return [
this.pos[0] + offset[0],
this.pos[1] + offset[1],
];
}
offsetCollides(offset) {
let newPos = this.offsetToPos(offset);
return this.nodesByPos_.get(newPos);
}
moveTo(pos) {
this.nodesByPos_.delete(this.pos);
this.pos = pos;
this.nodesByPos_.set(this.pos, this);
}
moveBy(offset) {
this.moveTo(this.offsetToPos(offset));
}
savePos() {
this.savedPos_ = this.pos;
this.savedVec_ = Array.from(this.vec);
}
restorePos() {
this.moveTo(this.savedPos_);
this.vec = this.savedVec_;
}
getStep() {
return {
type: 'node',
pos: this.pos,
id: this.graphNode_.id,
label: this.graphNode_.label,
tags: Array.from(this.tags),
};
}
}