Source: line/index.js

import {dispatch} from 'd3';
import Facet from '../facet/';
import brushMixin from '../brushMixin';
import fitLineMixin from '../fitLineMixin';
import fixLineMixin from '../fixLineMixin';
import seriesMixin from '../seriesMixin';
import zoomMixin from '../zoomMixin';
import paddingMixin from '../paddingMixin';
import shapeMixin from '../shapeMixin';
import stackMixin from '../stackMixin';
import streamMixin from '../streamMixin';
import {mixedMeasure} from '../../modules/measureField';
import {attrFunc, genFunc, mix} from '../../modules/util';
import _brush from './_brush';
import _brushZoom from './_brushZoom';
import _mark from './_mark';
import _munge from './_munge';
import _panning from './_panning';
import _domain from './_domain';
import _range from './_range';
import _axis from './_axis';
import _meanLine from './_meanLine';
import _legend from './_legend';
import _region from './_region';
import _facet from './_facet';
import _fitLine from './_fitLine';
import _fixLine from './_fixLine';
import _tooltip from './_tooltip';
import _zoom from './_zoom';
import {leastSquare as lsFunc} from '../../modules/transform';

const size = {range: [2, 2], scale: 'linear', reverse: false};
const shapes = ['line', 'area'];
const conditions = ['normal', 'count', 'mixed'];
const _attrs = {
  meanLine : false,
  multiTooltip: false,
  padding: 0,
  pointRatio : 2,
  regionPadding: 0.1,
	shape: shapes[0],
	areaGradient: false,
  scaleBandMode : false,
  size: size,
  individualScale: false
};

/**
 * renders a line chart
 * @class Line
 * @augments Core
 * @augments RectLinear
 * @augments Facet
 * @augments FitLineMixin
 * @augments SeriesMixin
 * @augments BrushMixin
 * @augments ZoomMixin
 * @augments PaddingMixin
 * @augments ShapeMixin
 * @augments StreamMixin
 */
class Line extends mix(Facet).with(fitLineMixin, fixLineMixin, seriesMixin, brushMixin, zoomMixin, paddingMixin, shapeMixin, stackMixin, streamMixin) {
  constructor() {
    super();
    this.setAttrs(_attrs);
    this.__execs__.multiTooltipDispatch = dispatch('selectStart', 'selectMove', 'selectEnd', 'multiTooltip');
    this.rebindOnMethod(this.__execs__.multiTooltipDispatch);
    this.process('munge', _munge, {isPre: true})
      .process('brushZoom', _brushZoom, {isPre: true, allow: function() {return this.isBrushZoom()}})
      .process('domain', _domain, {isPre: true, allow: function() {return !this.isBrushZoom()}})
      .process('range', _range, {isPre: true, allow: function() {return !this.isBrushZoom()}})
      .process('axis', _axis, {allow: function() {return !this.isBrushZoom()}})
      .process('region', _region, {allow: function() {return !this.isBrushZoom()}})
      .process('facet', _facet, {allow: function() {return !this.isBrushZoom() && this.isFacet()}})
      .process('mark', _mark, {allow: function() {return !this.isBrushZoom() && !this.isFacet()}})
      .process('meanLine', _meanLine, {allow: function() {return !this.isBrushZoom()}})
      .process('fitLine', _fitLine, {allow: function() {return !this.isBrushZoom()}})
      .process('fixLine', _fixLine, {allow: function() {return !this.isBrushZoom()}})
      .process('tooltip', _tooltip, {allow: function() {return !this.isBrushZoom()}})
      .process('panning', _panning, {allow: function() {return !this.isBrushZoom()}})
      .process('zoom', _zoom, {allow: function() {return !this.isBrushZoom()}})
      .process('brush', _brush, {allow: function() {return !this.isBrushZoom()}})
      .process('legend', _legend, {allow: function() {return !this.isBrushZoom()}})
  }
  /**
   * @override
   */
  renderCanvas() {
    return super.renderCanvas(this.point() ? this.size().range[0]*2 : 0);
  }
  /**
   * If is true, renders the tooltip showing multiple points on the same horizontal position. If is a string or object, sets sorting order of items by each value. If multiTooltip is not specified, returns the instance's multiTooltip setting.
   * @example
   * line.multiTooltip(true) // show multiple points on the same horizontal position on a tooltip
   * line.multiTooltip('ascending') //sort items in ascending order by their each value
   * line.multiTooltip(false) 
   * line.multiTooltip() 
   * @param {boolean|string|object} [multiTooltip=false]
   * @param {string} [multiTooltip.sortByValue=natural] (natural|ascending|descending)
   * @return {multiTooltip|Line}
   */
  multiTooltip(multiTooltip) {
    if (!arguments.length) return this.__attrs__.multiTooltip;
    if (typeof multiTooltip === 'boolean') {
      if (multiTooltip) {
        multiTooltip = {sortByValue: 'natural'};
      } 
    } 
    if (typeof multiTooltip === 'object') {
      if (!multiTooltip.sortByValue) multiTooltip.sortByValue = 'natural';
    }
    this.__attrs__.multiTooltip = multiTooltip;
    return this;
  }

  /**
   * gets a result of linear least squres from serieses. If key is specified, returns the value only froma specific series. It is used for draw fit-lines.
   * @param {string} [key] a name of a series 
   * @example
   * let l = line.data([
   *  {name: 'A', sales: 10, profit: 5},
   *  {name: 'B', sales: 20, profit: 10},
   *  {name: 'C', sales: 30, profit: 3},
   *  ...
   *  ]) //sets data
   *  .dimensions(['name'])
   *  .measures(['sales', 'profit'])
   *  .render();
   * l.leastSquare('A') // returns a result of the series A
   * l.leastSquare() // returns from all serieses
   * @return {object[]} returns [{key: fitLineVal, slope, intercept, rSquare}...] 
   */
  leastSquare(key) {
    const measureName = this.measureName();
    const individualScale = this.isIndividualScale();
    return this.__execs__.munged.filter(series => {
      if (typeof key === 'string') {
        return series.data.key === key;
      } else {
        return true;
      }
    }).map(series => {
      let targets = series.children.map(d => {return {x: d.data.key, y:d.data.value[measureName]}});
      let ls = lsFunc(targets);
      if (individualScale && series.scale) {
        ls.scale = series.scale;
      }
      ls.key = series.data.key;
      return ls;
    });
  }

  measureName() {
    let measures = this.measures();
    let yField;
    if (this.condition() === conditions[2]) yField = mixedMeasure.field; 
    else if (this.aggregated() && measures[0].field === mixedMeasure.field) yField = measures[0].field;
    else yField = measures[0].field + '-' + measures[0].op;
    return yField;
  }

  isCount() {
    return this.condition() === conditions[1];
  }

  isFacet() {
    return this.facet() && this.isNested() && !this.stacked();
  }

  isIndividualScale() {
    return this.individualScale() && this.isNested() && !this.stacked();
  }

  isMixed() {
    return this.condition() == conditions[2] ;
  }

  isNested() {
    let dimensions = this.dimensions();
    let condition = this.condition();
    return dimensions.length === 2 || (condition == conditions[2] && dimensions.length === 1);
  }

  isStacked() {
    return this.stacked() && this.isNested();
  }

  muteFromLegend(legend) {
    this.muteRegions(legend.key);
  }
  
  muteToLegend(d) {
    this.muteLegend(d.parent.data.key);
  }
  
  demuteFromLegend(legend) {
    this.demuteRegions(legend.key);
  }
  
  demuteToLegend(d) {
    this.demuteLegend(d.parent.data.key);
  }
  
  showMultiTooltip(tick, start) { //for the facet condition
    if (this.multiTooltip()) {
      let mt = this.__execs__.tooltip;
      mt.tick(tick, start);
    }
  }
} 
/**
 * If meanLine is specified sets the meanLine setting and returns the Line instance itself. If meanLine is true renders a mean-line on each series. If meanLine is not specified, returns the current meanLine setting.
 * @function
 * @example
 * line.meanLine(true)
 * @param {boolean} [meanLine=false] If is true, renders a mean-line.
 * @return {meanLine|Line}
 */
Line.prototype.meanLine = attrFunc('meanLine');
Line.prototype.scaleBandMode = attrFunc('scaleBandMode');
/**
 * If individualScale is specified sets the individualScale setting and returns the Line instance itself. When a line chart has multiple measures, each measure will be a series. If individualScale is true, when has multiple measures, each series will be drawn based on an individual scale of itself.
 * @function
 * @example
 * line.individualScale(true)
 * @param {boolean} [individualScale=false] If is true, renders a mean-line.
 * @return {individualScale|Line}
 */
Line.prototype.individualScale = attrFunc('individualScale');

/**
 * If areaGradient is specified sets the areaGradient setting and returns the Line instance itself. If areaGradient is true, when a line chart shape area, each area filled gradient.
 * @function
 * @example
 * line.areaGradient(true)
 * @param {boolean} [areaGradient=false] If is true, each area filled gradient.
 * @return {areaGradient|Line}
 */
Line.prototype.areaGradient = attrFunc('areaGradient');

function domainY(fieldY, munged, level=0, aggregated=false, stacked=false) {
  return fieldY.munged(munged).level(level).aggregated(aggregated).domain(0, stacked);
}
export default genFunc(Line);
export {conditions, domainY, shapes};