graph: Add d3js backend.
* 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.
This commit is contained in:
parent
642339dc3f
commit
4d93f312f0
|
@ -382,6 +382,8 @@ EXTRA_DIST = \
|
||||||
build-aux/generate-authors.scm \
|
build-aux/generate-authors.scm \
|
||||||
build-aux/test-driver.scm \
|
build-aux/test-driver.scm \
|
||||||
build-aux/run-system-tests.scm \
|
build-aux/run-system-tests.scm \
|
||||||
|
d3.v3.js \
|
||||||
|
graph.js \
|
||||||
srfi/srfi-37.scm.in \
|
srfi/srfi-37.scm.in \
|
||||||
srfi/srfi-64.scm \
|
srfi/srfi-64.scm \
|
||||||
srfi/srfi-64.upstream.scm \
|
srfi/srfi-64.upstream.scm \
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
// 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));
|
|
@ -22,6 +22,7 @@
|
||||||
#:use-module (guix monads)
|
#:use-module (guix monads)
|
||||||
#:use-module (guix records)
|
#:use-module (guix records)
|
||||||
#:use-module (guix sets)
|
#:use-module (guix sets)
|
||||||
|
#:use-module (rnrs io ports)
|
||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
#:use-module (srfi srfi-9)
|
#:use-module (srfi srfi-9)
|
||||||
#:use-module (srfi srfi-26)
|
#:use-module (srfi srfi-26)
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
node-reachable-count
|
node-reachable-count
|
||||||
|
|
||||||
%graph-backends
|
%graph-backends
|
||||||
|
%d3js-backend
|
||||||
%graphviz-backend
|
%graphviz-backend
|
||||||
graph-backend?
|
graph-backend?
|
||||||
graph-backend
|
graph-backend
|
||||||
|
@ -181,13 +183,60 @@ typically returned by 'node-edges' or 'node-back-edges'."
|
||||||
emit-prologue emit-epilogue
|
emit-prologue emit-epilogue
|
||||||
emit-node emit-edge))
|
emit-node emit-edge))
|
||||||
|
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; d3js export.
|
||||||
|
;;;
|
||||||
|
|
||||||
|
(define (emit-d3js-prologue name port)
|
||||||
|
(format port "\
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset=\"utf-8\">
|
||||||
|
<style>
|
||||||
|
text {
|
||||||
|
font: 10px sans-serif;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type=\"text/javascript\" src=\"~a\"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type=\"text/javascript\">
|
||||||
|
var nodes = {},
|
||||||
|
nodeArray = [],
|
||||||
|
links = [];
|
||||||
|
" (search-path %load-path "d3.v3.js")))
|
||||||
|
|
||||||
|
(define (emit-d3js-epilogue port)
|
||||||
|
(format port "</script><script type=\"text/javascript\" src=\"~a\"></script></body></html>"
|
||||||
|
(search-path %load-path "graph.js")))
|
||||||
|
|
||||||
|
(define (emit-d3js-node id label port)
|
||||||
|
(format port "\
|
||||||
|
nodes[\"~a\"] = {\"id\": \"~a\", \"label\": \"~a\", \"index\": nodeArray.length};
|
||||||
|
nodeArray.push(nodes[\"~a\"]);~%"
|
||||||
|
id id label id))
|
||||||
|
|
||||||
|
(define (emit-d3js-edge id1 id2 port)
|
||||||
|
(format port "links.push({\"source\": \"~a\", \"target\": \"~a\"});~%"
|
||||||
|
id1 id2))
|
||||||
|
|
||||||
|
(define %d3js-backend
|
||||||
|
(graph-backend "d3js"
|
||||||
|
"Generate chord diagrams with d3js."
|
||||||
|
emit-d3js-prologue emit-d3js-epilogue
|
||||||
|
emit-d3js-node emit-d3js-edge))
|
||||||
|
|
||||||
|
|
||||||
;;;
|
;;;
|
||||||
;;; Shared.
|
;;; Shared.
|
||||||
;;;
|
;;;
|
||||||
|
|
||||||
(define %graph-backends
|
(define %graph-backends
|
||||||
(list %graphviz-backend))
|
(list %graphviz-backend
|
||||||
|
%d3js-backend))
|
||||||
|
|
||||||
(define* (export-graph sinks port
|
(define* (export-graph sinks port
|
||||||
#:key
|
#:key
|
||||||
|
|
Loading…
Reference in New Issue