gnu: Add duperemove.
[guix.git] / graph.js
blobad8279395d8eb1fe5a836d54ec563a4577f4d135
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.
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.
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/>.
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 = [];
26 function neighborsOf (node) {
27     return links.filter(function (e) {
28         return e.source === node;
29     }).map(function (e) {
30        return e.target;
31     });
34 function zoomed () {
35     zoomer.attr("transform",
36                 "translate(" + d3.event.translate + ")" +
37                 "scale(" + d3.event.scale + ")");
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     };
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 });
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 });
69 // chord layout
70 var chord = d3.layout.chord()
71     .padding(0.01)
72     .sortSubgroups(d3.descending)
73     .sortChords(d3.descending)
74     .matrix(matrix);
76 var arc = d3.svg.arc()
77     .innerRadius(innerRadius)
78     .outerRadius(innerRadius + 20);
80 var zoom = d3.behavior.zoom()
81     .scaleExtent([0.1, 10])
82     .on("zoom", zoomed);
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);
91 var zoomer = svg.append("g");
93 var container = zoomer.append("g")
94     .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
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));
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);
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; });
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));