Added charts abstruction
This commit is contained in:
Родитель
f65edfe839
Коммит
ee54ae541e
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
export interface ChartTypeOptions {
|
||||
chartType: string;
|
||||
plotOptions?: Highcharts.PlotOptions; // See: https://api.highcharts.com/highcharts/plotOptions
|
||||
}
|
||||
|
||||
export const PERCENTAGE = 'percent';
|
||||
export const STACKED = 'normal';
|
||||
export const UNSTACKED = undefined;
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
import { HighchartsChart } from './highchartsChart';
|
||||
import { ChartTypeOptions } from '../chartTypeOptions';
|
||||
|
||||
export abstract class Area extends HighchartsChart {
|
||||
//#region Methods override
|
||||
|
||||
protected getChartTypeOptions(): ChartTypeOptions {
|
||||
return {
|
||||
chartType: 'area',
|
||||
plotOptions: {
|
||||
area: {
|
||||
stacking: this.getStackingOption()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
|
||||
protected abstract getStackingOption(): any;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
import { HighchartsChart } from './highchartsChart';
|
||||
import { ChartTypeOptions } from '../chartTypeOptions';
|
||||
|
||||
export abstract class Column extends HighchartsChart {
|
||||
//#region Methods override
|
||||
|
||||
protected getChartTypeOptions(): ChartTypeOptions {
|
||||
return {
|
||||
chartType: 'column',
|
||||
plotOptions: {
|
||||
column: {
|
||||
stacking: this.getStackingOption()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
|
||||
protected abstract getStackingOption(): any;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
import { Pie } from './pie';
|
||||
|
||||
export class Donut extends Pie {
|
||||
//#region Methods override
|
||||
|
||||
protected getInnerSize(): any {
|
||||
return '40%';
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
'use strict';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import * as Highcharts from 'highcharts';
|
||||
import { IVisualizerOptions } from '../../IVisualizerOptions';
|
||||
import { ChartTypeOptions } from '../chartTypeOptions';
|
||||
import { Utilities } from '../../../common/utilities';
|
||||
|
||||
export interface ICategoriesAndSeries {
|
||||
categories?: string[];
|
||||
series: any[];
|
||||
}
|
||||
|
||||
export abstract class HighchartsChart {
|
||||
protected options: IVisualizerOptions;
|
||||
|
||||
public constructor(options: IVisualizerOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public getHighchartsOptions(): Highcharts.Options {
|
||||
const chartOptions = this.options.chartOptions;
|
||||
const chartTypeOptions = this.getChartTypeOptions();
|
||||
const isDatetimeAxis = Utilities.isDate(chartOptions.columnsSelection.xAxis.type);
|
||||
const categoriesAndSeries = this.getCategoriesAndSeries(isDatetimeAxis);
|
||||
|
||||
const highchartsOptions: Highcharts.Options = {
|
||||
chart: {
|
||||
type: chartTypeOptions.chartType
|
||||
},
|
||||
plotOptions: chartTypeOptions.plotOptions,
|
||||
xAxis: {
|
||||
type: isDatetimeAxis ? 'datetime' : undefined,
|
||||
categories: categoriesAndSeries.categories,
|
||||
title: {
|
||||
text: this.getXAxisTitle(),
|
||||
align: 'middle'
|
||||
}
|
||||
},
|
||||
yAxis: this.getYAxis(),
|
||||
series: categoriesAndSeries.series
|
||||
};
|
||||
|
||||
return highchartsOptions;
|
||||
}
|
||||
|
||||
protected getCategoriesAndSeries(isDatetimeAxis: boolean): ICategoriesAndSeries {
|
||||
const columnsSelection = this.options.chartOptions.columnsSelection;
|
||||
const xAxisColumn = columnsSelection.xAxis;
|
||||
const xAxisColumnIndex = Utilities.getColumnIndex(this.options.queryResultData, xAxisColumn);
|
||||
let categoriesAndSeries = {
|
||||
series: [],
|
||||
categories: isDatetimeAxis ? undefined : []
|
||||
};
|
||||
|
||||
if(columnsSelection.splitBy && columnsSelection.splitBy.length > 0) {
|
||||
this.getSplitByCategoriesAndSeries(xAxisColumnIndex, isDatetimeAxis, categoriesAndSeries);
|
||||
} else {
|
||||
this.getStandardCategoriesAndSeries(xAxisColumnIndex, isDatetimeAxis, categoriesAndSeries);
|
||||
}
|
||||
|
||||
return categoriesAndSeries;
|
||||
}
|
||||
|
||||
protected getStandardCategoriesAndSeries(xAxisColumnIndex: number, isDatetimeAxis: boolean, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const chartOptions = this.options.chartOptions;
|
||||
const yAxesIndexes = _.map(chartOptions.columnsSelection.yAxes, (yAxisColumn) => {
|
||||
return Utilities.getColumnIndex(this.options.queryResultData, yAxisColumn);
|
||||
});
|
||||
|
||||
const seriesMap = {};
|
||||
|
||||
this.options.queryResultData.rows.forEach((row) => {
|
||||
let xAxisValue: any = row[xAxisColumnIndex];
|
||||
|
||||
// If the x-axis is a date, convert it's value to milliseconds as this is what expected by 'Highcharts'
|
||||
if(isDatetimeAxis) {
|
||||
const dateValue = Utilities.getValidDate(xAxisValue, chartOptions.utcOffset);
|
||||
|
||||
xAxisValue = dateValue.valueOf();
|
||||
} else {
|
||||
categoriesAndSeries.categories.push(xAxisValue);
|
||||
}
|
||||
|
||||
_.forEach(yAxesIndexes, (yAxisIndex, i) => {
|
||||
const yAxisColumnName = chartOptions.columnsSelection.yAxes[i].name;
|
||||
const yAxisValue = row[yAxisIndex];
|
||||
|
||||
if(!seriesMap[yAxisColumnName]) {
|
||||
seriesMap[yAxisColumnName] = [];
|
||||
}
|
||||
|
||||
const data = isDatetimeAxis? [xAxisValue, yAxisValue] : yAxisValue;
|
||||
|
||||
seriesMap[yAxisColumnName].push(data);
|
||||
});
|
||||
});
|
||||
|
||||
for (let yAxisColumnName in seriesMap) {
|
||||
categoriesAndSeries.series.push({
|
||||
name: yAxisColumnName,
|
||||
data: seriesMap[yAxisColumnName]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected getSplitByCategoriesAndSeries( xAxisColumnIndex: number, isDatetimeAxis: boolean, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
if(isDatetimeAxis) {
|
||||
this.getSplitByCategoriesAndSeriesForDateXAxis(this.options, xAxisColumnIndex, categoriesAndSeries);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const columnsSelection = this.options.chartOptions.columnsSelection;
|
||||
const yAxisColumn = columnsSelection.yAxes[0];
|
||||
const splitByColumn = columnsSelection.splitBy[0];
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(this.options.queryResultData, yAxisColumn);
|
||||
const splitByColumnIndex = Utilities.getColumnIndex(this.options.queryResultData, splitByColumn);
|
||||
const uniqueXValues = {};
|
||||
const uniqueSplitByValues = {};
|
||||
|
||||
this.options.queryResultData.rows.forEach((row) => {
|
||||
const xValue = row[xAxisColumnIndex];
|
||||
const yValue = row[yAxisColumnIndex];
|
||||
const splitByValue = row[splitByColumnIndex];
|
||||
|
||||
if(!uniqueXValues[xValue]) {
|
||||
uniqueXValues[xValue] = true;
|
||||
}
|
||||
|
||||
if(!uniqueSplitByValues[splitByValue]) {
|
||||
uniqueSplitByValues[splitByValue] = {};
|
||||
}
|
||||
|
||||
uniqueSplitByValues[splitByValue][xValue] = yValue;
|
||||
});
|
||||
|
||||
// Populate X-Axis
|
||||
categoriesAndSeries.categories = _.keys(uniqueXValues);
|
||||
|
||||
// Populate Split by
|
||||
for (let splitByValue in uniqueSplitByValues) {
|
||||
const currentSeries = {
|
||||
name: splitByValue,
|
||||
data: []
|
||||
};
|
||||
|
||||
const xValueToYValueMap = uniqueSplitByValues[splitByValue];
|
||||
|
||||
// Set a split-by value for each unique x value
|
||||
categoriesAndSeries.categories.forEach((xValue) => {
|
||||
const yValue = xValueToYValueMap[xValue] || null;
|
||||
|
||||
currentSeries.data.push(yValue);
|
||||
});
|
||||
|
||||
categoriesAndSeries.series.push(currentSeries);
|
||||
}
|
||||
}
|
||||
|
||||
protected getSplitByCategoriesAndSeriesForDateXAxis(options: IVisualizerOptions, xAxisColumnIndex: number, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const columnsSelection = options.chartOptions.columnsSelection;
|
||||
const yAxisColumn = columnsSelection.yAxes[0];
|
||||
const splitByColumn = columnsSelection.splitBy[0];
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(options.queryResultData, yAxisColumn);
|
||||
const splitByColumnIndex = Utilities.getColumnIndex(options.queryResultData, splitByColumn);
|
||||
const splitByMap = {};
|
||||
|
||||
options.queryResultData.rows.forEach((row) => {
|
||||
const splitByValue: string = <string>row[splitByColumnIndex];
|
||||
const yValue = row[yAxisColumnIndex];
|
||||
let xValue = row[xAxisColumnIndex];
|
||||
|
||||
// For date the a-axis, convert it's value to ms as this is what expected by Highcharts
|
||||
const dateValue = Utilities.getValidDate(<string>xValue, options.chartOptions.utcOffset);
|
||||
|
||||
xValue = dateValue.valueOf();
|
||||
|
||||
if(!splitByMap[splitByValue]) {
|
||||
splitByMap[splitByValue] = [];
|
||||
}
|
||||
|
||||
splitByMap[splitByValue].push([xValue, yValue]);
|
||||
});
|
||||
|
||||
for (let splitByValue in splitByMap) {
|
||||
categoriesAndSeries.series.push({
|
||||
name: splitByValue,
|
||||
data: splitByMap[splitByValue]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//#region Abstract methods
|
||||
|
||||
protected abstract getChartTypeOptions(): ChartTypeOptions;
|
||||
|
||||
//#endregion Abstract methods
|
||||
|
||||
//#region Private methods
|
||||
|
||||
private getYAxis(): Highcharts.YAxisOptions {
|
||||
const yAxis = this.options.chartOptions.columnsSelection.yAxes[0];
|
||||
const yAxisOptions = {
|
||||
title: {
|
||||
text: yAxis.name
|
||||
}
|
||||
}
|
||||
|
||||
return yAxisOptions;
|
||||
}
|
||||
|
||||
private getXAxisTitle(): string {
|
||||
const xAxisColumn = this.options.chartOptions.columnsSelection.xAxis;
|
||||
|
||||
return xAxisColumn.name;
|
||||
}
|
||||
|
||||
//#endregion Private methods
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
'use strict';
|
||||
|
||||
//#region Imports
|
||||
|
||||
import { ChartType } from '../../../common/chartModels';
|
||||
import { IVisualizerOptions } from '../../IVisualizerOptions';
|
||||
import { HighchartsChart } from './highchartsChart';
|
||||
import { Line } from './line';
|
||||
import { Scatter } from './scatter';
|
||||
import { UnstackedArea } from './unstackedArea';
|
||||
import { StackedArea } from './stackedArea';
|
||||
import { PercentageArea } from './percentageArea';
|
||||
import { UnstackedColumn } from './unstackedColumn';
|
||||
import { StackedColumn } from './stackedColumn';
|
||||
import { PercentageColumn } from './percentageColumn';
|
||||
import { Pie } from './pie';
|
||||
import { Donut } from './donut';
|
||||
|
||||
//#endregion Imports
|
||||
|
||||
export class HighchartsChartFactory {
|
||||
public static create(options: IVisualizerOptions): HighchartsChart {
|
||||
switch (options.chartOptions.chartType) {
|
||||
case ChartType.Line: {
|
||||
return new Line(options);
|
||||
}
|
||||
case ChartType.Scatter: {
|
||||
return new Scatter(options);
|
||||
}
|
||||
case ChartType.UnstackedArea: {
|
||||
return new UnstackedArea(options);
|
||||
}
|
||||
case ChartType.StackedArea: {
|
||||
return new StackedArea(options);
|
||||
}
|
||||
case ChartType.PercentageArea: {
|
||||
return new PercentageArea(options);
|
||||
}
|
||||
case ChartType.UnstackedColumn: {
|
||||
return new UnstackedColumn(options);
|
||||
}
|
||||
case ChartType.StackedColumn: {
|
||||
return new StackedColumn(options);
|
||||
}
|
||||
case ChartType.PercentageColumn: {
|
||||
return new PercentageColumn(options);
|
||||
}
|
||||
case ChartType.Pie: {
|
||||
return new Pie(options);
|
||||
}
|
||||
case ChartType.Donut: {
|
||||
return new Donut(options);
|
||||
}
|
||||
default: {
|
||||
return new UnstackedColumn(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
import { HighchartsChart } from './highchartsChart';
|
||||
import { ChartTypeOptions, UNSTACKED } from '../chartTypeOptions';
|
||||
|
||||
export class Line extends HighchartsChart {
|
||||
//#region Methods override
|
||||
|
||||
protected getChartTypeOptions(): ChartTypeOptions {
|
||||
return {
|
||||
chartType: 'line',
|
||||
plotOptions: {
|
||||
line: {
|
||||
stacking: UNSTACKED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
import { Area } from './area';
|
||||
import { PERCENTAGE } from '../chartTypeOptions';
|
||||
|
||||
export class PercentageArea extends Area {
|
||||
//#region Methods override
|
||||
|
||||
protected getStackingOption(): any {
|
||||
return PERCENTAGE;
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
import { Column } from './column';
|
||||
import { PERCENTAGE } from '../chartTypeOptions';
|
||||
|
||||
export class PercentageColumn extends Column {
|
||||
//#region Methods override
|
||||
|
||||
protected getStackingOption(): any {
|
||||
return PERCENTAGE;
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
'use strict';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { HighchartsChart, ICategoriesAndSeries } from './highchartsChart';
|
||||
import { ChartTypeOptions } from '../chartTypeOptions';
|
||||
import { Utilities } from '../../../common/utilities';
|
||||
|
||||
export class Pie extends HighchartsChart {
|
||||
//#region Methods override
|
||||
|
||||
protected getChartTypeOptions(): ChartTypeOptions {
|
||||
return {
|
||||
chartType: 'pie',
|
||||
plotOptions: {
|
||||
pie: {
|
||||
innerSize: this.getInnerSize(),
|
||||
showInLegend: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getStandardCategoriesAndSeries(xAxisColumnIndex: number, isDatetimeAxis: boolean, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const yAxisColumn = this.options.chartOptions.columnsSelection.yAxes[0]; // We allow only 1 yAxis in pie charts
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(this.options.queryResultData, yAxisColumn);
|
||||
|
||||
// Build the data for the pie
|
||||
const pieSeries = {
|
||||
name: yAxisColumn.name,
|
||||
data: []
|
||||
}
|
||||
|
||||
this.options.queryResultData.rows.forEach((row) => {
|
||||
const xAxisValue = row[xAxisColumnIndex];
|
||||
const yAxisValue = row[yAxisColumnIndex];
|
||||
|
||||
pieSeries.data.push({
|
||||
name: xAxisValue,
|
||||
y: yAxisValue
|
||||
})
|
||||
});
|
||||
|
||||
categoriesAndSeries.series.push(pieSeries);
|
||||
categoriesAndSeries.categories = undefined;
|
||||
}
|
||||
|
||||
protected getSplitByCategoriesAndSeries(xAxisColumnIndex: number, isDatetimeAxis: boolean, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const yAxisColumn = this.options.chartOptions.columnsSelection.yAxes[0]; // We allow only 1 yAxis in pie charts
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(this.options.queryResultData, yAxisColumn);
|
||||
const splitByIndexes = [xAxisColumnIndex];
|
||||
|
||||
this.options.chartOptions.columnsSelection.splitBy.forEach((splitByColumn) => {
|
||||
splitByIndexes.push(Utilities.getColumnIndex(this.options.queryResultData, splitByColumn));
|
||||
});
|
||||
|
||||
// Build the data for the multi-level pie
|
||||
let pieData = {};
|
||||
let pieLevelData = pieData;
|
||||
|
||||
this.options.queryResultData.rows.forEach((row) => {
|
||||
const yAxisValue = row[yAxisColumnIndex];
|
||||
|
||||
splitByIndexes.forEach((splitByIndex) => {
|
||||
const splitByValue: string = <string>row[splitByIndex];
|
||||
let splitByMap = pieLevelData[splitByValue];
|
||||
|
||||
if(!splitByMap) {
|
||||
pieLevelData[splitByValue] = {
|
||||
drillDown: {},
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
pieLevelData[splitByValue].y += yAxisValue;
|
||||
pieLevelData = pieLevelData[splitByValue].drillDown;
|
||||
});
|
||||
|
||||
pieLevelData = pieData;
|
||||
});
|
||||
|
||||
categoriesAndSeries.series = this.spreadMultiLevelSeries(pieData);
|
||||
categoriesAndSeries.categories = undefined;
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
|
||||
protected getInnerSize(): string {
|
||||
return '0';
|
||||
}
|
||||
|
||||
//#region Private methods
|
||||
|
||||
private spreadMultiLevelSeries(pieData: any, level: number = 0, series: any[] = []): any[] {
|
||||
const chartOptions = this.options.chartOptions;
|
||||
const levelsCount = chartOptions.columnsSelection.splitBy.length + 1;
|
||||
const firstLevelSize = Math.round(100 / levelsCount);
|
||||
|
||||
for (let key in pieData) {
|
||||
let currentSeries = series[level];
|
||||
let pieLevelValue = pieData[key];
|
||||
|
||||
if(!currentSeries) {
|
||||
let column = (level === 0) ? chartOptions.columnsSelection.xAxis : chartOptions.columnsSelection.splitBy[level - 1];
|
||||
|
||||
currentSeries = {
|
||||
name: column.name,
|
||||
data: []
|
||||
};
|
||||
|
||||
if(level === 0) {
|
||||
currentSeries.size = `${firstLevelSize}%`;
|
||||
} else {
|
||||
const prevLevelSizeStr = series[level - 1].size;
|
||||
const prevLevelSize = Number(prevLevelSizeStr.substring(0, 2));
|
||||
|
||||
currentSeries.size = `${prevLevelSize + 10}%`;
|
||||
currentSeries.innerSize = `${prevLevelSize}%`;
|
||||
}
|
||||
|
||||
// We do not show labels for multi-level pie
|
||||
currentSeries.dataLabels = {
|
||||
enabled: false
|
||||
}
|
||||
|
||||
series.push(currentSeries);
|
||||
}
|
||||
|
||||
currentSeries.data.push({
|
||||
name: key,
|
||||
y: pieLevelValue.y
|
||||
});
|
||||
|
||||
let drillDown = pieLevelValue.drillDown;
|
||||
|
||||
if(!_.isEmpty(drillDown)) {
|
||||
this.spreadMultiLevelSeries(drillDown, level + 1, series);
|
||||
}
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
//#endregion Private methods
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
import { HighchartsChart } from './highchartsChart';
|
||||
import { ChartTypeOptions, UNSTACKED } from '../chartTypeOptions';
|
||||
|
||||
export class Scatter extends HighchartsChart {
|
||||
//#region Methods override
|
||||
|
||||
protected getChartTypeOptions(): ChartTypeOptions {
|
||||
return {
|
||||
chartType: 'scatter',
|
||||
plotOptions: {
|
||||
scatter: {
|
||||
stacking: UNSTACKED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
import { Area } from './area';
|
||||
import { STACKED } from '../chartTypeOptions';
|
||||
|
||||
export class StackedArea extends Area {
|
||||
//#region Methods override
|
||||
|
||||
protected getStackingOption(): any {
|
||||
return STACKED;
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
import { Column } from './column';
|
||||
import { STACKED } from '../chartTypeOptions';
|
||||
|
||||
export class StackedColumn extends Column {
|
||||
//#region Methods override
|
||||
|
||||
protected getStackingOption(): any {
|
||||
return STACKED;
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
import { Area } from './area';
|
||||
import { UNSTACKED } from '../chartTypeOptions';
|
||||
|
||||
export class UnstackedArea extends Area {
|
||||
//#region Methods override
|
||||
|
||||
protected getStackingOption(): any {
|
||||
return UNSTACKED;
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
import { Column } from './column';
|
||||
import { UNSTACKED } from '../chartTypeOptions';
|
||||
|
||||
export class UnstackedColumn extends Column {
|
||||
//#region Methods override
|
||||
|
||||
protected getStackingOption(): any {
|
||||
return UNSTACKED;
|
||||
}
|
||||
|
||||
//#endregion Methods override
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { ChartType } from '../../common/chartModels';
|
||||
|
||||
export interface ChartTypeOptions {
|
||||
chartType: string;
|
||||
plotOptions?: Highcharts.PlotOptions;
|
||||
}
|
||||
|
||||
const PERCENTAGE = 'percent';
|
||||
const STACKED = 'normal';
|
||||
const UNSTACKED = undefined;
|
||||
|
||||
// See: https://api.highcharts.com/highcharts/plotOptions
|
||||
export const CommonChartTypeToHighcharts: { [key in ChartType]: ChartTypeOptions; } = {
|
||||
[ChartType.Line]: {
|
||||
chartType: 'line',
|
||||
plotOptions: {
|
||||
line: {
|
||||
stacking: UNSTACKED
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.Scatter]: {
|
||||
chartType: 'scatter',
|
||||
plotOptions: {
|
||||
scatter: {
|
||||
stacking: UNSTACKED
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.UnstackedArea]: {
|
||||
chartType: 'area',
|
||||
plotOptions: {
|
||||
area: {
|
||||
stacking: UNSTACKED
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.StackedArea]: {
|
||||
chartType: 'area',
|
||||
plotOptions: {
|
||||
area: {
|
||||
stacking: STACKED
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.PercentageArea]: {
|
||||
chartType: 'area',
|
||||
plotOptions: {
|
||||
area: {
|
||||
stacking: PERCENTAGE
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.UnstackedColumn]: {
|
||||
chartType: 'column',
|
||||
plotOptions: {
|
||||
column: {
|
||||
stacking: UNSTACKED
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.StackedColumn]: {
|
||||
chartType: 'column',
|
||||
plotOptions: {
|
||||
column: {
|
||||
stacking: STACKED
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.PercentageColumn]: {
|
||||
chartType: 'column',
|
||||
plotOptions: {
|
||||
column: {
|
||||
stacking: PERCENTAGE
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.Pie]: {
|
||||
chartType: 'pie',
|
||||
plotOptions: {
|
||||
pie: {
|
||||
innerSize: '0',
|
||||
showInLegend: true
|
||||
}
|
||||
}
|
||||
},
|
||||
[ChartType.Donut]: {
|
||||
chartType: 'pie',
|
||||
plotOptions: {
|
||||
pie: {
|
||||
innerSize: '40%',
|
||||
showInLegend: true
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
|
@ -1,286 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
//#region Imports
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { Utilities } from '../../common/utilities';
|
||||
import { IVisualizerOptions } from '../IVisualizerOptions';
|
||||
import { ChartType, IChartOptions } from '../../common/chartModels';
|
||||
|
||||
//#endregion Imports
|
||||
|
||||
export interface ICategoriesAndSeries {
|
||||
categories?: string[];
|
||||
series: any[];
|
||||
}
|
||||
|
||||
export class DataTransformer {
|
||||
//#region Public static methods
|
||||
|
||||
public static getCategoriesAndSeries(options: IVisualizerOptions, isDatetimeAxis: boolean): ICategoriesAndSeries {
|
||||
const columnsSelection = options.chartOptions.columnsSelection;
|
||||
const xAxisColumn = columnsSelection.xAxis;
|
||||
const xAxisColumnIndex = Utilities.getColumnIndex(options.queryResultData, xAxisColumn);
|
||||
const isSplitByChart = columnsSelection.splitBy && columnsSelection.splitBy.length > 0;
|
||||
let categoriesAndSeries = {
|
||||
series: [],
|
||||
categories: isDatetimeAxis ? undefined : []
|
||||
};
|
||||
|
||||
if(Utilities.isPieOrDonut(options.chartOptions.chartType)) {
|
||||
if(isSplitByChart) {
|
||||
DataTransformer.getPieSplitByCategoriesAndSeries(options, xAxisColumnIndex, categoriesAndSeries);
|
||||
} else {
|
||||
DataTransformer.getPieStandardCategoriesAndSeries(options, xAxisColumnIndex, categoriesAndSeries);
|
||||
}
|
||||
} else {
|
||||
if(isSplitByChart) {
|
||||
DataTransformer.getSplitByCategoriesAndSeries(options, xAxisColumnIndex, isDatetimeAxis, categoriesAndSeries);
|
||||
} else {
|
||||
DataTransformer.getStandardCategoriesAndSeries(options, xAxisColumnIndex, isDatetimeAxis, categoriesAndSeries);
|
||||
}
|
||||
}
|
||||
|
||||
return categoriesAndSeries;
|
||||
}
|
||||
|
||||
//#endregion Public static methods
|
||||
|
||||
private static getStandardCategoriesAndSeries(options: IVisualizerOptions, xAxisColumnIndex: number, isDatetimeAxis: boolean, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const chartOptions = options.chartOptions;
|
||||
const yAxesIndexes = _.map(chartOptions.columnsSelection.yAxes, (yAxisColumn) => {
|
||||
return Utilities.getColumnIndex(options.queryResultData, yAxisColumn);
|
||||
});
|
||||
|
||||
const seriesMap = {};
|
||||
|
||||
options.queryResultData.rows.forEach((row) => {
|
||||
let xAxisValue: any = row[xAxisColumnIndex];
|
||||
|
||||
// If the x-axis is a date, convert it's value to milliseconds as this is what expected by 'Highcharts'
|
||||
if(isDatetimeAxis) {
|
||||
const dateValue = Utilities.getValidDate(xAxisValue, chartOptions.utcOffset);
|
||||
|
||||
xAxisValue = dateValue.valueOf();
|
||||
} else {
|
||||
categoriesAndSeries.categories.push(xAxisValue);
|
||||
}
|
||||
|
||||
_.forEach(yAxesIndexes, (yAxisIndex, i) => {
|
||||
const yAxisColumnName = chartOptions.columnsSelection.yAxes[i].name;
|
||||
const yAxisValue = row[yAxisIndex];
|
||||
|
||||
if(!seriesMap[yAxisColumnName]) {
|
||||
seriesMap[yAxisColumnName] = [];
|
||||
}
|
||||
|
||||
const data = isDatetimeAxis? [xAxisValue, yAxisValue] : yAxisValue;
|
||||
|
||||
seriesMap[yAxisColumnName].push(data);
|
||||
});
|
||||
});
|
||||
|
||||
for (let yAxisColumnName in seriesMap) {
|
||||
categoriesAndSeries.series.push({
|
||||
name: yAxisColumnName,
|
||||
data: seriesMap[yAxisColumnName]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static getSplitByCategoriesAndSeries(options: IVisualizerOptions, xAxisColumnIndex: number, isDatetimeAxis: boolean, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
if(isDatetimeAxis) {
|
||||
DataTransformer.getSplitByCategoriesAndSeriesForDateXAxis(options, xAxisColumnIndex, categoriesAndSeries);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const columnsSelection = options.chartOptions.columnsSelection;
|
||||
const yAxisColumn = columnsSelection.yAxes[0];
|
||||
const splitByColumn = columnsSelection.splitBy[0];
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(options.queryResultData, yAxisColumn);
|
||||
const splitByColumnIndex = Utilities.getColumnIndex(options.queryResultData, splitByColumn);
|
||||
const uniqueXValues = {};
|
||||
const uniqueSplitByValues = {};
|
||||
|
||||
options.queryResultData.rows.forEach((row) => {
|
||||
const xValue = row[xAxisColumnIndex];
|
||||
const yValue = row[yAxisColumnIndex];
|
||||
const splitByValue = row[splitByColumnIndex];
|
||||
|
||||
if(!uniqueXValues[xValue]) {
|
||||
uniqueXValues[xValue] = true;
|
||||
}
|
||||
|
||||
if(!uniqueSplitByValues[splitByValue]) {
|
||||
uniqueSplitByValues[splitByValue] = {};
|
||||
}
|
||||
|
||||
uniqueSplitByValues[splitByValue][xValue] = yValue;
|
||||
});
|
||||
|
||||
// Populate X-Axis
|
||||
categoriesAndSeries.categories = _.keys(uniqueXValues);
|
||||
|
||||
// Populate Split by
|
||||
for (let splitByValue in uniqueSplitByValues) {
|
||||
const currentSeries = {
|
||||
name: splitByValue,
|
||||
data: []
|
||||
};
|
||||
|
||||
const xValueToYValueMap = uniqueSplitByValues[splitByValue];
|
||||
|
||||
// Set a split-by value for each unique x value
|
||||
categoriesAndSeries.categories.forEach((xValue) => {
|
||||
const yValue = xValueToYValueMap[xValue] || null;
|
||||
|
||||
currentSeries.data.push(yValue);
|
||||
});
|
||||
|
||||
categoriesAndSeries.series.push(currentSeries);
|
||||
}
|
||||
}
|
||||
|
||||
private static getSplitByCategoriesAndSeriesForDateXAxis(options: IVisualizerOptions, xAxisColumnIndex: number, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const columnsSelection = options.chartOptions.columnsSelection;
|
||||
const yAxisColumn = columnsSelection.yAxes[0];
|
||||
const splitByColumn = columnsSelection.splitBy[0];
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(options.queryResultData, yAxisColumn);
|
||||
const splitByColumnIndex = Utilities.getColumnIndex(options.queryResultData, splitByColumn);
|
||||
const splitByMap = {};
|
||||
|
||||
options.queryResultData.rows.forEach((row) => {
|
||||
const splitByValue: string = <string>row[splitByColumnIndex];
|
||||
const yValue = row[yAxisColumnIndex];
|
||||
let xValue = row[xAxisColumnIndex];
|
||||
|
||||
// For date the a-axis, convert it's value to ms as this is what expected by Highcharts
|
||||
const dateValue = Utilities.getValidDate(<string>xValue, options.chartOptions.utcOffset);
|
||||
|
||||
xValue = dateValue.valueOf();
|
||||
|
||||
if(!splitByMap[splitByValue]) {
|
||||
splitByMap[splitByValue] = [];
|
||||
}
|
||||
|
||||
splitByMap[splitByValue].push([xValue, yValue]);
|
||||
});
|
||||
|
||||
for (let splitByValue in splitByMap) {
|
||||
categoriesAndSeries.series.push({
|
||||
name: splitByValue,
|
||||
data: splitByMap[splitByValue]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static getPieStandardCategoriesAndSeries(options: IVisualizerOptions, xAxisColumnIndex: number, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const yAxisColumn = options.chartOptions.columnsSelection.yAxes[0]; // We allow only 1 yAxis in pie charts
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(options.queryResultData, yAxisColumn);
|
||||
|
||||
// Build the data for the pie
|
||||
const pieSeries = {
|
||||
name: yAxisColumn.name,
|
||||
data: []
|
||||
}
|
||||
|
||||
options.queryResultData.rows.forEach((row) => {
|
||||
const xAxisValue = row[xAxisColumnIndex];
|
||||
const yAxisValue = row[yAxisColumnIndex];
|
||||
|
||||
pieSeries.data.push({
|
||||
name: xAxisValue,
|
||||
y: yAxisValue
|
||||
})
|
||||
});
|
||||
|
||||
categoriesAndSeries.series.push(pieSeries);
|
||||
}
|
||||
|
||||
private static getPieSplitByCategoriesAndSeries(options: IVisualizerOptions, xAxisColumnIndex: number, categoriesAndSeries: ICategoriesAndSeries): void {
|
||||
const yAxisColumn = options.chartOptions.columnsSelection.yAxes[0]; // We allow only 1 yAxis in pie charts
|
||||
const yAxisColumnIndex = Utilities.getColumnIndex(options.queryResultData, yAxisColumn);
|
||||
const splitByIndexes = [xAxisColumnIndex];
|
||||
|
||||
options.chartOptions.columnsSelection.splitBy.forEach((splitByColumn) => {
|
||||
splitByIndexes.push(Utilities.getColumnIndex(options.queryResultData, splitByColumn));
|
||||
});
|
||||
|
||||
// Build the data for the multi-level pie
|
||||
let pieData = {};
|
||||
let pieLevelData = pieData;
|
||||
|
||||
options.queryResultData.rows.forEach((row) => {
|
||||
const yAxisValue = row[yAxisColumnIndex];
|
||||
|
||||
splitByIndexes.forEach((splitByIndex) => {
|
||||
const splitByValue: string = <string>row[splitByIndex];
|
||||
let splitByMap = pieLevelData[splitByValue];
|
||||
|
||||
if(!splitByMap) {
|
||||
pieLevelData[splitByValue] = {
|
||||
drillDown: {},
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
pieLevelData[splitByValue].y += yAxisValue;
|
||||
pieLevelData = pieLevelData[splitByValue].drillDown;
|
||||
});
|
||||
|
||||
pieLevelData = pieData;
|
||||
});
|
||||
|
||||
categoriesAndSeries.series = DataTransformer.spreadMultiLevelSeries(options.chartOptions, pieData);
|
||||
}
|
||||
|
||||
private static spreadMultiLevelSeries(chartOptions: IChartOptions, pieData: any, level: number = 0, series: any[] = []): any[] {
|
||||
const levelsCount = chartOptions.columnsSelection.splitBy.length + 1;
|
||||
const firstLevelSize = Math.round(100 / levelsCount);
|
||||
|
||||
for (let key in pieData) {
|
||||
let currentSeries = series[level];
|
||||
let pieLevelValue = pieData[key];
|
||||
|
||||
if(!currentSeries) {
|
||||
let column = (level === 0) ? chartOptions.columnsSelection.xAxis : chartOptions.columnsSelection.splitBy[level - 1];
|
||||
|
||||
currentSeries = {
|
||||
name: column.name,
|
||||
data: []
|
||||
};
|
||||
|
||||
if(level === 0) {
|
||||
currentSeries.size = `${firstLevelSize}%`;
|
||||
} else {
|
||||
const prevLevelSizeStr = series[level - 1].size;
|
||||
const prevLevelSize = Number(prevLevelSizeStr.substring(0, 2));
|
||||
|
||||
currentSeries.size = `${prevLevelSize + 10}%`;
|
||||
currentSeries.innerSize = `${prevLevelSize}%`;
|
||||
}
|
||||
|
||||
// We do not show labels for multi-level pie
|
||||
currentSeries.dataLabels = {
|
||||
enabled: false
|
||||
}
|
||||
|
||||
series.push(currentSeries);
|
||||
}
|
||||
|
||||
currentSeries.data.push({
|
||||
name: key,
|
||||
y: pieLevelValue.y
|
||||
});
|
||||
|
||||
let drillDown = pieLevelValue.drillDown;
|
||||
|
||||
if(!_.isEmpty(drillDown)) {
|
||||
DataTransformer.spreadMultiLevelSeries(chartOptions, drillDown, level + 1, series);
|
||||
}
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
}
|
|
@ -4,61 +4,18 @@
|
|||
|
||||
import * as _ from 'lodash';
|
||||
import * as Highcharts from 'highcharts';
|
||||
import { IChartOptions } from '../../common/chartModels';
|
||||
import { Utilities } from '../../common/utilities';
|
||||
import { IVisualizer } from '../IVisualizer';
|
||||
import { IVisualizerOptions } from '../IVisualizerOptions';
|
||||
import { DataTransformer } from './dataTransformer';
|
||||
import { CommonChartTypeToHighcharts } from './commonChartTypeToHighcharts';
|
||||
import { HighchartsChartFactory } from './charts/highchartsChartFactory';
|
||||
|
||||
//#endregion Imports
|
||||
|
||||
export class HighchartsVisualizer implements IVisualizer {
|
||||
public drawNewChart(options: IVisualizerOptions): void {
|
||||
const chartOptions = options.chartOptions;
|
||||
const chartTypeOptions = CommonChartTypeToHighcharts[chartOptions.chartType];
|
||||
const isDatetimeAxis = Utilities.isDate(chartOptions.columnsSelection.xAxis.type);
|
||||
const categoriesAndSeries = DataTransformer.getCategoriesAndSeries(options, isDatetimeAxis);
|
||||
|
||||
const highchartsOptions: Highcharts.Options = {
|
||||
chart: {
|
||||
type: chartTypeOptions.chartType
|
||||
},
|
||||
plotOptions: chartTypeOptions.plotOptions,
|
||||
xAxis: {
|
||||
type: isDatetimeAxis ? 'datetime' : undefined,
|
||||
categories: categoriesAndSeries.categories,
|
||||
title: {
|
||||
text: this.getXAxisTitle(options.chartOptions),
|
||||
align: 'middle'
|
||||
}
|
||||
},
|
||||
yAxis: this.getYAxis(chartOptions),
|
||||
series: categoriesAndSeries.series
|
||||
};
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const highchartsOptions = chart.getHighchartsOptions();
|
||||
|
||||
// Draw the chart
|
||||
Highcharts.chart(options.elementId, highchartsOptions);
|
||||
}
|
||||
|
||||
//#region Private methods
|
||||
|
||||
private getYAxis(chartOptions: IChartOptions): Highcharts.YAxisOptions {
|
||||
const yAxis = chartOptions.columnsSelection.yAxes[0];
|
||||
const yAxisOptions = {
|
||||
title: {
|
||||
text: yAxis.name
|
||||
}
|
||||
}
|
||||
|
||||
return yAxisOptions;
|
||||
}
|
||||
|
||||
private getXAxisTitle(chartOptions: IChartOptions): string {
|
||||
const xAxisColumn = chartOptions.columnsSelection.xAxis;
|
||||
|
||||
return xAxisColumn.name;
|
||||
}
|
||||
|
||||
//#endregion Private methods
|
||||
}
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
import * as _ from 'lodash';
|
||||
import { DraftColumnType, IColumn, ChartType } from '../../src/common/chartModels';
|
||||
import { DataTransformer, ICategoriesAndSeries } from '../../src/visualizers/highcharts/dataTransformer';
|
||||
import { HighchartsChartFactory } from '../../src/visualizers/highcharts/charts/highchartsChartFactory';
|
||||
import { ICategoriesAndSeries } from '../../src/visualizers/highcharts/charts/highchartsChart';
|
||||
|
||||
describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
||||
describe('Unit tests for HighchartsChart.getCategoriesAndSeries method', () => {
|
||||
//#region beforeEach
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -24,9 +25,9 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
//#region Tests
|
||||
|
||||
describe('Validate getCategoriesAndSeries method', () => {
|
||||
//#region getStandardCategoriesAndSeries
|
||||
//#region Line chart getStandardCategoriesAndSeries
|
||||
|
||||
it('Validate getStandardCategoriesAndSeries: non-date x-axis and 1 y-axis', () => {
|
||||
it('Validate getStandardCategoriesAndSeries for Line chart: non-date x-axis and 1 y-axis', () => {
|
||||
const rows = [
|
||||
['Israel', 'Herzliya', 30],
|
||||
['United States', 'New York', 100],
|
||||
|
@ -41,6 +42,7 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
|
||||
// Input
|
||||
const options: any = {
|
||||
chartType: ChartType.Line,
|
||||
chartOptions: {
|
||||
columnsSelection: {
|
||||
xAxis: columns[0], // country
|
||||
|
@ -55,9 +57,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ false);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'request_count',
|
||||
data: [30, 100, 20]
|
||||
|
@ -66,10 +69,11 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
};
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedCategoriesAndSeries);
|
||||
expect(result.series).toEqual(expected.series);
|
||||
expect(result.xAxis.categories).toEqual(expected.categories);
|
||||
});
|
||||
|
||||
it('Validate getStandardCategoriesAndSeries: date x-axis and 1 y-axis', () => {
|
||||
it('Validate getStandardCategoriesAndSeries for Line chart: date x-axis and 1 y-axis', () => {
|
||||
const rows = [
|
||||
['Israel', '2019-05-25T00:00:00Z', 'Herzliya', 30],
|
||||
['Japan', '2019-05-25T00:00:00Z', 'Tokyo', 20],
|
||||
|
@ -85,6 +89,7 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
|
||||
// Input
|
||||
const options: any = {
|
||||
chartType: ChartType.Line,
|
||||
chartOptions: {
|
||||
columnsSelection: {
|
||||
xAxis: columns[1], // timestamp
|
||||
|
@ -99,9 +104,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ true);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'request_count',
|
||||
data: [[2019, 30], [2019, 20], [2000, 100]]
|
||||
|
@ -110,10 +116,11 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
};
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedCategoriesAndSeries);
|
||||
expect(result.series).toEqual(expected.series);
|
||||
expect(result.xAxis.categories).toEqual(expected.categories);
|
||||
});
|
||||
|
||||
it('Validate getStandardCategoriesAndSeries: non-date x-axis and multiple y-axis', () => {
|
||||
it('Validate getStandardCategoriesAndSeries for Line chart: non-date x-axis and multiple y-axis', () => {
|
||||
const rows = [
|
||||
['Israel', 'Herzliya', 30, 300],
|
||||
['United States', 'New York', 100, 150],
|
||||
|
@ -129,6 +136,7 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
|
||||
// Input
|
||||
const options: any = {
|
||||
chartType: ChartType.Line,
|
||||
chartOptions: {
|
||||
columnsSelection: {
|
||||
xAxis: columns[1], // city
|
||||
|
@ -143,9 +151,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ false);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'request_count',
|
||||
data: [30, 100, 20]
|
||||
|
@ -158,10 +167,11 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
};
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedCategoriesAndSeries);
|
||||
expect(result.series).toEqual(expected.series);
|
||||
expect(result.xAxis.categories).toEqual(expected.categories);
|
||||
});
|
||||
|
||||
it('Validate getStandardCategoriesAndSeries: date x-axis and multiple y-axis', () => {
|
||||
|
||||
it('Validate getStandardCategoriesAndSeries for Line chart: date x-axis and multiple y-axis', () => {
|
||||
const rows = [
|
||||
['2019-05-25T00:00:00Z', 'Israel', 'Herzliya', 30, 300],
|
||||
['2019-05-25T00:00:00Z', 'Japan', 'Tokyo', 20, 150],
|
||||
|
@ -192,9 +202,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ true);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'request_count',
|
||||
data: [[2019, 30], [2019, 20], [2000, 100]]
|
||||
|
@ -207,14 +218,15 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
};
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedCategoriesAndSeries);
|
||||
expect(result.series).toEqual(expected.series);
|
||||
expect(result.xAxis.categories).toEqual(expected.categories);
|
||||
});
|
||||
|
||||
//#endregion getStandardCategoriesAndSeries
|
||||
//#endregion Line chart getStandardCategoriesAndSeries
|
||||
|
||||
//#region getSplitByCategoriesAndSeries
|
||||
//#region Line chart getSplitByCategoriesAndSeries
|
||||
|
||||
it('Validate getCategoriesAndSeries: non-date x-axis with splitBy', () => {
|
||||
it('Validate getCategoriesAndSeries for Line chart: non-date x-axis with splitBy', () => {
|
||||
const rows = [
|
||||
['United States', 'Atlanta', 300],
|
||||
['United States', 'Redmond', 20],
|
||||
|
@ -234,6 +246,7 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
|
||||
// Input
|
||||
const options: any = {
|
||||
chartType: ChartType.Line,
|
||||
chartOptions: {
|
||||
columnsSelection: {
|
||||
xAxis: columns[0], // country
|
||||
|
@ -249,9 +262,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ false);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'Atlanta',
|
||||
data: [300, null, null]
|
||||
|
@ -288,10 +302,11 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
};
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedCategoriesAndSeries);
|
||||
expect(result.series).toEqual(expected.series);
|
||||
expect(result.xAxis.categories).toEqual(expected.categories);
|
||||
});
|
||||
|
||||
it('Validate getCategoriesAndSeries: date x-axis with splitBy', () => {
|
||||
it('Validate getCategoriesAndSeries for Line chart: date x-axis with splitBy', () => {
|
||||
const rows = [
|
||||
['Israel', '1988-06-26T00:00:00Z', 'Jerusalem', 500],
|
||||
['Israel', '2000-06-26T00:00:00Z', 'Herzliya', 1000],
|
||||
|
@ -313,6 +328,7 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
// Input
|
||||
const options: any = {
|
||||
chartOptions: {
|
||||
chartType: ChartType.Line,
|
||||
columnsSelection: {
|
||||
xAxis: columns[1], // timestamp
|
||||
yAxes: [columns[3]], // request_count
|
||||
|
@ -327,9 +343,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ true);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'Jerusalem',
|
||||
data: [[1988, 500]]
|
||||
|
@ -366,14 +383,15 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
};
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedCategoriesAndSeries);
|
||||
expect(result.series).toEqual(expected.series);
|
||||
expect(result.xAxis.categories).toEqual(expected.categories);
|
||||
});
|
||||
|
||||
//#endregion getSplitByCategoriesAndSeries
|
||||
//#endregion Line chart getSplitByCategoriesAndSeries
|
||||
|
||||
//#region getPieStandardCategoriesAndSeries
|
||||
|
||||
it('Validate getPieStandardCategoriesAndSeries', () => {
|
||||
//#region Pie chart getStandardCategoriesAndSeries
|
||||
|
||||
it('Validate getCategoriesAndSeries for Pie chart', () => {
|
||||
const rows = [
|
||||
['Israel', 'Tel Aviv', 10],
|
||||
['United States', 'Redmond', 5],
|
||||
|
@ -407,9 +425,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ false);
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'request_count',
|
||||
data: [
|
||||
|
@ -422,17 +441,17 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
{ name: 'Boston', y: 1 }
|
||||
]
|
||||
}],
|
||||
categories: []
|
||||
categories: undefined
|
||||
};
|
||||
|
||||
// Assert
|
||||
expect(result.series).toEqual(expectedCategoriesAndSeries.series);
|
||||
expect(result.categories).toEqual(expectedCategoriesAndSeries.categories);
|
||||
expect(result.series).toEqual(expected.series);
|
||||
expect(result.categories).toEqual(expected.categories);
|
||||
});
|
||||
|
||||
//#endregion getPieStandardCategoriesAndSeries
|
||||
//#endregion Pie chart getStandardCategoriesAndSeries
|
||||
|
||||
//#region getPieSplitByCategoriesAndSeries
|
||||
//#region Pie chart getSplitByCategoriesAndSeries
|
||||
|
||||
function validateResults(result, expected) {
|
||||
const seriesToValidate = _.map(result.series, (currentSeries) => {
|
||||
|
@ -449,7 +468,7 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
expect(result.categories).toEqual(expected.categories);
|
||||
}
|
||||
|
||||
it('Validate getPieSplitByCategoriesAndSeries: pie chart with 2 levels', () => {
|
||||
it('Validate getSplitByCategoriesAndSeries for Line chart: pie chart with 2 levels', () => {
|
||||
const rows = [
|
||||
['Israel', 'Tel Aviv', 10],
|
||||
['United States', 'Redmond', 5],
|
||||
|
@ -484,9 +503,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ false);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'country',
|
||||
size: '50%',
|
||||
|
@ -510,14 +530,14 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
|
||||
]
|
||||
}],
|
||||
categories: []
|
||||
categories: undefined
|
||||
};
|
||||
|
||||
// Assert
|
||||
validateResults(result, expectedCategoriesAndSeries);
|
||||
validateResults(result, expected);
|
||||
});
|
||||
|
||||
it('Validate getPieSplitByCategoriesAndSeries: pie chart with 3 levels', () => {
|
||||
it('Validate getSplitByCategoriesAndSeries for Line chart: pie chart with 3 levels', () => {
|
||||
const rows = [
|
||||
['Internet Explorer', 'v8', '0', 10],
|
||||
['Chrome', 'v65', '0', 5],
|
||||
|
@ -560,9 +580,10 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
}
|
||||
|
||||
// Act
|
||||
const result = DataTransformer.getCategoriesAndSeries(options, /*isDatetimeAxis*/ false);
|
||||
const chart = HighchartsChartFactory.create(options);
|
||||
const result: any = chart.getHighchartsOptions();
|
||||
|
||||
const expectedCategoriesAndSeries: ICategoriesAndSeries = {
|
||||
const expected: ICategoriesAndSeries = {
|
||||
series: [{
|
||||
name: 'browser',
|
||||
size: '33%',
|
||||
|
@ -608,14 +629,14 @@ describe('Unit tests for Highcharts CategoriesAndSeries', () => {
|
|||
{ name: '0', y: 20 }
|
||||
]
|
||||
}],
|
||||
categories: []
|
||||
categories: undefined
|
||||
};
|
||||
|
||||
// Assert
|
||||
validateResults(result, expectedCategoriesAndSeries);
|
||||
validateResults(result, expected);
|
||||
});
|
||||
|
||||
//#endregion getPieSplitByCategoriesAndSeries
|
||||
//#endregion Pie chart getSplitByCategoriesAndSeries
|
||||
});
|
||||
|
||||
//#endregion Tests
|
Загрузка…
Ссылка в новой задаче