Source: v-chart-plugin.js

/** 
 *  @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';

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.generateLegend(cs);
        },
        /**
         * Redraw the Chart when the data is recycled
         * @memberOf Chart
         */
        refreshChart() {
          this.clearAxis();
          this[this.chartData.chartType]('refresh');
        },
        /**
         * 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('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('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.layerX - 5 - 50)
            .attr('y', e.layerY - 13 - 25)
            .attr('height', '16px')
            .attr('width', '80px')
            .attr('class', 'tt')
            .attr('fill', 'white');

          d3.select(`#${this.chartData.selector}`)
            .append('text')
            .attr('x', e.layerX - 50)
            .attr('y', e.layerY - 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) {
            d3.select(`#${this.chartData.selector}`)
              .append('text')
              .attr('x', this.width - 60)
              .attr('y', this.height * 0.95)
              .style('text-anchor', 'middle')
              .text(this.chartData.metric);

            d3.select(`#${this.chartData.selector}`)
              .append("g")
              .attr("class", "legends")
              .append("rect")
              .attr('x', this.width - 30)
              .attr('y', this.height * 0.95 - 10)
              .attr("width", 30)
              .attr("height", 10)
              .style("fill", function () {
                const fill = cs.palette.lineFill || cs.palette.fill;
                return fill;
              });
          }
        },

        ...((typeof barChart !== 'undefined') && { barChart }),
        ...((typeof vBarChart !== 'undefined') && { vBarChart }),
        ...((typeof scatterPlot !== 'undefined') && { scatterPlot }),
        ...((typeof pieChart !== 'undefined') && { pieChart }),
        ...((typeof areaChart !== 'undefined') && { areaChart }),
        ...((typeof lineGraph !== 'undefined') && { lineGraph }),
      },
      computed: {
        /**
         * Dataset getter function
         * @memberOf Chart
         * @returns {Object} normalized dataset
         */
        ds() {
          return this.chartData.data.map((d) => {
            const td = {};
            td.metric = this.chartData.metric ? d[this.chartData.metric] : d;
            td.dim = this.chartData.dim ? d[this.chartData.dim] : null;
            return td;
          });
        },
        /**
         * Height getter function
         * @memberOf Chart
         * @returns {number} Chart Height
         */
        height() {
          return this.chartData.height || 200;
        },
        /**
         * Width getter function
         * @memberOf Chart
         * @returns {number} Chart width
         */
        width() {
          return this.chartData.width || 200;
        },
        /**
         * Get the maxium value for metric
         * @memberOf Chart
         * @returns {number} Max value for metric
         */
        max() {
          let max = 0;
          this.ds.forEach((e) => {
            max = max > e.metric ? max : e.metric;
          });
          return max;
        },
        /**
         * Get the minimum value for dataset
         * @memberOf Chart
         * @returns {number} Min value for metric
         */
        min() {
          return Math.min(...this.ds.map(o => o.metric));
        },
        /**
         * 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 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;
          }
        },
        /**
         * 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);
}