forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdraw_graph.js
More file actions
100 lines (85 loc) · 3.45 KB
/
draw_graph.js
File metadata and controls
100 lines (85 loc) · 3.45 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
// The familiar Vector type.
function Vector(x, y) { this.x = x; this.y = y; }
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
Vector.prototype.minus = function(other) {
return new Vector(this.x - other.x, this.y - other.y);
};
Vector.prototype.times = function(factor) {
return new Vector(this.x * factor, this.y * factor);
};
Object.defineProperty(Vector.prototype, "length", {
get: function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
});
// Since we will want to inspect the layouts our code produces, let's
// first write code to draw a graph onto a canvas. Since we don't know
// in advance how big the graph is, the `Scale` object computes a
// scale and offset so that all nodes fit onto the given canvas.
var nodeSize = 8;
function drawGraph(graph) {
var canvas = document.querySelector("canvas");
if (!canvas) {
canvas = document.body.appendChild(document.createElement("canvas"));
canvas.width = canvas.height = 500;
}
var cx = canvas.getContext("2d");
cx.clearRect(0, 0, canvas.width, canvas.height);
var scale = new Scale(graph, canvas.width, canvas.height);
// Draw the edges.
cx.strokeStyle = "orange";
cx.lineWidth = 3;
graph.forEach(function(origin, i) {
origin.edges.forEach(function(target) {
if (graph.indexOf(target) <= i) return;
cx.beginPath();
cx.moveTo(scale.x(origin.pos.x), scale.y(origin.pos.y));
cx.lineTo(scale.x(target.pos.x), scale.y(target.pos.y));
cx.stroke();
});
});
// Draw the nodes.
cx.fillStyle = "purple";
graph.forEach(function(node) {
cx.beginPath();
cx.arc(scale.x(node.pos.x), scale.y(node.pos.y), nodeSize, 0, 7);
cx.fill();
});
}
// The function starts by drawing the edges, so that they appear
// behind the nodes. Since the nodes on _both_ side of an edge refer
// to each other, and we don't want to draw every edge twice, edges
// are only drawn then the target comes _after_ the current node in
// the `graph` array.
// When the edges have been drawn, the nodes are drawn on top of them
// as purple discs. Remember that the last argument to `arc` gives the
// rotation, and we have to pass something bigger than 2π to get a
// full circle.
// Finding a scale at which to draw the graph is done by finding the
// top left and bottom right corners of the area taken up by the
// nodes. The offset at which nodes are drawn is based on the top left
// corner, and the scale is based on the size of the canvas divided by
// the distance between those corners. The function reserves space
// along the sides of the canvas based on the `nodeSize` variable, so
// that the circles drawn around nodes’ center points don't get cut off.
function Scale(graph, width, height) {
var xs = graph.map(function(node) { return node.pos.x });
var ys = graph.map(function(node) { return node.pos.y });
var minX = Math.min.apply(null, xs);
var minY = Math.min.apply(null, ys);
var maxX = Math.max.apply(null, xs);
var maxY = Math.max.apply(null, ys);
this.offsetX = minX; this.offsetY = minY;
this.scaleX = (width - 2 * nodeSize) / (maxX - minX);
this.scaleY = (height - 2 * nodeSize) / (maxY - minY);
}
Scale.prototype.x = function(x) {
return this.scaleX * (x - this.offsetX) + nodeSize;
};
Scale.prototype.y = function(y) {
return this.scaleY * (y - this.offsetY) + nodeSize;
};
// The `x` and `y` methods of the `Scale` type convert from graph
// coordinates into canvas coordinates.