bullets.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. /* ------------------------------------------------------------------------------
  2. *
  3. * # D3.js - horizontal bar chart
  4. *
  5. * Demo d3.js horizontal bar chart setup with .csv data source
  6. *
  7. * ---------------------------------------------------------------------------- */
  8. // Setup module
  9. // ------------------------------
  10. var DashboardBullets = function() {
  11. //
  12. // Setup module components
  13. //
  14. // Bullet chart
  15. var _BulletChart = function(element, height) {
  16. if (typeof d3 == 'undefined') {
  17. console.warn('Warning - d3.min.js is not loaded.');
  18. return;
  19. }
  20. // Initialize chart only if element exsists in the DOM
  21. if($(element).length > 0) {
  22. // Bullet chart core
  23. // ------------------------------
  24. function bulletCore() {
  25. // Construct
  26. d3.bullet = function() {
  27. // Default layout variables
  28. var orient = 'left',
  29. reverse = false,
  30. duration = 750,
  31. ranges = bulletRanges,
  32. markers = bulletMarkers,
  33. measures = bulletMeasures,
  34. height = 30,
  35. tickFormat = null;
  36. // For each small multiple…
  37. function bullet(g) {
  38. g.each(function(d, i) {
  39. // Define variables
  40. var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
  41. markerz = markers.call(this, d, i).slice().sort(d3.descending),
  42. measurez = measures.call(this, d, i).slice().sort(d3.descending),
  43. g = d3.select(this);
  44. // Compute the new x-scale.
  45. var x1 = d3.scale.linear()
  46. .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
  47. .range(reverse ? [width, 0] : [0, width]);
  48. // Retrieve the old x-scale, if this is an update.
  49. var x0 = this.__chart__ || d3.scale.linear()
  50. .domain([0, Infinity])
  51. .range(x1.range());
  52. // Stash the new scale.
  53. this.__chart__ = x1;
  54. // Derive width-scales from the x-scales.
  55. var w0 = bulletWidth(x0),
  56. w1 = bulletWidth(x1);
  57. // Setup range
  58. // ------------------------------
  59. // Update the range rects
  60. var range = g.selectAll('.bullet-range')
  61. .data(rangez);
  62. // Append range rect
  63. range.enter()
  64. .append('rect')
  65. .attr('class', function(d, i) { return 'bullet-range bullet-range-' + (i + 1); })
  66. .attr('width', w0)
  67. .attr('height', height)
  68. .attr('rx', 2)
  69. .attr('x', reverse ? x0 : 0)
  70. // Add loading animation
  71. .transition()
  72. .duration(duration)
  73. .attr('width', w1)
  74. .attr('x', reverse ? x1 : 0);
  75. // Add update animation
  76. range.transition()
  77. .duration(duration)
  78. .attr('x', reverse ? x1 : 0)
  79. .attr('width', w1)
  80. .attr('height', height);
  81. // Setup measures
  82. // ------------------------------
  83. // Update the measure rects
  84. var measure = g.selectAll('.bullet-measure')
  85. .data(measurez);
  86. // Append measure rect
  87. measure.enter()
  88. .append('rect')
  89. .attr('class', function(d, i) { return 'bullet-measure bullet-measure-' + (i + 1); })
  90. .attr('width', w0)
  91. .attr('height', height / 5)
  92. .attr('x', reverse ? x0 : 0)
  93. .attr('y', height / 2.5)
  94. .style('shape-rendering', 'crispEdges');
  95. // Add loading animation
  96. measure.transition()
  97. .duration(duration)
  98. .attr('width', w1)
  99. .attr('x', reverse ? x1 : 0);
  100. // Add update animation
  101. measure.transition()
  102. .duration(duration)
  103. .attr('width', w1)
  104. .attr('height', height / 5)
  105. .attr('x', reverse ? x1 : 0)
  106. .attr('y', height / 2.5);
  107. // Setup markers
  108. // ------------------------------
  109. // Update the marker lines
  110. var marker = g.selectAll('.bullet-marker')
  111. .data(markerz);
  112. // Append marker line
  113. marker.enter()
  114. .append('line')
  115. .attr('class', function(d, i) { return 'bullet-marker bullet-marker-' + (i + 1); })
  116. .attr('x1', x0)
  117. .attr('x2', x0)
  118. .attr('y1', height / 6)
  119. .attr('y2', height * 5 / 6);
  120. // Add loading animation
  121. marker.transition()
  122. .duration(duration)
  123. .attr('x1', x1)
  124. .attr('x2', x1);
  125. // Add update animation
  126. marker.transition()
  127. .duration(duration)
  128. .attr('x1', x1)
  129. .attr('x2', x1)
  130. .attr('y1', height / 6)
  131. .attr('y2', height * 5 / 6);
  132. // Setup axes
  133. // ------------------------------
  134. // Compute the tick format.
  135. var format = tickFormat || x1.tickFormat(8);
  136. // Update the tick groups.
  137. var tick = g.selectAll('.bullet-tick')
  138. .data(x1.ticks(8), function(d) {
  139. return this.textContent || format(d);
  140. });
  141. // Initialize the ticks with the old scale, x0.
  142. var tickEnter = tick.enter()
  143. .append('g')
  144. .attr('class', 'bullet-tick')
  145. .attr('transform', bulletTranslate(x0))
  146. .style('opacity', 1e-6);
  147. // Append line
  148. tickEnter.append('line')
  149. .attr('y1', height)
  150. .attr('y2', (height * 7 / 6) + 3);
  151. // Append text
  152. tickEnter.append('text')
  153. .attr('text-anchor', 'middle')
  154. .attr('dy', '1em')
  155. .attr('y', (height * 7 / 6) + 4)
  156. .text(format);
  157. // Transition the entering ticks to the new scale, x1.
  158. tickEnter.transition()
  159. .duration(duration)
  160. .attr('transform', bulletTranslate(x1))
  161. .style('opacity', 1);
  162. // Transition the updating ticks to the new scale, x1.
  163. var tickUpdate = tick.transition()
  164. .duration(duration)
  165. .attr('transform', bulletTranslate(x1))
  166. .style('opacity', 1);
  167. // Update tick line
  168. tickUpdate.select('line')
  169. .attr('y1', height + 3)
  170. .attr('y2', (height * 7 / 6) + 3);
  171. // Update tick text
  172. tickUpdate.select('text')
  173. .attr('y', (height * 7 / 6) + 4);
  174. // Transition the exiting ticks to the new scale, x1.
  175. tick.exit()
  176. .transition()
  177. .duration(duration)
  178. .attr('transform', bulletTranslate(x1))
  179. .style('opacity', 1e-6)
  180. .remove();
  181. // Resize chart
  182. // ------------------------------
  183. // Call function on window resize
  184. window.addEventListener('resize', resizeBulletsCore);
  185. // Call function on sidebar width change
  186. var sidebarToggle = document.querySelector('.sidebar-control');
  187. sidebarToggle && sidebarToggle.addEventListener('click', resizeBulletsCore);
  188. // Resize function
  189. //
  190. // Since D3 doesn't support SVG resize by default,
  191. // we need to manually specify parts of the graph that need to
  192. // be updated on window resize
  193. function resizeBulletsCore() {
  194. // Layout variables
  195. width = d3.select('#bullets').node().getBoundingClientRect().width - margin.left - margin.right;
  196. w1 = bulletWidth(x1);
  197. // Layout
  198. // -------------------------
  199. // Horizontal range
  200. x1.range(reverse ? [width, 0] : [0, width]);
  201. // Chart elements
  202. // -------------------------
  203. // Measures
  204. g.selectAll('.bullet-measure').attr('width', w1).attr('x', reverse ? x1 : 0);
  205. // Ranges
  206. g.selectAll('.bullet-range').attr('width', w1).attr('x', reverse ? x1 : 0);
  207. // Markers
  208. g.selectAll('.bullet-marker').attr('x1', x1).attr('x2', x1)
  209. // Ticks
  210. g.selectAll('.bullet-tick').attr('transform', bulletTranslate(x1))
  211. }
  212. });
  213. d3.timer.flush();
  214. }
  215. // Constructor functions
  216. // ------------------------------
  217. // Left, right, top, bottom
  218. bullet.orient = function(x) {
  219. if (!arguments.length) return orient;
  220. orient = x;
  221. reverse = orient == 'right' || orient == 'bottom';
  222. return bullet;
  223. };
  224. // Ranges (bad, satisfactory, good)
  225. bullet.ranges = function(x) {
  226. if (!arguments.length) return ranges;
  227. ranges = x;
  228. return bullet;
  229. };
  230. // Markers (previous, goal)
  231. bullet.markers = function(x) {
  232. if (!arguments.length) return markers;
  233. markers = x;
  234. return bullet;
  235. };
  236. // Measures (actual, forecast)
  237. bullet.measures = function(x) {
  238. if (!arguments.length) return measures;
  239. measures = x;
  240. return bullet;
  241. };
  242. // Width
  243. bullet.width = function(x) {
  244. if (!arguments.length) return width;
  245. width = x;
  246. return bullet;
  247. };
  248. // Height
  249. bullet.height = function(x) {
  250. if (!arguments.length) return height;
  251. height = x;
  252. return bullet;
  253. };
  254. // Axex tick format
  255. bullet.tickFormat = function(x) {
  256. if (!arguments.length) return tickFormat;
  257. tickFormat = x;
  258. return bullet;
  259. };
  260. // Transition duration
  261. bullet.duration = function(x) {
  262. if (!arguments.length) return duration;
  263. duration = x;
  264. return bullet;
  265. };
  266. return bullet;
  267. };
  268. // Ranges
  269. function bulletRanges(d) {
  270. return d.ranges;
  271. }
  272. // Markers
  273. function bulletMarkers(d) {
  274. return d.markers;
  275. }
  276. // Measures
  277. function bulletMeasures(d) {
  278. return d.measures;
  279. }
  280. // Positioning
  281. function bulletTranslate(x) {
  282. return function(d) {
  283. return 'translate(' + x(d) + ',0)';
  284. };
  285. }
  286. // Width
  287. function bulletWidth(x) {
  288. var x0 = x(0);
  289. return function(d) {
  290. return Math.abs(x(d) - x0);
  291. };
  292. }
  293. }
  294. bulletCore();
  295. // Basic setup
  296. // ------------------------------
  297. // Main variables
  298. var d3Container = d3.select(element),
  299. margin = {top: 20, right: 10, bottom: 35, left: 10},
  300. width = width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right,
  301. height = height - margin.top - margin.bottom;
  302. // Construct chart layout
  303. // ------------------------------
  304. var chart = d3.bullet()
  305. .width(width)
  306. .height(height);
  307. // Load data
  308. // ------------------------------
  309. d3.json('../../../../global_assets/demo_data/dashboard/bullets.json', function(error, data) {
  310. // Show what's wrong if error
  311. if (error) return console.error(error);
  312. // Create SVG
  313. // ------------------------------
  314. // SVG container
  315. var container = d3Container.selectAll('svg')
  316. .data(data)
  317. .enter()
  318. .append('svg');
  319. // SVG group
  320. var svg = container
  321. .attr('class', function(d, i) { return 'bullet-' + (i + 1); })
  322. .attr('width', width + margin.left + margin.right)
  323. .attr('height', height + margin.top + margin.bottom)
  324. .append('g')
  325. .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
  326. .call(chart);
  327. // Add title
  328. // ------------------------------
  329. // Title group
  330. var title = svg.append('g')
  331. .style('text-anchor', 'start');
  332. // Bullet title text
  333. title.append('text')
  334. .attr('class', 'bullet-title')
  335. .attr('y', -10)
  336. .text(function(d) { return d.title; });
  337. // Bullet subtitle text
  338. title.append('text')
  339. .attr('class', 'bullet-subtitle')
  340. .attr('x', width)
  341. .attr('y', -10)
  342. .style('text-anchor', 'end')
  343. .text(function(d) { return d.subtitle; })
  344. .style('opacity', 0)
  345. .transition()
  346. .duration(500)
  347. .delay(500)
  348. .style('opacity', 0.75);
  349. // Add random transition for demo
  350. // ------------------------------
  351. // Bind data
  352. var interval = function() {
  353. svg.datum(randomize).call(chart.duration(750));
  354. }
  355. // Set interval
  356. var intervalIds = [];
  357. intervalIds.push(
  358. setInterval(function() {
  359. interval()
  360. }, 5000)
  361. );
  362. // Enable or disable real time update
  363. document.getElementById('realtime').onchange = function() {
  364. if(realtime.checked) {
  365. intervalIds.push(setInterval(function() { interval() }, 5000));
  366. }
  367. else {
  368. for (var i=0; i < intervalIds.length; i++) {
  369. clearInterval(intervalIds[i]);
  370. }
  371. }
  372. };
  373. // Resize chart
  374. // ------------------------------
  375. // Call function on window resize
  376. window.addEventListener('resize', bulletResize);
  377. // Call function on sidebar width change
  378. var sidebarToggle = document.querySelector('.sidebar-control');
  379. sidebarToggle && sidebarToggle.addEventListener('click', bulletResize);
  380. // Resize function
  381. //
  382. // Since D3 doesn't support SVG resize by default,
  383. // we need to manually specify parts of the graph that need to
  384. // be updated on window resize
  385. function bulletResize() {
  386. // Layout variables
  387. width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right;
  388. // Layout
  389. // -------------------------
  390. // Main svg width
  391. container.attr('width', width + margin.left + margin.right);
  392. // Width of appended group
  393. svg.attr('width', width + margin.left + margin.right);
  394. // Chart elements
  395. // -------------------------
  396. // Subtitle
  397. svg.selectAll('.bullet-subtitle').attr('x', width);
  398. }
  399. });
  400. // Randomizers
  401. // ------------------------------
  402. function randomize(d) {
  403. if (!d.randomizer) d.randomizer = randomizer(d);
  404. d.ranges = d.ranges.map(d.randomizer);
  405. d.markers = d.markers.map(d.randomizer);
  406. d.measures = d.measures.map(d.randomizer);
  407. return d;
  408. }
  409. function randomizer(d) {
  410. var k = d3.max(d.ranges) * .2;
  411. return function(d) {
  412. return Math.max(0, d + k * (Math.random() - .5));
  413. };
  414. }
  415. }
  416. };
  417. //
  418. // Return objects assigned to module
  419. //
  420. return {
  421. init: function() {
  422. _BulletChart("#bullets", 80);
  423. }
  424. }
  425. }();
  426. // Initialize module
  427. // ------------------------------
  428. document.addEventListener('DOMContentLoaded', function() {
  429. DashboardBullets.init();
  430. });