Skip to content

Commit 9015061

Browse files
author
zhangyue0503
committed
20161210
1 parent 6661aa8 commit 9015061

File tree

1 file changed

+271
-0
lines changed

1 file changed

+271
-0
lines changed

eloquentjs/21.html

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Title</title>
6+
</head>
7+
<body>
8+
<script type="text/javascript">
9+
10+
function Vector(x,y){
11+
this.x = x;
12+
this.y = y;
13+
}
14+
15+
Vector.prototype.plus = function(other) {
16+
return new Vector(this.x + other.x, this.y + other.y);
17+
};
18+
Vector.prototype.minus = function(other) {
19+
return new Vector(this.x - other.x, this.y - other.y);
20+
};
21+
Vector.prototype.times = function(factor) {
22+
return new Vector(this.x * factor, this.y * factor);
23+
};
24+
Object.defineProperty(Vector.prototype, "length", {
25+
get: function() {
26+
return Math.sqrt(this.x * this.x + this.y * this.y);
27+
}
28+
});
29+
30+
var nodeSize = 8;
31+
32+
function drawGraph(graph) {
33+
var canvas = document.querySelector("canvas");
34+
if (!canvas) {
35+
canvas = document.body.appendChild(document.createElement("canvas"));
36+
canvas.width = canvas.height = 500;
37+
}
38+
var cx = canvas.getContext("2d");
39+
40+
cx.clearRect(0, 0, canvas.width, canvas.height);
41+
var scale = new Scale(graph, canvas.width, canvas.height);
42+
43+
// Draw the edges.
44+
cx.strokeStyle = "orange";
45+
cx.lineWidth = 3;
46+
graph.forEach(function(origin, i) {
47+
origin.edges.forEach(function(target) {
48+
if (graph.indexOf(target) <= i) return;
49+
cx.beginPath();
50+
cx.moveTo(scale.x(origin.pos.x), scale.y(origin.pos.y));
51+
cx.lineTo(scale.x(target.pos.x), scale.y(target.pos.y));
52+
cx.stroke();
53+
});
54+
});
55+
56+
// Draw the nodes.
57+
cx.fillStyle = "purple";
58+
graph.forEach(function(node) {
59+
cx.beginPath();
60+
cx.arc(scale.x(node.pos.x), scale.y(node.pos.y), nodeSize, 0, 7);
61+
cx.fill();
62+
});
63+
}
64+
65+
// The function starts by drawing the edges, so that they appear
66+
// behind the nodes. Since the nodes on _both_ side of an edge refer
67+
// to each other, and we don't want to draw every edge twice, edges
68+
// are only drawn then the target comes _after_ the current node in
69+
// the `graph` array.
70+
71+
// When the edges have been drawn, the nodes are drawn on top of them
72+
// as purple discs. Remember that the last argument to `arc` gives the
73+
// rotation, and we have to pass something bigger than 2π to get a
74+
// full circle.
75+
76+
// Finding a scale at which to draw the graph is done by finding the
77+
// top left and bottom right corners of the area taken up by the
78+
// nodes. The offset at which nodes are drawn is based on the top left
79+
// corner, and the scale is based on the size of the canvas divided by
80+
// the distance between those corners. The function reserves space
81+
// along the sides of the canvas based on the `nodeSize` variable, so
82+
// that the circles drawn around nodes’ center points don't get cut off.
83+
84+
function Scale(graph, width, height) {
85+
var xs = graph.map(function(node) { return node.pos.x });
86+
var ys = graph.map(function(node) { return node.pos.y });
87+
var minX = Math.min.apply(null, xs);
88+
var minY = Math.min.apply(null, ys);
89+
var maxX = Math.max.apply(null, xs);
90+
var maxY = Math.max.apply(null, ys);
91+
92+
this.offsetX = minX; this.offsetY = minY;
93+
this.scaleX = (width - 2 * nodeSize) / (maxX - minX);
94+
this.scaleY = (height - 2 * nodeSize) / (maxY - minY);
95+
}
96+
97+
Scale.prototype.x = function(x) {
98+
return this.scaleX * (x - this.offsetX) + nodeSize;
99+
};
100+
Scale.prototype.y = function(y) {
101+
return this.scaleY * (y - this.offsetY) + nodeSize;
102+
};
103+
104+
//http://eloquentjavascript.net/code/draw_graph.js
105+
106+
107+
//
108+
function GraphNode(pos, edges) {
109+
this.pos = new Vector(Math.random() * 1000,
110+
Math.random() * 1000);
111+
this.edges = [];
112+
}
113+
GraphNode.prototype.connect = function(other) {
114+
this.edges.push(other);
115+
other.edges.push(this);
116+
};
117+
GraphNode.prototype.hasEdge = function(other) {
118+
for (var i = 0; i < this.edges.length; i++)
119+
if (this.edges[i] == other)
120+
return true;
121+
};
122+
123+
function treeGraph(depth, branches) {
124+
var graph = [];
125+
function buildNode(depth) {
126+
var node = new GraphNode();
127+
graph.push(node);
128+
if (depth > 1)
129+
for (var i = 0; i < branches; i++)
130+
node.connect(buildNode(depth - 1));
131+
return node;
132+
}
133+
buildNode(depth);
134+
return graph;
135+
}
136+
137+
var springLength = 40;
138+
var springStrength = 0.1;
139+
140+
var repulsionStrength = 1500;
141+
142+
function forceDirected_simple(graph) {
143+
graph.forEach(function(node) {
144+
graph.forEach(function(other) {
145+
if (other == node) return;
146+
var apart = other.pos.minus(node.pos);
147+
var distance = Math.max(1, apart.length);
148+
var forceSize = -repulsionStrength / (distance * distance);
149+
if (node.hasEdge(other))
150+
forceSize += (distance - springLength) * springStrength;
151+
var normalized = apart.times(1 / distance);
152+
node.pos = node.pos.plus(normalized.times(forceSize));
153+
});
154+
});
155+
}
156+
157+
function runLayout(implementation, graph) {
158+
var totalSteps = 0, time = 0;
159+
function step() {
160+
var startTime = Date.now();
161+
for (var i = 0; i < 100; i++)
162+
implementation(graph);
163+
totalSteps += 100;
164+
time += Date.now() - startTime;
165+
drawGraph(graph);
166+
167+
if (totalSteps < 4000)
168+
requestAnimationFrame(step);
169+
else
170+
console.log(time);
171+
}
172+
step();
173+
}
174+
175+
function forceDirected_forloop(graph) {
176+
for (var i = 0; i < graph.length; i++) {
177+
var node = graph[i];
178+
for (var j = 0; j < graph.length; j++) {
179+
if (i == j) continue;
180+
var other = graph[j];
181+
var apart = other.pos.minus(node.pos);
182+
var distance = Math.max(1, apart.length);
183+
var forceSize = -1 * repulsionStrength / (distance * distance);
184+
if (node.hasEdge(other))
185+
forceSize += (distance - springLength) * springStrength;
186+
var normalized = apart.times(1 / distance);
187+
node.pos = node.pos.plus(normalized.times(forceSize));
188+
}
189+
}
190+
}
191+
192+
function forceDirected_norepeat(graph) {
193+
for (var i = 0; i < graph.length; i++) {
194+
var node = graph[i];
195+
for (var j = i + 1; j < graph.length; j++) {
196+
var other = graph[j];
197+
var apart = other.pos.minus(node.pos);
198+
var distance = Math.max(1, apart.length);
199+
var forceSize = -1 * repulsionStrength / (distance * distance);
200+
if (node.hasEdge(other))
201+
forceSize += (distance - springLength) * springStrength;
202+
var applied = apart.times(forceSize / distance);
203+
node.pos = node.pos.plus(applied);
204+
other.pos = other.pos.minus(applied);
205+
}
206+
}
207+
}
208+
209+
function forceDirected_novector(graph) {
210+
for (var i = 0; i < graph.length; i++) {
211+
var node = graph[i];
212+
for (var j = i + 1; j < graph.length; j++) {
213+
var other = graph[j];
214+
var apartX = other.pos.x - node.pos.x;
215+
var apartY = other.pos.y - node.pos.y;
216+
var distance = Math.max(1, Math.sqrt(apartX * apartX + apartY * apartY));
217+
var forceSize = -repulsionStrength / (distance * distance);
218+
if (node.hasEdge(other))
219+
forceSize += (distance - springLength) * springStrength;
220+
221+
var forceX = apartX * forceSize / distance;
222+
var forceY = apartY * forceSize / distance;
223+
node.pos.x += forceX; node.pos.y += forceY;
224+
other.pos.x -= forceX; other.pos.y -= forceY;
225+
}
226+
}
227+
}
228+
229+
function forceDirected_localforce(graph) {
230+
var forcesX = [], forcesY = [];
231+
for (var i = 0; i < graph.length; i++)
232+
forcesX[i] = forcesY[i] = 0;
233+
234+
for (var i = 0; i < graph.length; i++) {
235+
var node = graph[i];
236+
for (var j = i + 1; j < graph.length; j++) {
237+
var other = graph[j];
238+
var apartX = other.pos.x - node.pos.x;
239+
var apartY = other.pos.y - node.pos.y;
240+
var distance = Math.max(1, Math.sqrt(apartX * apartX + apartY * apartY));
241+
var forceSize = -repulsionStrength / (distance * distance);
242+
if (node.hasEdge(other))
243+
forceSize += (distance - springLength) * springStrength;
244+
245+
var forceX = apartX * forceSize / distance;
246+
var forceY = apartY * forceSize / distance;
247+
forcesX[i] += forceX; forcesY[i] += forceY;
248+
forcesX[j] -= forceX; forcesY[j] -= forceY;
249+
}
250+
}
251+
252+
for (var i = 0; i < graph.length; i++) {
253+
graph[i].pos.x += forcesX[i];
254+
graph[i].pos.y += forcesY[i];
255+
}
256+
}
257+
258+
var mangledGraph = treeGraph(4, 4);
259+
mangledGraph.forEach(function(node) {
260+
var letter = Math.floor(Math.random() * 26);
261+
node[String.fromCharCode("A".charCodeAt(0) + letter)] = true;
262+
});
263+
264+
265+
runLayout(forceDirected_simple,treeGraph(4,4));
266+
267+
268+
269+
</script>
270+
</body>
271+
</html>

0 commit comments

Comments
 (0)