blob: 3ba12d62097623dbae2731eb58fe8ab091fef580 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable camelcase */
import {
AnnotationLayer,
CategoricalColorNamespace,
ChartProps,
getNumberFormatter,
isEventAnnotationLayer,
isFormulaAnnotationLayer,
isIntervalAnnotationLayer,
isTimeseriesAnnotationLayer,
getTimeFormatter,
getTimeFormatterForGranularity,
smartDateFormatter,
TimeseriesChartDataResponseResult,
TimeFormatter,
} from '@superset-ui/core';
import { EChartsOption, SeriesOption } from 'echarts';
import { DEFAULT_FORM_DATA, EchartsTimeseriesFormData } from './types';
import { EchartsProps, ForecastSeriesEnum, ProphetValue } from '../types';
import { parseYAxisBound } from '../utils/controls';
import { extractTimeseriesSeries, getChartPadding, getLegendProps } from '../utils/series';
import { extractAnnotationLabels } from '../utils/annotation';
import {
extractForecastSeriesContext,
extractProphetValuesFromTooltipParams,
formatProphetTooltipSeries,
rebaseTimeseriesDatum,
} from '../utils/prophet';
import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults';
import {
transformEventAnnotation,
transformFormulaAnnotation,
transformIntervalAnnotation,
transformSeries,
transformTimeseriesAnnotation,
} from './transformers';
export default function transformProps(chartProps: ChartProps): EchartsProps {
const { width, height, formData, queriesData } = chartProps;
const {
annotation_data: annotationData_,
data = [],
} = queriesData[0] as TimeseriesChartDataResponseResult;
const annotationData = annotationData_ || {};
const {
annotationLayers,
colorScheme,
contributionMode,
legendMargin,
legendOrientation,
legendType,
logAxis,
minorSplitLine,
showLegend,
stack,
truncateYAxis,
yAxisFormat,
xAxisShowMinLabel,
xAxisShowMaxLabel,
xAxisTimeFormat,
yAxisBounds,
timeGrainSqla,
zoomable,
richTooltip,
xAxisLabelRotation,
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
const rebasedData = rebaseTimeseriesDatum(data);
const rawSeries = extractTimeseriesSeries(rebasedData);
const series: SeriesOption[] = [];
const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);
rawSeries.forEach(entry => {
const transformedSeries = transformSeries(
entry,
formData as EchartsTimeseriesFormData,
colorScale,
);
if (transformedSeries) series.push(transformedSeries);
});
annotationLayers
.filter((layer: AnnotationLayer) => layer.show)
.forEach((layer: AnnotationLayer) => {
if (isFormulaAnnotationLayer(layer))
series.push(transformFormulaAnnotation(layer, data, colorScale));
else if (isIntervalAnnotationLayer(layer)) {
series.push(...transformIntervalAnnotation(layer, data, annotationData, colorScale));
} else if (isEventAnnotationLayer(layer)) {
series.push(...transformEventAnnotation(layer, data, annotationData, colorScale));
} else if (isTimeseriesAnnotationLayer(layer)) {
series.push(
...transformTimeseriesAnnotation(
layer,
formData as EchartsTimeseriesFormData,
data,
annotationData,
),
);
}
});
// yAxisBounds need to be parsed to replace incompatible values with undefined
let [min, max] = (yAxisBounds || []).map(parseYAxisBound);
// default to 0-100% range when doing row-level contribution chart
if (contributionMode === 'row' && stack) {
if (min === undefined) min = 0;
if (max === undefined) max = 1;
}
let xAxisFormatter: TimeFormatter | StringConstructor;
if (xAxisTimeFormat === smartDateFormatter.id) {
xAxisFormatter = getTimeFormatterForGranularity(timeGrainSqla);
} else if (xAxisTimeFormat) {
xAxisFormatter = getTimeFormatter(xAxisTimeFormat);
} else {
xAxisFormatter = String;
}
const echartOptions: EChartsOption = {
useUTC: true,
grid: {
...defaultGrid,
...getChartPadding(showLegend, legendOrientation, legendMargin, {
top: 20,
bottom: zoomable ? 80 : 20,
left: 20,
right: 20,
}),
},
xAxis: {
type: 'time',
axisLabel: {
showMinLabel: xAxisShowMinLabel,
showMaxLabel: xAxisShowMaxLabel,
formatter: xAxisFormatter,
rotate: xAxisLabelRotation,
},
},
yAxis: {
...defaultYAxis,
type: logAxis ? 'log' : 'value',
min,
max,
minorTick: { show: true },
minorSplitLine: { show: minorSplitLine },
axisLabel: { formatter },
scale: truncateYAxis,
},
tooltip: {
...defaultTooltip,
trigger: richTooltip ? 'axis' : 'item',
formatter: (params: any) => {
const value: number = !richTooltip ? params.value : params[0].value[0];
const prophetValue = !richTooltip ? [params] : params;
const rows: Array<string> = [`${smartDateFormatter(value)}`];
const prophetValues: Record<string, ProphetValue> = extractProphetValuesFromTooltipParams(
prophetValue,
);
Object.keys(prophetValues).forEach(key => {
const value = prophetValues[key];
rows.push(
formatProphetTooltipSeries({
...value,
seriesName: key,
formatter,
}),
);
});
return rows.join('<br />');
},
},
legend: {
...getLegendProps(legendType, legendOrientation, showLegend),
// @ts-ignore
data: rawSeries
.filter(
entry =>
extractForecastSeriesContext((entry.name || '') as string).type ===
ForecastSeriesEnum.Observation,
)
.map(entry => entry.name || '')
.concat(extractAnnotationLabels(annotationLayers, annotationData)),
},
series,
toolbox: {
show: zoomable,
feature: {
dataZoom: {
yAxisIndex: false,
title: {
zoom: 'zoom area',
back: 'restore zoom',
},
},
},
},
dataZoom: zoomable
? [
{
type: 'slider',
start: 0,
end: 100,
bottom: 20,
},
]
: [],
};
return {
echartOptions,
width,
height,
};
}