From 18b45485c8d984a91b081f76cfc6b8d9d8f48e4f Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Thu, 16 Mar 2023 17:36:30 +0100 Subject: [PATCH 01/13] feat(echarts): Implement stream graph for Area chart --- .../src/Timeseries/Area/controlPanel.tsx | 2 + .../Timeseries/Regular/Bar/controlPanel.tsx | 22 ++++- .../src/Timeseries/transformProps.ts | 30 +++++- .../src/Timeseries/transformers.ts | 93 +++++++++++++++++-- .../plugin-chart-echarts/src/constants.ts | 17 +++- .../plugin-chart-echarts/src/controls.tsx | 3 +- .../plugins/plugin-chart-echarts/src/types.ts | 4 +- .../plugin-chart-echarts/src/utils/series.ts | 4 +- 8 files changed, 153 insertions(+), 22 deletions(-) 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 a1e877cf1c9a1..3acefb6e3fe52 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 @@ -35,6 +35,7 @@ import { showValueControl, richTooltipSection, seriesOrderSection, + percentageThresholdControl, } from '../../controls'; import { AreaChartExtraControlsOptions } from '../../constants'; @@ -116,6 +117,7 @@ const config: ControlPanelConfig = { }, ], [onlyTotalControl], + [percentageThresholdControl], [ { name: 'show_extra_controls', diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index f69099866ca7d..18a978a55c0d0 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -37,10 +37,13 @@ import { } from '../../constants'; import { legendSection, + onlyTotalControl, + percentageThresholdControl, richTooltipSection, seriesOrderSection, - showValueSection, + showValueControl, } from '../../../controls'; +import { BarChartExtraControlsOptions } from '../../../constants'; const { logAxis, @@ -304,7 +307,22 @@ const config: ControlPanelConfig = { controlSetRows: [ ...seriesOrderSection, ['color_scheme'], - ...showValueSection, + [showValueControl], + [ + { + name: 'stack', + config: { + type: 'SelectControl', + label: t('Stacked Style'), + renderTrigger: true, + choices: BarChartExtraControlsOptions, + default: null, + description: t('Stack series on top of each other'), + }, + }, + ], + [onlyTotalControl], + [percentageThresholdControl], [ { name: 'zoomable', 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 1342e860ba40a..9b32b311032f3 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -74,6 +74,7 @@ import { import { convertInteger } from '../utils/convertInteger'; import { defaultGrid, defaultYAxis } from '../defaults'; import { + getBaselineSeriesForStream, getPadding, getTooltipTimeFormatter, getXAxisFormatter, @@ -84,7 +85,7 @@ import { transformTimeseriesAnnotation, } from './transformers'; import { - AreaChartExtraControlsValue, + StackControlsValue, TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP, } from '../constants'; @@ -210,7 +211,7 @@ export default function transformProps( const seriesContexts = extractForecastSeriesContexts( Object.values(rawSeries).map(series => series.name as string), ); - const isAreaExpand = stack === AreaChartExtraControlsValue.Expand; + const isAreaExpand = stack === StackControlsValue.Expand; const xAxisDataType = dataTypes?.[xAxisLabel] ?? dataTypes?.[xAxisOrig]; const xAxisType = getAxisType(xAxisDataType); @@ -243,9 +244,29 @@ export default function transformProps( isHorizontal, lineStyle, }); - if (transformedSeries) series.push(transformedSeries); + if (transformedSeries) { + if (stack === StackControlsValue.Stream) { + // bug in Echarts - `stackStrategy: 'all'` doesn't work with nulls, so we cast them to 0 + series.push({ + ...transformedSeries, + data: (transformedSeries.data as any).map( + (row: [string | number, number]) => [row[0], row[1] ?? 0], + ), + }); + } else { + series.push(transformedSeries); + } + } }); + if (stack === StackControlsValue.Stream) { + const baselineSeries = getBaselineSeriesForStream( + series.map(entry => entry.data) as [string | number, number][][], + seriesType, + ); + + series.unshift(baselineSeries); + } const selectedValues = (filterState.selectedValues || []).reduce( (acc: Record, selectedValue: string) => { const index = series.findIndex(({ name }) => name === selectedValue); @@ -428,6 +449,9 @@ export default function transformProps( Object.keys(forecastValues).forEach(key => { const value = forecastValues[key]; + if (value.observation === 0 && stack) { + return; + } const content = formatForecastTooltipSeries({ ...value, seriesName: key, 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 b49f9f546bd28..f08e47672bb65 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -19,6 +19,7 @@ import { AnnotationData, AnnotationOpacity, + AxisType, CategoricalColorScale, EventAnnotationLayer, FilterState, @@ -33,7 +34,6 @@ import { TimeFormatter, TimeseriesAnnotationLayer, TimeseriesDataRecord, - AxisType, } from '@superset-ui/core'; import { SeriesOption } from 'echarts'; import { @@ -53,8 +53,12 @@ import { import { MarkLine1DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; import { extractForecastSeriesContext } from '../utils/forecast'; -import { ForecastSeriesEnum, LegendOrientation, StackType } from '../types'; -import { EchartsTimeseriesSeriesType } from './types'; +import { + EchartsTimeseriesSeriesType, + ForecastSeriesEnum, + LegendOrientation, + StackType, +} from '../types'; import { evalFormula, @@ -64,11 +68,78 @@ import { } from '../utils/annotation'; import { currentSeries, getChartPadding } from '../utils/series'; import { - AreaChartExtraControlsValue, OpacityEnum, + StackControlsValue, TIMESERIES_CONSTANTS, } from '../constants'; +// based on weighted wiggle algorithm +export const getBaselineSeriesForStream = ( + series: [string | number, number][][], + seriesType: EchartsTimeseriesSeriesType, +) => { + const seriesLength = series[0].length; + const baselineSeriesDelta = new Array(seriesLength).fill([0, 0]); + const getVal = (value: number | null) => value ?? 0; + for (let i = 0; i < seriesLength; i += 1) { + let seriesSum = 0; + let weightedSeriesSum = 0; + for (let j = 0; j < series.length; j += 1) { + const delta = + i > 0 + ? getVal(series[j][i][1]) - getVal(series[j][i - 1][1]) + : getVal(series[j][i][1]); + let deltaPrev = 0; + for (let k = 1; k < j - 1; k += 1) { + deltaPrev += + i > 0 + ? getVal(series[k][i][1]) - getVal(series[k][i - 1][1]) + : getVal(series[k][i][1]); + } + weightedSeriesSum += (0.5 * delta + deltaPrev) * getVal(series[j][i][1]); + seriesSum += getVal(series[j][i][1]); + } + baselineSeriesDelta[i] = [series[0][i][0], -weightedSeriesSum / seriesSum]; + } + const baselineSeries = baselineSeriesDelta.reduce((acc, curr, i) => { + if (i === 0) { + acc.push(curr); + } else { + acc.push([curr[0], acc[i - 1][1] + curr[1]]); + } + return acc; + }, []); + return { + data: baselineSeries, + name: 'baseline', + stack: 'obs', + stackStrategy: 'all' as const, + type: 'line' as const, + lineStyle: { + opacity: 0, + }, + tooltip: { + show: false, + }, + silent: true, + showSymbol: false, + areaStyle: { + opacity: 0, + }, + step: [ + EchartsTimeseriesSeriesType.Start, + EchartsTimeseriesSeriesType.Middle, + EchartsTimeseriesSeriesType.End, + ].includes(seriesType) + ? (seriesType as + | EchartsTimeseriesSeriesType.Start + | EchartsTimeseriesSeriesType.Middle + | EchartsTimeseriesSeriesType.End) + : undefined, + smooth: seriesType === EchartsTimeseriesSeriesType.Smooth, + }; +}; + export function transformSeries( series: SeriesOption, colorScale: CategoricalColorScale, @@ -190,9 +261,10 @@ export function transformSeries( showSymbol = true; } } - const lineStyle = isConfidenceBand - ? { ...opts.lineStyle, opacity: OpacityEnum.Transparent } - : { ...opts.lineStyle, opacity }; + const lineStyle = + isConfidenceBand || stack === StackControlsValue.Stream + ? { ...opts.lineStyle, opacity: OpacityEnum.Transparent } + : { ...opts.lineStyle, opacity }; return { ...series, queryIndex, @@ -208,7 +280,10 @@ export function transformSeries( ? seriesType : undefined, stack: stackId, - stackStrategy: isConfidenceBand ? 'all' : 'samesign', + stackStrategy: + isConfidenceBand || stack === StackControlsValue.Stream + ? 'all' + : 'samesign', lineStyle, areaStyle: area || forecastSeries.type === ForecastSeriesEnum.ForecastUpper @@ -234,7 +309,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; + const isAreaExpand = stack === StackControlsValue.Expand; if (!formatter) return numericValue; if (!stack || isSelectedLegend) return formatter(numericValue); if (!onlyTotal) { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index 3fc3fc999bc1e..e1e710c915404 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -71,18 +71,29 @@ export enum OpacityEnum { NonTransparent = 1, } -export enum AreaChartExtraControlsValue { +export enum StackControlsValue { Stack = 'Stack', + Stream = 'Stream', Expand = 'Expand', } +export const BarChartExtraControlsOptions: [ + JsonValue, + Exclude, +][] = [ + [null, t('None')], + [StackControlsValue.Stack, t('Stack')], + [StackControlsValue.Stream, t('Stream')], +]; + export const AreaChartExtraControlsOptions: [ JsonValue, Exclude, ][] = [ [null, t('None')], - [AreaChartExtraControlsValue.Stack, t('Stack')], - [AreaChartExtraControlsValue.Expand, t('Expand')], + [StackControlsValue.Stack, t('Stack')], + [StackControlsValue.Stream, t('Stream')], + [StackControlsValue.Expand, t('Expand')], ]; export const TIMEGRAIN_TO_TIMESTAMP = { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx index 0733721091994..c59422688b7cf 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx @@ -27,6 +27,7 @@ import { import { DEFAULT_LEGEND_FORM_DATA, DEFAULT_SORT_SERIES_DATA, + BarChartExtraControlsOptions, } from './constants'; import { DEFAULT_FORM_DATA } from './Timeseries/constants'; import { SortSeriesType } from './types'; @@ -142,7 +143,7 @@ export const onlyTotalControl: ControlSetItem = { }, }; -const percentageThresholdControl: ControlSetItem = { +export const percentageThresholdControl: ControlSetItem = { name: 'percentage_threshold', config: { type: 'TextControl', diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts index 7408d0a1125ea..cb44f17ed3a60 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts @@ -29,7 +29,7 @@ import { } from '@superset-ui/core'; import { EChartsCoreOption, ECharts } from 'echarts'; import { TooltipMarker } from 'echarts/types/src/util/format'; -import { AreaChartExtraControlsValue } from './constants'; +import { StackControlsValue } from './constants'; export type EchartsStylesProps = { height: number; @@ -159,7 +159,7 @@ export interface TitleFormData { yAxisTitlePosition: string; } -export type StackType = boolean | null | Partial; +export type StackType = boolean | null | Partial; export interface TreePathInfo { name: string; 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 6d1396afc240f..3e6e7827a6a69 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -32,7 +32,7 @@ import { import { format, LegendComponentOption, SeriesOption } from 'echarts'; import { sumBy, meanBy, minBy, maxBy, orderBy } from 'lodash'; import { - AreaChartExtraControlsValue, + StackControlsValue, NULL_STRING, TIMESERIES_CONSTANTS, } from '../constants'; @@ -207,7 +207,7 @@ export function extractSeries( if (isFillNeighborValue) { value = fillNeighborValue; } else if ( - stack === AreaChartExtraControlsValue.Expand && + stack === StackControlsValue.Expand && totalStackedValues.length > 0 ) { value = ((value || 0) as number) / totalStackedValues[idx]; From c5c202f66555ac21503acd97a2fcf41c23df038c Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 13:35:25 +0100 Subject: [PATCH 02/13] Add migration --- .../plugin-chart-echarts/src/controls.tsx | 1 - ...24_b5ea9d343307_bar_chart_stack_options.py | 95 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx index c59422688b7cf..3e38b97ebb3d8 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx @@ -27,7 +27,6 @@ import { import { DEFAULT_LEGEND_FORM_DATA, DEFAULT_SORT_SERIES_DATA, - BarChartExtraControlsOptions, } from './constants'; import { DEFAULT_FORM_DATA } from './Timeseries/constants'; import { SortSeriesType } from './types'; diff --git a/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py b/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py new file mode 100644 index 0000000000000..87bd0a4165f4d --- /dev/null +++ b/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py @@ -0,0 +1,95 @@ +# 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. +"""bar_chart_stack_options + +Revision ID: b5ea9d343307 +Revises: d0ac08bb5b83 +Create Date: 2023-03-17 13:24:54.662754 + +""" + +# revision identifiers, used by Alembic. +revision = "b5ea9d343307" +down_revision = "d0ac08bb5b83" + +import json + +import sqlalchemy as sa +from alembic import op +from sqlalchemy import and_, Column, Integer, String, Text +from sqlalchemy.ext.declarative import declarative_base + +from superset import db + +Base = declarative_base() + +CHART_TYPE = "echarts_timeseries_bar" + + +class Slice(Base): + """Declarative class to do query in upgrade""" + + __tablename__ = "slices" + id = Column(Integer, primary_key=True) + viz_type = Column(String(250)) + params = Column(Text) + + +def upgrade(): + bind = op.get_bind() + session = db.Session(bind=bind) + + slices = session.query(Slice).filter(Slice.viz_type == CHART_TYPE).all() + for slc in slices: + try: + params = json.loads(slc.params) + stack = params.get("stack") + if stack: + params["stack"] = "Stack" + else: + params["stack"] = None + slc.params = json.dumps(params, sort_keys=True) + except Exception as e: + print(e) + print(f"Parsing params for slice {slc.id} failed.") + pass + + session.commit() + session.close() + + +def downgrade(): + bind = op.get_bind() + session = db.Session(bind=bind) + + slices = session.query(Slice).filter(Slice.viz_type == CHART_TYPE).all() + for slc in slices: + try: + params = json.loads(slc.params) + stack = params.get("stack") + if stack == "Stack" or stack == "Stream": + params["stack"] = True + else: + params["stack"] = False + slc.params = json.dumps(params, sort_keys=True) + except Exception as e: + print(e) + print(f"Parsing params for slice {slc.id} failed.") + pass + + session.commit() + session.close() From 4f9fae55c8907e8033416765391dbb8b2ba37dc9 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 14:19:39 +0100 Subject: [PATCH 03/13] Add ut --- .../test/Timeseries/transformProps.test.ts | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts index 63ca50449ed09..d3328f8fcc974 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts @@ -295,6 +295,110 @@ describe('EchartsTimeseries transformProps', () => { }), ); }); + + it('Should add a baseline series for stream graph', () => { + const streamQueriesData = [ + { + data: [ + { + 'San Francisco': 120, + 'New York': 220, + Boston: 150, + Miami: 270, + Denver: 800, + __timestamp: 599616000000, + }, + { + 'San Francisco': 150, + 'New York': 190, + Boston: 240, + Miami: 350, + Denver: 700, + __timestamp: 599616000001, + }, + { + 'San Francisco': 130, + 'New York': 300, + Boston: 250, + Miami: 410, + Denver: 650, + __timestamp: 599616000002, + }, + { + 'San Francisco': 90, + 'New York': 340, + Boston: 300, + Miami: 480, + Denver: 590, + __timestamp: 599616000003, + }, + { + 'San Francisco': 260, + 'New York': 200, + Boston: 420, + Miami: 490, + Denver: 760, + __timestamp: 599616000004, + }, + { + 'San Francisco': 250, + 'New York': 250, + Boston: 380, + Miami: 360, + Denver: 400, + __timestamp: 599616000005, + }, + { + 'San Francisco': 160, + 'New York': 210, + Boston: 330, + Miami: 440, + Denver: 580, + __timestamp: 599616000006, + }, + ], + }, + ]; + const streamFormData = { ...formData, stack: 'Stream' }; + const props = { + ...chartPropsConfig, + formData: streamFormData, + queriesData: streamQueriesData, + }; + + const chartProps = new ChartProps(props); + expect( + transformProps(chartProps as EchartsTimeseriesChartProps).echartOptions + .series[0], + ).toEqual({ + areaStyle: { + opacity: 0, + }, + lineStyle: { + opacity: 0, + }, + name: 'baseline', + showSymbol: false, + silent: true, + smooth: false, + stack: 'obs', + stackStrategy: 'all', + step: undefined, + tooltip: { + show: false, + }, + type: 'line', + data: [ + [599616000000, -415.7692307692308], + [599616000001, -403.6219915054271], + [599616000002, -476.32314093071443], + [599616000003, -514.2120298196033], + [599616000004, -485.7378514158475], + [599616000005, -419.6402904402378], + [599616000006, -442.9833136960517], + ], + }); + }); }); describe('Does transformProps transform series correctly', () => { From 5234ef8a31fb43dfd96e0e95e8fdef679ef5ae80 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 14:20:03 +0100 Subject: [PATCH 04/13] Added algorithm source + lint fixes --- .../src/Timeseries/Regular/Bar/controlPanel.tsx | 16 ++++++++-------- .../src/Timeseries/transformers.ts | 1 + .../plugins/plugin-chart-echarts/src/index.ts | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index 18a978a55c0d0..a6a31e7c0474c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -29,21 +29,21 @@ import { sections, sharedControls, } from '@superset-ui/chart-controls'; - -import { OrientationType } from '../../types'; -import { - DEFAULT_FORM_DATA, - TIME_SERIES_DESCRIPTION_TEXT, -} from '../../constants'; import { + BarChartExtraControlsOptions, legendSection, onlyTotalControl, percentageThresholdControl, richTooltipSection, seriesOrderSection, showValueControl, -} from '../../../controls'; -import { BarChartExtraControlsOptions } from '../../../constants'; +} from '@superset-ui/plugin-chart-echarts'; + +import { OrientationType } from '../../types'; +import { + DEFAULT_FORM_DATA, + TIME_SERIES_DESCRIPTION_TEXT, +} from '../../constants'; const { logAxis, 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 f08e47672bb65..96bdd30a05798 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -74,6 +74,7 @@ import { } from '../constants'; // based on weighted wiggle algorithm +// source: https://ieeexplore.ieee.org/document/4658136 export const getBaselineSeriesForStream = ( series: [string | number, number][][], seriesType: EchartsTimeseriesSeriesType, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/index.ts index 0301f265b051a..7fd77f77c8e06 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/index.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/index.ts @@ -50,6 +50,8 @@ export { default as SunburstTransformProps } from './Sunburst/transformProps'; export { DEFAULT_FORM_DATA as TimeseriesDefaultFormData } from './Timeseries/constants'; export * from './types'; +export * from './controls'; +export * from './constants'; /** * Note: this file exports the default export from EchartsTimeseries.tsx. From 295079d47bf88efcbb1d710cef969783643bc401 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 14:27:33 +0100 Subject: [PATCH 05/13] Lint fix --- .../test/Timeseries/transformProps.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts index d3328f8fcc974..cda213d72bb68 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts @@ -368,8 +368,10 @@ describe('EchartsTimeseries transformProps', () => { const chartProps = new ChartProps(props); expect( - transformProps(chartProps as EchartsTimeseriesChartProps).echartOptions - .series[0], + ( + transformProps(chartProps as EchartsTimeseriesChartProps).echartOptions + .series as any[] + )[0], ).toEqual({ areaStyle: { opacity: 0, From d3ec8cf7295231797325afec9dbeb6f3cc23d78d Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 15:24:57 +0100 Subject: [PATCH 06/13] Enable stream graph in all echarts timeseries --- .../Timeseries/Regular/Bar/controlPanel.tsx | 22 ++----------------- .../Timeseries/Regular/Line/controlPanel.tsx | 14 ++++++------ .../plugin-chart-echarts/src/constants.ts | 2 +- .../plugin-chart-echarts/src/controls.tsx | 8 ++++--- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index a6a31e7c0474c..76ebca2102a5f 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -30,13 +30,10 @@ import { sharedControls, } from '@superset-ui/chart-controls'; import { - BarChartExtraControlsOptions, legendSection, - onlyTotalControl, - percentageThresholdControl, richTooltipSection, seriesOrderSection, - showValueControl, + showValueSection, } from '@superset-ui/plugin-chart-echarts'; import { OrientationType } from '../../types'; @@ -307,22 +304,7 @@ const config: ControlPanelConfig = { controlSetRows: [ ...seriesOrderSection, ['color_scheme'], - [showValueControl], - [ - { - name: 'stack', - config: { - type: 'SelectControl', - label: t('Stacked Style'), - renderTrigger: true, - choices: BarChartExtraControlsOptions, - default: null, - description: t('Stack series on top of each other'), - }, - }, - ], - [onlyTotalControl], - [percentageThresholdControl], + ...showValueSection, [ { name: 'zoomable', diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx index a2c9648ea04dc..4ed145ecdd42a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx @@ -26,18 +26,18 @@ import { sections, sharedControls, } from '@superset-ui/chart-controls'; - -import { EchartsTimeseriesSeriesType } from '../../types'; -import { - DEFAULT_FORM_DATA, - TIME_SERIES_DESCRIPTION_TEXT, -} from '../../constants'; import { legendSection, richTooltipSection, seriesOrderSection, showValueSection, -} from '../../../controls'; + EchartsTimeseriesSeriesType, +} from '@superset-ui/plugin-chart-echarts'; + +import { + DEFAULT_FORM_DATA, + TIME_SERIES_DESCRIPTION_TEXT, +} from '../../constants'; const { area, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index e1e710c915404..05292b7f40d93 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -77,7 +77,7 @@ export enum StackControlsValue { Expand = 'Expand', } -export const BarChartExtraControlsOptions: [ +export const ExtraControlsOptions: [ JsonValue, Exclude, ][] = [ diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx index 3e38b97ebb3d8..fb064d8fca7a9 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx @@ -27,6 +27,7 @@ import { import { DEFAULT_LEGEND_FORM_DATA, DEFAULT_SORT_SERIES_DATA, + ExtraControlsOptions, } from './constants'; import { DEFAULT_FORM_DATA } from './Timeseries/constants'; import { SortSeriesType } from './types'; @@ -119,10 +120,11 @@ export const showValueControl: ControlSetItem = { export const stackControl: ControlSetItem = { name: 'stack', config: { - type: 'CheckboxControl', - label: t('Stack series'), + type: 'SelectControl', + label: t('Stacked Style'), renderTrigger: true, - default: false, + choices: ExtraControlsOptions, + default: null, description: t('Stack series on top of each other'), }, }; From 99c32dbd0445daa8b67fc68cc8b0337c1f518695 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 15:25:20 +0100 Subject: [PATCH 07/13] Fix lines not showing in Line chart with Stream enabled --- .../plugins/plugin-chart-echarts/src/Timeseries/transformers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 96bdd30a05798..953037c3d5dbd 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts @@ -263,7 +263,7 @@ export function transformSeries( } } const lineStyle = - isConfidenceBand || stack === StackControlsValue.Stream + isConfidenceBand || (stack === StackControlsValue.Stream && area) ? { ...opts.lineStyle, opacity: OpacityEnum.Transparent } : { ...opts.lineStyle, opacity }; return { From bf07b694478b59878bde72328f226e7b811ea726 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 15:25:36 +0100 Subject: [PATCH 08/13] Update migration --- ...23-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py b/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py index 87bd0a4165f4d..a5fb5f223da51 100644 --- a/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py +++ b/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py @@ -37,7 +37,7 @@ Base = declarative_base() -CHART_TYPE = "echarts_timeseries_bar" +CHART_TYPE = "%echarts_timeseries%" class Slice(Base): @@ -53,11 +53,11 @@ def upgrade(): bind = op.get_bind() session = db.Session(bind=bind) - slices = session.query(Slice).filter(Slice.viz_type == CHART_TYPE).all() + slices = session.query(Slice).filter(Slice.viz_type.like(CHART_TYPE)).all() for slc in slices: try: params = json.loads(slc.params) - stack = params.get("stack") + stack = params.get("stack", None) if stack: params["stack"] = "Stack" else: @@ -76,7 +76,7 @@ def downgrade(): bind = op.get_bind() session = db.Session(bind=bind) - slices = session.query(Slice).filter(Slice.viz_type == CHART_TYPE).all() + slices = session.query(Slice).filter(Slice.viz_type.like(CHART_TYPE)).all() for slc in slices: try: params = json.loads(slc.params) From 1ed6a3a80554d32b69fc814873fadb2432e386f5 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 15:28:35 +0100 Subject: [PATCH 09/13] Fix scatterplot --- .../plugin-chart-echarts/src/Timeseries/transformProps.ts | 1 - 1 file changed, 1 deletion(-) 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 9b32b311032f3..19b8ca52a7cb1 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -196,7 +196,6 @@ export default function transformProps( fillNeighborValue: stack && !forecastEnabled ? 0 : undefined, xAxis: xAxisLabel, extraMetricLabels, - removeNulls: seriesType === EchartsTimeseriesSeriesType.Scatter, stack, totalStackedValues, isHorizontal, From 21b049cf187e8f01d70f121ab3081e2a8019c989 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 15:30:54 +0100 Subject: [PATCH 10/13] Fix lint --- .../plugin-chart-echarts/src/Timeseries/transformProps.ts | 1 - 1 file changed, 1 deletion(-) 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 19b8ca52a7cb1..8ae67f6f31aa7 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -43,7 +43,6 @@ import { ZRLineType } from 'echarts/types/src/util/types'; import { EchartsTimeseriesChartProps, EchartsTimeseriesFormData, - EchartsTimeseriesSeriesType, TimeseriesChartTransformedProps, OrientationType, } from './types'; From 0eaae1b6a5421768660e0586f92d86c85e5b5c7e Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 17:40:17 +0100 Subject: [PATCH 11/13] Revert import changes to unblock CI --- .../src/Timeseries/Regular/Bar/controlPanel.tsx | 2 +- .../src/Timeseries/Regular/Line/controlPanel.tsx | 14 +++++++------- .../plugins/plugin-chart-echarts/src/index.ts | 2 -- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index 76ebca2102a5f..358c2dc949331 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -34,7 +34,7 @@ import { richTooltipSection, seriesOrderSection, showValueSection, -} from '@superset-ui/plugin-chart-echarts'; +} from '../../../controls'; import { OrientationType } from '../../types'; import { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx index 4ed145ecdd42a..a2c9648ea04dc 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx @@ -26,18 +26,18 @@ import { sections, sharedControls, } from '@superset-ui/chart-controls'; -import { - legendSection, - richTooltipSection, - seriesOrderSection, - showValueSection, - EchartsTimeseriesSeriesType, -} from '@superset-ui/plugin-chart-echarts'; +import { EchartsTimeseriesSeriesType } from '../../types'; import { DEFAULT_FORM_DATA, TIME_SERIES_DESCRIPTION_TEXT, } from '../../constants'; +import { + legendSection, + richTooltipSection, + seriesOrderSection, + showValueSection, +} from '../../../controls'; const { area, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/index.ts index 7fd77f77c8e06..0301f265b051a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/index.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/index.ts @@ -50,8 +50,6 @@ export { default as SunburstTransformProps } from './Sunburst/transformProps'; export { DEFAULT_FORM_DATA as TimeseriesDefaultFormData } from './Timeseries/constants'; export * from './types'; -export * from './controls'; -export * from './constants'; /** * Note: this file exports the default export from EchartsTimeseries.tsx. From 2c8d003ce027c4fa0637977b7f70a9fe46b9fca8 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 17 Mar 2023 17:40:29 +0100 Subject: [PATCH 12/13] Add fallback to migration --- .../2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py b/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py index a5fb5f223da51..49844cda1114b 100644 --- a/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py +++ b/superset/migrations/versions/2023-03-17_13-24_b5ea9d343307_bar_chart_stack_options.py @@ -80,7 +80,7 @@ def downgrade(): for slc in slices: try: params = json.loads(slc.params) - stack = params.get("stack") + stack = params.get("stack", None) if stack == "Stack" or stack == "Stream": params["stack"] = True else: From 9b4a0e8109f5ab1a2eca876a9f46292649a62503 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Sat, 18 Mar 2023 15:37:39 +0100 Subject: [PATCH 13/13] Rename variable --- .../src/Timeseries/Area/controlPanel.tsx | 4 ++-- .../src/components/ExtraControls.tsx | 4 ++-- .../plugins/plugin-chart-echarts/src/constants.ts | 11 +++-------- .../plugins/plugin-chart-echarts/src/controls.tsx | 4 ++-- 4 files changed, 9 insertions(+), 14 deletions(-) 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 3acefb6e3fe52..99ec771d1ed9e 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 @@ -37,7 +37,7 @@ import { seriesOrderSection, percentageThresholdControl, } from '../../controls'; -import { AreaChartExtraControlsOptions } from '../../constants'; +import { AreaChartStackControlOptions } from '../../constants'; const { logAxis, @@ -110,7 +110,7 @@ const config: ControlPanelConfig = { type: 'SelectControl', label: t('Stacked Style'), renderTrigger: true, - choices: AreaChartExtraControlsOptions, + choices: AreaChartStackControlOptions, default: null, description: t('Stack series on top of each other'), }, 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 10217b3add730..33e9ab016eb0e 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/components/ExtraControls.tsx @@ -22,7 +22,7 @@ import { RadioButtonOption, sharedControlComponents, } from '@superset-ui/chart-controls'; -import { AreaChartExtraControlsOptions } from '../constants'; +import { AreaChartStackControlOptions } from '../constants'; const { RadioButtonControl } = sharedControlComponents; @@ -53,7 +53,7 @@ export function useExtraControl< const extraControlsOptions = useMemo(() => { if (area) { - return AreaChartExtraControlsOptions; + return AreaChartStackControlOptions; } return []; }, [area]); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index 05292b7f40d93..bfc6c98fa5afa 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -77,7 +77,7 @@ export enum StackControlsValue { Expand = 'Expand', } -export const ExtraControlsOptions: [ +export const StackControlOptions: [ JsonValue, Exclude, ][] = [ @@ -86,15 +86,10 @@ export const ExtraControlsOptions: [ [StackControlsValue.Stream, t('Stream')], ]; -export const AreaChartExtraControlsOptions: [ +export const AreaChartStackControlOptions: [ JsonValue, Exclude, -][] = [ - [null, t('None')], - [StackControlsValue.Stack, t('Stack')], - [StackControlsValue.Stream, t('Stream')], - [StackControlsValue.Expand, t('Expand')], -]; +][] = [...StackControlOptions, [StackControlsValue.Expand, t('Expand')]]; export const TIMEGRAIN_TO_TIMESTAMP = { [TimeGranularity.HOUR]: 3600 * 1000, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx index fb064d8fca7a9..26f10e0fe45ed 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx @@ -27,7 +27,7 @@ import { import { DEFAULT_LEGEND_FORM_DATA, DEFAULT_SORT_SERIES_DATA, - ExtraControlsOptions, + StackControlOptions, } from './constants'; import { DEFAULT_FORM_DATA } from './Timeseries/constants'; import { SortSeriesType } from './types'; @@ -123,7 +123,7 @@ export const stackControl: ControlSetItem = { type: 'SelectControl', label: t('Stacked Style'), renderTrigger: true, - choices: ExtraControlsOptions, + choices: StackControlOptions, default: null, description: t('Stack series on top of each other'), },