/
sugiyama.js
118 lines (100 loc) · 3.69 KB
/
sugiyama.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
import * as d3_base from "d3";
import * as d3_dag from "d3-dag";
export default function() {
const width = 400;
const height = 400;
const layering = "Simplex (slow)";
const decross = "Optimal (slow)";
const coord = "Vertical (slow)";
const d3 = Object.assign({}, d3_base, d3_dag);
function sugiyama(dag) {
const layerings = {
"Simplex (slow)": d3.layeringSimplex(),
"Longest Path (fast)": d3.layeringLongestPath(),
"Coffman Graham (medium)": d3.layeringCoffmanGraham(),
}
const decrossings = {
"Optimal (slow)": d3.decrossOpt(),
"Two Layer Opt (medium)": d3.decrossTwoLayer().order(d3.twolayerOpt()),
"Two Layer (flast)": d3.decrossTwoLayer()
}
const coords = {
"Vertical (slow)": d3.coordVert(),
"Minimum Curves (slow)": d3.coordMinCurve(),
"Greedy (medium)": d3.coordGreedy(),
"Center (fast)": d3.coordCenter(),
}
const layout = d3.sugiyama()
.size([width, height])
.layering(layerings[layering])
.decross(decrossings[decross])
.coord(coords[coord]);
layout(dag);
draw(dag);
}
return sugiyama;
function draw(dag) {
// This code only handles rendering
const nodeRadius = 20;
const svgSelection = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `${-nodeRadius} ${-nodeRadius} ${width + 2 * nodeRadius} ${height + 2 * nodeRadius}`);
const defs = svgSelection.append('defs'); // For gradients
// Use computed layout
// layout(dag);
const steps = dag.size();
const interp = d3.interpolateRainbow;
const colorMap = {};
dag.each((node, i) => {
colorMap[node.id] = interp(i / steps);
});
// How to draw edges
const line = d3.line()
.curve(d3.curveCatmullRom)
.x(d => d.x)
.y(d => d.y);
// Plot edges
svgSelection.append('g')
.selectAll('path')
.data(dag.links())
.enter()
.append('path')
.attr('d', ({ data }) => line(data.points))
.attr('fill', 'none')
.attr('stroke-width', 3)
.attr('stroke', ({source, target}) => {
const gradId = `${source.id}-${target.id}`;
const grad = defs.append('linearGradient')
.attr('id', gradId)
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', source.x)
.attr('x2', target.x)
.attr('y1', source.y)
.attr('y2', target.y);
grad.append('stop').attr('offset', '0%').attr('stop-color', colorMap[source.id]);
grad.append('stop').attr('offset', '100%').attr('stop-color', colorMap[target.id]);
return `url(#${gradId})`;
});
// Select nodes
const nodes = svgSelection.append('g')
.selectAll('g')
.data(dag.descendants())
.enter()
.append('g')
.attr('transform', ({x, y}) => `translate(${x}, ${y})`);
// Plot node circles
nodes.append('circle')
.attr('r', 20)
.attr('fill', n => colorMap[n.id]);
// Add text to nodes
nodes.append('text')
.text(d => d.id)
.attr('font-weight', 'bold')
.attr('font-family', 'sans-serif')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('fill', 'white');
}
}