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:
Stephen Liu 2022-06-09 00:59:10 +08:00 committed by GitHub
parent 0238492df7
commit eab0009101
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 349 additions and 70 deletions

View File

@ -31,7 +31,7 @@ export interface RadioButtonControlProps {
description?: string;
options: RadioButtonOption[];
hovered?: boolean;
value?: string;
value?: JsonValue;
onChange: (opt: RadioButtonOption[0]) => void;
}

View File

@ -190,7 +190,7 @@ export default function transformProps(
areaOpacity: opacity,
seriesType,
showValue,
stack,
stack: Boolean(stack),
yAxisIndex,
filterState,
seriesKey: entry.name,
@ -207,7 +207,7 @@ export default function transformProps(
areaOpacity: opacityB,
seriesType: seriesTypeB,
showValue: showValueB,
stack: stackB,
stack: Boolean(stackB),
yAxisIndex: yAxisIndexB,
filterState,
seriesKey: primarySeries.has(entry.name as string)

View File

@ -32,6 +32,7 @@ import {
EchartsLegendFormData,
EchartsTitleFormData,
DEFAULT_TITLE_FORM_DATA,
StackType,
} from '../types';
import {
DEFAULT_FORM_DATA as TIMESERIES_DEFAULTS,
@ -78,8 +79,8 @@ export type EchartsMixedTimeseriesFormData = QueryFormData & {
seriesTypeB: EchartsTimeseriesSeriesType;
showValue: boolean;
showValueB: boolean;
stack: boolean;
stackB: boolean;
stack: StackType;
stackB: StackType;
yAxisIndex?: number;
yAxisIndexB?: number;
groupby: QueryFormColumn[];

View File

@ -34,10 +34,12 @@ import {
} from '../types';
import {
legendSection,
onlyTotalControl,
showValueControl,
richTooltipSection,
showValueSection,
xAxisControl,
} from '../../controls';
import { AreaChartExtraControlsOptions } from '../../constants';
const {
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',

View File

@ -24,8 +24,10 @@ import { EchartsHandler, EventHandlers } from '../types';
import Echart from '../components/Echart';
import { TimeseriesChartTransformedProps } from './types';
import { currentSeries } from '../utils/series';
import { ExtraControls } from '../components/ExtraControls';
const TIMER_DURATION = 300;
// @ts-ignore
export default function EchartsTimeseries({
formData,
@ -36,6 +38,7 @@ export default function EchartsTimeseries({
labelMap,
selectedValues,
setDataMask,
setControlValue,
legendData = [],
}: TimeseriesChartTransformedProps) {
const { emitFilter, stack } = formData;
@ -120,7 +123,7 @@ export default function EchartsTimeseries({
},
});
},
[groupby, labelMap, setDataMask],
[groupby, labelMap, setDataMask, emitFilter],
);
const eventHandlers: EventHandlers = {
@ -195,14 +198,17 @@ export default function EchartsTimeseries({
};
return (
<Echart
ref={echartRef}
height={height}
width={width}
echartOptions={echartOptions}
eventHandlers={eventHandlers}
zrEventHandlers={zrEventHandlers}
selectedValues={selectedValues}
/>
<>
<ExtraControls formData={formData} setControlValue={setControlValue} />
<Echart
ref={echartRef}
height={height}
width={width}
echartOptions={echartOptions}
eventHandlers={eventHandlers}
zrEventHandlers={zrEventHandlers}
selectedValues={selectedValues}
/>
</>
);
}

View File

@ -30,6 +30,7 @@ import {
isIntervalAnnotationLayer,
isTimeseriesAnnotationLayer,
TimeseriesChartDataResponseResult,
t,
} from '@superset-ui/core';
import { isDerivedSeries } from '@superset-ui/chart-controls';
import { EChartsCoreOption, SeriesOption } from 'echarts';
@ -51,6 +52,8 @@ import {
getAxisType,
getColtypesMapping,
getLegendProps,
extractDataTotalValues,
extractShowValueIndexes,
} from '../utils/series';
import { extractAnnotationLabels } from '../utils/annotation';
import {
@ -72,7 +75,11 @@ import {
transformSeries,
transformTimeseriesAnnotation,
} from './transformers';
import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';
import {
AreaChartExtraControlsValue,
TIMESERIES_CONSTANTS,
TIMEGRAIN_TO_TIMESTAMP,
} from '../constants';
export default function transformProps(
chartProps: EchartsTimeseriesChartProps,
@ -140,46 +147,35 @@ export default function transformProps(
const xAxisCol =
verboseMap[xAxisOrig] || getColumnLabel(xAxisOrig || DTTM_ALIAS);
const isHorizontal = orientation === OrientationType.horizontal;
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
rebasedData,
{
stack,
percentageThreshold,
xAxisCol,
},
);
const rawSeries = extractSeries(rebasedData, {
fillNeighborValue: stack && !forecastEnabled ? 0 : undefined,
xAxis: xAxisCol,
removeNulls: seriesType === EchartsTimeseriesSeriesType.Scatter,
stack,
totalStackedValues,
isHorizontal,
});
const showValueIndexes = extractShowValueIndexes(rawSeries, {
stack,
});
const seriesContexts = extractForecastSeriesContexts(
Object.values(rawSeries).map(series => series.name as string),
);
const isAreaExpand = stack === AreaChartExtraControlsValue.Expand;
const xAxisDataType = dataTypes?.[xAxisCol];
const xAxisType = getAxisType(xAxisDataType);
const series: SeriesOption[] = [];
const formatter = getNumberFormatter(contributionMode ? ',.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;
}
});
});
}
const formatter = getNumberFormatter(
contributionMode || isAreaExpand ? ',.0%' : yAxisFormat,
);
rawSeries.forEach(entry => {
const lineStyle = isDerivedSeries(entry, chartProps.rawFormData)
@ -266,7 +262,7 @@ export default function transformProps(
let [min, max] = (yAxisBounds || []).map(parseYAxisBound);
// 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 (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 addXAxisLabelOffset = !!xAxisTitle;
@ -406,8 +405,8 @@ export default function transformProps(
dataZoom: {
yAxisIndex: false,
title: {
zoom: 'zoom area',
back: 'restore zoom',
zoom: t('zoom area'),
back: t('restore zoom'),
},
},
},
@ -433,6 +432,7 @@ export default function transformProps(
labelMap,
selectedValues,
setDataMask,
setControlValue,
width,
legendData,
};

View File

@ -52,7 +52,7 @@ import {
import { MarkLine1DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
import { extractForecastSeriesContext } from '../utils/forecast';
import { ForecastSeriesEnum, LegendOrientation } from '../types';
import { ForecastSeriesEnum, LegendOrientation, StackType } from '../types';
import { EchartsTimeseriesSeriesType } from './types';
import {
@ -62,7 +62,11 @@ import {
parseAnnotationOpacity,
} from '../utils/annotation';
import { currentSeries, getChartPadding } from '../utils/series';
import { OpacityEnum, TIMESERIES_CONSTANTS } from '../constants';
import {
AreaChartExtraControlsValue,
OpacityEnum,
TIMESERIES_CONSTANTS,
} from '../constants';
export function transformSeries(
series: SeriesOption,
@ -75,7 +79,7 @@ export function transformSeries(
markerSize?: number;
areaOpacity?: number;
seriesType?: EchartsTimeseriesSeriesType;
stack?: boolean;
stack?: StackType;
yAxisIndex?: number;
showValue?: boolean;
onlyTotal?: boolean;
@ -225,6 +229,7 @@ export function transformSeries(
const { value, dataIndex, seriesIndex, seriesName } = params;
const numericValue = isHorizontal ? value[0] : value[1];
const isSelectedLegend = currentSeries.legend === seriesName;
const isAreaExpand = stack === AreaChartExtraControlsValue.Expand;
if (!formatter) return numericValue;
if (!stack || isSelectedLegend) return formatter(numericValue);
if (!onlyTotal) {
@ -234,7 +239,7 @@ export function transformSeries(
return '';
}
if (seriesIndex === showValueIndexes[dataIndex]) {
return formatter(totalStackedValues[dataIndex]);
return formatter(isAreaExpand ? 1 : totalStackedValues[dataIndex]);
}
return '';
},

View File

@ -31,6 +31,7 @@ import {
EChartTransformedProps,
EchartsTitleFormData,
DEFAULT_TITLE_FORM_DATA,
StackType,
} from '../types';
export enum EchartsTimeseriesContributionType {
@ -72,7 +73,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
orderDesc: boolean;
rowLimit: number;
seriesType: EchartsTimeseriesSeriesType;
stack: boolean;
stack: StackType;
tooltipTimeFormat?: string;
truncateYAxis: boolean;
yAxisFormat?: string;
@ -86,6 +87,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
groupby: QueryFormColumn[];
showValue: boolean;
onlyTotal: boolean;
showExtraControls: boolean;
percentageThreshold: number;
orientation?: OrientationType;
} & EchartsLegendFormData &

View File

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

View File

@ -17,7 +17,8 @@
* 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';
// eslint-disable-next-line import/prefer-default-export
@ -37,6 +38,7 @@ export const TIMESERIES_CONSTANTS = {
dataZoomStart: 0,
dataZoomEnd: 100,
yAxisLabelTopOffset: 20,
extraControlsOffset: 22,
};
export const LABEL_POSITION: [LabelPositionEnum, string][] = [
@ -61,6 +63,20 @@ export enum OpacityEnum {
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 = {
[TimeGranularity.HOUR]: 3600 * 1000,
[TimeGranularity.DAY]: 3600 * 1000 * 24,

View File

@ -108,7 +108,7 @@ export const legendSection: ControlSetRow[] = [
[legendMarginControl],
];
const showValueControl: ControlSetItem = {
export const showValueControl: ControlSetItem = {
name: 'show_value',
config: {
type: 'CheckboxControl',
@ -119,7 +119,7 @@ const showValueControl: ControlSetItem = {
},
};
const stackControl: ControlSetItem = {
export const stackControl: ControlSetItem = {
name: 'stack',
config: {
type: 'CheckboxControl',
@ -130,7 +130,7 @@ const stackControl: ControlSetItem = {
},
};
const onlyTotalControl: ControlSetItem = {
export const onlyTotalControl: ControlSetItem = {
name: 'only_total',
config: {
type: 'CheckboxControl',

View File

@ -18,12 +18,14 @@
*/
import {
DataRecordValue,
HandlerFunction,
QueryFormColumn,
SetDataMaskHook,
} from '@superset-ui/core';
import { EChartsCoreOption, ECharts } from 'echarts';
import { TooltipMarker } from 'echarts/types/src/util/format';
import { OptionName } from 'echarts/types/src/util/types';
import { AreaChartExtraControlsValue } from './constants';
export type EchartsStylesProps = {
height: number;
@ -115,6 +117,7 @@ export interface EChartTransformedProps<F> {
echartOptions: EChartsCoreOption;
emitFilter: boolean;
setDataMask: SetDataMaskHook;
setControlValue?: HandlerFunction;
labelMap: Record<string, DataRecordValue[]>;
groupby: QueryFormColumn[];
selectedValues: Record<number, string>;
@ -137,4 +140,6 @@ export const DEFAULT_TITLE_FORM_DATA: EchartsTitleFormData = {
yAxisTitlePosition: 'Top',
};
export type StackType = boolean | null | Partial<AreaChartExtraControlsValue>;
export * from './Timeseries/types';

View File

@ -28,20 +28,79 @@ import {
TimeFormatter,
} from '@superset-ui/core';
import { format, LegendComponentOption, SeriesOption } from 'echarts';
import { NULL_STRING, TIMESERIES_CONSTANTS } from '../constants';
import { LegendOrientation, LegendType } from '../types';
import {
AreaChartExtraControlsValue,
NULL_STRING,
TIMESERIES_CONSTANTS,
} from '../constants';
import { LegendOrientation, LegendType, StackType } from '../types';
import { defaultLegendPadding } from '../defaults';
function isDefined<T>(value: T | undefined | null): boolean {
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(
data: DataRecord[],
opts: {
fillNeighborValue?: number;
xAxis?: string;
removeNulls?: boolean;
stack?: StackType;
totalStackedValues?: number[];
isHorizontal?: boolean;
} = {},
): SeriesOption[] {
@ -49,6 +108,8 @@ export function extractSeries(
fillNeighborValue,
xAxis = DTTM_ALIAS,
removeNulls = false,
stack = false,
totalStackedValues = [],
isHorizontal = false,
} = opts;
if (data.length === 0) return [];
@ -66,14 +127,20 @@ export function extractSeries(
.map((row, idx) => {
const isNextToDefinedValue =
isDefined(rows[idx - 1]?.[key]) || isDefined(rows[idx + 1]?.[key]);
return [
row[xAxis],
const isFillNeighborValue =
!isDefined(row[key]) &&
isNextToDefinedValue &&
fillNeighborValue !== undefined
? fillNeighborValue
: row[key],
];
fillNeighborValue !== undefined;
let value: DataRecordValue | undefined = 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))
.map(obs => (isHorizontal ? [obs[1], obs[0]] : obs)),

View File

@ -113,6 +113,7 @@ class ChartRenderer extends React.Component {
nextProps.labelColors !== this.props.labelColors ||
nextProps.sharedLabelColors !== this.props.sharedLabelColors ||
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
nextProps.formData.stack !== this.props.formData.stack ||
nextProps.cacheBusterProp !== this.props.cacheBusterProp
);
}

View File

@ -51,6 +51,7 @@ const propTypes = {
updateSliceName: PropTypes.func.isRequired,
isComponentVisible: PropTypes.bool,
handleToggleFullSize: PropTypes.func.isRequired,
setControlValue: PropTypes.func,
// from redux
chart: chartPropShape.isRequired,
@ -348,6 +349,7 @@ export default class Chart extends React.Component {
filterState,
handleToggleFullSize,
isFullSize,
setControlValue,
filterboxMigrationState,
postTransformProps,
datasetsStatus,
@ -475,6 +477,7 @@ export default class Chart extends React.Component {
timeout={timeout}
triggerQuery={chart.triggerQuery}
vizType={slice.viz_type}
setControlValue={setControlValue}
isDeactivatedViz={isDeactivatedViz}
filterboxMigrationState={filterboxMigrationState}
postTransformProps={postTransformProps}

View File

@ -191,12 +191,14 @@ class ChartHolder extends React.Component {
outlinedComponentId: null,
outlinedColumnName: null,
directPathLastUpdated: 0,
extraControls: {},
};
this.handleChangeFocus = this.handleChangeFocus.bind(this);
this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
this.handleUpdateSliceName = this.handleUpdateSliceName.bind(this);
this.handleToggleFullSize = this.handleToggleFullSize.bind(this);
this.handleExtraControl = this.handleExtraControl.bind(this);
this.handlePostTransformProps = this.handlePostTransformProps.bind(this);
}
@ -252,13 +254,22 @@ class ChartHolder extends React.Component {
setFullSizeChartId(isFullSize ? null : chartId);
}
handleExtraControl(name, value) {
this.setState(prevState => ({
extraControls: {
...prevState.extraControls,
[name]: value,
},
}));
}
handlePostTransformProps(props) {
this.props.postAddSliceFromDashboard();
return props;
}
render() {
const { isFocused } = this.state;
const { isFocused, extraControls } = this.state;
const {
component,
parentComponent,
@ -374,6 +385,8 @@ class ChartHolder extends React.Component {
isComponentVisible={isComponentVisible}
handleToggleFullSize={this.handleToggleFullSize}
isFullSize={isFullSize}
setControlValue={this.handleExtraControl}
extraControls={extraControls}
postTransformProps={this.handlePostTransformProps}
/>
{editMode && (

View File

@ -55,7 +55,7 @@ function mapStateToProps(
},
ownProps,
) {
const { id } = ownProps;
const { id, extraControls, setControlValue } = ownProps;
const chart = chartQueries[id] || EMPTY_OBJECT;
const datasource =
(chart && chart.form_data && datasources[chart.form_data.datasource]) ||
@ -76,6 +76,7 @@ function mapStateToProps(
sliceId: id,
nativeFilters,
dataMask,
extraControls,
labelColors,
sharedLabelColors,
});
@ -100,6 +101,7 @@ function mapStateToProps(
ownState: dataMask[id]?.ownState,
filterState: dataMask[id]?.filterState,
maxRows: common.conf.SQL_MAX_ROW,
setControlValue,
filterboxMigrationState: dashboardState.filterboxMigrationState,
datasetsStatus,
};

View File

@ -45,6 +45,7 @@ export interface GetFormDataWithExtraFiltersArguments {
sliceId: number;
dataMask: DataMaskStateWithId;
nativeFilters: NativeFiltersState;
extraControls: Record<string, string | boolean | null>;
labelColors?: Record<string, string>;
sharedLabelColors?: Record<string, string>;
}
@ -63,6 +64,7 @@ export default function getFormDataWithExtraFilters({
sliceId,
layout,
dataMask,
extraControls,
labelColors,
sharedLabelColors,
}: GetFormDataWithExtraFiltersArguments) {
@ -85,6 +87,9 @@ export default function getFormDataWithExtraFilters({
!!cachedFormData &&
areObjectsEqual(cachedFormData?.dataMask, dataMask, {
ignoreUndefined: true,
}) &&
areObjectsEqual(cachedFormData?.extraControls, extraControls, {
ignoreUndefined: true,
})
) {
return cachedFormData;
@ -117,10 +122,11 @@ export default function getFormDataWithExtraFilters({
...(colorScheme && { color_scheme: colorScheme }),
extra_filters: getEffectiveExtraFilters(filters),
...extraData,
...extraControls,
};
cachedFiltersByChart[sliceId] = filters;
cachedFormdataByChart[sliceId] = { ...formData, dataMask };
cachedFormdataByChart[sliceId] = { ...formData, dataMask, extraControls };
return formData;
}

View File

@ -71,6 +71,9 @@ describe('getFormDataWithExtraFilters', () => {
},
},
layout: {},
extraControls: {
stack: 'Stacked',
},
};
it('should include filters from the passed filters', () => {
@ -87,4 +90,9 @@ describe('getFormDataWithExtraFilters', () => {
val: ['pink', 'purple'],
});
});
it('should compose extra control', () => {
const result = getFormDataWithExtraFilters(mockArgs);
expect(result.stack).toEqual('Stacked');
});
});