Source: import/lineGraph.js

/** 
 *  @fileOverview Line Graph component definition
 *
 *  @author       Brian Greig
 *
 *  @requires     NPM:d3:Vue
 *  @requires     src/v-chart-plugin.js
 */
const d3 = Object.assign({},
  require('d3-selection'),
  require('d3-scale'),
  require('d3-axis'),
  require('d3-shape'));
/**
 * Builds a Line Graph.
 * @module lineGraph
 */

const lineGraph = function chart(mode) {
  /**
   * The SVG that stores the chart
   * @member svgContainer
   */
  const svgContainer = d3.select(`#${this.chartData.selector}`);
  /**
   * The configuration of the coordinate system
   * @member cs
   */
  let cs = {
    palette: {
      lineFill: ['#ffcdcd', '#005792'],
      pointFill: '#005792',
      pointStroke: '#d1f4fa',
    },
    x: {
      label: this.dim,
      domain: [],
      range: [],
      axisHeight: 20,
    },
    y: {
      label: this.metric,
      axisWidth: 40,
      ticks: 5,
    },
  };

  /**
   * Runs when a new element is added to the dataset
   * @member enter
   * @function
   * @param {Object} points (svg element) 
   */
  const enter = (points, path) => {
    this.metric.forEach( (e, i) => {
      path[i].enter().append('path')
        .attr('d', cs.lineFunction[i](this.ds))
        .attr('fill', 'none')
        .attr('id', 'p' + i)
        .attr('stroke', cs.palette.lineFill[i])
        .attr('stroke-width', 3)
    })    
    this.metric.forEach( (e, i) => {
      cs.offset = i;      
      points[i].enter()
        .append('circle')
        .attr('class', this.selector)
        .attr('class', "r" + i)
        .attr('r', 2)
        .on('mouseover', (d) => {
          this.addTooltip(d, window.event);
        })
        .on('mouseout', (d) => {
          this.removeTooltip(d);
        })
        .attr('cx', d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
        .attr('cy', d => cs.y.scale(d.metric));
    });
    if (this.goal) this.generateGoal(cs, true, 0);
    return points;
  };
  /**
   * Runs when a value of an element in dataset is changed
   * @member transition
   * @function
   * @param {Object} points (svg element) 
   */
  const transition = (points, path) => {
    this.metric.forEach( (e, i) => {
      path[i].transition()
      .attr('d', cs.lineFunction[i](this.ds));
    })
  
    this.metric.forEach( (e, i) => {
      cs.offset = i;      
      points[i].transition()
        .attr('cx', d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
        .attr('cy', d => cs.y.scale(d.metric))
        .attr('cx', d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
        .attr('cy', d => cs.y.scale(d.metric));
    });
    if (this.goal) this.generateGoal(cs, true, 0);
    return points;
  };

  /**
   * Runs when an element is removed from the dataset
   * @member exit
   * @function
   * @param {Object} points (svg element)
   */
  const exit = (points, path) => {
    this.metric.forEach( (e, i) => {
      points[i].exit().remove();
    });
    this.metric.forEach( (e, i) => {
      path[i].exit().remove();
    });
    return points;
  };

  /**
   * Builds the scales for the x and y axes
   * @member buildScales
   * @function
   */
  const buildScales = cs => {
    cs.y.scale = d3.scaleLinear()
      .domain([this.min, this.max])
      .range([this.displayHeight - cs.x.axisHeight, this.header]);
    this.ds.forEach(t => cs.x.domain.push(t.dim));
    this.ds.forEach((t, i) => cs.x.range.push(((this.width * i) - this.header) / this.ds.length));
    cs.x.scale = d3.scaleOrdinal().domain(cs.x.domain).range(cs.x.range);
  };
  /**
   * Draws the x and y axes on the svg
   * @member drawAxis
   * @function
   */
  const drawAxis = cs => {
    this.drawGrid(cs);
    cs.x.axis = d3.axisBottom().scale(cs.x.scale);
    cs.x.xOffset = cs.y.axisWidth + 5;
    cs.x.yOffset = this.displayHeight - cs.x.axisHeight;
    cs.y.axis = d3.axisLeft().ticks(cs.y.ticks, 's').scale(cs.y.scale);
    cs.y.xOffset = cs.y.axisWidth;
    cs.y.yOffset = 0;
    svgContainer.append('g').attr('class', 'axis').attr('transform', `translate(${cs.x.xOffset}, ${cs.x.yOffset})`)
      .call(cs.x.axis);
    svgContainer.append('g').attr('class', 'axis').attr('transform', `translate(${cs.y.xOffset},${cs.y.yOffset})`)
      .call(cs.y.axis);
  };

  cs.lineFunction = [];
  this.metric.forEach( (e, i) => {
    cs.lineFunction.push( 
      d3.line()
        .x(d => cs.x.scale(d.dim) + cs.y.axisWidth + 5)
        .y(d => cs.y.scale(d.metric[i]))
      )  
  });
  
  const points = [];
  this.metric.forEach( (e, i) => {
    points.push(svgContainer.selectAll('circle.r' + i).data(this.ds.map(d => {
      return  {
        metric: d.metric[i],
        dim: d.dim
      }      
    })))
  })

  const path = []
  this.metric.forEach( (e, i) => {
    path.push(svgContainer.selectAll('path#p' + i).data(this.ds))
  })

  cs = this.setOverrides(cs, this.chartData.overrides);

  buildScales(cs);
  drawAxis(cs);
  enter(points, path);
  transition(points, path);
  exit(points, path);

  return cs;
};

export default lineGraph;