* d3.v3.js, graph.js: New files. * Makefile.am (EXTRA_DIST): List them. * guix/graph.scm (%d3js-backend): New variable. (emit-d3js-prologue, emit-d3js-epilogue, emit-d3js-node, emit-d3js-edge): New procedures. (%graph-backends): Add %d3js-backend.
		
			
				
	
	
		
			129 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			129 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // GNU Guix --- Functional package management for GNU
 | |
| // Copyright © 2016 Ricardo Wurmus <rekado@elephly.net>
 | |
| //
 | |
| // This file is part of GNU Guix.
 | |
| //
 | |
| // GNU Guix is free software; you can redistribute it and/or modify it
 | |
| // under the terms of the GNU General Public License as published by
 | |
| // the Free Software Foundation; either version 3 of the License, or (at
 | |
| // your option) any later version.
 | |
| //
 | |
| // GNU Guix is distributed in the hope that it will be useful, but
 | |
| // WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| // GNU General Public License for more details.
 | |
| //
 | |
| // You should have received a copy of the GNU General Public License
 | |
| // along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| var outerRadius = Math.max(nodeArray.length * 15, 500) / 2,
 | |
|     innerRadius = outerRadius - Math.min(nodeArray.length * 5, 200),
 | |
|     width = outerRadius * 2,
 | |
|     height = outerRadius * 2,
 | |
|     colors = d3.scale.category20c(),
 | |
|     matrix = [];
 | |
| 
 | |
| function neighborsOf (node) {
 | |
|     return links.filter(function (e) {
 | |
|         return e.source === node;
 | |
|     }).map(function (e) {
 | |
|        return e.target;
 | |
|     });
 | |
| }
 | |
| 
 | |
| function zoomed () {
 | |
|     zoomer.attr("transform",
 | |
|                 "translate(" + d3.event.translate + ")" +
 | |
|                 "scale(" + d3.event.scale + ")");
 | |
| }
 | |
| 
 | |
| function fade (opacity, root) {
 | |
|     return function (g, i) {
 | |
|         root.selectAll("g path.chord")
 | |
|             .filter(function (d) {
 | |
|                 return d.source.index != i && d.target.index != i;
 | |
|             })
 | |
|             .transition()
 | |
|             .style("opacity", opacity);
 | |
|     };
 | |
| }
 | |
| 
 | |
| // Now that we have all nodes in an object we can replace each reference
 | |
| // with the actual node object.
 | |
| links.forEach(function (link) {
 | |
|   link.target = nodes[link.target];
 | |
|   link.source = nodes[link.source];
 | |
| });
 | |
| 
 | |
| // Construct a square matrix for package dependencies
 | |
| nodeArray.forEach(function (d, index, arr) {
 | |
|     var source = index,
 | |
|         row = matrix[source];
 | |
|     if (!row) {
 | |
|         row = matrix[source] = [];
 | |
|         for (var i = -1; ++i < arr.length;) row[i] = 0;
 | |
|     }
 | |
|     neighborsOf(d).forEach(function (d) { row[d.index]++; });
 | |
| });
 | |
| 
 | |
| // chord layout
 | |
| var chord = d3.layout.chord()
 | |
|     .padding(0.01)
 | |
|     .sortSubgroups(d3.descending)
 | |
|     .sortChords(d3.descending)
 | |
|     .matrix(matrix);
 | |
| 
 | |
| var arc = d3.svg.arc()
 | |
|     .innerRadius(innerRadius)
 | |
|     .outerRadius(innerRadius + 20);
 | |
| 
 | |
| var zoom = d3.behavior.zoom()
 | |
|     .scaleExtent([0.1, 10])
 | |
|     .on("zoom", zoomed);
 | |
| 
 | |
| var svg = d3.select("body").append("svg")
 | |
|     .attr("width", "100%")
 | |
|     .attr("height", "100%")
 | |
|     .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height))
 | |
|     .attr('preserveAspectRatio', 'xMinYMin')
 | |
|     .call(zoom);
 | |
| 
 | |
| var zoomer = svg.append("g");
 | |
| 
 | |
| var container = zoomer.append("g")
 | |
|     .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
 | |
| 
 | |
| // Group for arcs and labels
 | |
| var g = container.selectAll(".group")
 | |
|     .data(chord.groups)
 | |
|     .enter().append("g")
 | |
|     .attr("class", "group")
 | |
|     .on("mouseout", fade(1, container))
 | |
|     .on("mouseover", fade(0.1, container));
 | |
| 
 | |
| // Draw one segment per package
 | |
| g.append("path")
 | |
|     .style("fill",   function (d) { return colors(d.index); })
 | |
|     .style("stroke", function (d) { return colors(d.index); })
 | |
|     .attr("d", arc);
 | |
| 
 | |
| // Add circular labels
 | |
| g.append("text")
 | |
|     .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; })
 | |
|     .attr("dy", ".35em")
 | |
|     .attr("transform", function (d) {
 | |
|       return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
 | |
|           + "translate(" + (innerRadius + 26) + ")"
 | |
|           + (d.angle > Math.PI ? "rotate(180)" : "");
 | |
|     })
 | |
|     .style("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; })
 | |
|     .text(function (d) { return nodeArray[d.index].label; });
 | |
| 
 | |
| // Draw chords from source to target; color by source.
 | |
| container.selectAll(".chord")
 | |
|     .data(chord.chords)
 | |
|     .enter().append("path")
 | |
|     .attr("class", "chord")
 | |
|     .style("stroke", function (d) { return d3.rgb(colors(d.source.index)).darker(); })
 | |
|     .style("fill", function (d) { return colors(d.source.index); })
 | |
|     .attr("d", d3.svg.chord().radius(innerRadius));
 |