import { Datapoint, Color, ValueX, Opacity, BarPodType, BarSerie } from '../types'; import { BarConfig } from './bar_options'; import * as _ from 'lodash'; export type DataRow = { key: number; // x items: DataItem[]; maxSumm: number; minSubtraction: number; }; export type DataItem = { target: string; // serie target values: number[]; // y list colors: string[]; opacity: number[]; }; const DEFAULT_BAR_COLOR = 'green'; const DEFAULT_OPACITY = 1; export class DataProcessor { _formattedDataRows: DataRow[] = []; constructor(protected visibleSeries: BarSerie[], protected options: BarConfig) { this.validateData(); switch(this.options.barType) { case BarPodType.DISCRETE: this.setDataRowsForDiscreteType(); break; case BarPodType.NON_DISCRETE: this.setDataRowsForNonDiscreteType(); break; default: throw new Error(`Bar DataProcessor: Unknown BarPodType ${this.options.barType}`); } } public get dataRows(): DataRow[] { return this._formattedDataRows; } protected setDataRowsForDiscreteType(): void { const zippedData = _.zip(..._.map(this.visibleSeries, serie => serie.datapoints)); this._formattedDataRows = _.map(zippedData, (dataRow: Datapoint[], rowIdx: number) => { return { key: _.first(_.first(dataRow)), // x items: _.map(dataRow, (datapoint: Datapoint, pointIdx: number) => { const serie = this.visibleSeries[pointIdx]; const values = _.tail(datapoint); return { target: serie.target, values, // y list colors: this.getColorsForEachValue(values, serie.color, datapoint[0], serie.target), opacity: this.getOpacityForEachValue(values, serie.opacity, datapoint[0], serie.target), } }), maxSumm: _.max(_.map(dataRow, (datapoint: Datapoint) => this.getMaxSumm(_.tail(datapoint)))), // max y axis scale minSubtraction: _.min(_.map(dataRow, (datapoint: Datapoint) => this.getMinSubtraction(_.tail(datapoint)))), // min y axis scale } }); } protected setDataRowsForNonDiscreteType(): void { for(let serie of this.visibleSeries) { const rows = _.map(serie.datapoints, (datapoint: Datapoint) => { const values = _.tail(datapoint); return { key: datapoint[0], // x items: [ { target: serie.target, values, // y list colors: this.getColorsForEachValue(values, serie.color, datapoint[0], serie.target), opacity: this.getOpacityForEachValue(values, serie.opacity, datapoint[0], serie.target), } ], maxSumm: this.getMaxSumm(values), // max y axis scale minSubtraction: this.getMinSubtraction(values), // min y axis scale } }); this._formattedDataRows = _.concat(this._formattedDataRows, rows); } this._formattedDataRows = _.sortBy(this._formattedDataRows, 'key'); } getColorsForEachValue(values: number[], color: Color, key: ValueX, target: string): string[] { if(_.isString(color)) { return _.map(values, value => color); } if(_.isArray(color)) { return _.map(values, (value, i) => color[i] || DEFAULT_BAR_COLOR); } if(_.isFunction(color)) { return _.map(values, (value, i) => (color as Function)({ value, barIndex: i, key, target })); } throw new Error(`Unknown type of serie color: ${target} ${color}`); } getOpacityForEachValue(values: number[], opacity: Opacity | undefined, key: ValueX, target: string): number[] { if(opacity === undefined) { return _.map(values, value => DEFAULT_OPACITY); } if(_.isNumber(opacity)) { return _.map(values, value => opacity); } if(_.isArray(opacity)) { return _.map(values, (value, i) => !_.isNil(opacity[i]) ? opacity[i] : DEFAULT_OPACITY); } if(_.isFunction(opacity)) { return _.map(values, (value, i) => (opacity as Function)({ value, barIndex: i, key, target })); } throw new Error(`Unknown type of serie opacity: ${target} ${opacity}`); } getMaxSumm(values: number[]): number { return values.reduce((prev, curr) => curr > 0 ? prev + curr : prev, 0); } getMinSubtraction(values: number[]): number { return values.reduce((prev, curr) => curr < 0 ? prev + curr : prev, 0); } validateData(): void { if(this.options.barType === BarPodType.NON_DISCRETE) { return; } // TODO: move non-empty datapoints validation to core if(_.isEmpty(this.visibleSeries)) { return; } const xValuesList = _.map(_.first(this.visibleSeries).datapoints, dp => dp[0]); _.forEach(this.visibleSeries, serie => { const serieXList = _.map(serie.datapoints, dp => dp[0]); if(!_.isEqual(xValuesList, serieXList)) { throw new Error(`Bar DataProcessor: All series should have equal X values lists`); } }); } }