Commit | Line | Data |
---|---|---|
4d93f312 RW |
1 | // GNU Guix --- Functional package management for GNU |
2 | // Copyright © 2016 Ricardo Wurmus <rekado@elephly.net> | |
3 | // | |
4 | // This file is part of GNU Guix. | |
5 | // | |
6 | // GNU Guix is free software; you can redistribute it and/or modify it | |
7 | // under the terms of the GNU General Public License as published by | |
8 | // the Free Software Foundation; either version 3 of the License, or (at | |
9 | // your option) any later version. | |
10 | // | |
11 | // GNU Guix is distributed in the hope that it will be useful, but | |
12 | // WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | // GNU General Public License for more details. | |
15 | // | |
16 | // You should have received a copy of the GNU General Public License | |
17 | // along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | |
18 | ||
19 | var outerRadius = Math.max(nodeArray.length * 15, 500) / 2, | |
20 | innerRadius = outerRadius - Math.min(nodeArray.length * 5, 200), | |
21 | width = outerRadius * 2, | |
22 | height = outerRadius * 2, | |
23 | colors = d3.scale.category20c(), | |
24 | matrix = []; | |
25 | ||
26 | function neighborsOf (node) { | |
27 | return links.filter(function (e) { | |
28 | return e.source === node; | |
29 | }).map(function (e) { | |
30 | return e.target; | |
31 | }); | |
32 | } | |
33 | ||
34 | function zoomed () { | |
35 | zoomer.attr("transform", | |
36 | "translate(" + d3.event.translate + ")" + | |
37 | "scale(" + d3.event.scale + ")"); | |
38 | } | |
39 | ||
40 | function fade (opacity, root) { | |
41 | return function (g, i) { | |
42 | root.selectAll("g path.chord") | |
43 | .filter(function (d) { | |
44 | return d.source.index != i && d.target.index != i; | |
45 | }) | |
46 | .transition() | |
47 | .style("opacity", opacity); | |
48 | }; | |
49 | } | |
50 | ||
51 | // Now that we have all nodes in an object we can replace each reference | |
52 | // with the actual node object. | |
53 | links.forEach(function (link) { | |
54 | link.target = nodes[link.target]; | |
55 | link.source = nodes[link.source]; | |
56 | }); | |
57 | ||
58 | // Construct a square matrix for package dependencies | |
59 | nodeArray.forEach(function (d, index, arr) { | |
60 | var source = index, | |
61 | row = matrix[source]; | |
62 | if (!row) { | |
63 | row = matrix[source] = []; | |
64 | for (var i = -1; ++i < arr.length;) row[i] = 0; | |
65 | } | |
66 | neighborsOf(d).forEach(function (d) { row[d.index]++; }); | |
67 | }); | |
68 | ||
69 | // chord layout | |
70 | var chord = d3.layout.chord() | |
71 | .padding(0.01) | |
72 | .sortSubgroups(d3.descending) | |
73 | .sortChords(d3.descending) | |
74 | .matrix(matrix); | |
75 | ||
76 | var arc = d3.svg.arc() | |
77 | .innerRadius(innerRadius) | |
78 | .outerRadius(innerRadius + 20); | |
79 | ||
80 | var zoom = d3.behavior.zoom() | |
81 | .scaleExtent([0.1, 10]) | |
82 | .on("zoom", zoomed); | |
83 | ||
84 | var svg = d3.select("body").append("svg") | |
85 | .attr("width", "100%") | |
86 | .attr("height", "100%") | |
87 | .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) | |
88 | .attr('preserveAspectRatio', 'xMinYMin') | |
89 | .call(zoom); | |
90 | ||
91 | var zoomer = svg.append("g"); | |
92 | ||
93 | var container = zoomer.append("g") | |
94 | .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")"); | |
95 | ||
96 | // Group for arcs and labels | |
97 | var g = container.selectAll(".group") | |
98 | .data(chord.groups) | |
99 | .enter().append("g") | |
100 | .attr("class", "group") | |
101 | .on("mouseout", fade(1, container)) | |
102 | .on("mouseover", fade(0.1, container)); | |
103 | ||
104 | // Draw one segment per package | |
105 | g.append("path") | |
106 | .style("fill", function (d) { return colors(d.index); }) | |
107 | .style("stroke", function (d) { return colors(d.index); }) | |
108 | .attr("d", arc); | |
109 | ||
110 | // Add circular labels | |
111 | g.append("text") | |
112 | .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; }) | |
113 | .attr("dy", ".35em") | |
114 | .attr("transform", function (d) { | |
115 | return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" | |
116 | + "translate(" + (innerRadius + 26) + ")" | |
117 | + (d.angle > Math.PI ? "rotate(180)" : ""); | |
118 | }) | |
119 | .style("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; }) | |
120 | .text(function (d) { return nodeArray[d.index].label; }); | |
121 | ||
122 | // Draw chords from source to target; color by source. | |
123 | container.selectAll(".chord") | |
124 | .data(chord.chords) | |
125 | .enter().append("path") | |
126 | .attr("class", "chord") | |
127 | .style("stroke", function (d) { return d3.rgb(colors(d.source.index)).darker(); }) | |
128 | .style("fill", function (d) { return colors(d.source.index); }) | |
129 | .attr("d", d3.svg.chord().radius(innerRadius)); |