Skip to content

Commit

Permalink
fix(plugin-chart-echarts): fix forecasts on verbose metrics (apache#1…
Browse files Browse the repository at this point in the history
…8252)

* fix(plugin-chart-echarts): fix forecasts on verbose metrics

* oops! 🙊

* check for DTTM_ALIAS
  • Loading branch information
villebro authored and ofekisr committed Feb 8, 2022
1 parent a4bf84d commit 4df6f73
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export const forecastIntervalControls: ControlPanelSectionConfig = {
),
},
},
],
[
{
name: 'forecastSeasonalityYearly',
config: {
Expand Down Expand Up @@ -111,6 +113,8 @@ export const forecastIntervalControls: ControlPanelSectionConfig = {
),
},
},
],
[
{
name: 'forecastSeasonalityDaily',
config: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ import {
import { extractAnnotationLabels } from '../utils/annotation';
import {
extractForecastSeriesContext,
extractProphetValuesFromTooltipParams,
formatProphetTooltipSeries,
rebaseTimeseriesDatum,
} from '../utils/prophet';
extractForecastValuesFromTooltipParams,
formatForecastTooltipSeries,
rebaseForecastDatum,
} from '../utils/forecast';
import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults';
import {
getPadding,
Expand Down Expand Up @@ -130,11 +130,11 @@ export default function transformProps(
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };

const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
const rebasedDataA = rebaseTimeseriesDatum(data1, verboseMap);
const rebasedDataA = rebaseForecastDatum(data1, verboseMap);
const rawSeriesA = extractSeries(rebasedDataA, {
fillNeighborValue: stack ? 0 : undefined,
});
const rebasedDataB = rebaseTimeseriesDatum(data2, verboseMap);
const rebasedDataB = rebaseForecastDatum(data2, verboseMap);
const rawSeriesB = extractSeries(rebasedDataB, {
fillNeighborValue: stackB ? 0 : undefined,
});
Expand Down Expand Up @@ -321,19 +321,19 @@ export default function transformProps(
const xValue: number = richTooltip
? params[0].value[0]
: params.value[0];
const prophetValue: any[] = richTooltip ? params : [params];
const forecastValue: any[] = richTooltip ? params : [params];

if (richTooltip && tooltipSortByMetric) {
prophetValue.sort((a, b) => b.data[1] - a.data[1]);
forecastValue.sort((a, b) => b.data[1] - a.data[1]);
}

const rows: Array<string> = [`${tooltipTimeFormatter(xValue)}`];
const prophetValues =
extractProphetValuesFromTooltipParams(prophetValue);
const forecastValues =
extractForecastValuesFromTooltipParams(forecastValue);

Object.keys(prophetValues).forEach(key => {
const value = prophetValues[key];
const content = formatProphetTooltipSeries({
Object.keys(forecastValues).forEach(key => {
const value = forecastValues[key];
const content = formatForecastTooltipSeries({
...value,
seriesName: key,
formatter: primarySeries.has(key) ? formatter : formatterSecondary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
EchartsTimeseriesSeriesType,
TimeseriesChartTransformedProps,
} from './types';
import { ForecastSeriesEnum, ProphetValue } from '../types';
import { ForecastSeriesEnum, ForecastValue } from '../types';
import { parseYAxisBound } from '../utils/controls';
import {
currentSeries,
Expand All @@ -51,10 +51,10 @@ import { extractAnnotationLabels } from '../utils/annotation';
import {
extractForecastSeriesContext,
extractForecastSeriesContexts,
extractProphetValuesFromTooltipParams,
formatProphetTooltipSeries,
rebaseTimeseriesDatum,
} from '../utils/prophet';
extractForecastValuesFromTooltipParams,
formatForecastTooltipSeries,
rebaseForecastDatum,
} from '../utils/forecast';
import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults';
import {
getPadding,
Expand Down Expand Up @@ -126,7 +126,7 @@ export default function transformProps(
yAxisTitlePosition,
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
const rebasedData = rebaseTimeseriesDatum(data, verboseMap);
const rebasedData = rebaseForecastDatum(data, verboseMap);
const xAxisCol = verboseMap[xAxisOrig] || xAxisOrig || DTTM_ALIAS;
const rawSeries = extractSeries(rebasedData, {
fillNeighborValue: stack && !forecastEnabled ? 0 : undefined,
Expand Down Expand Up @@ -333,19 +333,19 @@ export default function transformProps(
const xValue: number = richTooltip
? params[0].value[0]
: params.value[0];
const prophetValue: any[] = richTooltip ? params : [params];
const forecastValue: any[] = richTooltip ? params : [params];

if (richTooltip && tooltipSortByMetric) {
prophetValue.sort((a, b) => b.data[1] - a.data[1]);
forecastValue.sort((a, b) => b.data[1] - a.data[1]);
}

const rows: Array<string> = [`${tooltipFormatter(xValue)}`];
const prophetValues: Record<string, ProphetValue> =
extractProphetValuesFromTooltipParams(prophetValue);
const forecastValues: Record<string, ForecastValue> =
extractForecastValuesFromTooltipParams(forecastValue);

Object.keys(prophetValues).forEach(key => {
const value = prophetValues[key];
const content = formatProphetTooltipSeries({
Object.keys(forecastValues).forEach(key => {
const value = forecastValues[key];
const content = formatForecastTooltipSeries({
...value,
seriesName: key,
formatter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import {
} from 'echarts/types/src/component/marker/MarkAreaModel';
import { MarkLine1DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';

import { extractForecastSeriesContext } from '../utils/prophet';
import { extractForecastSeriesContext } from '../utils/forecast';
import { ForecastSeriesEnum, LegendOrientation } from '../types';
import { EchartsTimeseriesSeriesType } from './types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export enum LegendType {
Plain = 'plain',
}

export type ProphetValue = {
export type ForecastValue = {
marker: TooltipMarker;
observation?: number;
forecastTrend?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
TimeseriesDataRecord,
NumberFormatter,
DTTM_ALIAS,
} from '@superset-ui/core';
import { DataRecord, DTTM_ALIAS, NumberFormatter } from '@superset-ui/core';
import { CallbackDataParams, OptionName } from 'echarts/types/src/util/types';
import { TooltipMarker } from 'echarts/types/src/util/format';
import {
ForecastSeriesContext,
ForecastSeriesEnum,
ProphetValue,
ForecastValue,
} from '../types';
import { sanitizeHtml } from './series';

Expand Down Expand Up @@ -55,10 +51,10 @@ export const extractForecastSeriesContexts = (
return { ...agg, [context.name]: currentContexts };
}, {} as { [key: string]: ForecastSeriesEnum[] });

export const extractProphetValuesFromTooltipParams = (
export const extractForecastValuesFromTooltipParams = (
params: (CallbackDataParams & { seriesId: string })[],
): Record<string, ProphetValue> => {
const values: Record<string, ProphetValue> = {};
): Record<string, ForecastValue> => {
const values: Record<string, ForecastValue> = {};
params.forEach(param => {
const { marker, seriesId, value } = param;
const context = extractForecastSeriesContext(seriesId);
Expand All @@ -68,29 +64,29 @@ export const extractProphetValuesFromTooltipParams = (
values[context.name] = {
marker: marker || '',
};
const prophetValues = values[context.name];
const forecastValues = values[context.name];
if (context.type === ForecastSeriesEnum.Observation)
prophetValues.observation = numericValue;
forecastValues.observation = numericValue;
if (context.type === ForecastSeriesEnum.ForecastTrend)
prophetValues.forecastTrend = numericValue;
forecastValues.forecastTrend = numericValue;
if (context.type === ForecastSeriesEnum.ForecastLower)
prophetValues.forecastLower = numericValue;
forecastValues.forecastLower = numericValue;
if (context.type === ForecastSeriesEnum.ForecastUpper)
prophetValues.forecastUpper = numericValue;
forecastValues.forecastUpper = numericValue;
}
});
return values;
};

export const formatProphetTooltipSeries = ({
export const formatForecastTooltipSeries = ({
seriesName,
observation,
forecastTrend,
forecastLower,
forecastUpper,
marker,
formatter,
}: ProphetValue & {
}: ForecastValue & {
seriesName: string;
marker: TooltipMarker;
formatter: NumberFormatter;
Expand All @@ -113,30 +109,34 @@ export const formatProphetTooltipSeries = ({
return `${row.trim()}`;
};

export function rebaseTimeseriesDatum(
data: TimeseriesDataRecord[],
export function rebaseForecastDatum(
data: DataRecord[],
verboseMap: Record<string, string> = {},
) {
const keys = data.length > 0 ? Object.keys(data[0]) : [];
const keys = data.length ? Object.keys(data[0]) : [];

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return data.map(row => {
const newRow: TimeseriesDataRecord = { [DTTM_ALIAS]: '' };
const newRow: DataRecord = {};
keys.forEach(key => {
const forecastContext = extractForecastSeriesContext(key);
const lowerKey = `${forecastContext.name}${ForecastSeriesEnum.ForecastLower}`;
let value = row[key] as number;
const verboseKey =
key !== DTTM_ALIAS && verboseMap[forecastContext.name]
? `${verboseMap[forecastContext.name]}${forecastContext.type}`
: key;

// check if key is equal to lower confidence level. If so, extract it
// from the upper bound
const lowerForecastKey = `${forecastContext.name}${ForecastSeriesEnum.ForecastLower}`;
let value = row[key] as number | null;
if (
forecastContext.type === ForecastSeriesEnum.ForecastUpper &&
keys.includes(lowerKey) &&
keys.includes(lowerForecastKey) &&
value !== null &&
row[lowerKey] !== null
row[lowerForecastKey] !== null
) {
value -= row[lowerKey] as number;
value -= row[lowerForecastKey] as number;
}
const newKey =
key !== DTTM_ALIAS && verboseMap[key] ? verboseMap[key] : key;
newRow[newKey] = value;
newRow[verboseKey] = value;
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return newRow;
Expand Down

0 comments on commit 4df6f73

Please sign in to comment.