Source: v-chart-plugin.js

  1. /**
  2. * @fileOverview Chart component containing all of the generic components required for charts
  3. *
  4. * @author Brian Greig
  5. *
  6. * @requires NPM:d3:Vue
  7. */
  8. /* eslint-env browser */
  9. import barChart from './import/barChart';
  10. import vBarChart from './import/vBarChart';
  11. import lineGraph from './import/lineGraph';
  12. import scatterPlot from './import/scatterPlot';
  13. import pieChart from './import/pieChart';
  14. import areaChart from './import/areaChart';
  15. import bubbleChart from './import/bubbleChart';
  16. const d3 = Object.assign({},
  17. require('d3-selection'));
  18. /**
  19. * Chart is the generic component used for any chart type
  20. * @namespace
  21. */
  22. const Chart = {
  23. install(Vue) {
  24. Vue.component('v-chart', {
  25. props: ['chartData'],
  26. data() {
  27. return {
  28. selector: `${this.chartData.selector}-${this.chartData.chartType}`,
  29. };
  30. },
  31. methods: {
  32. /**
  33. * Generate a new Chart of type chartType
  34. * @memberOf Chart
  35. */
  36. initalizeChart() {
  37. const cs = this[this.chartData.chartType]('init');
  38. this.drawTitle();
  39. this.generateAxisLabels(cs);
  40. this.generateLegend(cs);
  41. },
  42. /**
  43. * Redraw the Chart when the data is recycled
  44. * @memberOf Chart
  45. */
  46. refreshChart() {
  47. this.clearAxis();
  48. this[this.chartData.chartType]('refresh');
  49. },
  50. /**
  51. * Redraw the Chart when the data is recycled
  52. * @memberOf Chart
  53. */
  54. drawGrid(cs) {
  55. if (this.chartData.grid && this.chartData.grid.enabled === true) {
  56. const grid = {
  57. x: [],
  58. y: []
  59. }
  60. for (let i = this.header; i < (this.height - this.header) * .80; i += this.gridTicks) {
  61. grid.y.push(i);
  62. }
  63. d3.select(`#${this.chartData.selector}`)
  64. .selectAll('line.gridLine')
  65. .data(grid.y).enter()
  66. .append('line')
  67. .attr('class', 'gridLine')
  68. .attr('x1', cs.y.axisWidth)
  69. .attr('x2', this.width)
  70. .attr('y1', d => d)
  71. .attr('y2', d => d)
  72. .style('stroke', '#D3D3D3')
  73. .style('stroke-width', 1)
  74. }
  75. },
  76. /**
  77. * Remove x and y axes
  78. * @memberOf Chart
  79. */
  80. clearAxis() {
  81. d3.select(`#${this.chartData.selector}`).selectAll('.axis').remove();
  82. },
  83. /**
  84. * Remove all content from the SVG
  85. * @memberOf Chart
  86. */
  87. clearCanvas() {
  88. d3.select(`#${this.chartData.selector}`).selectAll('*').remove();
  89. },
  90. /**
  91. * Appends title and subtitle to the chart
  92. * @memberOf Chart
  93. */
  94. drawTitle() {
  95. d3.select(`#${this.chartData.selector}`)
  96. .append('text')
  97. .attr('font-size', '20')
  98. .attr('x', this.width / 2)
  99. .attr('y', this.titleHeight - this.titleHeight * 0.1)
  100. .style('text-anchor', 'middle')
  101. .text(this.chartData.title);
  102. d3.select(`#${this.chartData.selector}`)
  103. .append('text')
  104. .attr('font-size', '12')
  105. .attr('x', this.width / 2)
  106. .attr('y', this.titleHeight - this.titleHeight * 0.1 + this.subtitleHeight)
  107. .style('text-anchor', 'middle')
  108. .text(this.chartData.subtitle);
  109. },
  110. /**
  111. * Adds a tooltip to the SVG
  112. * @memberOf Chart
  113. * @param {Object} d dataset
  114. * @param {Object} e event x and y coordinates
  115. */
  116. addTooltip(d, e) {
  117. d3.select(`#${this.chartData.selector}`)
  118. .append('rect')
  119. .attr('x', e.offsetX - 5 - 50)
  120. .attr('y', e.offsetY - 13 - 25)
  121. .attr('height', '16px')
  122. .attr('width', '80px')
  123. .attr('class', 'tt')
  124. .attr('fill', 'white');
  125. d3.select(`#${this.chartData.selector}`)
  126. .append('text')
  127. .attr('x', e.offsetX - 50)
  128. .attr('y', e.offsetY - 25)
  129. .attr('class', 'tt')
  130. .attr('font-size', '10px')
  131. .text(`${d.dim}:${d.metric}`);
  132. },
  133. /**
  134. * Removes all tooltips from the SVG
  135. * @memberOf Chart
  136. * @param {Object} d dataset
  137. */
  138. removeTooltip() {
  139. d3.select(`#${this.chartData.selector}`)
  140. .selectAll('.tt').remove();
  141. },
  142. /**
  143. * Override default values
  144. * @param {Object} cs configuration of the coordinate systems
  145. * @param {Object} overrides the additional values that can be used for an object
  146. * @returns {Object} updated configuration of coordinate system
  147. */
  148. setOverrides(cs, overrides) {
  149. overrides = overrides || {};
  150. const keys = Object.keys(cs);
  151. for (const key of keys)
  152. Object.assign(cs[key], overrides[key]);
  153. return cs;
  154. },
  155. /**
  156. * Generate legend if option -legends- defined as true
  157. * @memberOf Chart
  158. * @param {Object} cs configuration of the coordinate system
  159. */
  160. generateLegend(cs) {
  161. if (this.chartData.legends && this.chartData.legends.enabled === true) {
  162. cs.palette.lineFill = (Array.isArray(cs.palette.lineFill)) ? cs.palette.lineFill : new Array(cs.palette.lineFill);
  163. cs.palette.fill = (Array.isArray(cs.palette.fill)) ? cs.palette.fill : new Array(cs.palette.fill);
  164. this.metric.forEach( (e, i) => {
  165. d3.select(`#${this.chartData.selector}`)
  166. .append('text')
  167. .attr('font-size', '10')
  168. .attr('x', this.width - 60)
  169. .attr('y', this.height * 0.95 - (i * 15))
  170. .style('text-anchor', 'middle')
  171. .text(this.metric[i]);
  172. d3.select(`#${this.chartData.selector}`)
  173. .append("g")
  174. .attr("class", "legends")
  175. .append("rect")
  176. .attr('x', this.width - 30)
  177. .attr('y', this.height * 0.95 - (i * 15) - 10)
  178. .attr("width", 30)
  179. .attr("height", 10)
  180. .style("fill", function () {
  181. const fill = cs.palette.lineFill[i] || cs.palette.fill[i];
  182. return fill;
  183. });
  184. })
  185. }
  186. },
  187. /**
  188. * Generate Goal
  189. * @memberOf Chart
  190. * @param {Object} cs configuration of the coordinate system
  191. */
  192. generateGoal(cs, shiftAxis, padding) {
  193. d3.select(`#${this.chartData.selector}`).selectAll('line#goal').remove();
  194. const x1 = shiftAxis ? cs.y.axisWidth: cs.x.scale(this.goal) + padding;
  195. const x2 = shiftAxis ? this.width : cs.x.scale(this.goal) + padding;
  196. const y1 = shiftAxis ? cs.y.scale(this.goal) + padding : this.header;
  197. const y2 = shiftAxis ? cs.y.scale(this.goal) + padding : this.displayHeight - cs.x.axisHeight;
  198. d3.select(`#${this.chartData.selector}`).append('line')
  199. .attr('x1', x1)
  200. .attr('x2', x2)
  201. .attr('y1', y1)
  202. .attr('y2', y2)
  203. .attr('id', 'goal')
  204. .style('stroke', '#708090')
  205. .style('stroke-width', 1)
  206. },
  207. /**
  208. * Generate Axis Lables
  209. * @memberOf Chart
  210. * @param {Object} cs configuration of the coordinate system
  211. */
  212. generateAxisLabels(cs) {
  213. let footer = (this.chartData.legends) ? .85 : .95;
  214. if (!this.chartData.label) return;
  215. d3.select(`#${this.chartData.selector}`).selectAll('text.axisLabel').remove();
  216. if (cs.x && cs.x.label)
  217. d3.select(`#${this.chartData.selector}`).append('text')
  218. .attr('font-size', '10')
  219. .attr('x', this.width / 2)
  220. .attr('y', this.height * footer)
  221. .attr('id', 'xAxisLabel')
  222. .attr('class', 'axisLabel')
  223. .style('text-anchor', 'middle')
  224. .text(cs.x.label)
  225. if (cs.y && cs.y.label)
  226. d3.select(`#${this.chartData.selector}`).append('text')
  227. .attr('font-size', '10')
  228. .attr('x', 10)
  229. .attr('y', this.height / 2)
  230. .attr('id', 'xAxisLabel')
  231. .attr('class', 'axisLabel')
  232. .style('text-anchor', 'middle')
  233. .text(cs.y.label)
  234. .attr('transform', `rotate(-90,10, ${this.height / 2})`)
  235. },
  236. /**
  237. * get the values of a metric as an array
  238. * @memberOf Chart
  239. * @returns {Array} metric values
  240. */
  241. metricAsArray(metric) {
  242. metric = this.chartData.data.map(d => d[metric]);
  243. return metric;
  244. },
  245. ...((typeof barChart !== 'undefined') && { barChart }),
  246. ...((typeof vBarChart !== 'undefined') && { vBarChart }),
  247. ...((typeof scatterPlot !== 'undefined') && { scatterPlot }),
  248. ...((typeof pieChart !== 'undefined') && { pieChart }),
  249. ...((typeof areaChart !== 'undefined') && { areaChart }),
  250. ...((typeof lineGraph !== 'undefined') && { lineGraph }),
  251. ...((typeof bubbleChart !== 'undefined') && { bubbleChart }),
  252. },
  253. computed: {
  254. /**
  255. * Dataset getter function
  256. * @memberOf Chart
  257. * @returns {Object} normalized dataset
  258. */
  259. ds() {
  260. const ds = { metric: [] };
  261. ds.metric = (Array.isArray(this.chartData.metric)) ? ds.metric = this.chartData.metric : new Array(this.chartData.metric);
  262. ds.dim = this.chartData.dim;
  263. ds.data = this.chartData.data;
  264. return ds.data.map((d) => {
  265. const td = { metric: [] };
  266. if (!ds.metric[0])
  267. td.metric[0] = d || 0;
  268. else {
  269. ds.metric.forEach(function(e, i){
  270. td.metric[i] = d[e] || 0;
  271. })
  272. }
  273. td.dim = this.chartData.dim ? d[this.chartData.dim] : null;
  274. return td;
  275. });
  276. },
  277. /**
  278. * Dimension getter function
  279. * @memberOf Chart
  280. * @returns {string} dim
  281. */
  282. dim() {
  283. return this.chartData.dim || "undefined";
  284. },
  285. /**
  286. * Goal getter function
  287. * @memberOf Chart
  288. * @returns {number} Goal
  289. */
  290. goal() {
  291. return this.chartData.goal;
  292. },
  293. /**
  294. * Metric getter function
  295. * @memberOf Chart
  296. * @returns {array} Metrics
  297. */
  298. metric() {
  299. const metric = (Array.isArray(this.chartData.metric)) ? this.chartData.metric : new Array(this.chartData.metric);
  300. return metric;
  301. },
  302. /**
  303. * Height getter function
  304. * @memberOf Chart
  305. * @returns {number} Chart Height
  306. */
  307. height() {
  308. return this.chartData.height - 10 || 190;
  309. },
  310. /**
  311. * Width getter function
  312. * @memberOf Chart
  313. * @returns {number} Chart width
  314. */
  315. width() {
  316. return this.chartData.width - 10 || 190;
  317. },
  318. /**
  319. * Grid Tick getter function
  320. * @memberOf Chart
  321. * @returns {number} gridTicks
  322. */
  323. gridTicks() {
  324. if (this.chartData.grid && this.chartData.grid.gridTicks != null) {
  325. return this.chartData.grid.gridTicks;
  326. }
  327. return 100;
  328. },
  329. /**
  330. * Get the maxium value for metric
  331. * @memberOf Chart
  332. * @returns {number} Max value for metric
  333. */
  334. max() {
  335. let max = 0;
  336. var results = [];
  337. this.ds.forEach(e => {
  338. results = results.concat([...e.metric]);
  339. });
  340. results.forEach((e) => {
  341. max = max > e ? max : e;
  342. });
  343. return max;
  344. },
  345. /**
  346. * Get the maxium value for triplet
  347. * @memberOf Chart
  348. * @returns {Array} Max values for triplet
  349. */
  350. maxTriplet() {
  351. const max = {
  352. v1: 0,
  353. v2: 0,
  354. v3: 0
  355. };
  356. this.ds.forEach(e => {
  357. max.v1 = max.v1 > e.metric[0] ? max.v1 : e.metric[0];
  358. max.v2 = max.v2 > e.metric[1] ? max.v2 : e.metric[1];
  359. max.v3 = max.v3 > e.metric[2] ? max.v3 : e.metric[2];
  360. });
  361. return max;
  362. },
  363. /**
  364. * Get the minimum value for dataset
  365. * @memberOf Chart
  366. * @returns {number} Min value for metric
  367. */
  368. min() {
  369. var results = [];
  370. this.ds.forEach(e => {
  371. results = results.concat([...e.metric]);
  372. });
  373. return Math.min(...results.map(o => o));
  374. },
  375. /**
  376. * Get the minimum value for triplet
  377. * @memberOf Chart
  378. * @returns {Array} Min values for triplet
  379. */
  380. minTriplet() {
  381. var results = {
  382. v1: [],
  383. v2: [],
  384. v3: []
  385. };
  386. this.ds.forEach(e => {
  387. results.v1.push(e.metric[0])
  388. results.v2.push(e.metric[1])
  389. results.v3.push(e.metric[2])
  390. })
  391. return {
  392. v1: (Math.min(...results.v1.map(o => o))),
  393. v2: (Math.min(...results.v2.map(o => o))),
  394. v3: (Math.min(...results.v3.map(o => o)))
  395. };
  396. },
  397. /**
  398. * Gets the height of the dispaly area
  399. * @memberOf Chart
  400. * @returns {number} Height of the chart display
  401. */
  402. displayHeight() {
  403. if (this.chartData.legends && this.chartData.legends.enabled === true) {
  404. return this.height * .80;
  405. } else {
  406. return this.height * .90;
  407. }
  408. },
  409. /**
  410. * Gets the height of the title
  411. * @memberOf Chart
  412. * @returns {number} Height of the chart title
  413. */
  414. titleHeight() {
  415. if (this.chartData.title) return this.chartData.textHeight || 25;
  416. return 0;
  417. },
  418. /**
  419. * Gets the subtitle height
  420. * @memberOf Chart
  421. * @returns {number} Height of chart subtitle
  422. */
  423. subtitleHeight() {
  424. if (this.chartData.subtitle) return this.chartData.textHeight * 0.66 || 25 * 0.66;
  425. return 0;
  426. },
  427. /**
  428. * Gets the combined height of the title and subtitle
  429. * @memberOf Chart
  430. * @returns {number} Total header height
  431. */
  432. header() {
  433. return (this.titleHeight + this.subtitleHeight);
  434. },
  435. },
  436. mounted() {
  437. this.initalizeChart();
  438. },
  439. watch: {
  440. chartData: {
  441. handler() {
  442. this.refreshChart();
  443. },
  444. deep: true,
  445. },
  446. },
  447. template:
  448. '<svg :id=\'this.chartData.selector\' x=\'5\' y=\'5\' :height=\'this.height + 20\' :width=\'this.width + 20\'> </svg>',
  449. });
  450. },
  451. };
  452. export default Chart;
  453. if (typeof window !== 'undefined' && window.Vue) {
  454. window.Vue.use(Chart);
  455. }