streamgraph.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. /* ------------------------------------------------------------------------------
  2. *
  3. * # D3.js - streamgraph
  4. *
  5. * Demo of streamgraph chart setup with tooltip and .csv data source
  6. *
  7. * ---------------------------------------------------------------------------- */
  8. // Setup module
  9. // ------------------------------
  10. var D3Streamgraph = function() {
  11. //
  12. // Setup module components
  13. //
  14. // Chart
  15. var _streamgraph = function() {
  16. if (typeof d3 == 'undefined') {
  17. console.warn('Warning - d3.min.js is not loaded.');
  18. return;
  19. }
  20. // Main variables
  21. var element = document.getElementById('traffic-sources'),
  22. height = 340;
  23. // Initialize chart only if element exsists in the DOM
  24. if(element) {
  25. // Basic setup
  26. // ------------------------------
  27. // Define main variables
  28. var d3Container = d3.select(element),
  29. margin = {top: 5, right: 50, bottom: 40, left: 50},
  30. width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right,
  31. height = height - margin.top - margin.bottom,
  32. tooltipOffset = 30;
  33. // Tooltip
  34. var tooltip = d3Container
  35. .append("div")
  36. .attr("class", "d3-tip e")
  37. .style("display", "none")
  38. // Format date
  39. var format = d3.time.format("%m/%d/%y %H:%M");
  40. var formatDate = d3.time.format("%H:%M");
  41. // Colors
  42. var colorrange = ['#03A9F4', '#29B6F6', '#4FC3F7', '#81D4FA', '#B3E5FC', '#E1F5FE'];
  43. // Construct scales
  44. // ------------------------------
  45. // Horizontal
  46. var x = d3.time.scale().range([0, width]);
  47. // Vertical
  48. var y = d3.scale.linear().range([height, 0]);
  49. // Colors
  50. var z = d3.scale.ordinal().range(colorrange);
  51. // Create axes
  52. // ------------------------------
  53. // Horizontal
  54. var xAxis = d3.svg.axis()
  55. .scale(x)
  56. .orient("bottom")
  57. .ticks(d3.time.hours, 4)
  58. .innerTickSize(4)
  59. .tickPadding(8)
  60. .tickFormat(d3.time.format("%H:%M")); // Display hours and minutes in 24h format
  61. // Left vertical
  62. var yAxis = d3.svg.axis()
  63. .scale(y)
  64. .ticks(6)
  65. .innerTickSize(4)
  66. .outerTickSize(0)
  67. .tickPadding(8)
  68. .tickFormat(function (d) { return (d/1000) + "k"; });
  69. // Right vertical
  70. var yAxis2 = yAxis;
  71. // Dash lines
  72. var gridAxis = d3.svg.axis()
  73. .scale(y)
  74. .orient("left")
  75. .ticks(6)
  76. .tickPadding(8)
  77. .tickFormat("")
  78. .tickSize(-width, 0, 0);
  79. // Create chart
  80. // ------------------------------
  81. // Container
  82. var container = d3Container.append("svg")
  83. // SVG element
  84. var svg = container
  85. .attr('width', width + margin.left + margin.right)
  86. .attr("height", height + margin.top + margin.bottom)
  87. .append("g")
  88. .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  89. // Construct chart layout
  90. // ------------------------------
  91. // Stack
  92. var stack = d3.layout.stack()
  93. .offset("silhouette")
  94. .values(function(d) { return d.values; })
  95. .x(function(d) { return d.date; })
  96. .y(function(d) { return d.value; });
  97. // Nest
  98. var nest = d3.nest()
  99. .key(function(d) { return d.key; });
  100. // Area
  101. var area = d3.svg.area()
  102. .interpolate("cardinal")
  103. .x(function(d) { return x(d.date); })
  104. .y0(function(d) { return y(d.y0); })
  105. .y1(function(d) { return y(d.y0 + d.y); });
  106. // Load data
  107. // ------------------------------
  108. d3.csv("../../../../global_assets/demo_data/dashboard/traffic_sources.csv", function (error, data) {
  109. // Pull out values
  110. data.forEach(function (d) {
  111. d.date = format.parse(d.date);
  112. d.value = +d.value;
  113. });
  114. // Stack and nest layers
  115. var layers = stack(nest.entries(data));
  116. // Set input domains
  117. // ------------------------------
  118. // Horizontal
  119. x.domain(d3.extent(data, function(d, i) { return d.date; }));
  120. // Vertical
  121. y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
  122. // Add grid
  123. // ------------------------------
  124. // Horizontal grid. Must be before the group
  125. svg.append("g")
  126. .attr("class", "d3-grid-dashed")
  127. .call(gridAxis);
  128. //
  129. // Append chart elements
  130. //
  131. // Stream layers
  132. // ------------------------------
  133. // Create group
  134. var group = svg.append('g')
  135. .attr('class', 'streamgraph-layers-group');
  136. // And append paths to this group
  137. var layer = group.selectAll(".streamgraph-layer")
  138. .data(layers)
  139. .enter()
  140. .append("path")
  141. .attr("class", "streamgraph-layer d3-slice-border")
  142. .attr("d", function(d) { return area(d.values); })
  143. .style('stroke-width', 1)
  144. .style('box-shadow', '0 4px 8px rgba(0,0,0,0.5)')
  145. .style("fill", function(d, i) { return z(i); });
  146. // Add transition
  147. var layerTransition = layer
  148. .style('opacity', 0)
  149. .transition()
  150. .duration(750)
  151. .delay(function(d, i) { return i * 50; })
  152. .style('opacity', 1)
  153. // Append axes
  154. // ------------------------------
  155. //
  156. // Left vertical
  157. //
  158. svg.append("g")
  159. .attr("class", "d3-axis d3-axis-left")
  160. .call(yAxis.orient("left"));
  161. // Hide first tick
  162. d3.select(svg.selectAll('.d3-axis-left .tick text')[0][0])
  163. .style("visibility", "hidden");
  164. //
  165. // Right vertical
  166. //
  167. svg.append("g")
  168. .attr("class", "d3-axis d3-axis-right")
  169. .attr("transform", "translate(" + width + ", 0)")
  170. .call(yAxis2.orient("right"));
  171. // Hide first tick
  172. d3.select(svg.selectAll('.d3-axis-right .tick text')[0][0])
  173. .style("visibility", "hidden");
  174. //
  175. // Horizontal
  176. //
  177. var xaxisg = svg.append("g")
  178. .attr("class", "d3-axis d3-axis-horizontal")
  179. .attr("transform", "translate(0," + height + ")")
  180. .call(xAxis);
  181. // Add extra subticks for hidden hours
  182. xaxisg.selectAll(".d3-axis-subticks")
  183. .data(x.ticks(d3.time.hours), function(d) { return d; })
  184. .enter()
  185. .append("line")
  186. .attr("class", "d3-axis-subticks")
  187. .attr("y1", 0)
  188. .attr("y2", 4)
  189. .attr("x1", x)
  190. .attr("x2", x);
  191. // Add hover line and pointer
  192. // ------------------------------
  193. // Append group to the group of paths to prevent appearance outside chart area
  194. var hoverLineGroup = group.append("g")
  195. .attr("class", "hover-line");
  196. // Add line
  197. var hoverLine = hoverLineGroup
  198. .append("line")
  199. .attr("class", "d3-crosshair-line")
  200. .attr("y1", 0)
  201. .attr("y2", height)
  202. .style("opacity", 0);
  203. // Add pointer
  204. var hoverPointer = hoverLineGroup
  205. .append("rect")
  206. .attr("class", "d3-crosshair-line")
  207. .attr("x", 2)
  208. .attr("y", 2)
  209. .attr("width", 6)
  210. .attr("height", 6)
  211. .style('fill', '#03A9F4')
  212. .style("opacity", 0);
  213. // Append events to the layers group
  214. // ------------------------------
  215. layerTransition.each("end", function() {
  216. layer
  217. .on("mouseover", function (d, i) {
  218. svg.selectAll(".streamgraph-layer")
  219. .transition()
  220. .duration(250)
  221. .style("opacity", function (d, j) {
  222. return j != i ? 0.75 : 1; // Mute all except hovered
  223. });
  224. })
  225. .on("mousemove", function (d, i) {
  226. mouse = d3.mouse(this);
  227. mousex = mouse[0];
  228. mousey = mouse[1];
  229. datearray = [];
  230. var invertedx = x.invert(mousex);
  231. invertedx = invertedx.getHours();
  232. var selected = (d.values);
  233. for (var k = 0; k < selected.length; k++) {
  234. datearray[k] = selected[k].date
  235. datearray[k] = datearray[k].getHours();
  236. }
  237. mousedate = datearray.indexOf(invertedx);
  238. pro = d.values[mousedate].value;
  239. // Display mouse pointer
  240. hoverPointer
  241. .attr("x", mousex - 3)
  242. .attr("y", mousey - 6)
  243. .style("opacity", 1);
  244. hoverLine
  245. .attr("x1", mousex)
  246. .attr("x2", mousex)
  247. .style("opacity", 1);
  248. //
  249. // Tooltip
  250. //
  251. // Tooltip data
  252. tooltip.html(
  253. '<ul class="list-unstyled mb-1 p-0">' +
  254. '<li>' + '<div class="font-size-base my-1"><i class="icon-circle-left2"></i><span class="d-inline-block ml-2"></span>' + d.key + '</div>' + '</li>' +
  255. '<li>' + 'Visits: &nbsp;' + "<span class='font-weight-semibold float-right'>" + pro + '</span>' + '</li>' +
  256. '<li>' + 'Time: &nbsp; ' + '<span class="font-weight-semibold float-right">' + formatDate(d.values[mousedate].date) + '</span>' + '</li>' +
  257. '</ul>'
  258. )
  259. .style("display", "block");
  260. // Tooltip arrow
  261. tooltip.append('div').attr('class', 'd3-tip-arrow');
  262. })
  263. .on("mouseout", function (d, i) {
  264. // Revert full opacity to all paths
  265. svg.selectAll(".streamgraph-layer")
  266. .transition()
  267. .duration(250)
  268. .style("opacity", 1);
  269. // Hide cursor pointer
  270. hoverPointer.style("opacity", 0);
  271. // Hide tooltip
  272. tooltip.style("display", "none");
  273. hoverLine.style("opacity", 0);
  274. });
  275. });
  276. // Append events to the chart container
  277. // ------------------------------
  278. d3Container
  279. .on("mousemove", function (d, i) {
  280. mouse = d3.mouse(this);
  281. mousex = mouse[0];
  282. mousey = mouse[1];
  283. // Move tooltip vertically
  284. tooltip.style("top", (mousey - ($('.d3-tip').outerHeight() / 2)) - 2 + "px") // Half tooltip height - half arrow width
  285. // Move tooltip horizontally
  286. if(mousex >= ($(element).outerWidth() - $('.d3-tip').outerWidth() - margin.right - (tooltipOffset * 2))) {
  287. tooltip
  288. .style("left", (mousex - $('.d3-tip').outerWidth() - tooltipOffset) + "px") // Change tooltip direction from right to left to keep it inside graph area
  289. .attr("class", "d3-tip w");
  290. }
  291. else {
  292. tooltip
  293. .style("left", (mousex + tooltipOffset) + "px" )
  294. .attr("class", "d3-tip e");
  295. }
  296. });
  297. });
  298. // Resize chart
  299. // ------------------------------
  300. // Call function on window resize
  301. window.addEventListener('resize', resizeStream);
  302. // Call function on sidebar width change
  303. var sidebarToggle = document.querySelector('.sidebar-control');
  304. sidebarToggle && sidebarToggle.addEventListener('click', resizeStream);
  305. // Resize function
  306. //
  307. // Since D3 doesn't support SVG resize by default,
  308. // we need to manually specify parts of the graph that need to
  309. // be updated on window resize
  310. function resizeStream() {
  311. // Layout
  312. // -------------------------
  313. // Define width
  314. width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right;
  315. // Main svg width
  316. container.attr("width", width + margin.left + margin.right);
  317. // Width of appended group
  318. svg.attr("width", width + margin.left + margin.right);
  319. // Horizontal range
  320. x.range([0, width]);
  321. // Chart elements
  322. // -------------------------
  323. // Horizontal axis
  324. svg.selectAll('.d3-axis-horizontal').call(xAxis);
  325. // Horizontal axis subticks
  326. svg.selectAll('.d3-axis-subticks').attr("x1", x).attr("x2", x);
  327. // Grid lines width
  328. svg.selectAll(".d3-grid-dashed").call(gridAxis.tickSize(-width, 0, 0))
  329. // Right vertical axis
  330. svg.selectAll(".d3-axis-right").attr("transform", "translate(" + width + ", 0)");
  331. // Area paths
  332. svg.selectAll('.streamgraph-layer').attr("d", function(d) { return area(d.values); });
  333. }
  334. }
  335. };
  336. //
  337. // Return objects assigned to module
  338. //
  339. return {
  340. init: function() {
  341. _streamgraph();
  342. }
  343. }
  344. }();
  345. // Initialize module
  346. // ------------------------------
  347. document.addEventListener('DOMContentLoaded', function() {
  348. D3Streamgraph.init();
  349. });