From 6c58fb9fb5cba4ed3f5d40de87879c8b668262b6 Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Sun, 29 Aug 2021 19:48:02 +0800 Subject: [PATCH 1/5] feat(echarts): [feature-parity] support extra control --- .../util/getFormDataWithExtraFilters_spec.ts | 8 ++++++++ .../components/gridComponents/Chart.jsx | 6 ++++++ .../components/gridComponents/ChartHolder.jsx | 18 +++++++++++++++++- .../src/dashboard/containers/Chart.jsx | 5 ++++- .../util/charts/getFormDataWithExtraFilters.ts | 3 +++ 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts b/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts index ddcb3cf55f48..b7b577be865a 100644 --- a/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts +++ b/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts @@ -85,6 +85,9 @@ describe('getFormDataWithExtraFilters', () => { layout: (dashboardLayout.present as unknown) as { [key: string]: LayoutItem; }, + extraControls: { + stack: 'Stacked', + }, }; it('should include filters from the passed filters', () => { @@ -101,4 +104,9 @@ describe('getFormDataWithExtraFilters', () => { val: ['pink', 'purple'], }); }); + + it('should compose extra control', () => { + const result = getFormDataWithExtraFilters(mockArgs); + expect(result.stack).toEqual('Stacked'); + }); }); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx index 60cbab729530..7f63aefbb2a5 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx @@ -48,6 +48,8 @@ const propTypes = { updateSliceName: PropTypes.func.isRequired, isComponentVisible: PropTypes.bool, handleToggleFullSize: PropTypes.func.isRequired, + setControlValue: PropTypes.func, + triggerRender: PropTypes.bool, // from redux chart: chartPropShape.isRequired, @@ -288,6 +290,8 @@ export default class Chart extends React.Component { filterState, handleToggleFullSize, isFullSize, + setControlValue, + triggerRender, } = this.props; const { width } = this.state; @@ -402,6 +406,8 @@ export default class Chart extends React.Component { timeout={timeout} triggerQuery={chart.triggerQuery} vizType={slice.viz_type} + setControlValue={setControlValue} + triggerRender={triggerRender} /> diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx index 87f1cbc78de4..e5c28c0bfb09 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx @@ -191,12 +191,15 @@ class ChartHolder extends React.Component { outlinedComponentId: null, outlinedColumnName: null, directPathLastUpdated: 0, + extraControls: {}, + triggerRender: false, }; 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); } componentDidMount() { @@ -251,8 +254,18 @@ class ChartHolder extends React.Component { setFullSizeChartId(isFullSize ? null : chartId); } + handleExtraControl(name, value) { + this.setState(prevState => ({ + extraControls: { + ...prevState.extraControls, + [name]: value, + }, + triggerRender: true, + })); + } + render() { - const { isFocused } = this.state; + const { isFocused, extraControls, triggerRender } = this.state; const { component, parentComponent, @@ -358,6 +371,9 @@ class ChartHolder extends React.Component { isComponentVisible={isComponentVisible} handleToggleFullSize={this.handleToggleFullSize} isFullSize={isFullSize} + setControlValue={this.handleExtraControl} + extraControls={extraControls} + triggerRender={triggerRender} /> {editMode && ( diff --git a/superset-frontend/src/dashboard/containers/Chart.jsx b/superset-frontend/src/dashboard/containers/Chart.jsx index c629209d4254..0f268f758ef9 100644 --- a/superset-frontend/src/dashboard/containers/Chart.jsx +++ b/superset-frontend/src/dashboard/containers/Chart.jsx @@ -53,7 +53,7 @@ function mapStateToProps( }, ownProps, ) { - const { id } = ownProps; + const { id, extraControls, setControlValue, triggerRender } = ownProps; const chart = chartQueries[id] || EMPTY_OBJECT; const datasource = (chart && chart.form_data && datasources[chart.form_data.datasource]) || @@ -73,6 +73,7 @@ function mapStateToProps( sliceId: id, nativeFilters, dataMask, + extraControls, }); formData.dashboardId = dashboardInfo.id; @@ -93,6 +94,8 @@ function mapStateToProps( ownState: dataMask[id]?.ownState, filterState: dataMask[id]?.filterState, maxRows: common.conf.SQL_MAX_ROW, + setControlValue, + triggerRender, }; } diff --git a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts index 2587588eac0c..e01ad5c462a5 100644 --- a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts +++ b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts @@ -46,6 +46,7 @@ export interface GetFormDataWithExtraFiltersArguments { sliceId: number; dataMask: DataMaskStateWithId; nativeFilters: NativeFiltersState; + extraControls: Record; } // this function merge chart's formData with dashboard filters value, @@ -62,6 +63,7 @@ export default function getFormDataWithExtraFilters({ sliceId, layout, dataMask, + extraControls, }: GetFormDataWithExtraFiltersArguments) { // Propagate color mapping to chart const scale = CategoricalColorNamespace.getScale(colorScheme, colorNamespace); @@ -108,6 +110,7 @@ export default function getFormDataWithExtraFilters({ label_colors: labelColors, extra_filters: getEffectiveExtraFilters(filters), ...extraData, + ...extraControls, }; cachedFiltersByChart[sliceId] = filters; cachedFormdataByChart[sliceId] = formData; From ae8a35acb4d6aeddb297c358f69255336eae535d Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Wed, 1 Dec 2021 22:16:59 +0800 Subject: [PATCH 2/5] add extra control for plugin --- .../components/RadioButtonControl.tsx | 4 +- .../src/MixedTimeseries/transformProps.ts | 4 +- .../src/MixedTimeseries/types.ts | 5 +- .../src/Timeseries/Area/controlPanel.tsx | 39 ++++++++++- .../src/Timeseries/EchartsTimeseries.tsx | 67 ++++++++++++++++--- .../src/Timeseries/transformProps.ts | 43 ++++++------ .../src/Timeseries/transformers.ts | 2 +- .../src/Timeseries/types.ts | 4 +- .../src/Timeseries/useExtraControl.ts | 66 ++++++++++++++++++ .../plugin-chart-echarts/src/constants.ts | 16 +++++ .../plugin-chart-echarts/src/controls.tsx | 6 +- .../plugins/plugin-chart-echarts/src/types.ts | 2 + .../plugin-chart-echarts/src/utils/series.ts | 35 +++++++--- 13 files changed, 241 insertions(+), 52 deletions(-) create mode 100644 superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/useExtraControl.ts diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx index e9f6a6f9bc4d..afa7483fb9fd 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx @@ -31,7 +31,7 @@ export interface RadioButtonControlProps { description?: string; options: RadioButtonOption[]; hovered?: boolean; - value?: string; + value?: JsonValue; onChange: (opt: RadioButtonOption[0]) => void; } @@ -41,7 +41,7 @@ export default function RadioButtonControl({ onChange, ...props }: RadioButtonControlProps) { - const currentValue = initialValue || options[0][0]; + const currentValue = initialValue === null ? options[0][0] : initialValue; const theme = useTheme(); return (
| null; + stackB: boolean | Partial | null; yAxisIndex?: number; yAxisIndexB?: number; groupby: QueryFormColumn[]; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx index 44d1e80d196b..f458a935a642 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx @@ -34,9 +34,11 @@ import { } from '../types'; import { legendSection, + onlyTotalControl, + showValueControl, richTooltipSection, - showValueSection, } from '../../controls'; +import { AreaChartExtraControlsValue } from '../../constants'; const { contributionMode, @@ -140,7 +142,40 @@ const config: ControlPanelConfig = { }, }, ], - ...showValueSection, + [showValueControl], + [ + { + name: 'stack', + config: { + type: 'SelectControl', + label: t('Stacked Style'), + renderTrigger: true, + choices: [ + [AreaChartExtraControlsValue.Stacked, 'stack'], + [AreaChartExtraControlsValue.Expanded, 'expand'], + ], + default: 'stack', + description: t('Stack series on top of each other'), + }, + }, + ], + [onlyTotalControl], + [ + { + name: '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', diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx index 2bf103e2bd9e..2d73e722137c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx @@ -20,12 +20,53 @@ import React, { useCallback, useRef } from 'react'; import { ViewRootGroup } from 'echarts/types/src/util/types'; import GlobalModel from 'echarts/types/src/model/Global'; import ComponentModel from 'echarts/types/src/model/Component'; +import { sharedControlComponents } from '@superset-ui/chart-controls'; +import { HandlerFunction, styled } from '@superset-ui/core'; import { EchartsHandler, EventHandlers } from '../types'; import Echart from '../components/Echart'; -import { TimeseriesChartTransformedProps } from './types'; +import { + EchartsTimeseriesFormData, + TimeseriesChartTransformedProps, +} from './types'; import { currentSeries } from '../utils/series'; +import useExtraControl from './useExtraControl'; + +const { RadioButtonControl } = sharedControlComponents; const TIMER_DURATION = 300; + +const ExtraControlsWrapper = styled.div` + text-align: center; +`; + +function ExtraControls({ + formData, + setControlValue, +}: { + formData: EchartsTimeseriesFormData; + setControlValue?: HandlerFunction; +}) { + const { extraControlsOptions, extraControlsHandler, extraValue } = + useExtraControl({ + formData, + setControlValue, + }); + + if (formData.extraControls) { + return null; + } + + return ( + + + + ); +} + // @ts-ignore export default function EchartsTimeseries({ formData, @@ -36,6 +77,7 @@ export default function EchartsTimeseries({ labelMap, selectedValues, setDataMask, + setControlValue, legendData = [], }: TimeseriesChartTransformedProps) { const { emitFilter, stack } = formData; @@ -120,7 +162,7 @@ export default function EchartsTimeseries({ }, }); }, - [groupby, labelMap, setDataMask], + [groupby, labelMap, setDataMask, emitFilter], ); const eventHandlers: EventHandlers = { @@ -195,14 +237,17 @@ export default function EchartsTimeseries({ }; return ( - + <> + + + ); } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index b63feb35737c..55408ea2cfb5 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -27,6 +27,7 @@ import { isTimeseriesAnnotationLayer, TimeseriesChartDataResponseResult, DataRecordValue, + t, } from '@superset-ui/core'; import { EChartsCoreOption, SeriesOption } from 'echarts'; import { @@ -42,6 +43,7 @@ import { extractTimeseriesSeries, getLegendProps, currentSeries, + extractTotalValues, } from '../utils/series'; import { extractAnnotationLabels } from '../utils/annotation'; import { @@ -62,7 +64,10 @@ import { transformSeries, transformTimeseriesAnnotation, } from './transformers'; -import { TIMESERIES_CONSTANTS } from '../constants'; +import { + AreaChartExtraControlsValue, + TIMESERIES_CONSTANTS, +} from '../constants'; export default function transformProps( chartProps: EchartsTimeseriesChartProps, @@ -119,29 +124,23 @@ export default function transformProps( }: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData }; const colorScale = CategoricalColorNamespace.getScale(colorScheme as string); const rebasedData = rebaseTimeseriesDatum(data, verboseMap); + const totalValues = extractTotalValues(rebasedData); + const isAreaExpanded = stack === AreaChartExtraControlsValue.Expanded; const rawSeries = extractTimeseriesSeries(rebasedData, { fillNeighborValue: stack && !forecastEnabled ? 0 : undefined, + isExpended: isAreaExpanded, + totalValues, }); const seriesContexts = extractForecastSeriesContexts( Object.values(rawSeries).map(series => series.name as string), ); const series: SeriesOption[] = []; - const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat); + const formatter = getNumberFormatter( + contributionMode || isAreaExpanded ? ',.0%' : yAxisFormat, + ); - const totalStackedValues: number[] = []; const showValueIndexes: number[] = []; - rebasedData.forEach(data => { - const values = Object.keys(data).reduce((prev, curr) => { - if (curr === '__timestamp') { - return prev; - } - const value = data[curr] || 0; - return prev + (value as number); - }, 0); - totalStackedValues.push(values); - }); - if (stack) { rawSeries.forEach((entry, seriesIndex) => { const { data = [] } = entry; @@ -162,11 +161,11 @@ export default function transformProps( markerSize, areaOpacity: opacity, seriesType, - stack, + stack: Boolean(stack), formatter, showValue, onlyTotal, - totalStackedValues, + totalStackedValues: isAreaExpanded ? totalValues.fill(1) : totalValues, showValueIndexes, richTooltip, }); @@ -218,7 +217,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' || isAreaExpanded) && stack) { if (min === undefined) min = 0; if (max === undefined) max = 1; } @@ -237,7 +236,10 @@ export default function transformProps( {}, ); - const { setDataMask = () => {} } = hooks; + const { + setDataMask = () => {}, + setControlValue = (...args: unknown[]) => {}, + } = hooks; const addYAxisLabelOffset = !!yAxisTitle; const addXAxisLabelOffset = !!xAxisTitle; @@ -339,8 +341,8 @@ export default function transformProps( dataZoom: { yAxisIndex: false, title: { - zoom: 'zoom area', - back: 'restore zoom', + zoom: t('zoom area'), + back: t('restore zoom'), }, }, }, @@ -366,6 +368,7 @@ export default function transformProps( labelMap, selectedValues, setDataMask, + setControlValue, width, legendData, }; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts index 3ca56006adce..544dbcb3684e 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -212,7 +212,7 @@ export function transformSeries( const isSelectedLegend = currentSeries.legend === seriesName; if (!formatter) return numericValue; if (!stack || !onlyTotal || isSelectedLegend) { - return formatter(numericValue); + return numericValue ? formatter(numericValue) : ''; } if (seriesIndex === showValueIndexes[dataIndex]) { return formatter(totalStackedValues[dataIndex]); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts index ac634239229d..611bbd82998c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts @@ -32,6 +32,7 @@ import { EchartsTitleFormData, DEFAULT_TITLE_FORM_DATA, } from '../types'; +import { AreaChartExtraControlsValue } from '../constants'; export enum EchartsTimeseriesContributionType { Row = 'row', @@ -67,7 +68,7 @@ export type EchartsTimeseriesFormData = QueryFormData & { orderDesc: boolean; rowLimit: number; seriesType: EchartsTimeseriesSeriesType; - stack: boolean; + stack: boolean | null | Partial; tooltipTimeFormat?: string; truncateYAxis: boolean; yAxisFormat?: string; @@ -81,6 +82,7 @@ export type EchartsTimeseriesFormData = QueryFormData & { groupby: QueryFormColumn[]; showValue: boolean; onlyTotal: boolean; + extraControls: boolean; } & EchartsLegendFormData & EchartsTitleFormData; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/useExtraControl.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/useExtraControl.ts new file mode 100644 index 000000000000..dccbf9a38a42 --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/useExtraControl.ts @@ -0,0 +1,66 @@ +/** + * 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 { useState, useEffect, useMemo, useCallback } from 'react'; +import { HandlerFunction, JsonValue } from '@superset-ui/core'; +import { RadioButtonOption } from '@superset-ui/chart-controls'; +import { EchartsTimeseriesFormData } from './types'; +import { AreaChartExtraControlsOptions } from '../constants'; + +export default function ({ + formData, + setControlValue, +}: { + formData: EchartsTimeseriesFormData; + setControlValue?: HandlerFunction; +}) { + const { stack, area } = formData; + const [extraValue, setExtraValue] = useState( + stack ?? undefined, + ); + + useEffect(() => { + setExtraValue(stack ?? undefined); + }, [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 ?? undefined); + } + } + }, + [area, setControlValue], + ); + + return { + extraControlsOptions, + extraControlsHandler, + extraValue, + }; +} diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index b2f3a28a6656..f4b8d3e6867e 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -17,6 +17,8 @@ * under the License. */ +import { JsonValue, t } from '@superset-ui/core'; +import { ReactNode } from 'react'; import { LabelPositionEnum } from './types'; // eslint-disable-next-line import/prefer-default-export @@ -36,6 +38,7 @@ export const TIMESERIES_CONSTANTS = { dataZoomStart: 0, dataZoomEnd: 100, yAxisLabelTopOffset: 20, + extraControlsOffset: 22, }; export const LABEL_POSITION: [LabelPositionEnum, string][] = [ @@ -59,3 +62,16 @@ export enum OpacityEnum { SemiTransparent = 0.3, NonTransparent = 1, } + +export enum AreaChartExtraControlsValue { + Stacked = 'Stacked', + Expanded = 'Expanded', +} + +export const AreaChartExtraControlsOptions: [ + JsonValue, + Exclude, +][] = [ + [AreaChartExtraControlsValue.Stacked, t('Stacked')], + [AreaChartExtraControlsValue.Expanded, t('Expanded')], +]; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx index 76c7c42186ff..23f7b5cbf49c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx @@ -99,7 +99,7 @@ export const legendSection = [ [legendMarginControl], ]; -const showValueControl = { +export const showValueControl = { name: 'show_value', config: { type: 'CheckboxControl', @@ -110,7 +110,7 @@ const showValueControl = { }, }; -const stackControl = { +export const stackControl = { name: 'stack', config: { type: 'CheckboxControl', @@ -121,7 +121,7 @@ const stackControl = { }, }; -const onlyTotalControl = { +export const onlyTotalControl = { name: 'only_total', config: { type: 'CheckboxControl', diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts index a66c8a34847d..e915259391ca 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts @@ -18,6 +18,7 @@ */ import { DataRecordValue, + HandlerFunction, QueryFormColumn, SetDataMaskHook, } from '@superset-ui/core'; @@ -115,6 +116,7 @@ export interface EChartTransformedProps { echartOptions: EChartsCoreOption; emitFilter: boolean; setDataMask: SetDataMaskHook; + setControlValue?: HandlerFunction; labelMap: Record; groupby: QueryFormColumn[]; selectedValues: Record; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts index 324ab06d1088..14f4236bdafc 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -36,11 +36,26 @@ function isDefined(value: T | undefined | null): boolean { return value !== undefined && value !== null; } +export function extractTotalValues(data: TimeseriesDataRecord[]) { + return data.map(datum => + Object.keys(datum) + .filter(key => key !== '__timestamp') + .reduce((sum, key) => { + const value = datum[key] || 0; + return sum + (value as number); + }, 0), + ); +} + export function extractTimeseriesSeries( data: TimeseriesDataRecord[], - opts: { fillNeighborValue?: number } = {}, + opts: { + fillNeighborValue?: number; + isExpended?: boolean; + totalValues?: number[]; + } = {}, ): SeriesOption[] { - const { fillNeighborValue } = opts; + const { fillNeighborValue, isExpended, totalValues = [] } = opts; if (data.length === 0) return []; const rows: TimeseriesDataRecord[] = data.map(datum => ({ ...datum, @@ -58,14 +73,18 @@ export function extractTimeseriesSeries( data: rows.map((row, idx) => { const isNextToDefinedValue = isDefined(rows[idx - 1]?.[key]) || isDefined(rows[idx + 1]?.[key]); - return [ - row.__timestamp, + 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 (isExpended) { + value = ((value || 0) as number) / totalValues[idx]; + } + return [row.__timestamp, value]; }), })); } From c0a7e7dc553d326172326422ed80e5a943274301 Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Fri, 6 May 2022 22:00:16 +0800 Subject: [PATCH 3/5] refactor: extract ExtraControl --- .../src/Timeseries/Area/controlPanel.tsx | 2 +- .../src/Timeseries/EchartsTimeseries.tsx | 43 +------------ .../ExtraControls.tsx} | 60 ++++++++++++++++--- 3 files changed, 56 insertions(+), 49 deletions(-) rename superset-frontend/plugins/plugin-chart-echarts/src/{Timeseries/useExtraControl.ts => components/ExtraControls.tsx} (60%) diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx index 75bbc45ecc02..fa02e9415d8a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx @@ -154,7 +154,7 @@ const config: ControlPanelConfig = { [onlyTotalControl], [ { - name: 'extra_controls', + name: 'show_extra_controls', config: { type: 'CheckboxControl', label: t('Extra Controls'), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx index 2d73e722137c..a9947e0d5520 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx @@ -20,53 +20,14 @@ import React, { useCallback, useRef } from 'react'; import { ViewRootGroup } from 'echarts/types/src/util/types'; import GlobalModel from 'echarts/types/src/model/Global'; import ComponentModel from 'echarts/types/src/model/Component'; -import { sharedControlComponents } from '@superset-ui/chart-controls'; -import { HandlerFunction, styled } from '@superset-ui/core'; import { EchartsHandler, EventHandlers } from '../types'; import Echart from '../components/Echart'; -import { - EchartsTimeseriesFormData, - TimeseriesChartTransformedProps, -} from './types'; +import { TimeseriesChartTransformedProps } from './types'; import { currentSeries } from '../utils/series'; -import useExtraControl from './useExtraControl'; - -const { RadioButtonControl } = sharedControlComponents; +import { ExtraControls } from '../components/ExtraControls'; const TIMER_DURATION = 300; -const ExtraControlsWrapper = styled.div` - text-align: center; -`; - -function ExtraControls({ - formData, - setControlValue, -}: { - formData: EchartsTimeseriesFormData; - setControlValue?: HandlerFunction; -}) { - const { extraControlsOptions, extraControlsHandler, extraValue } = - useExtraControl({ - formData, - setControlValue, - }); - - if (formData.extraControls) { - return null; - } - - return ( - - - - ); -} - // @ts-ignore export default function EchartsTimeseries({ formData, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/useExtraControl.ts b/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx similarity index 60% rename from superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/useExtraControl.ts rename to superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx index dccbf9a38a42..836c29bea875 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/useExtraControl.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx @@ -16,18 +16,30 @@ * specific language governing permissions and limitations * under the License. */ - -import { useState, useEffect, useMemo, useCallback } from 'react'; -import { HandlerFunction, JsonValue } from '@superset-ui/core'; -import { RadioButtonOption } from '@superset-ui/chart-controls'; -import { EchartsTimeseriesFormData } from './types'; +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'; -export default function ({ +const { RadioButtonControl } = sharedControlComponents; + +const ExtraControlsWrapper = styled.div` + text-align: center; +`; + +export function useExtraControl< + F extends { + stack: any; + area: boolean; + }, +>({ formData, setControlValue, }: { - formData: EchartsTimeseriesFormData; + formData: F; setControlValue?: HandlerFunction; }) { const { stack, area } = formData; @@ -64,3 +76,37 @@ export default function ({ extraValue, }; } + +export function ExtraControls< + F extends { + stack: any; + area: boolean; + showExtraControls: boolean; + }, +>({ + formData, + setControlValue, +}: { + formData: F; + setControlValue?: HandlerFunction; +}) { + const { extraControlsOptions, extraControlsHandler, extraValue } = + useExtraControl({ + formData, + setControlValue, + }); + + if (!formData.showExtraControls) { + return null; + } + + return ( + + + + ); +} From 04f9dc44e8c7904cd7d366827ceb6cff0261e5e4 Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Sat, 7 May 2022 18:40:08 +0800 Subject: [PATCH 4/5] fix: lint --- .../plugins/plugin-chart-echarts/src/Timeseries/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts index 212923572c49..bcf465a0685e 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts @@ -82,7 +82,7 @@ export type EchartsTimeseriesFormData = QueryFormData & { groupby: QueryFormColumn[]; showValue: boolean; onlyTotal: boolean; - extraControls: boolean; + showExtraControls: boolean; percentageThreshold: number; } & EchartsLegendFormData & EchartsTitleFormData; From c665ce43fe3f6501196fcd3da274ec046cc27899 Mon Sep 17 00:00:00 2001 From: stephenLYZ <750188453@qq.com> Date: Sun, 8 May 2022 20:03:03 +0800 Subject: [PATCH 5/5] fix some problems --- .../components/RadioButtonControl.tsx | 2 +- .../src/MixedTimeseries/types.ts | 6 +- .../src/Timeseries/Area/controlPanel.tsx | 9 +- .../src/Timeseries/transformProps.ts | 33 ++++--- .../src/Timeseries/transformers.ts | 31 ++++--- .../src/Timeseries/types.ts | 4 +- .../src/components/ExtraControls.tsx | 4 +- .../plugin-chart-echarts/src/constants.ts | 9 +- .../plugins/plugin-chart-echarts/src/types.ts | 3 + .../plugin-chart-echarts/src/utils/series.ts | 90 +++++++++++-------- .../src/components/Chart/ChartRenderer.jsx | 1 + .../components/gridComponents/Chart.jsx | 3 - .../components/gridComponents/ChartHolder.jsx | 5 +- .../src/dashboard/containers/Chart.jsx | 3 +- .../charts/getFormDataWithExtraFilters.ts | 5 +- 15 files changed, 115 insertions(+), 93 deletions(-) diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx index daab457eae87..a877ae724782 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/RadioButtonControl.tsx @@ -41,7 +41,7 @@ export default function RadioButtonControl({ onChange, ...props }: RadioButtonControlProps) { - const currentValue = initialValue === null ? options[0][0] : initialValue; + const currentValue = initialValue || options[0][0]; const theme = useTheme(); return (
| null; - stackB: boolean | Partial | null; + stack: StackType; + stackB: StackType; yAxisIndex?: number; yAxisIndexB?: number; groupby: QueryFormColumn[]; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx index fa02e9415d8a..e43dda890386 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx @@ -39,7 +39,7 @@ import { richTooltipSection, xAxisControl, } from '../../controls'; -import { AreaChartExtraControlsValue } from '../../constants'; +import { AreaChartExtraControlsOptions } from '../../constants'; const { contributionMode, @@ -142,11 +142,8 @@ const config: ControlPanelConfig = { type: 'SelectControl', label: t('Stacked Style'), renderTrigger: true, - choices: [ - [AreaChartExtraControlsValue.Stacked, 'stack'], - [AreaChartExtraControlsValue.Expanded, 'expand'], - ], - default: 'stack', + choices: AreaChartExtraControlsOptions, + default: null, description: t('Stack series on top of each other'), }, }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 5bf6be7fbb80..64d89be20630 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -48,7 +48,8 @@ import { extractSeries, getColtypesMapping, getLegendProps, - extractSeriesLabelValues, + extractDataTotalValues, + extractShowValueIndexes, } from '../utils/series'; import { extractAnnotationLabels } from '../utils/annotation'; import { @@ -146,20 +147,28 @@ export default function transformProps( const colorScale = CategoricalColorNamespace.getScale(colorScheme as string); const rebasedData = rebaseForecastDatum(data, verboseMap); const xAxisCol = verboseMap[xAxisOrig] || xAxisOrig || DTTM_ALIAS; + 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, + }); + const showValueIndexes = extractShowValueIndexes(rawSeries, { + stack, }); const seriesContexts = extractForecastSeriesContexts( Object.values(rawSeries).map(series => series.name as string), ); - const isAreaExpanded = stack === AreaChartExtraControlsValue.Expanded; - const seriesLabelValues = extractSeriesLabelValues(rebasedData, rawSeries, { - stack, - percentageThreshold, - xAxisCol, - }); + const isAreaExpand = stack === AreaChartExtraControlsValue.Expand; const xAxisDataType = dataTypes?.[xAxisCol]; let xAxisType: 'time' | 'value' | 'category'; switch (xAxisDataType) { @@ -175,7 +184,7 @@ export default function transformProps( } const series: SeriesOption[] = []; const formatter = getNumberFormatter( - contributionMode || isAreaExpanded ? ',.0%' : yAxisFormat, + contributionMode || isAreaExpand ? ',.0%' : yAxisFormat, ); rawSeries.forEach(entry => { @@ -187,11 +196,13 @@ export default function transformProps( markerSize, areaOpacity: opacity, seriesType, - stack: Boolean(stack), + stack, formatter, showValue, onlyTotal, - seriesLabelValues, + totalStackedValues, + showValueIndexes, + thresholdValues, richTooltip, sliceId, }); @@ -254,7 +265,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' || isAreaExpanded) && stack) { + if ((contributionMode === 'row' || isAreaExpand) && stack) { if (min === undefined) min = 0; if (max === undefined) max = 1; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts index 391050000c9c..4720e39c0095 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -51,7 +51,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 { @@ -60,12 +60,12 @@ import { formatAnnotationLabel, parseAnnotationOpacity, } from '../utils/annotation'; +import { currentSeries, getChartPadding } from '../utils/series'; import { - currentSeries, - getChartPadding, - SeriesLabelValues, -} from '../utils/series'; -import { OpacityEnum, TIMESERIES_CONSTANTS } from '../constants'; + AreaChartExtraControlsValue, + OpacityEnum, + TIMESERIES_CONSTANTS, +} from '../constants'; export function transformSeries( series: SeriesOption, @@ -78,12 +78,14 @@ export function transformSeries( markerSize?: number; areaOpacity?: number; seriesType?: EchartsTimeseriesSeriesType; - stack?: boolean; + stack?: StackType; yAxisIndex?: number; showValue?: boolean; onlyTotal?: boolean; formatter?: NumberFormatter; - seriesLabelValues?: SeriesLabelValues; + totalStackedValues?: number[]; + showValueIndexes?: number[]; + thresholdValues?: number[]; richTooltip?: boolean; seriesKey?: OptionName; sliceId?: number; @@ -103,17 +105,13 @@ export function transformSeries( showValue, onlyTotal, formatter, - seriesLabelValues = { - totalStackedValues: [], - showValueIndexes: [], - thresholdValues: [], - }, + totalStackedValues = [], + showValueIndexes = [], + thresholdValues = [], richTooltip, seriesKey, sliceId, } = opts; - const { totalStackedValues, showValueIndexes, thresholdValues } = - seriesLabelValues; const contexts = seriesContexts[name || ''] || []; const hasForecast = contexts.includes(ForecastSeriesEnum.ForecastTrend) || @@ -231,6 +229,7 @@ export function transformSeries( seriesName, } = params; const isSelectedLegend = currentSeries.legend === seriesName; + const isAreaExpand = stack === AreaChartExtraControlsValue.Expand; if (!formatter) return numericValue; if (!stack || isSelectedLegend) return formatter(numericValue); if (!onlyTotal) { @@ -240,7 +239,7 @@ export function transformSeries( return ''; } if (seriesIndex === showValueIndexes[dataIndex]) { - return formatter(totalStackedValues[dataIndex]); + return formatter(isAreaExpand ? 1 : totalStackedValues[dataIndex]); } return ''; }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts index bcf465a0685e..351ff387b215 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts @@ -31,8 +31,8 @@ import { EChartTransformedProps, EchartsTitleFormData, DEFAULT_TITLE_FORM_DATA, + StackType, } from '../types'; -import { AreaChartExtraControlsValue } from '../constants'; export enum EchartsTimeseriesContributionType { Row = 'row', @@ -68,7 +68,7 @@ export type EchartsTimeseriesFormData = QueryFormData & { orderDesc: boolean; rowLimit: number; seriesType: EchartsTimeseriesSeriesType; - stack: boolean | null | Partial; + stack: StackType; tooltipTimeFormat?: string; truncateYAxis: boolean; yAxisFormat?: string; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx index 836c29bea875..10217b3add73 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx @@ -48,7 +48,7 @@ export function useExtraControl< ); useEffect(() => { - setExtraValue(stack ?? undefined); + setExtraValue(stack); }, [stack]); const extraControlsOptions = useMemo(() => { @@ -63,7 +63,7 @@ export function useExtraControl< if (area) { if (setControlValue) { setControlValue('stack', value); - setExtraValue(value ?? undefined); + setExtraValue(value); } } }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index f4b8d3e6867e..9c7b3b1ee3e0 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -64,14 +64,15 @@ export enum OpacityEnum { } export enum AreaChartExtraControlsValue { - Stacked = 'Stacked', - Expanded = 'Expanded', + Stack = 'Stack', + Expand = 'Expand', } export const AreaChartExtraControlsOptions: [ JsonValue, Exclude, ][] = [ - [AreaChartExtraControlsValue.Stacked, t('Stacked')], - [AreaChartExtraControlsValue.Expanded, t('Expanded')], + [null, t('None')], + [AreaChartExtraControlsValue.Stack, t('Stack')], + [AreaChartExtraControlsValue.Expand, t('Expand')], ]; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts index c72870e98965..d84b7079c479 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts @@ -25,6 +25,7 @@ import { 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; @@ -139,4 +140,6 @@ export const DEFAULT_TITLE_FORM_DATA: EchartsTitleFormData = { yAxisTitlePosition: 'Top', }; +export type StackType = boolean | null | Partial; + export * from './Timeseries/types'; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts index a2c0fba6fcbb..40a2be343fbc 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -33,50 +33,54 @@ import { NULL_STRING, TIMESERIES_CONSTANTS, } from '../constants'; -import { LegendOrientation, LegendType } from '../types'; +import { LegendOrientation, LegendType, StackType } from '../types'; import { defaultLegendPadding } from '../defaults'; function isDefined(value: T | undefined | null): boolean { return value !== undefined && value !== null; } -export type SeriesLabelValues = { - totalStackedValues: number[]; - showValueIndexes: number[]; - thresholdValues: number[]; -}; - -export function extractSeriesLabelValues( +export function extractDataTotalValues( data: DataRecord[], - series: SeriesOption[], opts: { - stack: boolean | null | Partial; + stack: StackType; percentageThreshold: number; - xAxisCol: any; + xAxisCol: string; }, -): SeriesLabelValues { +): { + totalStackedValues: number[]; + thresholdValues: number[]; +} { const totalStackedValues: number[] = []; - const showValueIndexes: number[] = []; const thresholdValues: number[] = []; const { stack, percentageThreshold, xAxisCol } = opts; if (stack) { - const isAreaExpanded = stack === AreaChartExtraControlsValue.Expanded; data.forEach(datum => { - let values = 0; - if (isAreaExpanded) { - values = 1; - } else { - values = Object.keys(datum).reduce((prev, curr) => { - if (curr === xAxisCol) { - return prev; - } - const value = datum[curr] || 0; - return prev + (value as number); - }, 0); - } + 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) => { @@ -86,11 +90,7 @@ export function extractSeriesLabelValues( }); }); } - return { - totalStackedValues, - showValueIndexes, - thresholdValues, - }; + return showValueIndexes; } export function extractSeries( @@ -99,9 +99,17 @@ export function extractSeries( fillNeighborValue?: number; xAxis?: string; removeNulls?: boolean; + stack?: StackType; + totalStackedValues?: number[]; } = {}, ): SeriesOption[] { - const { fillNeighborValue, xAxis = DTTM_ALIAS, removeNulls = false } = opts; + const { + fillNeighborValue, + xAxis = DTTM_ALIAS, + removeNulls = false, + stack = false, + totalStackedValues = [], + } = opts; if (data.length === 0) return []; const rows: DataRecord[] = data.map(datum => ({ ...datum, @@ -117,14 +125,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)), })); diff --git a/superset-frontend/src/components/Chart/ChartRenderer.jsx b/superset-frontend/src/components/Chart/ChartRenderer.jsx index 45feb6ffd57e..ed330ab7afc9 100644 --- a/superset-frontend/src/components/Chart/ChartRenderer.jsx +++ b/superset-frontend/src/components/Chart/ChartRenderer.jsx @@ -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 ); } diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx index efa4f17e4cc4..a37331664e7c 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx @@ -52,7 +52,6 @@ const propTypes = { isComponentVisible: PropTypes.bool, handleToggleFullSize: PropTypes.func.isRequired, setControlValue: PropTypes.func, - triggerRender: PropTypes.bool, // from redux chart: chartPropShape.isRequired, @@ -349,7 +348,6 @@ export default class Chart extends React.Component { handleToggleFullSize, isFullSize, setControlValue, - triggerRender, filterboxMigrationState, postTransformProps, datasetsStatus, @@ -478,7 +476,6 @@ export default class Chart extends React.Component { triggerQuery={chart.triggerQuery} vizType={slice.viz_type} setControlValue={setControlValue} - triggerRender={triggerRender} isDeactivatedViz={isDeactivatedViz} filterboxMigrationState={filterboxMigrationState} postTransformProps={postTransformProps} diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx index 5b82d63f687a..84263790f9ef 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx @@ -192,7 +192,6 @@ class ChartHolder extends React.Component { outlinedColumnName: null, directPathLastUpdated: 0, extraControls: {}, - triggerRender: false, }; this.handleChangeFocus = this.handleChangeFocus.bind(this); @@ -261,7 +260,6 @@ class ChartHolder extends React.Component { ...prevState.extraControls, [name]: value, }, - triggerRender: true, })); } @@ -271,7 +269,7 @@ class ChartHolder extends React.Component { } render() { - const { isFocused, extraControls, triggerRender } = this.state; + const { isFocused, extraControls } = this.state; const { component, parentComponent, @@ -387,7 +385,6 @@ class ChartHolder extends React.Component { isFullSize={isFullSize} setControlValue={this.handleExtraControl} extraControls={extraControls} - triggerRender={triggerRender} postTransformProps={this.handlePostTransformProps} /> {editMode && ( diff --git a/superset-frontend/src/dashboard/containers/Chart.jsx b/superset-frontend/src/dashboard/containers/Chart.jsx index 7a97551530e6..81d06b856623 100644 --- a/superset-frontend/src/dashboard/containers/Chart.jsx +++ b/superset-frontend/src/dashboard/containers/Chart.jsx @@ -55,7 +55,7 @@ function mapStateToProps( }, ownProps, ) { - const { id, extraControls, setControlValue, triggerRender } = ownProps; + const { id, extraControls, setControlValue } = ownProps; const chart = chartQueries[id] || EMPTY_OBJECT; const datasource = (chart && chart.form_data && datasources[chart.form_data.datasource]) || @@ -102,7 +102,6 @@ function mapStateToProps( filterState: dataMask[id]?.filterState, maxRows: common.conf.SQL_MAX_ROW, setControlValue, - triggerRender, filterboxMigrationState: dashboardState.filterboxMigrationState, datasetsStatus, }; diff --git a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts index 8c9fbe7ce948..0bbabfcde52d 100644 --- a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts +++ b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts @@ -87,6 +87,9 @@ export default function getFormDataWithExtraFilters({ !!cachedFormData && areObjectsEqual(cachedFormData?.dataMask, dataMask, { ignoreUndefined: true, + }) && + areObjectsEqual(cachedFormData?.extraControls, extraControls, { + ignoreUndefined: true, }) ) { return cachedFormData; @@ -123,7 +126,7 @@ export default function getFormDataWithExtraFilters({ }; cachedFiltersByChart[sliceId] = filters; - cachedFormdataByChart[sliceId] = { ...formData, dataMask }; + cachedFormdataByChart[sliceId] = { ...formData, dataMask, extraControls }; return formData; }