import {ascending} from 'd3-array'; import {CompositeChart} from './composite-chart'; import {lineChart} from './line-chart'; import {utils} from '../core/utils'; import {d3compat} from '../core/config'; /** * A series chart is a chart that shows multiple series of data overlaid on one chart, where the * series is specified in the data. It is a specialization of Composite Chart and inherits all * composite features other than recomposing the chart. * * Examples: * - {@link http://dc-js.github.io/dc.js/examples/series.html Series Chart} * @mixes CompositeChart */ export class SeriesChart extends CompositeChart { /** * Create a Series Chart. * @example * // create a series chart under #chart-container1 element using the default global chart group * var seriesChart1 = new SeriesChart("#chart-container1"); * // create a series chart under #chart-container2 element using chart group A * var seriesChart2 = new SeriesChart("#chart-container2", "chartGroupA"); * @param {String|node|d3.selection} parent - Any valid * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying * a dom block element such as a div; or a dom element or d3 selection. * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in. * Interaction with a chart will only trigger events and redraws within the chart's group. */ constructor (parent, chartGroup) { super(parent, chartGroup); this._keySort = (a, b) => ascending(this.keyAccessor()(a), this.keyAccessor()(b)); this._charts = {}; this._chartFunction = lineChart; this._chartGroup = chartGroup; this._seriesAccessor = undefined; this._seriesSort = ascending; this._valueSort = this._keySort; this._mandatoryAttributes().push('seriesAccessor', 'chart'); this.shareColors(true); } _compose (subChartArray) { super.compose(subChartArray); } compose (subChartArray) { throw new Error('Not supported for this chart type'); } _preprocessData () { const keep = []; let childrenChanged; const nesting = d3compat.nester({ key: this._seriesAccessor, sortKeys: this._seriesSort, sortValues: this._valueSort, entries: this.data() }); const children = nesting.map((sub, i) => { const subChart = this._charts[sub.key] || this._chartFunction(this, this._chartGroup , sub.key, i); if (!this._charts[sub.key]) { childrenChanged = true; } this._charts[sub.key] = subChart; keep.push(sub.key); return subChart .dimension(this.dimension()) .group({ all: typeof sub.values === 'function' ? sub.values : utils.constant(sub.values) }, sub.key) .keyAccessor(this.keyAccessor()) .valueAccessor(this.valueAccessor()) .brushOn(false); }); // this works around the fact compositeChart doesn't really // have a removal interface Object.keys(this._charts) .filter(c => keep.indexOf(c) === -1) .forEach(c => { this._clearChart(c); childrenChanged = true; }); this._compose(children); if (childrenChanged && this.legend()) { this.legend().render(); } } _clearChart (c) { if (this._charts[c].g()) { this._charts[c].g().remove(); } delete this._charts[c]; } _resetChildren () { Object.keys(this._charts).map(this._clearChart.bind(this)); this._charts = {}; } /** * Get or set the chart function, which generates the child charts. * @example * // put curve on the line charts used for the series * chart.chart(function(c) { return new LineChart(c).curve(d3.curveBasis); }) * // do a scatter series chart * chart.chart(anchor => new ScatterPlot(anchor)) * @param {Function} [chartFunction= (anchor) => new LineChart(anchor)] * @returns {Function|SeriesChart} */ chart (chartFunction) { if (!arguments.length) { return this._chartFunction; } this._chartFunction = chartFunction; this._resetChildren(); return this; } /** * **mandatory** * * Get or set accessor function for the displayed series. Given a datum, this function * should return the series that datum belongs to. * @example * // simple series accessor * chart.seriesAccessor(function(d) { return "Expt: " + d.key[0]; }) * @param {Function} [accessor] * @returns {Function|SeriesChart} */ seriesAccessor (accessor) { if (!arguments.length) { return this._seriesAccessor; } this._seriesAccessor = accessor; this._resetChildren(); return this; } /** * Get or set a function to sort the list of series by, given series values. * @see {@link https://github.com/d3/d3-array/blob/master/README.md#ascending d3.ascending} * @see {@link https://github.com/d3/d3-array/blob/master/README.md#descending d3.descending} * @example * chart.seriesSort(d3.descending); * @param {Function} [sortFunction=d3.ascending] * @returns {Function|SeriesChart} */ seriesSort (sortFunction) { if (!arguments.length) { return this._seriesSort; } this._seriesSort = sortFunction; this._resetChildren(); return this; } /** * Get or set a function to sort each series values by. By default this is the key accessor which, * for example, will ensure a lineChart series connects its points in increasing key/x order, * rather than haphazardly. * @see {@link https://github.com/d3/d3-array/blob/master/README.md#ascending d3.ascending} * @see {@link https://github.com/d3/d3-array/blob/master/README.md#descending d3.descending} * @example * // Default value sort * _chart.valueSort(function keySort (a, b) { * return d3.ascending(_chart.keyAccessor()(a), _chart.keyAccessor()(b)); * }); * @param {Function} [sortFunction] * @returns {Function|SeriesChart} */ valueSort (sortFunction) { if (!arguments.length) { return this._valueSort; } this._valueSort = sortFunction; this._resetChildren(); return this; } } export const seriesChart = (parent, chartGroup) => new SeriesChart(parent, chartGroup);