graph.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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. var outerRadius = Math.max(nodeArray.length * 15, 500) / 2,
  19. innerRadius = outerRadius - Math.min(nodeArray.length * 5, 200),
  20. width = outerRadius * 2,
  21. height = outerRadius * 2,
  22. colors = d3.scale.category20c(),
  23. matrix = [];
  24. function neighborsOf (node) {
  25. return links.filter(function (e) {
  26. return e.source === node;
  27. }).map(function (e) {
  28. return e.target;
  29. });
  30. }
  31. function zoomed () {
  32. zoomer.attr("transform",
  33. "translate(" + d3.event.translate + ")" +
  34. "scale(" + d3.event.scale + ")");
  35. }
  36. function fade (opacity, root) {
  37. return function (g, i) {
  38. root.selectAll("g path.chord")
  39. .filter(function (d) {
  40. return d.source.index != i && d.target.index != i;
  41. })
  42. .transition()
  43. .style("opacity", opacity);
  44. };
  45. }
  46. // Now that we have all nodes in an object we can replace each reference
  47. // with the actual node object.
  48. links.forEach(function (link) {
  49. link.target = nodes[link.target];
  50. link.source = nodes[link.source];
  51. });
  52. // Construct a square matrix for package dependencies
  53. nodeArray.forEach(function (d, index, arr) {
  54. var source = index,
  55. row = matrix[source];
  56. if (!row) {
  57. row = matrix[source] = [];
  58. for (var i = -1; ++i < arr.length;) row[i] = 0;
  59. }
  60. neighborsOf(d).forEach(function (d) { row[d.index]++; });
  61. });
  62. // chord layout
  63. var chord = d3.layout.chord()
  64. .padding(0.01)
  65. .sortSubgroups(d3.descending)
  66. .sortChords(d3.descending)
  67. .matrix(matrix);
  68. var arc = d3.svg.arc()
  69. .innerRadius(innerRadius)
  70. .outerRadius(innerRadius + 20);
  71. var zoom = d3.behavior.zoom()
  72. .scaleExtent([0.1, 10])
  73. .on("zoom", zoomed);
  74. var svg = d3.select("body").append("svg")
  75. .attr("width", "100%")
  76. .attr("height", "100%")
  77. .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height))
  78. .attr('preserveAspectRatio', 'xMinYMin')
  79. .call(zoom);
  80. var zoomer = svg.append("g");
  81. var container = zoomer.append("g")
  82. .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
  83. // Group for arcs and labels
  84. var g = container.selectAll(".group")
  85. .data(chord.groups)
  86. .enter().append("g")
  87. .attr("class", "group")
  88. .on("mouseout", fade(1, container))
  89. .on("mouseover", fade(0.1, container));
  90. // Draw one segment per package
  91. g.append("path")
  92. .style("fill", function (d) { return colors(d.index); })
  93. .style("stroke", function (d) { return colors(d.index); })
  94. .attr("d", arc);
  95. // Add circular labels
  96. g.append("text")
  97. .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; })
  98. .attr("dy", ".35em")
  99. .attr("transform", function (d) {
  100. return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
  101. + "translate(" + (innerRadius + 26) + ")"
  102. + (d.angle > Math.PI ? "rotate(180)" : "");
  103. })
  104. .style("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; })
  105. .text(function (d) { return nodeArray[d.index].label; });
  106. // Draw chords from source to target; color by source.
  107. container.selectAll(".chord")
  108. .data(chord.chords)
  109. .enter().append("path")
  110. .attr("class", "chord")
  111. .style("stroke", function (d) { return d3.rgb(colors(d.source.index)).darker(); })
  112. .style("fill", function (d) { return colors(d.source.index); })
  113. .attr("d", d3.svg.chord().radius(innerRadius));