Source: line/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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};