Source: import/lineGraph.js

  1. /**
  2. * @fileOverview Line Graph component definition
  3. *
  4. * @author Brian Greig
  5. *
  6. * @requires NPM:d3:Vue
  7. * @requires src/v-chart-plugin.js
  8. */
  9. const d3 = Object.assign({},
  10. require('d3-selection'),
  11. require('d3-scale'),
  12. require('d3-axis'),
  13. require('d3-shape'));
  14. /**
  15. * Builds a Line Graph.
  16. * @module lineGraph
  17. */
  18. const lineGraph = function chart(mode) {
  19. /**
  20. * The SVG that stores the chart
  21. * @member svgContainer
  22. */
  23. const svgContainer = d3.select(`#${this.chartData.selector}`);
  24. /**
  25. * The configuration of the coordinate system
  26. * @member cs
  27. */
  28. let cs = {
  29. palette: {
  30. lineFill: ['#ffcdcd', '#005792'],
  31. pointFill: '#005792',
  32. pointStroke: '#d1f4fa',
  33. },
  34. x: {
  35. label: this.dim,
  36. domain: [],
  37. range: [],
  38. axisHeight: 20,
  39. },
  40. y: {
  41. label: this.metric,
  42. axisWidth: 40,
  43. ticks: 5,
  44. },
  45. };
  46. /**
  47. * Runs when a new element is added to the dataset
  48. * @member enter
  49. * @function
  50. * @param {Object} points (svg element)
  51. */
  52. const enter = (points, path) => {
  53. this.metric.forEach( (e, i) => {
  54. path[i].enter().append('path')
  55. .attr('d', cs.lineFunction[i](this.ds))
  56. .attr('fill', 'none')
  57. .attr('id', 'p' + i)
  58. .attr('stroke', cs.palette.lineFill[i])
  59. .attr('stroke-width', 3)
  60. })
  61. this.metric.forEach( (e, i) => {
  62. cs.offset = i;
  63. points[i].enter()
  64. .append('circle')
  65. .attr('class', this.selector)
  66. .attr('class', "r" + i)
  67. .attr('r', 2)
  68. .on('mouseover', (d) => {
  69. this.addTooltip(d, window.event);
  70. })
  71. .on('mouseout', (d) => {
  72. this.removeTooltip(d);
  73. })
  74. .attr('cx', d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
  75. .attr('cy', d => cs.y.scale(d.metric));
  76. });
  77. if (this.goal) this.generateGoal(cs, true, 0);
  78. return points;
  79. };
  80. /**
  81. * Runs when a value of an element in dataset is changed
  82. * @member transition
  83. * @function
  84. * @param {Object} points (svg element)
  85. */
  86. const transition = (points, path) => {
  87. this.metric.forEach( (e, i) => {
  88. path[i].transition()
  89. .attr('d', cs.lineFunction[i](this.ds));
  90. })
  91. this.metric.forEach( (e, i) => {
  92. cs.offset = i;
  93. points[i].transition()
  94. .attr('cx', d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
  95. .attr('cy', d => cs.y.scale(d.metric))
  96. .attr('cx', d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
  97. .attr('cy', d => cs.y.scale(d.metric));
  98. });
  99. if (this.goal) this.generateGoal(cs, true, 0);
  100. return points;
  101. };
  102. /**
  103. * Runs when an element is removed from the dataset
  104. * @member exit
  105. * @function
  106. * @param {Object} points (svg element)
  107. */
  108. const exit = (points, path) => {
  109. this.metric.forEach( (e, i) => {
  110. points[i].exit().remove();
  111. });
  112. this.metric.forEach( (e, i) => {
  113. path[i].exit().remove();
  114. });
  115. return points;
  116. };
  117. /**
  118. * Builds the scales for the x and y axes
  119. * @member buildScales
  120. * @function
  121. */
  122. const buildScales = cs => {
  123. cs.y.scale = d3.scaleLinear()
  124. .domain([this.min, this.max])
  125. .range([this.displayHeight - cs.x.axisHeight, this.header]);
  126. this.ds.forEach(t => cs.x.domain.push(t.dim));
  127. this.ds.forEach((t, i) => cs.x.range.push(((this.width * i) - this.header) / this.ds.length));
  128. cs.x.scale = d3.scaleOrdinal().domain(cs.x.domain).range(cs.x.range);
  129. };
  130. /**
  131. * Draws the x and y axes on the svg
  132. * @member drawAxis
  133. * @function
  134. */
  135. const drawAxis = cs => {
  136. this.drawGrid(cs);
  137. cs.x.axis = d3.axisBottom().scale(cs.x.scale);
  138. cs.x.xOffset = cs.y.axisWidth + 5;
  139. cs.x.yOffset = this.displayHeight - cs.x.axisHeight;
  140. cs.y.axis = d3.axisLeft().ticks(cs.y.ticks, 's').scale(cs.y.scale);
  141. cs.y.xOffset = cs.y.axisWidth;
  142. cs.y.yOffset = 0;
  143. svgContainer.append('g').attr('class', 'axis').attr('transform', `translate(${cs.x.xOffset}, ${cs.x.yOffset})`)
  144. .call(cs.x.axis);
  145. svgContainer.append('g').attr('class', 'axis').attr('transform', `translate(${cs.y.xOffset},${cs.y.yOffset})`)
  146. .call(cs.y.axis);
  147. };
  148. cs.lineFunction = [];
  149. this.metric.forEach( (e, i) => {
  150. cs.lineFunction.push(
  151. d3.line()
  152. .x(d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
  153. .y(d => cs.y.scale(d.metric[i]))
  154. )
  155. });
  156. const points = [];
  157. this.metric.forEach( (e, i) => {
  158. points.push(svgContainer.selectAll('circle.r' + i).data(this.ds.map(d => {
  159. return {
  160. metric: d.metric[i],
  161. dim: d.dim
  162. }
  163. })))
  164. })
  165. const path = []
  166. this.metric.forEach( (e, i) => {
  167. path.push(svgContainer.selectAll('path#p' + i).data(this.ds))
  168. })
  169. cs = this.setOverrides(cs, this.chartData.overrides);
  170. buildScales(cs);
  171. drawAxis(cs);
  172. enter(points, path);
  173. transition(points, path);
  174. exit(points, path);
  175. return cs;
  176. };
  177. export default lineGraph;