UNPKG

v-chart-plugin

Version:

This plugin is designed to allow Vue.js developers to incorporate fully reactive and customizable charts into your applications. Uses D3.js for charting.

469 lines (455 loc) 15.3 kB
/** * @fileOverview Chart component containing all of the generic components required for charts * * @author Brian Greig * * @requires NPM:d3:Vue */ /* eslint-env browser */ import barChart from './import/barChart'; import vBarChart from './import/vBarChart'; import lineGraph from './import/lineGraph'; import scatterPlot from './import/scatterPlot'; import pieChart from './import/pieChart'; import areaChart from './import/areaChart'; import bubbleChart from './import/bubbleChart'; const d3 = Object.assign({}, require('d3-selection')); /** * Chart is the generic component used for any chart type * @namespace */ const Chart = { install(Vue) { Vue.component('v-chart', { props: ['chartData'], data() { return { selector: `${this.chartData.selector}-${this.chartData.chartType}`, }; }, methods: { /** * Generate a new Chart of type chartType * @memberOf Chart */ initalizeChart() { const cs = this[this.chartData.chartType]('init'); this.drawTitle(); this.generateAxisLabels(cs); this.generateLegend(cs); }, /** * Redraw the Chart when the data is recycled * @memberOf Chart */ refreshChart() { this.clearAxis(); this[this.chartData.chartType]('refresh'); }, /** * Redraw the Chart when the data is recycled * @memberOf Chart */ drawGrid(cs) { if (this.chartData.grid && this.chartData.grid.enabled === true) { const grid = { x: [], y: [] } for (let i = this.header; i < (this.height - this.header) * .80; i += this.gridTicks) { grid.y.push(i); } d3.select(`#${this.chartData.selector}`) .selectAll('line.gridLine') .data(grid.y).enter() .append('line') .attr('class', 'gridLine') .attr('x1', cs.y.axisWidth) .attr('x2', this.width) .attr('y1', d => d) .attr('y2', d => d) .style('stroke', '#D3D3D3') .style('stroke-width', 1) } }, /** * Remove x and y axes * @memberOf Chart */ clearAxis() { d3.select(`#${this.chartData.selector}`).selectAll('.axis').remove(); }, /** * Remove all content from the SVG * @memberOf Chart */ clearCanvas() { d3.select(`#${this.chartData.selector}`).selectAll('*').remove(); }, /** * Appends title and subtitle to the chart * @memberOf Chart */ drawTitle() { d3.select(`#${this.chartData.selector}`) .append('text') .attr('font-size', '20') .attr('x', this.width / 2) .attr('y', this.titleHeight - this.titleHeight * 0.1) .style('text-anchor', 'middle') .text(this.chartData.title); d3.select(`#${this.chartData.selector}`) .append('text') .attr('font-size', '12') .attr('x', this.width / 2) .attr('y', this.titleHeight - this.titleHeight * 0.1 + this.subtitleHeight) .style('text-anchor', 'middle') .text(this.chartData.subtitle); }, /** * Adds a tooltip to the SVG * @memberOf Chart * @param {Object} d dataset * @param {Object} e event x and y coordinates */ addTooltip(d, e) { d3.select(`#${this.chartData.selector}`) .append('rect') .attr('x', e.offsetX - 5 - 50) .attr('y', e.offsetY - 13 - 25) .attr('height', '16px') .attr('width', '80px') .attr('class', 'tt') .attr('fill', 'white'); d3.select(`#${this.chartData.selector}`) .append('text') .attr('x', e.offsetX - 50) .attr('y', e.offsetY - 25) .attr('class', 'tt') .attr('font-size', '10px') .text(`${d.dim}:${d.metric}`); }, /** * Removes all tooltips from the SVG * @memberOf Chart * @param {Object} d dataset */ removeTooltip() { d3.select(`#${this.chartData.selector}`) .selectAll('.tt').remove(); }, /** * Override default values * @param {Object} cs configuration of the coordinate systems * @param {Object} overrides the additional values that can be used for an object * @returns {Object} updated configuration of coordinate system */ setOverrides(cs, overrides) { overrides = overrides || {}; const keys = Object.keys(cs); for (const key of keys) Object.assign(cs[key], overrides[key]); return cs; }, /** * Generate legend if option -legends- defined as true * @memberOf Chart * @param {Object} cs configuration of the coordinate system */ generateLegend(cs) { if (this.chartData.legends && this.chartData.legends.enabled === true) { cs.palette.lineFill = (Array.isArray(cs.palette.lineFill)) ? cs.palette.lineFill : new Array(cs.palette.lineFill); cs.palette.fill = (Array.isArray(cs.palette.fill)) ? cs.palette.fill : new Array(cs.palette.fill); this.metric.forEach( (e, i) => { d3.select(`#${this.chartData.selector}`) .append('text') .attr('font-size', '10') .attr('x', this.width - 60) .attr('y', this.height * 0.95 - (i * 15)) .style('text-anchor', 'middle') .text(this.metric[i]); d3.select(`#${this.chartData.selector}`) .append("g") .attr("class", "legends") .append("rect") .attr('x', this.width - 30) .attr('y', this.height * 0.95 - (i * 15) - 10) .attr("width", 30) .attr("height", 10) .style("fill", function () { const fill = cs.palette.lineFill[i] || cs.palette.fill[i]; return fill; }); }) } }, /** * Generate Goal * @memberOf Chart * @param {Object} cs configuration of the coordinate system */ generateGoal(cs, shiftAxis, padding) { d3.select(`#${this.chartData.selector}`).selectAll('line#goal').remove(); const x1 = shiftAxis ? cs.y.axisWidth: cs.x.scale(this.goal) + padding; const x2 = shiftAxis ? this.width : cs.x.scale(this.goal) + padding; const y1 = shiftAxis ? cs.y.scale(this.goal) + padding : this.header; const y2 = shiftAxis ? cs.y.scale(this.goal) + padding : this.displayHeight - cs.x.axisHeight; d3.select(`#${this.chartData.selector}`).append('line') .attr('x1', x1) .attr('x2', x2) .attr('y1', y1) .attr('y2', y2) .attr('id', 'goal') .style('stroke', '#708090') .style('stroke-width', 1) }, /** * Generate Axis Lables * @memberOf Chart * @param {Object} cs configuration of the coordinate system */ generateAxisLabels(cs) { let footer = (this.chartData.legends) ? .85 : .95; if (!this.chartData.label) return; d3.select(`#${this.chartData.selector}`).selectAll('text.axisLabel').remove(); if (cs.x && cs.x.label) d3.select(`#${this.chartData.selector}`).append('text') .attr('font-size', '10') .attr('x', this.width / 2) .attr('y', this.height * footer) .attr('id', 'xAxisLabel') .attr('class', 'axisLabel') .style('text-anchor', 'middle') .text(cs.x.label) if (cs.y && cs.y.label) d3.select(`#${this.chartData.selector}`).append('text') .attr('font-size', '10') .attr('x', 10) .attr('y', this.height / 2) .attr('id', 'xAxisLabel') .attr('class', 'axisLabel') .style('text-anchor', 'middle') .text(cs.y.label) .attr('transform', `rotate(-90,10, ${this.height / 2})`) }, /** * get the values of a metric as an array * @memberOf Chart * @returns {Array} metric values */ metricAsArray(metric) { metric = this.chartData.data.map(d => d[metric]); return metric; }, ...((typeof barChart !== 'undefined') && { barChart }), ...((typeof vBarChart !== 'undefined') && { vBarChart }), ...((typeof scatterPlot !== 'undefined') && { scatterPlot }), ...((typeof pieChart !== 'undefined') && { pieChart }), ...((typeof areaChart !== 'undefined') && { areaChart }), ...((typeof lineGraph !== 'undefined') && { lineGraph }), ...((typeof bubbleChart !== 'undefined') && { bubbleChart }), }, computed: { /** * Dataset getter function * @memberOf Chart * @returns {Object} normalized dataset */ ds() { const ds = { metric: [] }; ds.metric = (Array.isArray(this.chartData.metric)) ? ds.metric = this.chartData.metric : new Array(this.chartData.metric); ds.dim = this.chartData.dim; ds.data = this.chartData.data; return ds.data.map((d) => { const td = { metric: [] }; if (!ds.metric[0]) td.metric[0] = d || 0; else { ds.metric.forEach(function(e, i){ td.metric[i] = d[e] || 0; }) } td.dim = this.chartData.dim ? d[this.chartData.dim] : null; return td; }); }, /** * Dimension getter function * @memberOf Chart * @returns {string} dim */ dim() { return this.chartData.dim || "undefined"; }, /** * Goal getter function * @memberOf Chart * @returns {number} Goal */ goal() { return this.chartData.goal; }, /** * Metric getter function * @memberOf Chart * @returns {array} Metrics */ metric() { const metric = (Array.isArray(this.chartData.metric)) ? this.chartData.metric : new Array(this.chartData.metric); return metric; }, /** * Height getter function * @memberOf Chart * @returns {number} Chart Height */ height() { return this.chartData.height - 10 || 190; }, /** * Width getter function * @memberOf Chart * @returns {number} Chart width */ width() { return this.chartData.width - 10 || 190; }, /** * Grid Tick getter function * @memberOf Chart * @returns {number} gridTicks */ gridTicks() { if (this.chartData.grid && this.chartData.grid.gridTicks != null) { return this.chartData.grid.gridTicks; } return 100; }, /** * Get the maxium value for metric * @memberOf Chart * @returns {number} Max value for metric */ max() { let max = 0; var results = []; this.ds.forEach(e => { results = results.concat([...e.metric]); }); results.forEach((e) => { max = max > e ? max : e; }); return max; }, /** * Get the maxium value for triplet * @memberOf Chart * @returns {Array} Max values for triplet */ maxTriplet() { const max = { v1: 0, v2: 0, v3: 0 }; this.ds.forEach(e => { max.v1 = max.v1 > e.metric[0] ? max.v1 : e.metric[0]; max.v2 = max.v2 > e.metric[1] ? max.v2 : e.metric[1]; max.v3 = max.v3 > e.metric[2] ? max.v3 : e.metric[2]; }); return max; }, /** * Get the minimum value for dataset * @memberOf Chart * @returns {number} Min value for metric */ min() { var results = []; this.ds.forEach(e => { results = results.concat([...e.metric]); }); return Math.min(...results.map(o => o)); }, /** * Get the minimum value for triplet * @memberOf Chart * @returns {Array} Min values for triplet */ minTriplet() { var results = { v1: [], v2: [], v3: [] }; this.ds.forEach(e => { results.v1.push(e.metric[0]) results.v2.push(e.metric[1]) results.v3.push(e.metric[2]) }) return { v1: (Math.min(...results.v1.map(o => o))), v2: (Math.min(...results.v2.map(o => o))), v3: (Math.min(...results.v3.map(o => o))) }; }, /** * Gets the height of the dispaly area * @memberOf Chart * @returns {number} Height of the chart display */ displayHeight() { if (this.chartData.legends && this.chartData.legends.enabled === true) { return this.height * .80; } else { return this.height * .90; } }, /** * Gets the height of the title * @memberOf Chart * @returns {number} Height of the chart title */ titleHeight() { if (this.chartData.title) return this.chartData.textHeight || 25; return 0; }, /** * Gets the subtitle height * @memberOf Chart * @returns {number} Height of chart subtitle */ subtitleHeight() { if (this.chartData.subtitle) return this.chartData.textHeight * 0.66 || 25 * 0.66; return 0; }, /** * Gets the combined height of the title and subtitle * @memberOf Chart * @returns {number} Total header height */ header() { return (this.titleHeight + this.subtitleHeight); }, }, mounted() { this.initalizeChart(); }, watch: { chartData: { handler() { this.refreshChart(); }, deep: true, }, }, template: '<svg :id=\'this.chartData.selector\' x=\'5\' y=\'5\' :height=\'this.height + 20\' :width=\'this.width + 20\'> </svg>', }); }, }; export default Chart; if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(Chart); }