|
|
|
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`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|