概要 实例 介绍 源码

子弹图

源文件:index.html

  1. <!DOCTYPE html>
  2. <meta charset="UTF-8">
  3. <style>
  4. body {
  5. font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  6. margin: auto;
  7. padding-top: 40px;
  8. position: relative;
  9. width: 960px;
  10. }
  11. button {
  12. position: absolute;
  13. right: 10px;
  14. top: 10px;
  15. }
  16. .bullet { font: 10px sans-serif; }
  17. .bullet .marker { stroke: #000; stroke-width: 2px; }
  18. .bullet .tick line { stroke: #666; stroke-width: .5px; }
  19. .bullet .range.s0 { fill: #eee; }
  20. .bullet .range.s1 { fill: #ddd; }
  21. .bullet .range.s2 { fill: #ccc; }
  22. .bullet .measure.s0 { fill: lightsteelblue; }
  23. .bullet .measure.s1 { fill: steelblue; }
  24. .bullet .title { font-size: 14px; font-weight: bold; }
  25. .bullet .subtitle { fill: #999; }
  26. </style>
  27. <button>Update</button>
  28. <script src="//d3js.org/d3.v3.min.js"></script>
  29. <script src="bullet.js"></script>
  30. <script>
  31. var margin = {top: 5, right: 40, bottom: 20, left: 120},
  32. width = 960 - margin.left - margin.right,
  33. height = 50 - margin.top - margin.bottom;
  34. var chart = d3.bullet()
  35. .width(width)
  36. .height(height);
  37. d3.json("bullets.json", function(error, data) {
  38. if (error) throw error;
  39. var svg = d3.select("body").selectAll("svg")
  40. .data(data)
  41. .enter().append("svg")
  42. .attr("class", "bullet")
  43. .attr("width", width + margin.left + margin.right)
  44. .attr("height", height + margin.top + margin.bottom)
  45. .append("g")
  46. .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
  47. .call(chart);
  48. var title = svg.append("g")
  49. .style("text-anchor", "end")
  50. .attr("transform", "translate(-6," + height / 2 + ")");
  51. title.append("text")
  52. .attr("class", "title")
  53. .text(function(d) { return d.title; });
  54. title.append("text")
  55. .attr("class", "subtitle")
  56. .attr("dy", "1em")
  57. .text(function(d) { return d.subtitle; });
  58. d3.selectAll("button").on("click", function() {
  59. svg.datum(randomize).call(chart.duration(1000)); // TODO automatic transition
  60. });
  61. });
  62. function randomize(d) {
  63. if (!d.randomizer) d.randomizer = randomizer(d);
  64. d.ranges = d.ranges.map(d.randomizer);
  65. d.markers = d.markers.map(d.randomizer);
  66. d.measures = d.measures.map(d.randomizer);
  67. return d;
  68. }
  69. function randomizer(d) {
  70. var k = d3.max(d.ranges) * .2;
  71. return function(d) {
  72. return Math.max(0, d + k * (Math.random() - .5));
  73. };
  74. }
  75. </script>

源文件:bullet.js

  1. (function() {
  2. // Chart design based on the recommendations of Stephen Few. Implementation
  3. // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
  4. // http://projects.instantcognition.com/protovis/bulletchart/
  5. d3.bullet = function() {
  6. var orient = "left", // TODO top & bottom
  7. reverse = false,
  8. duration = 0,
  9. ranges = bulletRanges,
  10. markers = bulletMarkers,
  11. measures = bulletMeasures,
  12. width = 380,
  13. height = 30,
  14. tickFormat = null;
  15. // For each small multiple…
  16. function bullet(g) {
  17. g.each(function(d, i) {
  18. var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
  19. markerz = markers.call(this, d, i).slice().sort(d3.descending),
  20. measurez = measures.call(this, d, i).slice().sort(d3.descending),
  21. g = d3.select(this);
  22. // Compute the new x-scale.
  23. var x1 = d3.scale.linear()
  24. .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
  25. .range(reverse ? [width, 0] : [0, width]);
  26. // Retrieve the old x-scale, if this is an update.
  27. var x0 = this.__chart__ || d3.scale.linear()
  28. .domain([0, Infinity])
  29. .range(x1.range());
  30. // Stash the new scale.
  31. this.__chart__ = x1;
  32. // Derive width-scales from the x-scales.
  33. var w0 = bulletWidth(x0),
  34. w1 = bulletWidth(x1);
  35. // Update the range rects.
  36. var range = g.selectAll("rect.range")
  37. .data(rangez);
  38. range.enter().append("rect")
  39. .attr("class", function(d, i) { return "range s" + i; })
  40. .attr("width", w0)
  41. .attr("height", height)
  42. .attr("x", reverse ? x0 : 0)
  43. .transition()
  44. .duration(duration)
  45. .attr("width", w1)
  46. .attr("x", reverse ? x1 : 0);
  47. range.transition()
  48. .duration(duration)
  49. .attr("x", reverse ? x1 : 0)
  50. .attr("width", w1)
  51. .attr("height", height);
  52. // Update the measure rects.
  53. var measure = g.selectAll("rect.measure")
  54. .data(measurez);
  55. measure.enter().append("rect")
  56. .attr("class", function(d, i) { return "measure s" + i; })
  57. .attr("width", w0)
  58. .attr("height", height / 3)
  59. .attr("x", reverse ? x0 : 0)
  60. .attr("y", height / 3)
  61. .transition()
  62. .duration(duration)
  63. .attr("width", w1)
  64. .attr("x", reverse ? x1 : 0);
  65. measure.transition()
  66. .duration(duration)
  67. .attr("width", w1)
  68. .attr("height", height / 3)
  69. .attr("x", reverse ? x1 : 0)
  70. .attr("y", height / 3);
  71. // Update the marker lines.
  72. var marker = g.selectAll("line.marker")
  73. .data(markerz);
  74. marker.enter().append("line")
  75. .attr("class", "marker")
  76. .attr("x1", x0)
  77. .attr("x2", x0)
  78. .attr("y1", height / 6)
  79. .attr("y2", height * 5 / 6)
  80. .transition()
  81. .duration(duration)
  82. .attr("x1", x1)
  83. .attr("x2", x1);
  84. marker.transition()
  85. .duration(duration)
  86. .attr("x1", x1)
  87. .attr("x2", x1)
  88. .attr("y1", height / 6)
  89. .attr("y2", height * 5 / 6);
  90. // Compute the tick format.
  91. var format = tickFormat || x1.tickFormat(8);
  92. // Update the tick groups.
  93. var tick = g.selectAll("g.tick")
  94. .data(x1.ticks(8), function(d) {
  95. return this.textContent || format(d);
  96. });
  97. // Initialize the ticks with the old scale, x0.
  98. var tickEnter = tick.enter().append("g")
  99. .attr("class", "tick")
  100. .attr("transform", bulletTranslate(x0))
  101. .style("opacity", 1e-6);
  102. tickEnter.append("line")
  103. .attr("y1", height)
  104. .attr("y2", height * 7 / 6);
  105. tickEnter.append("text")
  106. .attr("text-anchor", "middle")
  107. .attr("dy", "1em")
  108. .attr("y", height * 7 / 6)
  109. .text(format);
  110. // Transition the entering ticks to the new scale, x1.
  111. tickEnter.transition()
  112. .duration(duration)
  113. .attr("transform", bulletTranslate(x1))
  114. .style("opacity", 1);
  115. // Transition the updating ticks to the new scale, x1.
  116. var tickUpdate = tick.transition()
  117. .duration(duration)
  118. .attr("transform", bulletTranslate(x1))
  119. .style("opacity", 1);
  120. tickUpdate.select("line")
  121. .attr("y1", height)
  122. .attr("y2", height * 7 / 6);
  123. tickUpdate.select("text")
  124. .attr("y", height * 7 / 6);
  125. // Transition the exiting ticks to the new scale, x1.
  126. tick.exit().transition()
  127. .duration(duration)
  128. .attr("transform", bulletTranslate(x1))
  129. .style("opacity", 1e-6)
  130. .remove();
  131. });
  132. d3.timer.flush();
  133. }
  134. // left, right, top, bottom
  135. bullet.orient = function(x) {
  136. if (!arguments.length) return orient;
  137. orient = x;
  138. reverse = orient == "right" || orient == "bottom";
  139. return bullet;
  140. };
  141. // ranges (bad, satisfactory, good)
  142. bullet.ranges = function(x) {
  143. if (!arguments.length) return ranges;
  144. ranges = x;
  145. return bullet;
  146. };
  147. // markers (previous, goal)
  148. bullet.markers = function(x) {
  149. if (!arguments.length) return markers;
  150. markers = x;
  151. return bullet;
  152. };
  153. // measures (actual, forecast)
  154. bullet.measures = function(x) {
  155. if (!arguments.length) return measures;
  156. measures = x;
  157. return bullet;
  158. };
  159. bullet.width = function(x) {
  160. if (!arguments.length) return width;
  161. width = x;
  162. return bullet;
  163. };
  164. bullet.height = function(x) {
  165. if (!arguments.length) return height;
  166. height = x;
  167. return bullet;
  168. };
  169. bullet.tickFormat = function(x) {
  170. if (!arguments.length) return tickFormat;
  171. tickFormat = x;
  172. return bullet;
  173. };
  174. bullet.duration = function(x) {
  175. if (!arguments.length) return duration;
  176. duration = x;
  177. return bullet;
  178. };
  179. return bullet;
  180. };
  181. function bulletRanges(d) {
  182. return d.ranges;
  183. }
  184. function bulletMarkers(d) {
  185. return d.markers;
  186. }
  187. function bulletMeasures(d) {
  188. return d.measures;
  189. }
  190. function bulletTranslate(x) {
  191. return function(d) {
  192. return "translate(" + x(d) + ",0)";
  193. };
  194. }
  195. function bulletWidth(x) {
  196. var x0 = x(0);
  197. return function(d) {
  198. return Math.abs(x(d) - x0);
  199. };
  200. }
  201. })();

源文件:bullets.json

  1. [
  2. {"title":"Revenue","subtitle":"US$, in thousands","ranges":[150,225,300],"measures":[220,270],"markers":[250]},
  3. {"title":"Profit","subtitle":"%","ranges":[20,25,30],"measures":[21,23],"markers":[26]},
  4. {"title":"Order Size","subtitle":"US$, average","ranges":[350,500,600],"measures":[100,320],"markers":[550]},
  5. {"title":"New Customers","subtitle":"count","ranges":[1400,2000,2500],"measures":[1000,1650],"markers":[2100]},
  6. {"title":"Satisfaction","subtitle":"out of 5","ranges":[3.5,4.25,5],"measures":[3.2,4.7],"markers":[4.4]}
  7. ]