概要 实例 介绍 源码

桑基图

源文件:index.html

  1. <!DOCTYPE html>
  2. <html class="ocks-org do-not-copy">
  3. <meta charset="utf-8">
  4. <title>Sankey Diagram</title>
  5. <style>
  6. @import url(style.css?aea6f0a);
  7. #chart {
  8. height: 500px;
  9. }
  10. .node rect {
  11. cursor: move;
  12. fill-opacity: .9;
  13. shape-rendering: crispEdges;
  14. }
  15. .node text {
  16. pointer-events: none;
  17. text-shadow: 0 1px 0 #fff;
  18. }
  19. .link {
  20. fill: none;
  21. stroke: #000;
  22. stroke-opacity: .2;
  23. }
  24. .link:hover {
  25. stroke-opacity: .5;
  26. }
  27. </style>
  28. <body>
  29. <p id="chart">
  30. <script src="//d3js.org/d3.v2.min.js" charset="utf-8"></script>
  31. <script src="sankey.js"></script>
  32. <script>
  33. var margin = {top: 1, right: 1, bottom: 6, left: 1},
  34. width = 960 - margin.left - margin.right,
  35. height = 500 - margin.top - margin.bottom;
  36. var formatNumber = d3.format(",.0f"),
  37. format = function(d) { return formatNumber(d) + " TWh"; },
  38. color = d3.scale.category20();
  39. var svg = d3.select("#chart").append("svg")
  40. .attr("width", width + margin.left + margin.right)
  41. .attr("height", height + margin.top + margin.bottom)
  42. .append("g")
  43. .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  44. var sankey = d3.sankey()
  45. .nodeWidth(15)
  46. .nodePadding(10)
  47. .size([width, height]);
  48. var path = sankey.link();
  49. d3.json("energy.json", function(energy) {
  50. sankey
  51. .nodes(energy.nodes)
  52. .links(energy.links)
  53. .layout(32);
  54. var link = svg.append("g").selectAll(".link")
  55. .data(energy.links)
  56. .enter().append("path")
  57. .attr("class", "link")
  58. .attr("d", path)
  59. .style("stroke-width", function(d) { return Math.max(1, d.dy); })
  60. .sort(function(a, b) { return b.dy - a.dy; });
  61. link.append("title")
  62. .text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
  63. var node = svg.append("g").selectAll(".node")
  64. .data(energy.nodes)
  65. .enter().append("g")
  66. .attr("class", "node")
  67. .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
  68. .call(d3.behavior.drag()
  69. .origin(function(d) { return d; })
  70. .on("dragstart", function() { this.parentNode.appendChild(this); })
  71. .on("drag", dragmove));
  72. node.append("rect")
  73. .attr("height", function(d) { return d.dy; })
  74. .attr("width", sankey.nodeWidth())
  75. .style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
  76. .style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
  77. .append("title")
  78. .text(function(d) { return d.name + "\n" + format(d.value); });
  79. node.append("text")
  80. .attr("x", -6)
  81. .attr("y", function(d) { return d.dy / 2; })
  82. .attr("dy", ".35em")
  83. .attr("text-anchor", "end")
  84. .attr("transform", null)
  85. .text(function(d) { return d.name; })
  86. .filter(function(d) { return d.x < width / 2; })
  87. .attr("x", 6 + sankey.nodeWidth())
  88. .attr("text-anchor", "start");
  89. function dragmove(d) {
  90. d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
  91. sankey.relayout();
  92. link.attr("d", path);
  93. }
  94. });
  95. </script>
  96. <script>
  97. GoogleAnalyticsObject = "ga", ga = function() { ga.q.push(arguments); }, ga.q = [], ga.l = +new Date;
  98. ga("create", "UA-48272912-3", "ocks.org");
  99. ga("send", "pageview");
  100. </script>
  101. <script async src="//www.google-analytics.com/analytics.js"></script>

源文件:style.css

  1. /* Copyright 2013 Michael Bostock. All rights reserved. Do not copy. */
  2. @import url(//fonts.googleapis.com/css?family=PT+Serif|PT+Serif:b|PT+Serif:i|PT+Sans|PT+Sans:b);
  3. html {
  4. min-width: 1040px;
  5. }
  6. .ocks-org body {
  7. background: #fcfcfa;
  8. color: #333;
  9. font-family: "PT Serif", serif;
  10. margin: 1em auto 4em auto;
  11. position: relative;
  12. width: 960px;
  13. }
  14. .ocks-org header,
  15. .ocks-org footer,
  16. .ocks-org aside,
  17. .ocks-org h1,
  18. .ocks-org h2,
  19. .ocks-org h3,
  20. .ocks-org h4 {
  21. font-family: "PT Sans", sans-serif;
  22. }
  23. .ocks-org h1,
  24. .ocks-org h2,
  25. .ocks-org h3,
  26. .ocks-org h4 {
  27. color: #000;
  28. }
  29. .ocks-org header,
  30. .ocks-org footer {
  31. color: #636363;
  32. }
  33. h1 {
  34. font-size: 64px;
  35. font-weight: 300;
  36. letter-spacing: -2px;
  37. margin: .3em 0 .1em 0;
  38. }
  39. h2 {
  40. margin-top: 2em;
  41. }
  42. h1, h2 {
  43. text-rendering: optimizeLegibility;
  44. }
  45. h2 a[name],
  46. h2 a[id] {
  47. color: #ccc;
  48. right: 100%;
  49. padding: 0 .3em;
  50. position: absolute;
  51. }
  52. header,
  53. footer {
  54. font-size: small;
  55. }
  56. .ocks-org header aside,
  57. .ocks-org footer aside {
  58. float: left;
  59. margin-right: .5em;
  60. }
  61. .ocks-org header aside:after,
  62. .ocks-org footer aside:after {
  63. padding-left: .5em;
  64. content: "/";
  65. }
  66. footer {
  67. margin-top: 8em;
  68. }
  69. h1 ~ aside {
  70. font-size: small;
  71. right: 0;
  72. position: absolute;
  73. width: 180px;
  74. }
  75. .attribution {
  76. font-size: small;
  77. margin-bottom: 2em;
  78. }
  79. body > p, li > p {
  80. line-height: 1.5em;
  81. }
  82. body > p {
  83. width: 720px;
  84. }
  85. body > blockquote {
  86. width: 640px;
  87. }
  88. blockquote q {
  89. display: block;
  90. font-style: oblique;
  91. }
  92. ul {
  93. padding: 0;
  94. }
  95. li {
  96. width: 690px;
  97. margin-left: 30px;
  98. }
  99. a {
  100. color: steelblue;
  101. }
  102. a:not(:hover) {
  103. text-decoration: none;
  104. }
  105. pre, code, textarea {
  106. font-family: "Menlo", monospace;
  107. }
  108. code {
  109. line-height: 1em;
  110. }
  111. textarea {
  112. font-size: 100%;
  113. }
  114. body > pre {
  115. border-left: solid 2px #ccc;
  116. padding-left: 18px;
  117. margin: 2em 0 2em -20px;
  118. }
  119. .html .value,
  120. .javascript .string,
  121. .javascript .regexp {
  122. color: #756bb1;
  123. }
  124. .html .tag,
  125. .css .tag,
  126. .javascript .keyword {
  127. color: #3182bd;
  128. }
  129. .comment {
  130. color: #636363;
  131. }
  132. .html .doctype,
  133. .javascript .number {
  134. color: #31a354;
  135. }
  136. .html .attribute,
  137. .css .attribute,
  138. .javascript .class,
  139. .javascript .special {
  140. color: #e6550d;
  141. }
  142. svg {
  143. font: 10px sans-serif;
  144. }
  145. .axis path, .axis line {
  146. fill: none;
  147. stroke: #000;
  148. shape-rendering: crispEdges;
  149. }
  150. sup, sub {
  151. line-height: 0;
  152. }
  153. q:before {
  154. content: "“";
  155. }
  156. q:after {
  157. content: "”";
  158. }
  159. blockquote q {
  160. line-height: 1.5em;
  161. display: inline;
  162. }
  163. blockquote q:before,
  164. blockquote q:after {
  165. content: "";
  166. }

源文件:sankey.js

  1. d3.sankey = function() {
  2. var sankey = {},
  3. nodeWidth = 24,
  4. nodePadding = 8,
  5. size = [1, 1],
  6. nodes = [],
  7. links = [];
  8. sankey.nodeWidth = function(_) {
  9. if (!arguments.length) return nodeWidth;
  10. nodeWidth = +_;
  11. return sankey;
  12. };
  13. sankey.nodePadding = function(_) {
  14. if (!arguments.length) return nodePadding;
  15. nodePadding = +_;
  16. return sankey;
  17. };
  18. sankey.nodes = function(_) {
  19. if (!arguments.length) return nodes;
  20. nodes = _;
  21. return sankey;
  22. };
  23. sankey.links = function(_) {
  24. if (!arguments.length) return links;
  25. links = _;
  26. return sankey;
  27. };
  28. sankey.size = function(_) {
  29. if (!arguments.length) return size;
  30. size = _;
  31. return sankey;
  32. };
  33. sankey.layout = function(iterations) {
  34. computeNodeLinks();
  35. computeNodeValues();
  36. computeNodeBreadths();
  37. computeNodeDepths(iterations);
  38. computeLinkDepths();
  39. return sankey;
  40. };
  41. sankey.relayout = function() {
  42. computeLinkDepths();
  43. return sankey;
  44. };
  45. sankey.link = function() {
  46. var curvature = .5;
  47. function link(d) {
  48. var x0 = d.source.x + d.source.dx,
  49. x1 = d.target.x,
  50. xi = d3.interpolateNumber(x0, x1),
  51. x2 = xi(curvature),
  52. x3 = xi(1 - curvature),
  53. y0 = d.source.y + d.sy + d.dy / 2,
  54. y1 = d.target.y + d.ty + d.dy / 2;
  55. return "M" + x0 + "," + y0
  56. + "C" + x2 + "," + y0
  57. + " " + x3 + "," + y1
  58. + " " + x1 + "," + y1;
  59. }
  60. link.curvature = function(_) {
  61. if (!arguments.length) return curvature;
  62. curvature = +_;
  63. return link;
  64. };
  65. return link;
  66. };
  67. // Populate the sourceLinks and targetLinks for each node.
  68. // Also, if the source and target are not objects, assume they are indices.
  69. function computeNodeLinks() {
  70. nodes.forEach(function(node) {
  71. node.sourceLinks = [];
  72. node.targetLinks = [];
  73. });
  74. links.forEach(function(link) {
  75. var source = link.source,
  76. target = link.target;
  77. if (typeof source === "number") source = link.source = nodes[link.source];
  78. if (typeof target === "number") target = link.target = nodes[link.target];
  79. source.sourceLinks.push(link);
  80. target.targetLinks.push(link);
  81. });
  82. }
  83. // Compute the value (size) of each node by summing the associated links.
  84. function computeNodeValues() {
  85. nodes.forEach(function(node) {
  86. node.value = Math.max(
  87. d3.sum(node.sourceLinks, value),
  88. d3.sum(node.targetLinks, value)
  89. );
  90. });
  91. }
  92. // Iteratively assign the breadth (x-position) for each node.
  93. // Nodes are assigned the maximum breadth of incoming neighbors plus one;
  94. // nodes with no incoming links are assigned breadth zero, while
  95. // nodes with no outgoing links are assigned the maximum breadth.
  96. function computeNodeBreadths() {
  97. var remainingNodes = nodes,
  98. nextNodes,
  99. x = 0;
  100. while (remainingNodes.length) {
  101. nextNodes = [];
  102. remainingNodes.forEach(function(node) {
  103. node.x = x;
  104. node.dx = nodeWidth;
  105. node.sourceLinks.forEach(function(link) {
  106. nextNodes.push(link.target);
  107. });
  108. });
  109. remainingNodes = nextNodes;
  110. ++x;
  111. }
  112. //
  113. moveSinksRight(x);
  114. scaleNodeBreadths((width - nodeWidth) / (x - 1));
  115. }
  116. function moveSourcesRight() {
  117. nodes.forEach(function(node) {
  118. if (!node.targetLinks.length) {
  119. node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
  120. }
  121. });
  122. }
  123. function moveSinksRight(x) {
  124. nodes.forEach(function(node) {
  125. if (!node.sourceLinks.length) {
  126. node.x = x - 1;
  127. }
  128. });
  129. }
  130. function scaleNodeBreadths(kx) {
  131. nodes.forEach(function(node) {
  132. node.x *= kx;
  133. });
  134. }
  135. function computeNodeDepths(iterations) {
  136. var nodesByBreadth = d3.nest()
  137. .key(function(d) { return d.x; })
  138. .sortKeys(d3.ascending)
  139. .entries(nodes)
  140. .map(function(d) { return d.values; });
  141. //
  142. initializeNodeDepth();
  143. resolveCollisions();
  144. for (var alpha = 1; iterations > 0; --iterations) {
  145. relaxRightToLeft(alpha *= .99);
  146. resolveCollisions();
  147. relaxLeftToRight(alpha);
  148. resolveCollisions();
  149. }
  150. function initializeNodeDepth() {
  151. var ky = d3.min(nodesByBreadth, function(nodes) {
  152. return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
  153. });
  154. nodesByBreadth.forEach(function(nodes) {
  155. nodes.forEach(function(node, i) {
  156. node.y = i;
  157. node.dy = node.value * ky;
  158. });
  159. });
  160. links.forEach(function(link) {
  161. link.dy = link.value * ky;
  162. });
  163. }
  164. function relaxLeftToRight(alpha) {
  165. nodesByBreadth.forEach(function(nodes, breadth) {
  166. nodes.forEach(function(node) {
  167. if (node.targetLinks.length) {
  168. var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
  169. node.y += (y - center(node)) * alpha;
  170. }
  171. });
  172. });
  173. function weightedSource(link) {
  174. return center(link.source) * link.value;
  175. }
  176. }
  177. function relaxRightToLeft(alpha) {
  178. nodesByBreadth.slice().reverse().forEach(function(nodes) {
  179. nodes.forEach(function(node) {
  180. if (node.sourceLinks.length) {
  181. var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
  182. node.y += (y - center(node)) * alpha;
  183. }
  184. });
  185. });
  186. function weightedTarget(link) {
  187. return center(link.target) * link.value;
  188. }
  189. }
  190. function resolveCollisions() {
  191. nodesByBreadth.forEach(function(nodes) {
  192. var node,
  193. dy,
  194. y0 = 0,
  195. n = nodes.length,
  196. i;
  197. // Push any overlapping nodes down.
  198. nodes.sort(ascendingDepth);
  199. for (i = 0; i < n; ++i) {
  200. node = nodes[i];
  201. dy = y0 - node.y;
  202. if (dy > 0) node.y += dy;
  203. y0 = node.y + node.dy + nodePadding;
  204. }
  205. // If the bottommost node goes outside the bounds, push it back up.
  206. dy = y0 - nodePadding - size[1];
  207. if (dy > 0) {
  208. y0 = node.y -= dy;
  209. // Push any overlapping nodes back up.
  210. for (i = n - 2; i >= 0; --i) {
  211. node = nodes[i];
  212. dy = node.y + node.dy + nodePadding - y0;
  213. if (dy > 0) node.y -= dy;
  214. y0 = node.y;
  215. }
  216. }
  217. });
  218. }
  219. function ascendingDepth(a, b) {
  220. return a.y - b.y;
  221. }
  222. }
  223. function computeLinkDepths() {
  224. nodes.forEach(function(node) {
  225. node.sourceLinks.sort(ascendingTargetDepth);
  226. node.targetLinks.sort(ascendingSourceDepth);
  227. });
  228. nodes.forEach(function(node) {
  229. var sy = 0, ty = 0;
  230. node.sourceLinks.forEach(function(link) {
  231. link.sy = sy;
  232. sy += link.dy;
  233. });
  234. node.targetLinks.forEach(function(link) {
  235. link.ty = ty;
  236. ty += link.dy;
  237. });
  238. });
  239. function ascendingSourceDepth(a, b) {
  240. return a.source.y - b.source.y;
  241. }
  242. function ascendingTargetDepth(a, b) {
  243. return a.target.y - b.target.y;
  244. }
  245. }
  246. function center(node) {
  247. return node.y + node.dy / 2;
  248. }
  249. function value(link) {
  250. return link.value;
  251. }
  252. return sankey;
  253. };

源文件:energy.json

  1. {"nodes":[
  2. {"name":"Agricultural 'waste'"},
  3. {"name":"Bio-conversion"},
  4. {"name":"Liquid"},
  5. {"name":"Losses"},
  6. {"name":"Solid"},
  7. {"name":"Gas"},
  8. {"name":"Biofuel imports"},
  9. {"name":"Biomass imports"},
  10. {"name":"Coal imports"},
  11. {"name":"Coal"},
  12. {"name":"Coal reserves"},
  13. {"name":"District heating"},
  14. {"name":"Industry"},
  15. {"name":"Heating and cooling - commercial"},
  16. {"name":"Heating and cooling - homes"},
  17. {"name":"Electricity grid"},
  18. {"name":"Over generation / exports"},
  19. {"name":"H2 conversion"},
  20. {"name":"Road transport"},
  21. {"name":"Agriculture"},
  22. {"name":"Rail transport"},
  23. {"name":"Lighting & appliances - commercial"},
  24. {"name":"Lighting & appliances - homes"},
  25. {"name":"Gas imports"},
  26. {"name":"Ngas"},
  27. {"name":"Gas reserves"},
  28. {"name":"Thermal generation"},
  29. {"name":"Geothermal"},
  30. {"name":"H2"},
  31. {"name":"Hydro"},
  32. {"name":"International shipping"},
  33. {"name":"Domestic aviation"},
  34. {"name":"International aviation"},
  35. {"name":"National navigation"},
  36. {"name":"Marine algae"},
  37. {"name":"Nuclear"},
  38. {"name":"Oil imports"},
  39. {"name":"Oil"},
  40. {"name":"Oil reserves"},
  41. {"name":"Other waste"},
  42. {"name":"Pumped heat"},
  43. {"name":"Solar PV"},
  44. {"name":"Solar Thermal"},
  45. {"name":"Solar"},
  46. {"name":"Tidal"},
  47. {"name":"UK land based bioenergy"},
  48. {"name":"Wave"},
  49. {"name":"Wind"}
  50. ],
  51. "links":[
  52. {"source":0,"target":1,"value":124.729},
  53. {"source":1,"target":2,"value":0.597},
  54. {"source":1,"target":3,"value":26.862},
  55. {"source":1,"target":4,"value":280.322},
  56. {"source":1,"target":5,"value":81.144},
  57. {"source":6,"target":2,"value":35},
  58. {"source":7,"target":4,"value":35},
  59. {"source":8,"target":9,"value":11.606},
  60. {"source":10,"target":9,"value":63.965},
  61. {"source":9,"target":4,"value":75.571},
  62. {"source":11,"target":12,"value":10.639},
  63. {"source":11,"target":13,"value":22.505},
  64. {"source":11,"target":14,"value":46.184},
  65. {"source":15,"target":16,"value":104.453},
  66. {"source":15,"target":14,"value":113.726},
  67. {"source":15,"target":17,"value":27.14},
  68. {"source":15,"target":12,"value":342.165},
  69. {"source":15,"target":18,"value":37.797},
  70. {"source":15,"target":19,"value":4.412},
  71. {"source":15,"target":13,"value":40.858},
  72. {"source":15,"target":3,"value":56.691},
  73. {"source":15,"target":20,"value":7.863},
  74. {"source":15,"target":21,"value":90.008},
  75. {"source":15,"target":22,"value":93.494},
  76. {"source":23,"target":24,"value":40.719},
  77. {"source":25,"target":24,"value":82.233},
  78. {"source":5,"target":13,"value":0.129},
  79. {"source":5,"target":3,"value":1.401},
  80. {"source":5,"target":26,"value":151.891},
  81. {"source":5,"target":19,"value":2.096},
  82. {"source":5,"target":12,"value":48.58},
  83. {"source":27,"target":15,"value":7.013},
  84. {"source":17,"target":28,"value":20.897},
  85. {"source":17,"target":3,"value":6.242},
  86. {"source":28,"target":18,"value":20.897},
  87. {"source":29,"target":15,"value":6.995},
  88. {"source":2,"target":12,"value":121.066},
  89. {"source":2,"target":30,"value":128.69},
  90. {"source":2,"target":18,"value":135.835},
  91. {"source":2,"target":31,"value":14.458},
  92. {"source":2,"target":32,"value":206.267},
  93. {"source":2,"target":19,"value":3.64},
  94. {"source":2,"target":33,"value":33.218},
  95. {"source":2,"target":20,"value":4.413},
  96. {"source":34,"target":1,"value":4.375},
  97. {"source":24,"target":5,"value":122.952},
  98. {"source":35,"target":26,"value":839.978},
  99. {"source":36,"target":37,"value":504.287},
  100. {"source":38,"target":37,"value":107.703},
  101. {"source":37,"target":2,"value":611.99},
  102. {"source":39,"target":4,"value":56.587},
  103. {"source":39,"target":1,"value":77.81},
  104. {"source":40,"target":14,"value":193.026},
  105. {"source":40,"target":13,"value":70.672},
  106. {"source":41,"target":15,"value":59.901},
  107. {"source":42,"target":14,"value":19.263},
  108. {"source":43,"target":42,"value":19.263},
  109. {"source":43,"target":41,"value":59.901},
  110. {"source":4,"target":19,"value":0.882},
  111. {"source":4,"target":26,"value":400.12},
  112. {"source":4,"target":12,"value":46.477},
  113. {"source":26,"target":15,"value":525.531},
  114. {"source":26,"target":3,"value":787.129},
  115. {"source":26,"target":11,"value":79.329},
  116. {"source":44,"target":15,"value":9.452},
  117. {"source":45,"target":1,"value":182.01},
  118. {"source":46,"target":15,"value":19.013},
  119. {"source":47,"target":15,"value":289.366}
  120. ]}