mirror of
https://github.com/apache/superset.git
synced 2024-09-06 22:07:34 -04:00
feat(plugin-chart-echarts): [feature-parity] support extra control for the area chart V2 (#16493)
* feat(echarts): [feature-parity] support extra control * add extra control for plugin * refactor: extract ExtraControl * fix: lint * fix some problems
This commit is contained in:
parent
0238492df7
commit
eab0009101
@ -31,7 +31,7 @@ export interface RadioButtonControlProps {
|
|||||||
description?: string;
|
description?: string;
|
||||||
options: RadioButtonOption[];
|
options: RadioButtonOption[];
|
||||||
hovered?: boolean;
|
hovered?: boolean;
|
||||||
value?: string;
|
value?: JsonValue;
|
||||||
onChange: (opt: RadioButtonOption[0]) => void;
|
onChange: (opt: RadioButtonOption[0]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ export default function transformProps(
|
|||||||
areaOpacity: opacity,
|
areaOpacity: opacity,
|
||||||
seriesType,
|
seriesType,
|
||||||
showValue,
|
showValue,
|
||||||
stack,
|
stack: Boolean(stack),
|
||||||
yAxisIndex,
|
yAxisIndex,
|
||||||
filterState,
|
filterState,
|
||||||
seriesKey: entry.name,
|
seriesKey: entry.name,
|
||||||
@ -207,7 +207,7 @@ export default function transformProps(
|
|||||||
areaOpacity: opacityB,
|
areaOpacity: opacityB,
|
||||||
seriesType: seriesTypeB,
|
seriesType: seriesTypeB,
|
||||||
showValue: showValueB,
|
showValue: showValueB,
|
||||||
stack: stackB,
|
stack: Boolean(stackB),
|
||||||
yAxisIndex: yAxisIndexB,
|
yAxisIndex: yAxisIndexB,
|
||||||
filterState,
|
filterState,
|
||||||
seriesKey: primarySeries.has(entry.name as string)
|
seriesKey: primarySeries.has(entry.name as string)
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
EchartsLegendFormData,
|
EchartsLegendFormData,
|
||||||
EchartsTitleFormData,
|
EchartsTitleFormData,
|
||||||
DEFAULT_TITLE_FORM_DATA,
|
DEFAULT_TITLE_FORM_DATA,
|
||||||
|
StackType,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
DEFAULT_FORM_DATA as TIMESERIES_DEFAULTS,
|
DEFAULT_FORM_DATA as TIMESERIES_DEFAULTS,
|
||||||
@ -78,8 +79,8 @@ export type EchartsMixedTimeseriesFormData = QueryFormData & {
|
|||||||
seriesTypeB: EchartsTimeseriesSeriesType;
|
seriesTypeB: EchartsTimeseriesSeriesType;
|
||||||
showValue: boolean;
|
showValue: boolean;
|
||||||
showValueB: boolean;
|
showValueB: boolean;
|
||||||
stack: boolean;
|
stack: StackType;
|
||||||
stackB: boolean;
|
stackB: StackType;
|
||||||
yAxisIndex?: number;
|
yAxisIndex?: number;
|
||||||
yAxisIndexB?: number;
|
yAxisIndexB?: number;
|
||||||
groupby: QueryFormColumn[];
|
groupby: QueryFormColumn[];
|
||||||
|
@ -34,10 +34,12 @@ import {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
legendSection,
|
legendSection,
|
||||||
|
onlyTotalControl,
|
||||||
|
showValueControl,
|
||||||
richTooltipSection,
|
richTooltipSection,
|
||||||
showValueSection,
|
|
||||||
xAxisControl,
|
xAxisControl,
|
||||||
} from '../../controls';
|
} from '../../controls';
|
||||||
|
import { AreaChartExtraControlsOptions } from '../../constants';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
contributionMode,
|
contributionMode,
|
||||||
@ -132,7 +134,37 @@ const config: ControlPanelConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
...showValueSection,
|
[showValueControl],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'stack',
|
||||||
|
config: {
|
||||||
|
type: 'SelectControl',
|
||||||
|
label: t('Stacked Style'),
|
||||||
|
renderTrigger: true,
|
||||||
|
choices: AreaChartExtraControlsOptions,
|
||||||
|
default: null,
|
||||||
|
description: t('Stack series on top of each other'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[onlyTotalControl],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'show_extra_controls',
|
||||||
|
config: {
|
||||||
|
type: 'CheckboxControl',
|
||||||
|
label: t('Extra Controls'),
|
||||||
|
renderTrigger: true,
|
||||||
|
default: false,
|
||||||
|
description: t(
|
||||||
|
'Whether to show extra controls or not. Extra controls ' +
|
||||||
|
'include things like making mulitBar charts stacked ' +
|
||||||
|
'or side by side.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'markerEnabled',
|
name: 'markerEnabled',
|
||||||
|
@ -24,8 +24,10 @@ import { EchartsHandler, EventHandlers } from '../types';
|
|||||||
import Echart from '../components/Echart';
|
import Echart from '../components/Echart';
|
||||||
import { TimeseriesChartTransformedProps } from './types';
|
import { TimeseriesChartTransformedProps } from './types';
|
||||||
import { currentSeries } from '../utils/series';
|
import { currentSeries } from '../utils/series';
|
||||||
|
import { ExtraControls } from '../components/ExtraControls';
|
||||||
|
|
||||||
const TIMER_DURATION = 300;
|
const TIMER_DURATION = 300;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export default function EchartsTimeseries({
|
export default function EchartsTimeseries({
|
||||||
formData,
|
formData,
|
||||||
@ -36,6 +38,7 @@ export default function EchartsTimeseries({
|
|||||||
labelMap,
|
labelMap,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
setDataMask,
|
setDataMask,
|
||||||
|
setControlValue,
|
||||||
legendData = [],
|
legendData = [],
|
||||||
}: TimeseriesChartTransformedProps) {
|
}: TimeseriesChartTransformedProps) {
|
||||||
const { emitFilter, stack } = formData;
|
const { emitFilter, stack } = formData;
|
||||||
@ -120,7 +123,7 @@ export default function EchartsTimeseries({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[groupby, labelMap, setDataMask],
|
[groupby, labelMap, setDataMask, emitFilter],
|
||||||
);
|
);
|
||||||
|
|
||||||
const eventHandlers: EventHandlers = {
|
const eventHandlers: EventHandlers = {
|
||||||
@ -195,6 +198,8 @@ export default function EchartsTimeseries({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<ExtraControls formData={formData} setControlValue={setControlValue} />
|
||||||
<Echart
|
<Echart
|
||||||
ref={echartRef}
|
ref={echartRef}
|
||||||
height={height}
|
height={height}
|
||||||
@ -204,5 +209,6 @@ export default function EchartsTimeseries({
|
|||||||
zrEventHandlers={zrEventHandlers}
|
zrEventHandlers={zrEventHandlers}
|
||||||
selectedValues={selectedValues}
|
selectedValues={selectedValues}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
isIntervalAnnotationLayer,
|
isIntervalAnnotationLayer,
|
||||||
isTimeseriesAnnotationLayer,
|
isTimeseriesAnnotationLayer,
|
||||||
TimeseriesChartDataResponseResult,
|
TimeseriesChartDataResponseResult,
|
||||||
|
t,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { isDerivedSeries } from '@superset-ui/chart-controls';
|
import { isDerivedSeries } from '@superset-ui/chart-controls';
|
||||||
import { EChartsCoreOption, SeriesOption } from 'echarts';
|
import { EChartsCoreOption, SeriesOption } from 'echarts';
|
||||||
@ -51,6 +52,8 @@ import {
|
|||||||
getAxisType,
|
getAxisType,
|
||||||
getColtypesMapping,
|
getColtypesMapping,
|
||||||
getLegendProps,
|
getLegendProps,
|
||||||
|
extractDataTotalValues,
|
||||||
|
extractShowValueIndexes,
|
||||||
} from '../utils/series';
|
} from '../utils/series';
|
||||||
import { extractAnnotationLabels } from '../utils/annotation';
|
import { extractAnnotationLabels } from '../utils/annotation';
|
||||||
import {
|
import {
|
||||||
@ -72,7 +75,11 @@ import {
|
|||||||
transformSeries,
|
transformSeries,
|
||||||
transformTimeseriesAnnotation,
|
transformTimeseriesAnnotation,
|
||||||
} from './transformers';
|
} from './transformers';
|
||||||
import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';
|
import {
|
||||||
|
AreaChartExtraControlsValue,
|
||||||
|
TIMESERIES_CONSTANTS,
|
||||||
|
TIMEGRAIN_TO_TIMESTAMP,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
export default function transformProps(
|
export default function transformProps(
|
||||||
chartProps: EchartsTimeseriesChartProps,
|
chartProps: EchartsTimeseriesChartProps,
|
||||||
@ -140,46 +147,35 @@ export default function transformProps(
|
|||||||
const xAxisCol =
|
const xAxisCol =
|
||||||
verboseMap[xAxisOrig] || getColumnLabel(xAxisOrig || DTTM_ALIAS);
|
verboseMap[xAxisOrig] || getColumnLabel(xAxisOrig || DTTM_ALIAS);
|
||||||
const isHorizontal = orientation === OrientationType.horizontal;
|
const isHorizontal = orientation === OrientationType.horizontal;
|
||||||
|
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
|
||||||
|
rebasedData,
|
||||||
|
{
|
||||||
|
stack,
|
||||||
|
percentageThreshold,
|
||||||
|
xAxisCol,
|
||||||
|
},
|
||||||
|
);
|
||||||
const rawSeries = extractSeries(rebasedData, {
|
const rawSeries = extractSeries(rebasedData, {
|
||||||
fillNeighborValue: stack && !forecastEnabled ? 0 : undefined,
|
fillNeighborValue: stack && !forecastEnabled ? 0 : undefined,
|
||||||
xAxis: xAxisCol,
|
xAxis: xAxisCol,
|
||||||
removeNulls: seriesType === EchartsTimeseriesSeriesType.Scatter,
|
removeNulls: seriesType === EchartsTimeseriesSeriesType.Scatter,
|
||||||
|
stack,
|
||||||
|
totalStackedValues,
|
||||||
isHorizontal,
|
isHorizontal,
|
||||||
});
|
});
|
||||||
|
const showValueIndexes = extractShowValueIndexes(rawSeries, {
|
||||||
|
stack,
|
||||||
|
});
|
||||||
const seriesContexts = extractForecastSeriesContexts(
|
const seriesContexts = extractForecastSeriesContexts(
|
||||||
Object.values(rawSeries).map(series => series.name as string),
|
Object.values(rawSeries).map(series => series.name as string),
|
||||||
);
|
);
|
||||||
|
const isAreaExpand = stack === AreaChartExtraControlsValue.Expand;
|
||||||
const xAxisDataType = dataTypes?.[xAxisCol];
|
const xAxisDataType = dataTypes?.[xAxisCol];
|
||||||
const xAxisType = getAxisType(xAxisDataType);
|
const xAxisType = getAxisType(xAxisDataType);
|
||||||
const series: SeriesOption[] = [];
|
const series: SeriesOption[] = [];
|
||||||
const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);
|
const formatter = getNumberFormatter(
|
||||||
|
contributionMode || isAreaExpand ? ',.0%' : yAxisFormat,
|
||||||
const totalStackedValues: number[] = [];
|
);
|
||||||
const showValueIndexes: number[] = [];
|
|
||||||
const thresholdValues: number[] = [];
|
|
||||||
|
|
||||||
rebasedData.forEach(data => {
|
|
||||||
const values = Object.keys(data).reduce((prev, curr) => {
|
|
||||||
if (curr === xAxisCol) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
const value = data[curr] || 0;
|
|
||||||
return prev + (value as number);
|
|
||||||
}, 0);
|
|
||||||
totalStackedValues.push(values);
|
|
||||||
thresholdValues.push(((percentageThreshold || 0) / 100) * values);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (stack) {
|
|
||||||
rawSeries.forEach((entry, seriesIndex) => {
|
|
||||||
const { data = [] } = entry;
|
|
||||||
(data as [Date, number][]).forEach((datum, dataIndex) => {
|
|
||||||
if (datum[1] !== null) {
|
|
||||||
showValueIndexes[dataIndex] = seriesIndex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
rawSeries.forEach(entry => {
|
rawSeries.forEach(entry => {
|
||||||
const lineStyle = isDerivedSeries(entry, chartProps.rawFormData)
|
const lineStyle = isDerivedSeries(entry, chartProps.rawFormData)
|
||||||
@ -266,7 +262,7 @@ export default function transformProps(
|
|||||||
let [min, max] = (yAxisBounds || []).map(parseYAxisBound);
|
let [min, max] = (yAxisBounds || []).map(parseYAxisBound);
|
||||||
|
|
||||||
// default to 0-100% range when doing row-level contribution chart
|
// default to 0-100% range when doing row-level contribution chart
|
||||||
if (contributionMode === 'row' && stack) {
|
if ((contributionMode === 'row' || isAreaExpand) && stack) {
|
||||||
if (min === undefined) min = 0;
|
if (min === undefined) min = 0;
|
||||||
if (max === undefined) max = 1;
|
if (max === undefined) max = 1;
|
||||||
}
|
}
|
||||||
@ -291,7 +287,10 @@ export default function transformProps(
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { setDataMask = () => {} } = hooks;
|
const {
|
||||||
|
setDataMask = () => {},
|
||||||
|
setControlValue = (...args: unknown[]) => {},
|
||||||
|
} = hooks;
|
||||||
|
|
||||||
const addYAxisLabelOffset = !!yAxisTitle;
|
const addYAxisLabelOffset = !!yAxisTitle;
|
||||||
const addXAxisLabelOffset = !!xAxisTitle;
|
const addXAxisLabelOffset = !!xAxisTitle;
|
||||||
@ -406,8 +405,8 @@ export default function transformProps(
|
|||||||
dataZoom: {
|
dataZoom: {
|
||||||
yAxisIndex: false,
|
yAxisIndex: false,
|
||||||
title: {
|
title: {
|
||||||
zoom: 'zoom area',
|
zoom: t('zoom area'),
|
||||||
back: 'restore zoom',
|
back: t('restore zoom'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -433,6 +432,7 @@ export default function transformProps(
|
|||||||
labelMap,
|
labelMap,
|
||||||
selectedValues,
|
selectedValues,
|
||||||
setDataMask,
|
setDataMask,
|
||||||
|
setControlValue,
|
||||||
width,
|
width,
|
||||||
legendData,
|
legendData,
|
||||||
};
|
};
|
||||||
|
@ -52,7 +52,7 @@ import {
|
|||||||
import { MarkLine1DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
|
import { MarkLine1DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
|
||||||
|
|
||||||
import { extractForecastSeriesContext } from '../utils/forecast';
|
import { extractForecastSeriesContext } from '../utils/forecast';
|
||||||
import { ForecastSeriesEnum, LegendOrientation } from '../types';
|
import { ForecastSeriesEnum, LegendOrientation, StackType } from '../types';
|
||||||
import { EchartsTimeseriesSeriesType } from './types';
|
import { EchartsTimeseriesSeriesType } from './types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -62,7 +62,11 @@ import {
|
|||||||
parseAnnotationOpacity,
|
parseAnnotationOpacity,
|
||||||
} from '../utils/annotation';
|
} from '../utils/annotation';
|
||||||
import { currentSeries, getChartPadding } from '../utils/series';
|
import { currentSeries, getChartPadding } from '../utils/series';
|
||||||
import { OpacityEnum, TIMESERIES_CONSTANTS } from '../constants';
|
import {
|
||||||
|
AreaChartExtraControlsValue,
|
||||||
|
OpacityEnum,
|
||||||
|
TIMESERIES_CONSTANTS,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
export function transformSeries(
|
export function transformSeries(
|
||||||
series: SeriesOption,
|
series: SeriesOption,
|
||||||
@ -75,7 +79,7 @@ export function transformSeries(
|
|||||||
markerSize?: number;
|
markerSize?: number;
|
||||||
areaOpacity?: number;
|
areaOpacity?: number;
|
||||||
seriesType?: EchartsTimeseriesSeriesType;
|
seriesType?: EchartsTimeseriesSeriesType;
|
||||||
stack?: boolean;
|
stack?: StackType;
|
||||||
yAxisIndex?: number;
|
yAxisIndex?: number;
|
||||||
showValue?: boolean;
|
showValue?: boolean;
|
||||||
onlyTotal?: boolean;
|
onlyTotal?: boolean;
|
||||||
@ -225,6 +229,7 @@ export function transformSeries(
|
|||||||
const { value, dataIndex, seriesIndex, seriesName } = params;
|
const { value, dataIndex, seriesIndex, seriesName } = params;
|
||||||
const numericValue = isHorizontal ? value[0] : value[1];
|
const numericValue = isHorizontal ? value[0] : value[1];
|
||||||
const isSelectedLegend = currentSeries.legend === seriesName;
|
const isSelectedLegend = currentSeries.legend === seriesName;
|
||||||
|
const isAreaExpand = stack === AreaChartExtraControlsValue.Expand;
|
||||||
if (!formatter) return numericValue;
|
if (!formatter) return numericValue;
|
||||||
if (!stack || isSelectedLegend) return formatter(numericValue);
|
if (!stack || isSelectedLegend) return formatter(numericValue);
|
||||||
if (!onlyTotal) {
|
if (!onlyTotal) {
|
||||||
@ -234,7 +239,7 @@ export function transformSeries(
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (seriesIndex === showValueIndexes[dataIndex]) {
|
if (seriesIndex === showValueIndexes[dataIndex]) {
|
||||||
return formatter(totalStackedValues[dataIndex]);
|
return formatter(isAreaExpand ? 1 : totalStackedValues[dataIndex]);
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
EChartTransformedProps,
|
EChartTransformedProps,
|
||||||
EchartsTitleFormData,
|
EchartsTitleFormData,
|
||||||
DEFAULT_TITLE_FORM_DATA,
|
DEFAULT_TITLE_FORM_DATA,
|
||||||
|
StackType,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
export enum EchartsTimeseriesContributionType {
|
export enum EchartsTimeseriesContributionType {
|
||||||
@ -72,7 +73,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
|
|||||||
orderDesc: boolean;
|
orderDesc: boolean;
|
||||||
rowLimit: number;
|
rowLimit: number;
|
||||||
seriesType: EchartsTimeseriesSeriesType;
|
seriesType: EchartsTimeseriesSeriesType;
|
||||||
stack: boolean;
|
stack: StackType;
|
||||||
tooltipTimeFormat?: string;
|
tooltipTimeFormat?: string;
|
||||||
truncateYAxis: boolean;
|
truncateYAxis: boolean;
|
||||||
yAxisFormat?: string;
|
yAxisFormat?: string;
|
||||||
@ -86,6 +87,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
|
|||||||
groupby: QueryFormColumn[];
|
groupby: QueryFormColumn[];
|
||||||
showValue: boolean;
|
showValue: boolean;
|
||||||
onlyTotal: boolean;
|
onlyTotal: boolean;
|
||||||
|
showExtraControls: boolean;
|
||||||
percentageThreshold: number;
|
percentageThreshold: number;
|
||||||
orientation?: OrientationType;
|
orientation?: OrientationType;
|
||||||
} & EchartsLegendFormData &
|
} & EchartsLegendFormData &
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
|
import { HandlerFunction, JsonValue, styled } from '@superset-ui/core';
|
||||||
|
import {
|
||||||
|
RadioButtonOption,
|
||||||
|
sharedControlComponents,
|
||||||
|
} from '@superset-ui/chart-controls';
|
||||||
|
import { AreaChartExtraControlsOptions } from '../constants';
|
||||||
|
|
||||||
|
const { RadioButtonControl } = sharedControlComponents;
|
||||||
|
|
||||||
|
const ExtraControlsWrapper = styled.div`
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function useExtraControl<
|
||||||
|
F extends {
|
||||||
|
stack: any;
|
||||||
|
area: boolean;
|
||||||
|
},
|
||||||
|
>({
|
||||||
|
formData,
|
||||||
|
setControlValue,
|
||||||
|
}: {
|
||||||
|
formData: F;
|
||||||
|
setControlValue?: HandlerFunction;
|
||||||
|
}) {
|
||||||
|
const { stack, area } = formData;
|
||||||
|
const [extraValue, setExtraValue] = useState<JsonValue | undefined>(
|
||||||
|
stack ?? undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExtraValue(stack);
|
||||||
|
}, [stack]);
|
||||||
|
|
||||||
|
const extraControlsOptions = useMemo(() => {
|
||||||
|
if (area) {
|
||||||
|
return AreaChartExtraControlsOptions;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [area]);
|
||||||
|
|
||||||
|
const extraControlsHandler = useCallback(
|
||||||
|
(value: RadioButtonOption[0]) => {
|
||||||
|
if (area) {
|
||||||
|
if (setControlValue) {
|
||||||
|
setControlValue('stack', value);
|
||||||
|
setExtraValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[area, setControlValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
extraControlsOptions,
|
||||||
|
extraControlsHandler,
|
||||||
|
extraValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExtraControls<
|
||||||
|
F extends {
|
||||||
|
stack: any;
|
||||||
|
area: boolean;
|
||||||
|
showExtraControls: boolean;
|
||||||
|
},
|
||||||
|
>({
|
||||||
|
formData,
|
||||||
|
setControlValue,
|
||||||
|
}: {
|
||||||
|
formData: F;
|
||||||
|
setControlValue?: HandlerFunction;
|
||||||
|
}) {
|
||||||
|
const { extraControlsOptions, extraControlsHandler, extraValue } =
|
||||||
|
useExtraControl<F>({
|
||||||
|
formData,
|
||||||
|
setControlValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!formData.showExtraControls) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExtraControlsWrapper>
|
||||||
|
<RadioButtonControl
|
||||||
|
options={extraControlsOptions}
|
||||||
|
onChange={extraControlsHandler}
|
||||||
|
value={extraValue}
|
||||||
|
/>
|
||||||
|
</ExtraControlsWrapper>
|
||||||
|
);
|
||||||
|
}
|
@ -17,7 +17,8 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TimeGranularity } from '@superset-ui/core';
|
import { JsonValue, t, TimeGranularity } from '@superset-ui/core';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
import { LabelPositionEnum } from './types';
|
import { LabelPositionEnum } from './types';
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
@ -37,6 +38,7 @@ export const TIMESERIES_CONSTANTS = {
|
|||||||
dataZoomStart: 0,
|
dataZoomStart: 0,
|
||||||
dataZoomEnd: 100,
|
dataZoomEnd: 100,
|
||||||
yAxisLabelTopOffset: 20,
|
yAxisLabelTopOffset: 20,
|
||||||
|
extraControlsOffset: 22,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LABEL_POSITION: [LabelPositionEnum, string][] = [
|
export const LABEL_POSITION: [LabelPositionEnum, string][] = [
|
||||||
@ -61,6 +63,20 @@ export enum OpacityEnum {
|
|||||||
NonTransparent = 1,
|
NonTransparent = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AreaChartExtraControlsValue {
|
||||||
|
Stack = 'Stack',
|
||||||
|
Expand = 'Expand',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AreaChartExtraControlsOptions: [
|
||||||
|
JsonValue,
|
||||||
|
Exclude<ReactNode, null | undefined | boolean>,
|
||||||
|
][] = [
|
||||||
|
[null, t('None')],
|
||||||
|
[AreaChartExtraControlsValue.Stack, t('Stack')],
|
||||||
|
[AreaChartExtraControlsValue.Expand, t('Expand')],
|
||||||
|
];
|
||||||
|
|
||||||
export const TIMEGRAIN_TO_TIMESTAMP = {
|
export const TIMEGRAIN_TO_TIMESTAMP = {
|
||||||
[TimeGranularity.HOUR]: 3600 * 1000,
|
[TimeGranularity.HOUR]: 3600 * 1000,
|
||||||
[TimeGranularity.DAY]: 3600 * 1000 * 24,
|
[TimeGranularity.DAY]: 3600 * 1000 * 24,
|
||||||
|
@ -108,7 +108,7 @@ export const legendSection: ControlSetRow[] = [
|
|||||||
[legendMarginControl],
|
[legendMarginControl],
|
||||||
];
|
];
|
||||||
|
|
||||||
const showValueControl: ControlSetItem = {
|
export const showValueControl: ControlSetItem = {
|
||||||
name: 'show_value',
|
name: 'show_value',
|
||||||
config: {
|
config: {
|
||||||
type: 'CheckboxControl',
|
type: 'CheckboxControl',
|
||||||
@ -119,7 +119,7 @@ const showValueControl: ControlSetItem = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const stackControl: ControlSetItem = {
|
export const stackControl: ControlSetItem = {
|
||||||
name: 'stack',
|
name: 'stack',
|
||||||
config: {
|
config: {
|
||||||
type: 'CheckboxControl',
|
type: 'CheckboxControl',
|
||||||
@ -130,7 +130,7 @@ const stackControl: ControlSetItem = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const onlyTotalControl: ControlSetItem = {
|
export const onlyTotalControl: ControlSetItem = {
|
||||||
name: 'only_total',
|
name: 'only_total',
|
||||||
config: {
|
config: {
|
||||||
type: 'CheckboxControl',
|
type: 'CheckboxControl',
|
||||||
|
@ -18,12 +18,14 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
DataRecordValue,
|
DataRecordValue,
|
||||||
|
HandlerFunction,
|
||||||
QueryFormColumn,
|
QueryFormColumn,
|
||||||
SetDataMaskHook,
|
SetDataMaskHook,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { EChartsCoreOption, ECharts } from 'echarts';
|
import { EChartsCoreOption, ECharts } from 'echarts';
|
||||||
import { TooltipMarker } from 'echarts/types/src/util/format';
|
import { TooltipMarker } from 'echarts/types/src/util/format';
|
||||||
import { OptionName } from 'echarts/types/src/util/types';
|
import { OptionName } from 'echarts/types/src/util/types';
|
||||||
|
import { AreaChartExtraControlsValue } from './constants';
|
||||||
|
|
||||||
export type EchartsStylesProps = {
|
export type EchartsStylesProps = {
|
||||||
height: number;
|
height: number;
|
||||||
@ -115,6 +117,7 @@ export interface EChartTransformedProps<F> {
|
|||||||
echartOptions: EChartsCoreOption;
|
echartOptions: EChartsCoreOption;
|
||||||
emitFilter: boolean;
|
emitFilter: boolean;
|
||||||
setDataMask: SetDataMaskHook;
|
setDataMask: SetDataMaskHook;
|
||||||
|
setControlValue?: HandlerFunction;
|
||||||
labelMap: Record<string, DataRecordValue[]>;
|
labelMap: Record<string, DataRecordValue[]>;
|
||||||
groupby: QueryFormColumn[];
|
groupby: QueryFormColumn[];
|
||||||
selectedValues: Record<number, string>;
|
selectedValues: Record<number, string>;
|
||||||
@ -137,4 +140,6 @@ export const DEFAULT_TITLE_FORM_DATA: EchartsTitleFormData = {
|
|||||||
yAxisTitlePosition: 'Top',
|
yAxisTitlePosition: 'Top',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StackType = boolean | null | Partial<AreaChartExtraControlsValue>;
|
||||||
|
|
||||||
export * from './Timeseries/types';
|
export * from './Timeseries/types';
|
||||||
|
@ -28,20 +28,79 @@ import {
|
|||||||
TimeFormatter,
|
TimeFormatter,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { format, LegendComponentOption, SeriesOption } from 'echarts';
|
import { format, LegendComponentOption, SeriesOption } from 'echarts';
|
||||||
import { NULL_STRING, TIMESERIES_CONSTANTS } from '../constants';
|
import {
|
||||||
import { LegendOrientation, LegendType } from '../types';
|
AreaChartExtraControlsValue,
|
||||||
|
NULL_STRING,
|
||||||
|
TIMESERIES_CONSTANTS,
|
||||||
|
} from '../constants';
|
||||||
|
import { LegendOrientation, LegendType, StackType } from '../types';
|
||||||
import { defaultLegendPadding } from '../defaults';
|
import { defaultLegendPadding } from '../defaults';
|
||||||
|
|
||||||
function isDefined<T>(value: T | undefined | null): boolean {
|
function isDefined<T>(value: T | undefined | null): boolean {
|
||||||
return value !== undefined && value !== null;
|
return value !== undefined && value !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractDataTotalValues(
|
||||||
|
data: DataRecord[],
|
||||||
|
opts: {
|
||||||
|
stack: StackType;
|
||||||
|
percentageThreshold: number;
|
||||||
|
xAxisCol: string;
|
||||||
|
},
|
||||||
|
): {
|
||||||
|
totalStackedValues: number[];
|
||||||
|
thresholdValues: number[];
|
||||||
|
} {
|
||||||
|
const totalStackedValues: number[] = [];
|
||||||
|
const thresholdValues: number[] = [];
|
||||||
|
const { stack, percentageThreshold, xAxisCol } = opts;
|
||||||
|
if (stack) {
|
||||||
|
data.forEach(datum => {
|
||||||
|
const values = Object.keys(datum).reduce((prev, curr) => {
|
||||||
|
if (curr === xAxisCol) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
const value = datum[curr] || 0;
|
||||||
|
return prev + (value as number);
|
||||||
|
}, 0);
|
||||||
|
totalStackedValues.push(values);
|
||||||
|
thresholdValues.push(((percentageThreshold || 0) / 100) * values);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
totalStackedValues,
|
||||||
|
thresholdValues,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractShowValueIndexes(
|
||||||
|
series: SeriesOption[],
|
||||||
|
opts: {
|
||||||
|
stack: StackType;
|
||||||
|
},
|
||||||
|
): number[] {
|
||||||
|
const showValueIndexes: number[] = [];
|
||||||
|
if (opts.stack) {
|
||||||
|
series.forEach((entry, seriesIndex) => {
|
||||||
|
const { data = [] } = entry;
|
||||||
|
(data as [any, number][]).forEach((datum, dataIndex) => {
|
||||||
|
if (datum[1] !== null) {
|
||||||
|
showValueIndexes[dataIndex] = seriesIndex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return showValueIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
export function extractSeries(
|
export function extractSeries(
|
||||||
data: DataRecord[],
|
data: DataRecord[],
|
||||||
opts: {
|
opts: {
|
||||||
fillNeighborValue?: number;
|
fillNeighborValue?: number;
|
||||||
xAxis?: string;
|
xAxis?: string;
|
||||||
removeNulls?: boolean;
|
removeNulls?: boolean;
|
||||||
|
stack?: StackType;
|
||||||
|
totalStackedValues?: number[];
|
||||||
isHorizontal?: boolean;
|
isHorizontal?: boolean;
|
||||||
} = {},
|
} = {},
|
||||||
): SeriesOption[] {
|
): SeriesOption[] {
|
||||||
@ -49,6 +108,8 @@ export function extractSeries(
|
|||||||
fillNeighborValue,
|
fillNeighborValue,
|
||||||
xAxis = DTTM_ALIAS,
|
xAxis = DTTM_ALIAS,
|
||||||
removeNulls = false,
|
removeNulls = false,
|
||||||
|
stack = false,
|
||||||
|
totalStackedValues = [],
|
||||||
isHorizontal = false,
|
isHorizontal = false,
|
||||||
} = opts;
|
} = opts;
|
||||||
if (data.length === 0) return [];
|
if (data.length === 0) return [];
|
||||||
@ -66,14 +127,20 @@ export function extractSeries(
|
|||||||
.map((row, idx) => {
|
.map((row, idx) => {
|
||||||
const isNextToDefinedValue =
|
const isNextToDefinedValue =
|
||||||
isDefined(rows[idx - 1]?.[key]) || isDefined(rows[idx + 1]?.[key]);
|
isDefined(rows[idx - 1]?.[key]) || isDefined(rows[idx + 1]?.[key]);
|
||||||
return [
|
const isFillNeighborValue =
|
||||||
row[xAxis],
|
|
||||||
!isDefined(row[key]) &&
|
!isDefined(row[key]) &&
|
||||||
isNextToDefinedValue &&
|
isNextToDefinedValue &&
|
||||||
fillNeighborValue !== undefined
|
fillNeighborValue !== undefined;
|
||||||
? fillNeighborValue
|
let value: DataRecordValue | undefined = row[key];
|
||||||
: row[key],
|
if (isFillNeighborValue) {
|
||||||
];
|
value = fillNeighborValue;
|
||||||
|
} else if (
|
||||||
|
stack === AreaChartExtraControlsValue.Expand &&
|
||||||
|
totalStackedValues.length > 0
|
||||||
|
) {
|
||||||
|
value = ((value || 0) as number) / totalStackedValues[idx];
|
||||||
|
}
|
||||||
|
return [row[xAxis], value];
|
||||||
})
|
})
|
||||||
.filter(obs => !removeNulls || (obs[0] !== null && obs[1] !== null))
|
.filter(obs => !removeNulls || (obs[0] !== null && obs[1] !== null))
|
||||||
.map(obs => (isHorizontal ? [obs[1], obs[0]] : obs)),
|
.map(obs => (isHorizontal ? [obs[1], obs[0]] : obs)),
|
||||||
|
@ -113,6 +113,7 @@ class ChartRenderer extends React.Component {
|
|||||||
nextProps.labelColors !== this.props.labelColors ||
|
nextProps.labelColors !== this.props.labelColors ||
|
||||||
nextProps.sharedLabelColors !== this.props.sharedLabelColors ||
|
nextProps.sharedLabelColors !== this.props.sharedLabelColors ||
|
||||||
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
|
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
|
||||||
|
nextProps.formData.stack !== this.props.formData.stack ||
|
||||||
nextProps.cacheBusterProp !== this.props.cacheBusterProp
|
nextProps.cacheBusterProp !== this.props.cacheBusterProp
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ const propTypes = {
|
|||||||
updateSliceName: PropTypes.func.isRequired,
|
updateSliceName: PropTypes.func.isRequired,
|
||||||
isComponentVisible: PropTypes.bool,
|
isComponentVisible: PropTypes.bool,
|
||||||
handleToggleFullSize: PropTypes.func.isRequired,
|
handleToggleFullSize: PropTypes.func.isRequired,
|
||||||
|
setControlValue: PropTypes.func,
|
||||||
|
|
||||||
// from redux
|
// from redux
|
||||||
chart: chartPropShape.isRequired,
|
chart: chartPropShape.isRequired,
|
||||||
@ -348,6 +349,7 @@ export default class Chart extends React.Component {
|
|||||||
filterState,
|
filterState,
|
||||||
handleToggleFullSize,
|
handleToggleFullSize,
|
||||||
isFullSize,
|
isFullSize,
|
||||||
|
setControlValue,
|
||||||
filterboxMigrationState,
|
filterboxMigrationState,
|
||||||
postTransformProps,
|
postTransformProps,
|
||||||
datasetsStatus,
|
datasetsStatus,
|
||||||
@ -475,6 +477,7 @@ export default class Chart extends React.Component {
|
|||||||
timeout={timeout}
|
timeout={timeout}
|
||||||
triggerQuery={chart.triggerQuery}
|
triggerQuery={chart.triggerQuery}
|
||||||
vizType={slice.viz_type}
|
vizType={slice.viz_type}
|
||||||
|
setControlValue={setControlValue}
|
||||||
isDeactivatedViz={isDeactivatedViz}
|
isDeactivatedViz={isDeactivatedViz}
|
||||||
filterboxMigrationState={filterboxMigrationState}
|
filterboxMigrationState={filterboxMigrationState}
|
||||||
postTransformProps={postTransformProps}
|
postTransformProps={postTransformProps}
|
||||||
|
@ -191,12 +191,14 @@ class ChartHolder extends React.Component {
|
|||||||
outlinedComponentId: null,
|
outlinedComponentId: null,
|
||||||
outlinedColumnName: null,
|
outlinedColumnName: null,
|
||||||
directPathLastUpdated: 0,
|
directPathLastUpdated: 0,
|
||||||
|
extraControls: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleChangeFocus = this.handleChangeFocus.bind(this);
|
this.handleChangeFocus = this.handleChangeFocus.bind(this);
|
||||||
this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
|
this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
|
||||||
this.handleUpdateSliceName = this.handleUpdateSliceName.bind(this);
|
this.handleUpdateSliceName = this.handleUpdateSliceName.bind(this);
|
||||||
this.handleToggleFullSize = this.handleToggleFullSize.bind(this);
|
this.handleToggleFullSize = this.handleToggleFullSize.bind(this);
|
||||||
|
this.handleExtraControl = this.handleExtraControl.bind(this);
|
||||||
this.handlePostTransformProps = this.handlePostTransformProps.bind(this);
|
this.handlePostTransformProps = this.handlePostTransformProps.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,13 +254,22 @@ class ChartHolder extends React.Component {
|
|||||||
setFullSizeChartId(isFullSize ? null : chartId);
|
setFullSizeChartId(isFullSize ? null : chartId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleExtraControl(name, value) {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
extraControls: {
|
||||||
|
...prevState.extraControls,
|
||||||
|
[name]: value,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
handlePostTransformProps(props) {
|
handlePostTransformProps(props) {
|
||||||
this.props.postAddSliceFromDashboard();
|
this.props.postAddSliceFromDashboard();
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isFocused } = this.state;
|
const { isFocused, extraControls } = this.state;
|
||||||
const {
|
const {
|
||||||
component,
|
component,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
@ -374,6 +385,8 @@ class ChartHolder extends React.Component {
|
|||||||
isComponentVisible={isComponentVisible}
|
isComponentVisible={isComponentVisible}
|
||||||
handleToggleFullSize={this.handleToggleFullSize}
|
handleToggleFullSize={this.handleToggleFullSize}
|
||||||
isFullSize={isFullSize}
|
isFullSize={isFullSize}
|
||||||
|
setControlValue={this.handleExtraControl}
|
||||||
|
extraControls={extraControls}
|
||||||
postTransformProps={this.handlePostTransformProps}
|
postTransformProps={this.handlePostTransformProps}
|
||||||
/>
|
/>
|
||||||
{editMode && (
|
{editMode && (
|
||||||
|
@ -55,7 +55,7 @@ function mapStateToProps(
|
|||||||
},
|
},
|
||||||
ownProps,
|
ownProps,
|
||||||
) {
|
) {
|
||||||
const { id } = ownProps;
|
const { id, extraControls, setControlValue } = ownProps;
|
||||||
const chart = chartQueries[id] || EMPTY_OBJECT;
|
const chart = chartQueries[id] || EMPTY_OBJECT;
|
||||||
const datasource =
|
const datasource =
|
||||||
(chart && chart.form_data && datasources[chart.form_data.datasource]) ||
|
(chart && chart.form_data && datasources[chart.form_data.datasource]) ||
|
||||||
@ -76,6 +76,7 @@ function mapStateToProps(
|
|||||||
sliceId: id,
|
sliceId: id,
|
||||||
nativeFilters,
|
nativeFilters,
|
||||||
dataMask,
|
dataMask,
|
||||||
|
extraControls,
|
||||||
labelColors,
|
labelColors,
|
||||||
sharedLabelColors,
|
sharedLabelColors,
|
||||||
});
|
});
|
||||||
@ -100,6 +101,7 @@ function mapStateToProps(
|
|||||||
ownState: dataMask[id]?.ownState,
|
ownState: dataMask[id]?.ownState,
|
||||||
filterState: dataMask[id]?.filterState,
|
filterState: dataMask[id]?.filterState,
|
||||||
maxRows: common.conf.SQL_MAX_ROW,
|
maxRows: common.conf.SQL_MAX_ROW,
|
||||||
|
setControlValue,
|
||||||
filterboxMigrationState: dashboardState.filterboxMigrationState,
|
filterboxMigrationState: dashboardState.filterboxMigrationState,
|
||||||
datasetsStatus,
|
datasetsStatus,
|
||||||
};
|
};
|
||||||
|
@ -45,6 +45,7 @@ export interface GetFormDataWithExtraFiltersArguments {
|
|||||||
sliceId: number;
|
sliceId: number;
|
||||||
dataMask: DataMaskStateWithId;
|
dataMask: DataMaskStateWithId;
|
||||||
nativeFilters: NativeFiltersState;
|
nativeFilters: NativeFiltersState;
|
||||||
|
extraControls: Record<string, string | boolean | null>;
|
||||||
labelColors?: Record<string, string>;
|
labelColors?: Record<string, string>;
|
||||||
sharedLabelColors?: Record<string, string>;
|
sharedLabelColors?: Record<string, string>;
|
||||||
}
|
}
|
||||||
@ -63,6 +64,7 @@ export default function getFormDataWithExtraFilters({
|
|||||||
sliceId,
|
sliceId,
|
||||||
layout,
|
layout,
|
||||||
dataMask,
|
dataMask,
|
||||||
|
extraControls,
|
||||||
labelColors,
|
labelColors,
|
||||||
sharedLabelColors,
|
sharedLabelColors,
|
||||||
}: GetFormDataWithExtraFiltersArguments) {
|
}: GetFormDataWithExtraFiltersArguments) {
|
||||||
@ -85,6 +87,9 @@ export default function getFormDataWithExtraFilters({
|
|||||||
!!cachedFormData &&
|
!!cachedFormData &&
|
||||||
areObjectsEqual(cachedFormData?.dataMask, dataMask, {
|
areObjectsEqual(cachedFormData?.dataMask, dataMask, {
|
||||||
ignoreUndefined: true,
|
ignoreUndefined: true,
|
||||||
|
}) &&
|
||||||
|
areObjectsEqual(cachedFormData?.extraControls, extraControls, {
|
||||||
|
ignoreUndefined: true,
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
return cachedFormData;
|
return cachedFormData;
|
||||||
@ -117,10 +122,11 @@ export default function getFormDataWithExtraFilters({
|
|||||||
...(colorScheme && { color_scheme: colorScheme }),
|
...(colorScheme && { color_scheme: colorScheme }),
|
||||||
extra_filters: getEffectiveExtraFilters(filters),
|
extra_filters: getEffectiveExtraFilters(filters),
|
||||||
...extraData,
|
...extraData,
|
||||||
|
...extraControls,
|
||||||
};
|
};
|
||||||
|
|
||||||
cachedFiltersByChart[sliceId] = filters;
|
cachedFiltersByChart[sliceId] = filters;
|
||||||
cachedFormdataByChart[sliceId] = { ...formData, dataMask };
|
cachedFormdataByChart[sliceId] = { ...formData, dataMask, extraControls };
|
||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,9 @@ describe('getFormDataWithExtraFilters', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
layout: {},
|
layout: {},
|
||||||
|
extraControls: {
|
||||||
|
stack: 'Stacked',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should include filters from the passed filters', () => {
|
it('should include filters from the passed filters', () => {
|
||||||
@ -87,4 +90,9 @@ describe('getFormDataWithExtraFilters', () => {
|
|||||||
val: ['pink', 'purple'],
|
val: ['pink', 'purple'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should compose extra control', () => {
|
||||||
|
const result = getFormDataWithExtraFilters(mockArgs);
|
||||||
|
expect(result.stack).toEqual('Stacked');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user