lines.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  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 DashboardLines = function() {
  11. //
  12. // Setup module components
  13. //
  14. // App sales line chart
  15. var _AppSalesLinesChart = function(element, height) {
  16. if (typeof d3 == 'undefined' || typeof d3.tip == '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. // Basic setup
  23. // ------------------------------
  24. // Define main variables
  25. var d3Container = d3.select(element),
  26. margin = {top: 5, right: 30, bottom: 30, left: 50},
  27. width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right,
  28. height = height - margin.top - margin.bottom;
  29. // Tooltip
  30. var tooltip = d3.tip()
  31. .attr('class', 'd3-tip')
  32. .html(function (d) {
  33. return '<ul class="list-unstyled mb-1">' +
  34. '<li>' + '<div class="font-size-base my-1"><i class="icon-circle-left2 mr-2"></i>' + d.name + ' app' + '</div>' + '</li>' +
  35. '<li>' + 'Sales: &nbsp;' + '<span class="font-weight-semibold float-right">' + d.value + '</span>' + '</li>' +
  36. '<li>' + 'Revenue: &nbsp; ' + '<span class="font-weight-semibold float-right">' + '$' + (d.value * 25).toFixed(2) + '</span>' + '</li>' +
  37. '</ul>';
  38. });
  39. // Format date
  40. var parseDate = d3.time.format('%Y/%m/%d').parse,
  41. formatDate = d3.time.format('%b %d, %y');
  42. // Line colors
  43. var scale = ['#4CAF50', '#FF5722', '#5C6BC0'],
  44. color = d3.scale.ordinal().range(scale);
  45. // Create chart
  46. // ------------------------------
  47. // Container
  48. var container = d3Container.append('svg');
  49. // SVG element
  50. var svg = container
  51. .attr('width', width + margin.left + margin.right)
  52. .attr('height', height + margin.top + margin.bottom)
  53. .append('g')
  54. .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
  55. .call(tooltip);
  56. // Add date range switcher
  57. // ------------------------------
  58. var menu = document.getElementById('select_date');
  59. menu.addEventListener('change', change);
  60. // Load data
  61. // ------------------------------
  62. d3.csv('../../../../global_assets/demo_data/dashboard/app_sales.csv', function(error, data) {
  63. formatted = data;
  64. redraw();
  65. });
  66. // Construct layout
  67. // ------------------------------
  68. // Add events
  69. var altKey;
  70. d3.select(window)
  71. .on('keydown', function() { altKey = d3.event.altKey; })
  72. .on('keyup', function() { altKey = false; });
  73. // Set terms of transition on date change
  74. function change() {
  75. d3.transition()
  76. .duration(altKey ? 7500 : 500)
  77. .each(redraw);
  78. }
  79. // Main chart drawing function
  80. // ------------------------------
  81. function redraw() {
  82. // Construct chart layout
  83. // ------------------------------
  84. // Create data nests
  85. var nested = d3.nest()
  86. .key(function(d) { return d.type; })
  87. .map(formatted)
  88. // Get value from menu selection
  89. // the option values correspond
  90. //to the [type] value we used to nest the data
  91. var series = menu.value;
  92. // Only retrieve data from the selected series using nest
  93. var data = nested[series];
  94. // For object constancy we will need to set 'keys', one for each type of data (column name) exclude all others.
  95. color.domain(d3.keys(data[0]).filter(function(key) { return (key !== 'date' && key !== 'type'); }));
  96. // Setting up color map
  97. var linedata = color.domain().map(function(name) {
  98. return {
  99. name: name,
  100. values: data.map(function(d) {
  101. return {name: name, date: parseDate(d.date), value: parseFloat(d[name], 10)};
  102. })
  103. };
  104. });
  105. // Draw the line
  106. var line = d3.svg.line()
  107. .x(function(d) { return x(d.date); })
  108. .y(function(d) { return y(d.value); })
  109. .interpolate('cardinal');
  110. // Construct scales
  111. // ------------------------------
  112. // Horizontal
  113. var x = d3.time.scale()
  114. .domain([
  115. d3.min(linedata, function(c) { return d3.min(c.values, function(v) { return v.date; }); }),
  116. d3.max(linedata, function(c) { return d3.max(c.values, function(v) { return v.date; }); })
  117. ])
  118. .range([0, width]);
  119. // Vertical
  120. var y = d3.scale.linear()
  121. .domain([
  122. d3.min(linedata, function(c) { return d3.min(c.values, function(v) { return v.value; }); }),
  123. d3.max(linedata, function(c) { return d3.max(c.values, function(v) { return v.value; }); })
  124. ])
  125. .range([height, 0]);
  126. // Create axes
  127. // ------------------------------
  128. // Horizontal
  129. var xAxis = d3.svg.axis()
  130. .scale(x)
  131. .orient('bottom')
  132. .tickPadding(8)
  133. .ticks(d3.time.days)
  134. .innerTickSize(4)
  135. .tickFormat(d3.time.format('%a')); // Display hours and minutes in 24h format
  136. // Vertical
  137. var yAxis = d3.svg.axis()
  138. .scale(y)
  139. .orient('left')
  140. .ticks(6)
  141. .tickSize(0 -width)
  142. .tickPadding(8);
  143. //
  144. // Append chart elements
  145. //
  146. // Append axes
  147. // ------------------------------
  148. // Horizontal
  149. svg.append('g')
  150. .attr('class', 'd3-axis d3-axis-horizontal')
  151. .attr('transform', 'translate(0,' + height + ')');
  152. // Vertical
  153. svg.append('g')
  154. .attr('class', 'd3-axis d3-axis-vertical d3-axis-transparent d3-grid d3-grid-dashed');
  155. // Append lines
  156. // ------------------------------
  157. // Bind the data
  158. var lines = svg.selectAll('.app-sales-lines')
  159. .data(linedata)
  160. // Append a group tag for each line
  161. var lineGroup = lines
  162. .enter()
  163. .append('g')
  164. .attr('class', 'app-sales-lines')
  165. .attr('id', function(d){ return d.name + '-line'; });
  166. // Append the line to the graph
  167. lineGroup.append('path')
  168. .attr('class', 'd3-line d3-line-medium')
  169. .style('stroke', function(d) { return color(d.name); })
  170. .style('opacity', 0)
  171. .attr('d', function(d) { return line(d.values[0]); })
  172. .transition()
  173. .duration(500)
  174. .delay(function(d, i) { return i * 200; })
  175. .style('opacity', 1);
  176. // Append circles
  177. // ------------------------------
  178. var circles = lines.selectAll('circle')
  179. .data(function(d) { return d.values; })
  180. .enter()
  181. .append('circle')
  182. .attr('class', 'd3-line-circle d3-line-circle-medium')
  183. .attr('cx', function(d,i){return x(d.date)})
  184. .attr('cy',function(d,i){return y(d.value)})
  185. .attr('r', 3)
  186. .style('stroke', function(d) { return color(d.name); });
  187. // Add transition
  188. circles
  189. .style('opacity', 0)
  190. .transition()
  191. .duration(500)
  192. .delay(500)
  193. .style('opacity', 1);
  194. // Append tooltip
  195. // ------------------------------
  196. // Add tooltip on circle hover
  197. circles
  198. .on('mouseover', function (d) {
  199. tooltip.offset([-15, 0]).show(d);
  200. // Animate circle radius
  201. d3.select(this).transition().duration(250).attr('r', 4);
  202. })
  203. .on('mouseout', function (d) {
  204. tooltip.hide(d);
  205. // Animate circle radius
  206. d3.select(this).transition().duration(250).attr('r', 3);
  207. });
  208. // Change tooltip direction of first point
  209. // to always keep it inside chart, useful on mobiles
  210. lines.each(function (d) {
  211. d3.select(d3.select(this).selectAll('circle')[0][0])
  212. .on('mouseover', function (d) {
  213. tooltip.offset([0, 15]).direction('e').show(d);
  214. // Animate circle radius
  215. d3.select(this).transition().duration(250).attr('r', 4);
  216. })
  217. .on('mouseout', function (d) {
  218. tooltip.direction('n').hide(d);
  219. // Animate circle radius
  220. d3.select(this).transition().duration(250).attr('r', 3);
  221. });
  222. })
  223. // Change tooltip direction of last point
  224. // to always keep it inside chart, useful on mobiles
  225. lines.each(function (d) {
  226. d3.select(d3.select(this).selectAll('circle')[0][d3.select(this).selectAll('circle').size() - 1])
  227. .on('mouseover', function (d) {
  228. tooltip.offset([0, -15]).direction('w').show(d);
  229. // Animate circle radius
  230. d3.select(this).transition().duration(250).attr('r', 4);
  231. })
  232. .on('mouseout', function (d) {
  233. tooltip.direction('n').hide(d);
  234. // Animate circle radius
  235. d3.select(this).transition().duration(250).attr('r', 3);
  236. })
  237. })
  238. // Update chart on date change
  239. // ------------------------------
  240. // Set variable for updating visualization
  241. var lineUpdate = d3.transition(lines);
  242. // Update lines
  243. lineUpdate.select('path')
  244. .attr('d', function(d, i) { return line(d.values); });
  245. // Update circles
  246. lineUpdate.selectAll('circle')
  247. .attr('cy',function(d,i){return y(d.value)})
  248. .attr('cx', function(d,i){return x(d.date)});
  249. // Update vertical axes
  250. d3.transition(svg)
  251. .select('.d3-axis-vertical')
  252. .call(yAxis);
  253. // Update horizontal axes
  254. d3.transition(svg)
  255. .select('.d3-axis-horizontal')
  256. .attr('transform', 'translate(0,' + height + ')')
  257. .call(xAxis);
  258. // Resize chart
  259. // ------------------------------
  260. // Call function on window resize
  261. window.addEventListener('resize', appSalesResize);
  262. // Call function on sidebar width change
  263. var sidebarToggle = document.querySelector('.sidebar-control');
  264. sidebarToggle && sidebarToggle.addEventListener('click', appSalesResize);
  265. // Resize function
  266. //
  267. // Since D3 doesn't support SVG resize by default,
  268. // we need to manually specify parts of the graph that need to
  269. // be updated on window resize
  270. function appSalesResize() {
  271. // Layout
  272. // -------------------------
  273. // Define width
  274. width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right;
  275. // Main svg width
  276. container.attr('width', width + margin.left + margin.right);
  277. // Width of appended group
  278. svg.attr('width', width + margin.left + margin.right);
  279. // Horizontal range
  280. x.range([0, width]);
  281. // Vertical range
  282. y.range([height, 0]);
  283. // Chart elements
  284. // -------------------------
  285. // Horizontal axis
  286. svg.select('.d3-axis-horizontal').call(xAxis);
  287. // Vertical axis
  288. svg.select('.d3-axis-vertical').call(yAxis.tickSize(0-width));
  289. // Lines
  290. svg.selectAll('.d3-line').attr('d', function(d, i) { return line(d.values); });
  291. // Circles
  292. svg.selectAll('.d3-line-circle').attr('cx', function(d,i){return x(d.date)})
  293. }
  294. }
  295. }
  296. };
  297. // Daily revenue line chart
  298. var _DailyRevenueLineChart = function(element, height) {
  299. if (typeof d3 == 'undefined') {
  300. console.warn('Warning - d3.min.js is not loaded.');
  301. return;
  302. }
  303. // Initialize chart only if element exsists in the DOM
  304. if($(element).length > 0) {
  305. // Basic setup
  306. // ------------------------------
  307. // Add data set
  308. var dataset = [
  309. {
  310. 'date': '04/13/14',
  311. 'alpha': '60'
  312. }, {
  313. 'date': '04/14/14',
  314. 'alpha': '35'
  315. }, {
  316. 'date': '04/15/14',
  317. 'alpha': '65'
  318. }, {
  319. 'date': '04/16/14',
  320. 'alpha': '50'
  321. }, {
  322. 'date': '04/17/14',
  323. 'alpha': '65'
  324. }, {
  325. 'date': '04/18/14',
  326. 'alpha': '20'
  327. }, {
  328. 'date': '04/19/14',
  329. 'alpha': '60'
  330. }
  331. ];
  332. // Main variables
  333. var d3Container = d3.select(element),
  334. margin = {top: 0, right: 0, bottom: 0, left: 0},
  335. width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right,
  336. height = height - margin.top - margin.bottom,
  337. padding = 20;
  338. // Format date
  339. var parseDate = d3.time.format('%m/%d/%y').parse,
  340. formatDate = d3.time.format('%a, %B %e');
  341. // Colors
  342. var lineColor = '#fff',
  343. guideColor = 'rgba(255,255,255,0.3)';
  344. // Add tooltip
  345. // ------------------------------
  346. var tooltip = d3.tip()
  347. .attr('class', 'd3-tip')
  348. .html(function (d) {
  349. return '<ul class="list-unstyled mb-1">' +
  350. '<li>' + '<div class="font-size-base my-1"><i class="icon-check2 mr-2"></i>' + formatDate(d.date) + '</div>' + '</li>' +
  351. '<li>' + 'Sales: &nbsp;' + '<span class="font-weight-semibold float-right">' + d.alpha + '</span>' + '</li>' +
  352. '<li>' + 'Revenue: &nbsp; ' + '<span class="font-weight-semibold float-right">' + '$' + (d.alpha * 25).toFixed(2) + '</span>' + '</li>' +
  353. '</ul>';
  354. });
  355. // Create chart
  356. // ------------------------------
  357. // Add svg element
  358. var container = d3Container.append('svg');
  359. // Add SVG group
  360. var svg = container
  361. .attr('width', width + margin.left + margin.right)
  362. .attr('height', height + margin.top + margin.bottom)
  363. .append('g')
  364. .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
  365. .call(tooltip);
  366. // Load data
  367. // ------------------------------
  368. dataset.forEach(function (d) {
  369. d.date = parseDate(d.date);
  370. d.alpha = +d.alpha;
  371. });
  372. // Construct scales
  373. // ------------------------------
  374. // Horizontal
  375. var x = d3.time.scale()
  376. .range([padding, width - padding]);
  377. // Vertical
  378. var y = d3.scale.linear()
  379. .range([height, 5]);
  380. // Set input domains
  381. // ------------------------------
  382. // Horizontal
  383. x.domain(d3.extent(dataset, function (d) {
  384. return d.date;
  385. }));
  386. // Vertical
  387. y.domain([0, d3.max(dataset, function (d) {
  388. return Math.max(d.alpha);
  389. })]);
  390. // Construct chart layout
  391. // ------------------------------
  392. // Line
  393. var line = d3.svg.line()
  394. .x(function(d) {
  395. return x(d.date);
  396. })
  397. .y(function(d) {
  398. return y(d.alpha)
  399. });
  400. //
  401. // Append chart elements
  402. //
  403. // Add mask for animation
  404. // ------------------------------
  405. // Add clip path
  406. var clip = svg.append('defs')
  407. .append('clipPath')
  408. .attr('id', 'clip-line-small');
  409. // Add clip shape
  410. var clipRect = clip.append('rect')
  411. .attr('class', 'clip')
  412. .attr('width', 0)
  413. .attr('height', height);
  414. // Animate mask
  415. clipRect
  416. .transition()
  417. .duration(1000)
  418. .ease('linear')
  419. .attr('width', width);
  420. // Line
  421. // ------------------------------
  422. // Path
  423. var path = svg.append('path')
  424. .attr({
  425. 'd': line(dataset),
  426. 'clip-path': 'url(#clip-line-small)',
  427. 'class': 'd3-line d3-line-medium'
  428. })
  429. .style('stroke', lineColor);
  430. // Animate path
  431. svg.select('.line-tickets')
  432. .transition()
  433. .duration(1000)
  434. .ease('linear');
  435. // Add vertical guide lines
  436. // ------------------------------
  437. // Bind data
  438. var guide = svg.append('g')
  439. .selectAll('.d3-line-guides-group')
  440. .data(dataset);
  441. // Append lines
  442. guide
  443. .enter()
  444. .append('line')
  445. .attr('class', 'd3-line-guides')
  446. .attr('x1', function (d, i) {
  447. return x(d.date);
  448. })
  449. .attr('y1', function (d, i) {
  450. return height;
  451. })
  452. .attr('x2', function (d, i) {
  453. return x(d.date);
  454. })
  455. .attr('y2', function (d, i) {
  456. return height;
  457. })
  458. .style('stroke', guideColor)
  459. .style('stroke-dasharray', '4,2')
  460. .style('shape-rendering', 'crispEdges');
  461. // Animate guide lines
  462. guide
  463. .transition()
  464. .duration(1000)
  465. .delay(function(d, i) { return i * 150; })
  466. .attr('y2', function (d, i) {
  467. return y(d.alpha);
  468. });
  469. // Alpha app points
  470. // ------------------------------
  471. // Add points
  472. var points = svg.insert('g')
  473. .selectAll('.d3-line-circle')
  474. .data(dataset)
  475. .enter()
  476. .append('circle')
  477. .attr('class', 'd3-line-circle d3-line-circle-medium')
  478. .attr('cx', line.x())
  479. .attr('cy', line.y())
  480. .attr('r', 3)
  481. .style('stroke', lineColor)
  482. .style('fill', lineColor);
  483. // Animate points on page load
  484. points
  485. .style('opacity', 0)
  486. .transition()
  487. .duration(250)
  488. .ease('linear')
  489. .delay(1000)
  490. .style('opacity', 1);
  491. // Add user interaction
  492. points
  493. .on('mouseover', function (d) {
  494. tooltip.offset([-10, 0]).show(d);
  495. // Animate circle radius
  496. d3.select(this).transition().duration(250).attr('r', 4);
  497. })
  498. // Hide tooltip
  499. .on('mouseout', function (d) {
  500. tooltip.hide(d);
  501. // Animate circle radius
  502. d3.select(this).transition().duration(250).attr('r', 3);
  503. });
  504. // Change tooltip direction of first point
  505. d3.select(points[0][0])
  506. .on('mouseover', function (d) {
  507. tooltip.offset([0, 10]).direction('e').show(d);
  508. // Animate circle radius
  509. d3.select(this).transition().duration(250).attr('r', 4);
  510. })
  511. .on('mouseout', function (d) {
  512. tooltip.direction('n').hide(d);
  513. // Animate circle radius
  514. d3.select(this).transition().duration(250).attr('r', 3);
  515. });
  516. // Change tooltip direction of last point
  517. d3.select(points[0][points.size() - 1])
  518. .on('mouseover', function (d) {
  519. tooltip.offset([0, -10]).direction('w').show(d);
  520. // Animate circle radius
  521. d3.select(this).transition().duration(250).attr('r', 4);
  522. })
  523. .on('mouseout', function (d) {
  524. tooltip.direction('n').hide(d);
  525. // Animate circle radius
  526. d3.select(this).transition().duration(250).attr('r', 3);
  527. })
  528. // Resize chart
  529. // ------------------------------
  530. // Call function on window resize
  531. window.addEventListener('resize', revenueResize);
  532. // Call function on sidebar width change
  533. var sidebarToggle = document.querySelector('.sidebar-control');
  534. sidebarToggle && sidebarToggle.addEventListener('click', revenueResize);
  535. // Resize function
  536. //
  537. // Since D3 doesn't support SVG resize by default,
  538. // we need to manually specify parts of the graph that need to
  539. // be updated on window resize
  540. function revenueResize() {
  541. // Layout variables
  542. width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right;
  543. // Layout
  544. // -------------------------
  545. // Main svg width
  546. container.attr('width', width + margin.left + margin.right);
  547. // Width of appended group
  548. svg.attr('width', width + margin.left + margin.right);
  549. // Horizontal range
  550. x.range([padding, width - padding]);
  551. // Chart elements
  552. // -------------------------
  553. // Mask
  554. clipRect.attr('width', width);
  555. // Line path
  556. svg.selectAll('.d3-line').attr('d', line(dataset));
  557. // Circles
  558. svg.selectAll('.d3-line-circle').attr('cx', line.x());
  559. // Guide lines
  560. svg.selectAll('.d3-line-guides')
  561. .attr('x1', function (d, i) {
  562. return x(d.date);
  563. })
  564. .attr('x2', function (d, i) {
  565. return x(d.date);
  566. });
  567. }
  568. }
  569. };
  570. //
  571. // Return objects assigned to module
  572. //
  573. return {
  574. init: function() {
  575. _AppSalesLinesChart('#app_sales', 255);
  576. _DailyRevenueLineChart('#today-revenue', 50);
  577. }
  578. }
  579. }();
  580. // Initialize module
  581. // ------------------------------
  582. document.addEventListener('DOMContentLoaded', function() {
  583. DashboardLines.init();
  584. });