You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2023/06/23 14:57:59 UTC

[superset] branch master updated: fix: Total calculation in stacked Timeseries charts (#24477)

This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new c5b4ecdca5 fix: Total calculation in stacked Timeseries charts (#24477)
c5b4ecdca5 is described below

commit c5b4ecdca519ab4309a47bfc8feb4a1665c6ce96
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Fri Jun 23 11:57:48 2023 -0300

    fix: Total calculation in stacked Timeseries charts (#24477)
---
 .../src/chart/models/ChartProps.ts                 | 18 ++++-
 .../superset-ui-core/src/chart/types/Base.ts       |  4 ++
 .../src/MixedTimeseries/EchartsMixedTimeseries.tsx |  8 +--
 .../src/MixedTimeseries/transformProps.ts          |  7 +-
 .../src/Timeseries/EchartsTimeseries.tsx           | 79 +++++-----------------
 .../src/Timeseries/transformProps.ts               | 10 ++-
 .../src/Timeseries/transformers.ts                 | 15 ++--
 .../plugins/plugin-chart-echarts/src/types.ts      |  2 +
 .../plugin-chart-echarts/src/utils/series.ts       | 30 ++++----
 .../src/components/Chart/ChartRenderer.jsx         |  8 +++
 10 files changed, 88 insertions(+), 93 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
index e02aeca4f5..815a5df2f4 100644
--- a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
+++ b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
@@ -30,7 +30,12 @@ import {
   FilterState,
   JsonObject,
 } from '../..';
-import { HandlerFunction, PlainObject, SetDataMaskHook } from '../types/Base';
+import {
+  HandlerFunction,
+  LegendState,
+  PlainObject,
+  SetDataMaskHook,
+} from '../types/Base';
 import { QueryData, DataRecordFilters } from '..';
 import { SupersetTheme } from '../../style';
 
@@ -54,6 +59,8 @@ type Hooks = {
   onContextMenu?: HandlerFunction;
   /** handle errors */
   onError?: HandlerFunction;
+  /** handle legend state changes */
+  onLegendStateChanged?: HandlerFunction;
   /** use the vis as control to update state */
   setControlValue?: HandlerFunction;
   /** handle external filters */
@@ -88,6 +95,8 @@ export interface ChartPropsConfig {
   ownState?: JsonObject;
   /** Filter state that saved in dashboard */
   filterState?: FilterState;
+  /** Legend state */
+  legendState?: LegendState;
   /** Set of actual behaviors that this instance of chart should use */
   behaviors?: Behavior[];
   /** Chart display settings related to current view context */
@@ -128,6 +137,8 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
 
   filterState: FilterState;
 
+  legendState?: LegendState;
+
   queriesData: QueryData[];
 
   width: number;
@@ -156,6 +167,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
       hooks = {},
       ownState = {},
       filterState = {},
+      legendState,
       initialValues = {},
       queriesData = [],
       behaviors = [],
@@ -181,6 +193,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
     this.queriesData = queriesData;
     this.ownState = ownState;
     this.filterState = filterState;
+    this.legendState = legendState;
     this.behaviors = behaviors;
     this.displaySettings = displaySettings;
     this.appSection = appSection;
@@ -205,6 +218,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
     input => input.width,
     input => input.ownState,
     input => input.filterState,
+    input => input.legendState,
     input => input.behaviors,
     input => input.displaySettings,
     input => input.appSection,
@@ -224,6 +238,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
       width,
       ownState,
       filterState,
+      legendState,
       behaviors,
       displaySettings,
       appSection,
@@ -243,6 +258,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
         queriesData,
         ownState,
         filterState,
+        legendState,
         width,
         behaviors,
         displaySettings,
diff --git a/superset-frontend/packages/superset-ui-core/src/chart/types/Base.ts b/superset-frontend/packages/superset-ui-core/src/chart/types/Base.ts
index 418d6a36fc..b3884a8488 100644
--- a/superset-frontend/packages/superset-ui-core/src/chart/types/Base.ts
+++ b/superset-frontend/packages/superset-ui-core/src/chart/types/Base.ts
@@ -100,4 +100,8 @@ export enum AxisType {
   log = 'log',
 }
 
+export interface LegendState {
+  [key: string]: boolean;
+}
+
 export default {};
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx
index 8686042611..8c55ff7ae2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/EchartsMixedTimeseries.tsx
@@ -29,7 +29,7 @@ import {
 import { EchartsMixedTimeseriesChartTransformedProps } from './types';
 import Echart from '../components/Echart';
 import { EventHandlers } from '../types';
-import { currentSeries, formatSeriesName } from '../utils/series';
+import { formatSeriesName } from '../utils/series';
 
 export default function EchartsMixedTimeseries({
   height,
@@ -123,12 +123,6 @@ export default function EchartsMixedTimeseries({
       const { seriesName, seriesIndex } = props;
       handleChange(seriesName, seriesIndex);
     },
-    mouseout: () => {
-      currentSeries.name = '';
-    },
-    mouseover: params => {
-      currentSeries.name = params.seriesName;
-    },
     contextmenu: async eventParams => {
       if (onContextMenu) {
         eventParams.event.stop();
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
index a14904b05c..77e8550d09 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
@@ -51,7 +51,6 @@ import {
 import { parseYAxisBound } from '../utils/controls';
 import {
   getOverMaxHiddenFormatter,
-  currentSeries,
   dedupSeries,
   extractSeries,
   getAxisType,
@@ -481,11 +480,7 @@ export default function transformProps(
             seriesName: key,
             formatter: primarySeries.has(key) ? formatter : formatterSecondary,
           });
-          if (currentSeries.name === key) {
-            rows.push(`<span style="font-weight: 700">${content}</span>`);
-          } else {
-            rows.push(`<span style="opacity: 0.7">${content}</span>`);
-          }
+          rows.push(`<span style="opacity: 0.7">${content}</span>`);
         });
         return rows.join('<br />');
       },
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 7f75d27105..bd23c3f257 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx
@@ -24,6 +24,7 @@ import {
   getTimeFormatter,
   getColumnLabel,
   getNumberFormatter,
+  LegendState,
 } from '@superset-ui/core';
 import { ViewRootGroup } from 'echarts/types/src/util/types';
 import GlobalModel from 'echarts/types/src/model/Global';
@@ -31,12 +32,11 @@ import ComponentModel from 'echarts/types/src/model/Component';
 import { EchartsHandler, EventHandlers } from '../types';
 import Echart from '../components/Echart';
 import { TimeseriesChartTransformedProps } from './types';
-import { currentSeries, formatSeriesName } from '../utils/series';
+import { formatSeriesName } from '../utils/series';
 import { ExtraControls } from '../components/ExtraControls';
 
 const TIMER_DURATION = 300;
 
-// @ts-ignore
 export default function EchartsTimeseries({
   formData,
   height,
@@ -49,6 +49,7 @@ export default function EchartsTimeseries({
   setControlValue,
   legendData = [],
   onContextMenu,
+  onLegendStateChanged,
   xValueFormatter,
   xAxis,
   refs,
@@ -59,8 +60,6 @@ export default function EchartsTimeseries({
   const echartRef = useRef<EchartsHandler | null>(null);
   // eslint-disable-next-line no-param-reassign
   refs.echartRef = echartRef;
-  const lastTimeRef = useRef(Date.now());
-  const lastSelectedLegend = useRef('');
   const clickTimer = useRef<ReturnType<typeof setTimeout>>();
   const extraControlRef = useRef<HTMLDivElement>(null);
   const [extraControlHeight, setExtraControlHeight] = useState(0);
@@ -69,34 +68,6 @@ export default function EchartsTimeseries({
     setExtraControlHeight(updatedHeight);
   }, [formData.showExtraControls]);
 
-  const handleDoubleClickChange = useCallback(
-    (name?: string) => {
-      const echartInstance = echartRef.current?.getEchartInstance();
-      if (!name) {
-        currentSeries.legend = '';
-        echartInstance?.dispatchAction({
-          type: 'legendAllSelect',
-        });
-      } else {
-        legendData.forEach(datum => {
-          if (datum === name) {
-            currentSeries.legend = datum;
-            echartInstance?.dispatchAction({
-              type: 'legendSelect',
-              name: datum,
-            });
-          } else {
-            echartInstance?.dispatchAction({
-              type: 'legendUnSelect',
-              name: datum,
-            });
-          }
-        });
-      }
-    },
-    [legendData],
-  );
-
   const getModelInfo = (target: ViewRootGroup, globalModel: GlobalModel) => {
     let el = target;
     let model: ComponentModel | null = null;
@@ -175,30 +146,14 @@ export default function EchartsTimeseries({
         handleChange(name);
       }, TIMER_DURATION);
     },
-    mouseout: () => {
-      currentSeries.name = '';
+    legendselectchanged: payload => {
+      onLegendStateChanged?.(payload.selected);
     },
-    mouseover: params => {
-      currentSeries.name = params.seriesName;
+    legendselectall: payload => {
+      onLegendStateChanged?.(payload.selected);
     },
-    legendselectchanged: payload => {
-      const currentTime = Date.now();
-      // TIMER_DURATION is the interval between two legendselectchanged event
-      if (
-        currentTime - lastTimeRef.current < TIMER_DURATION &&
-        lastSelectedLegend.current === payload.name
-      ) {
-        // execute dbclick
-        handleDoubleClickChange(payload.name);
-      } else {
-        lastTimeRef.current = currentTime;
-        // remember last selected legend
-        lastSelectedLegend.current = payload.name;
-      }
-      // if all legend is unselected, we keep all selected
-      if (Object.values(payload.selected).every(i => !i)) {
-        handleDoubleClickChange();
-      }
+    legendinverseselect: payload => {
+      onLegendStateChanged?.(payload.selected);
     },
     contextmenu: async eventParams => {
       if (onContextMenu) {
@@ -272,15 +227,16 @@ export default function EchartsTimeseries({
         // @ts-ignore
         const globalModel = echartInstance.getModel();
         const model = getModelInfo(params.target, globalModel);
-        const seriesCount = globalModel.getSeriesCount();
-        const currentSeriesIndices = globalModel.getCurrentSeriesIndices();
         if (model) {
           const { name } = model;
-          if (seriesCount !== currentSeriesIndices.length) {
-            handleDoubleClickChange();
-          } else {
-            handleDoubleClickChange(name);
-          }
+          const legendState: LegendState = legendData.reduce(
+            (previous, datum) => ({
+              ...previous,
+              [datum]: datum === name,
+            }),
+            {},
+          );
+          onLegendStateChanged?.(legendState);
         }
       }
     },
@@ -292,6 +248,7 @@ export default function EchartsTimeseries({
         <ExtraControls formData={formData} setControlValue={setControlValue} />
       </div>
       <Echart
+        ref={echartRef}
         refs={refs}
         height={height - extraControlHeight}
         width={width}
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 6e1d0186ef..c89bff2e8c 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -54,7 +54,6 @@ import { ForecastSeriesEnum, ForecastValue, Refs } from '../types';
 import { parseYAxisBound } from '../utils/controls';
 import {
   calculateLowerLogTick,
-  currentSeries,
   dedupSeries,
   extractDataTotalValues,
   extractSeries,
@@ -101,6 +100,7 @@ export default function transformProps(
     width,
     height,
     filterState,
+    legendState,
     formData,
     hooks,
     queriesData,
@@ -192,6 +192,7 @@ export default function transformProps(
       stack,
       percentageThreshold,
       xAxisCol: xAxisLabel,
+      legendState,
     },
   );
   const extraMetricLabels = extractExtraMetrics(chartProps.rawFormData).map(
@@ -221,6 +222,7 @@ export default function transformProps(
     stack,
     onlyTotal,
     isHorizontal,
+    legendState,
   });
   const seriesContexts = extractForecastSeriesContexts(
     Object.values(rawSeries).map(series => series.name as string),
@@ -258,6 +260,7 @@ export default function transformProps(
         markerSize,
         areaOpacity: opacity,
         seriesType,
+        legendState,
         stack,
         formatter,
         showValue,
@@ -379,6 +382,7 @@ export default function transformProps(
     setDataMask = () => {},
     setControlValue = () => {},
     onContextMenu,
+    onLegendStateChanged,
   } = hooks;
 
   const addYAxisLabelOffset = !!yAxisTitle;
@@ -486,7 +490,7 @@ export default function transformProps(
             seriesName: key,
             formatter,
           });
-          if (currentSeries.name === key) {
+          if (!legendState || legendState[key]) {
             rows.push(`<span style="font-weight: 700">${content}</span>`);
           } else {
             rows.push(`<span style="opacity: 0.7">${content}</span>`);
@@ -506,6 +510,7 @@ export default function transformProps(
         showLegend,
         theme,
         zoomable,
+        legendState,
       ),
       data: legendData as string[],
     },
@@ -549,6 +554,7 @@ export default function transformProps(
     width,
     legendData,
     onContextMenu,
+    onLegendStateChanged,
     xValueFormatter: tooltipFormatter,
     xAxis: {
       label: xAxisLabel,
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 37e3eb9fce..fb4739dc74 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
@@ -27,6 +27,7 @@ import {
   getTimeFormatter,
   IntervalAnnotationLayer,
   isTimeseriesAnnotationResult,
+  LegendState,
   NumberFormatter,
   smartDateDetailedFormatter,
   smartDateFormatter,
@@ -65,7 +66,7 @@ import {
   formatAnnotationLabel,
   parseAnnotationOpacity,
 } from '../utils/annotation';
-import { currentSeries, getChartPadding } from '../utils/series';
+import { getChartPadding } from '../utils/series';
 import {
   OpacityEnum,
   StackControlsValue,
@@ -156,6 +157,7 @@ export function transformSeries(
     yAxisIndex?: number;
     showValue?: boolean;
     onlyTotal?: boolean;
+    legendState?: LegendState;
     formatter?: NumberFormatter;
     totalStackedValues?: number[];
     showValueIndexes?: number[];
@@ -182,6 +184,7 @@ export function transformSeries(
     showValue,
     onlyTotal,
     formatter,
+    legendState,
     totalStackedValues = [],
     showValueIndexes = [],
     thresholdValues = [],
@@ -308,10 +311,14 @@ export function transformSeries(
       formatter: (params: any) => {
         const { value, dataIndex, seriesIndex, seriesName } = params;
         const numericValue = isHorizontal ? value[0] : value[1];
-        const isSelectedLegend = currentSeries.legend === seriesName;
+        const isSelectedLegend = !legendState || legendState[seriesName];
         const isAreaExpand = stack === StackControlsValue.Expand;
-        if (!formatter) return numericValue;
-        if (!stack || isSelectedLegend) return formatter(numericValue);
+        if (!formatter) {
+          return numericValue;
+        }
+        if (!stack && isSelectedLegend) {
+          return formatter(numericValue);
+        }
         if (!onlyTotal) {
           if (
             numericValue >=
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
index 4b96f73bb9..d7280ea9d6 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
@@ -23,6 +23,7 @@ import {
   ContextMenuFilters,
   FilterState,
   HandlerFunction,
+  LegendState,
   PlainObject,
   QueryFormColumn,
   SetDataMaskHook,
@@ -127,6 +128,7 @@ export interface BaseTransformedProps<F> {
     filters?: ContextMenuFilters,
   ) => void;
   setDataMask?: SetDataMaskHook;
+  onLegendStateChanged?: (state: LegendState) => void;
   filterState?: FilterState;
   refs: Refs;
   width: number;
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 5235e168d9..0f6efd72f2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
@@ -30,6 +30,7 @@ import {
   TimeFormatter,
   SupersetTheme,
   normalizeTimestamp,
+  LegendState,
 } from '@superset-ui/core';
 import { SortSeriesType } from '@superset-ui/chart-controls';
 import { format, LegendComponentOption, SeriesOption } from 'echarts';
@@ -52,6 +53,7 @@ export function extractDataTotalValues(
     stack: StackType;
     percentageThreshold: number;
     xAxisCol: string;
+    legendState?: LegendState;
   },
 ): {
   totalStackedValues: number[];
@@ -59,13 +61,16 @@ export function extractDataTotalValues(
 } {
   const totalStackedValues: number[] = [];
   const thresholdValues: number[] = [];
-  const { stack, percentageThreshold, xAxisCol } = opts;
+  const { stack, percentageThreshold, xAxisCol, legendState } = opts;
   if (stack) {
     data.forEach(datum => {
       const values = Object.keys(datum).reduce((prev, curr) => {
         if (curr === xAxisCol) {
           return prev;
         }
+        if (legendState && !legendState[curr]) {
+          return prev;
+        }
         const value = datum[curr] || 0;
         return prev + (value as number);
       }, 0);
@@ -85,23 +90,28 @@ export function extractShowValueIndexes(
     stack: StackType;
     onlyTotal?: boolean;
     isHorizontal?: boolean;
+    legendState?: LegendState;
   },
 ): number[] {
   const showValueIndexes: number[] = [];
-  if (opts.stack) {
+  const { legendState, stack, isHorizontal, onlyTotal } = opts;
+  if (stack) {
     series.forEach((entry, seriesIndex) => {
       const { data = [] } = entry;
       (data as [any, number][]).forEach((datum, dataIndex) => {
-        if (!opts.onlyTotal && datum[opts.isHorizontal ? 0 : 1] !== null) {
+        if (entry.id && legendState && !legendState[entry.id]) {
+          return;
+        }
+        if (!onlyTotal && datum[isHorizontal ? 0 : 1] !== null) {
           showValueIndexes[dataIndex] = seriesIndex;
         }
-        if (opts.onlyTotal) {
-          if (datum[opts.isHorizontal ? 0 : 1] > 0) {
+        if (onlyTotal) {
+          if (datum[isHorizontal ? 0 : 1] > 0) {
             showValueIndexes[dataIndex] = seriesIndex;
           }
           if (
             !showValueIndexes[dataIndex] &&
-            datum[opts.isHorizontal ? 0 : 1] !== null
+            datum[isHorizontal ? 0 : 1] !== null
           ) {
             showValueIndexes[dataIndex] = seriesIndex;
           }
@@ -404,6 +414,7 @@ export function getLegendProps(
   show: boolean,
   theme: SupersetTheme,
   zoomable = false,
+  legendState?: LegendState,
 ): LegendComponentOption | LegendComponentOption[] {
   const legend: LegendComponentOption | LegendComponentOption[] = {
     orient: [LegendOrientation.Top, LegendOrientation.Bottom].includes(
@@ -413,6 +424,7 @@ export function getLegendProps(
       : 'vertical',
     show,
     type,
+    selected: legendState,
     selector: ['all', 'inverse'],
     selectorLabel: {
       fontFamily: theme.typography.families.sansSerif,
@@ -495,12 +507,6 @@ export function sanitizeHtml(text: string): string {
   return format.encodeHTML(text);
 }
 
-// TODO: Better use other method to maintain this state
-export const currentSeries = {
-  name: '',
-  legend: '',
-};
-
 export function getAxisType(dataType?: GenericDataType): AxisType {
   if (dataType === GenericDataType.TEMPORAL) {
     return AxisType.time;
diff --git a/superset-frontend/src/components/Chart/ChartRenderer.jsx b/superset-frontend/src/components/Chart/ChartRenderer.jsx
index 55f6b66df7..006dd54fcb 100644
--- a/superset-frontend/src/components/Chart/ChartRenderer.jsx
+++ b/superset-frontend/src/components/Chart/ChartRenderer.jsx
@@ -90,6 +90,7 @@ class ChartRenderer extends React.Component {
         (isFeatureEnabled(FeatureFlag.DRILL_TO_DETAIL) ||
           isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)),
       inContextMenu: false,
+      legendState: undefined,
     };
     this.hasQueryResponseChange = false;
 
@@ -102,6 +103,7 @@ class ChartRenderer extends React.Component {
     this.handleOnContextMenu = this.handleOnContextMenu.bind(this);
     this.handleContextMenuSelected = this.handleContextMenuSelected.bind(this);
     this.handleContextMenuClosed = this.handleContextMenuClosed.bind(this);
+    this.handleLegendStateChanged = this.handleLegendStateChanged.bind(this);
     this.onContextMenuFallback = this.onContextMenuFallback.bind(this);
 
     this.hooks = {
@@ -113,6 +115,7 @@ class ChartRenderer extends React.Component {
       setControlValue: this.handleSetControlValue,
       onFilterMenuOpen: this.props.onFilterMenuOpen,
       onFilterMenuClose: this.props.onFilterMenuClose,
+      onLegendStateChanged: this.handleLegendStateChanged,
       setDataMask: dataMask => {
         this.props.actions?.updateDataMask(this.props.chartId, dataMask);
       },
@@ -226,6 +229,10 @@ class ChartRenderer extends React.Component {
     this.setState({ inContextMenu: false });
   }
 
+  handleLegendStateChanged(legendState) {
+    this.setState({ legendState });
+  }
+
   // When viz plugins don't handle `contextmenu` event, fallback handler
   // calls `handleOnContextMenu` with no `filters` param.
   onContextMenuFallback(event) {
@@ -354,6 +361,7 @@ class ChartRenderer extends React.Component {
             noResults={noResultsComponent}
             postTransformProps={postTransformProps}
             emitCrossFilters={emitCrossFilters}
+            legendState={this.state.legendState}
             {...drillToDetailProps}
           />
         </div>