From 3d3c873f3cfee2def4db08af55087b4c5d2df16e Mon Sep 17 00:00:00 2001
From: Yaozong Liu <750188453@qq.com>
Date: Tue, 25 May 2021 23:58:57 +0800
Subject: [PATCH] feat(plugin-chart-echarts): add x-filtering to treemap
(#1115)
* fix(plugin-chart-echarts): add x-filtering to treemap
* fix(plugin-chart-echarts): add behavior
* fix(plugin-chart-echarts): one series at a time
* fix(plugin-chart-echarts): type
* fix(plugin-chart-echarts): color constant
---
.../src/Treemap/EchartsTreemap.tsx | 82 ++++++++++++++++++-
.../src/Treemap/constants.ts | 37 +++++++++
.../src/Treemap/controlPanel.tsx | 25 +++++-
.../plugin-chart-echarts/src/Treemap/index.ts | 3 +-
.../src/Treemap/transformProps.ts | 77 +++++++++++------
.../plugin-chart-echarts/src/Treemap/types.ts | 19 ++++-
6 files changed, 211 insertions(+), 32 deletions(-)
create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/constants.ts
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx
index c6250b4ae29..9c8d466ef99 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/EchartsTreemap.tsx
@@ -16,10 +16,84 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
-import { EchartsProps } from '../types';
+import React, { useCallback } from 'react';
import Echart from '../components/Echart';
+import { EventHandlers } from '../types';
+import { extractTreePathInfo } from './constants';
+import { TreemapTransformedProps } from './types';
-export default function EchartsTreemap({ height, width, echartOptions }: EchartsProps) {
- return ;
+export default function EchartsTreemap({
+ height,
+ width,
+ echartOptions,
+ setDataMask,
+ labelMap,
+ groupby,
+ selectedValues,
+ formData,
+}: TreemapTransformedProps) {
+ const handleChange = useCallback(
+ (values: string[]) => {
+ if (!formData.emitFilter) {
+ return;
+ }
+
+ const groupbyValues = values.map(value => labelMap[value]);
+
+ setDataMask({
+ extraFormData: {
+ filters:
+ values.length === 0
+ ? []
+ : groupby.map((col, idx) => {
+ const val = groupbyValues.map(v => v[idx]);
+ if (val === null || val === undefined)
+ return {
+ col,
+ op: 'IS NULL',
+ };
+ return {
+ col,
+ op: 'IN',
+ val: val as (string | number | boolean)[],
+ };
+ }),
+ },
+ filterState: {
+ value: groupbyValues.length ? groupbyValues : null,
+ selectedValues: values.length ? values : null,
+ },
+ });
+ },
+ [groupby, labelMap, setDataMask, selectedValues],
+ );
+
+ const eventHandlers: EventHandlers = {
+ click: props => {
+ const { data, treePathInfo } = props;
+ // do noting when clicking the parent node
+ if (data?.children) {
+ return;
+ }
+ const { treePath } = extractTreePathInfo(treePathInfo);
+ const name = treePath.join(',');
+ const values = Object.values(selectedValues);
+ if (values.includes(name)) {
+ handleChange(values.filter(v => v !== name));
+ } else {
+ handleChange([name]);
+ }
+ },
+ };
+
+ return (
+
+ );
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/constants.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/constants.ts
new file mode 100644
index 00000000000..b31e9da7921
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/constants.ts
@@ -0,0 +1,37 @@
+/**
+ * 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 { TreePathInfo } from './types';
+
+export const COLOR_SATURATION = [0.4, 0.7];
+export const LABEL_FONTSIZE = 11;
+export const BORDER_WIDTH = 2;
+export const GAP_WIDTH = 2;
+export const COLOR_ALPHA = 0.3;
+export const BORDER_COLOR = '#fff';
+
+export const extractTreePathInfo = (treePathInfo: TreePathInfo[] | undefined) => {
+ const treePath = (treePathInfo ?? [])
+ .map(pathInfo => pathInfo?.name || '')
+ .filter(path => path !== '');
+
+ // the 1st tree path is metric label
+ const metricLabel = treePath.shift() || '';
+ return { metricLabel, treePath };
+};
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
index d8e09df8328..b05845ec056 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
-import { t } from '@superset-ui/core';
+import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
import {
ControlPanelConfig,
D3_FORMAT_DOCS,
@@ -27,7 +27,14 @@ import {
} from '@superset-ui/chart-controls';
import { DEFAULT_FORM_DATA } from './types';
-const { labelType, numberFormat, showLabels, showUpperLabels, dateFormat } = DEFAULT_FORM_DATA;
+const {
+ labelType,
+ numberFormat,
+ showLabels,
+ showUpperLabels,
+ dateFormat,
+ emitFilter,
+} = DEFAULT_FORM_DATA;
const config: ControlPanelConfig = {
controlPanelSections: [
@@ -57,6 +64,20 @@ const config: ControlPanelConfig = {
expanded: true,
controlSetRows: [
['color_scheme'],
+ isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)
+ ? [
+ {
+ name: 'emit_filter',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Enable emitting filters'),
+ default: emitFilter,
+ renderTrigger: true,
+ description: t('Enable emmiting filters.'),
+ },
+ },
+ ]
+ : [],
[
{t('Labels')}
],
[
{
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/index.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/index.ts
index 2e3954853d1..947f48ba6f8 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/index.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/index.ts
@@ -17,7 +17,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { ChartMetadata, ChartPlugin, t } from '@superset-ui/core';
+import { Behavior, ChartMetadata, ChartPlugin, t } from '@superset-ui/core';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
@@ -44,6 +44,7 @@ export default class EchartsTreemapChartPlugin extends ChartPlugin<
controlPanel,
loadChart: () => import('./EchartsTreemap'),
metadata: new ChartMetadata({
+ behaviors: [Behavior.INTERACTIVE_CHART],
credits: ['https://echarts.apache.org'],
description: 'Treemap (Apache ECharts)',
name: t('Treemap v2'),
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts
index ce0ba854e95..bbec3216f8f 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts
@@ -19,6 +19,7 @@
import {
CategoricalColorNamespace,
DataRecord,
+ DataRecordValue,
getMetricLabel,
getNumberFormatter,
getTimeFormatter,
@@ -34,10 +35,19 @@ import {
EchartsTreemapFormData,
EchartsTreemapLabelType,
TreemapSeriesCallbackDataParams,
+ TreemapTransformedProps,
} from './types';
-import { EchartsProps } from '../types';
import { formatSeriesName, getColtypesMapping } from '../utils/series';
import { defaultTooltip } from '../defaults';
+import {
+ COLOR_ALPHA,
+ COLOR_SATURATION,
+ BORDER_WIDTH,
+ GAP_WIDTH,
+ LABEL_FONTSIZE,
+ extractTreePathInfo,
+ BORDER_COLOR,
+} from './constants';
export function formatLabel({
params,
@@ -72,6 +82,7 @@ export function formatTooltip({
}): string {
const { value, treePathInfo = [] } = params;
const formattedValue = numberFormatter(value as number);
+ const { metricLabel, treePath } = extractTreePathInfo(treePathInfo);
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
let formattedPercent = '';
@@ -85,12 +96,6 @@ export function formatTooltip({
formattedPercent = percentFormatter(percent);
}
- const treePath = (treePathInfo ?? [])
- .map(pathInfo => pathInfo?.name || '')
- .filter(path => path !== '');
- // the 1st tree path is metric label
- const metricLabel = treePath.shift() || '';
-
// groupby1/groupby2/...
// metric: value (percent of parent)
return [
@@ -100,9 +105,12 @@ export function formatTooltip({
].join('');
}
-export default function transformProps(chartProps: EchartsTreemapChartProps): EchartsProps {
- const { formData, height, queriesData, width } = chartProps;
+export default function transformProps(
+ chartProps: EchartsTreemapChartProps,
+): TreemapTransformedProps {
+ const { formData, height, queriesData, width, hooks, filterState } = chartProps;
const { data = [] } = queriesData[0];
+ const { setDataMask = () => {} } = hooks;
const coltypeMapping = getColtypesMapping(queriesData[0]);
const {
@@ -116,6 +124,7 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
showLabels,
showUpperLabels,
dashboardId,
+ emitFilter,
}: EchartsTreemapFormData = {
...DEFAULT_TREEMAP_FORM_DATA,
...formData,
@@ -130,11 +139,14 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
labelType,
});
+ const columnsLabelMap = new Map();
+
const transformer = (
data: DataRecord[],
groupbyData: string[],
metric: string,
depth: number,
+ path: string[],
): TreemapSeriesNodeItemOption[] => {
const [currGroupby, ...restGroupby] = groupbyData;
const currGrouping = groupBy(data, currGroupby);
@@ -148,10 +160,22 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
timeFormatter: getTimeFormatter(dateFormat),
...(coltypeMapping[currGroupby] && { coltype: coltypeMapping[currGroupby] }),
});
- result.push({
+ const item: TreemapSeriesNodeItemOption = {
name,
value: isNumber(datum[metric]) ? (datum[metric] as number) : 0,
- });
+ };
+ const joinedName = path.concat(name).join(',');
+ // map(joined_name: [columnLabel_1, columnLabel_2, ...])
+ columnsLabelMap.set(joinedName, path.concat(name));
+ if (filterState.selectedValues && !filterState.selectedValues.includes(joinedName)) {
+ item.itemStyle = {
+ colorAlpha: COLOR_ALPHA,
+ };
+ item.label = {
+ color: `rgba(0, 0, 0, ${COLOR_ALPHA})`,
+ };
+ }
+ result.push(item);
});
},
[] as TreemapSeriesNodeItemOption[],
@@ -165,7 +189,7 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
timeFormatter: getTimeFormatter(dateFormat),
...(coltypeMapping[currGroupby] && { coltype: coltypeMapping[currGroupby] }),
});
- const children = transformer(value, restGroupby, metric, depth + 1);
+ const children = transformer(value, restGroupby, metric, depth + 1, path.concat(name));
result.push({
name,
children,
@@ -178,12 +202,12 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
// sort according to the area and then take the color value in order
return sortedData.map(child => ({
...child,
- colorSaturation: [0.4, 0.7],
+ colorSaturation: COLOR_SATURATION,
itemStyle: {
- borderColor: '#fff',
+ borderColor: BORDER_COLOR,
color: colorFn(`${child.name}_${depth}`),
- borderWidth: 2,
- gapWidth: 2,
+ borderWidth: BORDER_WIDTH,
+ gapWidth: GAP_WIDTH,
},
}));
};
@@ -193,16 +217,16 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
const transformedData: TreemapSeriesNodeItemOption[] = [
{
name: metricLabel,
- colorSaturation: [0.4, 0.7],
+ colorSaturation: COLOR_SATURATION,
itemStyle: {
- borderColor: '#fff',
- borderWidth: 2,
- gapWidth: 2,
+ borderColor: BORDER_COLOR,
+ borderWidth: BORDER_WIDTH,
+ gapWidth: GAP_WIDTH,
},
upperLabel: {
show: false,
},
- children: transformer(data, groupby, metricLabel, initialDepth),
+ children: transformer(data, groupby, metricLabel, initialDepth, []),
},
];
@@ -233,7 +257,6 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
show: false,
emptyItemWidth: 25,
},
- squareRatio: 0.5 * (1 + Math.sqrt(5)), // golden ratio
emphasis: {
label: {
show: true,
@@ -245,13 +268,13 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
position: labelPosition,
formatter,
color: '#000',
- fontSize: 11,
+ fontSize: LABEL_FONTSIZE,
},
upperLabel: {
show: showUpperLabels,
formatter,
textBorderColor: 'transparent',
- fontSize: 11,
+ fontSize: LABEL_FONTSIZE,
},
data: transformedData,
},
@@ -271,8 +294,14 @@ export default function transformProps(chartProps: EchartsTreemapChartProps): Ec
};
return {
+ formData,
width,
height,
echartOptions,
+ setDataMask,
+ emitFilter,
+ labelMap: Object.fromEntries(columnsLabelMap),
+ groupby,
+ selectedValues: filterState.selectedValues || [],
};
}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/types.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/types.ts
index 8d7a74399a9..90494833410 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/types.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Treemap/types.ts
@@ -19,9 +19,12 @@
import {
ChartDataResponseResult,
ChartProps,
+ DataRecordValue,
QueryFormData,
QueryFormMetric,
+ SetDataMaskHook,
} from '@superset-ui/core';
+import { EChartsOption } from 'echarts';
import { CallbackDataParams } from 'echarts/types/src/util/types';
import { LabelPositionEnum } from '../types';
@@ -36,6 +39,7 @@ export type EchartsTreemapFormData = QueryFormData & {
numberFormat: string;
dateFormat: string;
dashboardId?: number;
+ emitFilter: boolean;
};
export enum EchartsTreemapLabelType {
@@ -57,9 +61,10 @@ export const DEFAULT_FORM_DATA: Partial = {
showLabels: true,
showUpperLabels: true,
dateFormat: 'smart_date',
+ emitFilter: false,
};
-interface TreePathInfo {
+export interface TreePathInfo {
name: string;
dataIndex: number;
value: number | number[];
@@ -67,3 +72,15 @@ interface TreePathInfo {
export interface TreemapSeriesCallbackDataParams extends CallbackDataParams {
treePathInfo?: TreePathInfo[];
}
+
+export interface TreemapTransformedProps {
+ formData: EchartsTreemapFormData;
+ height: number;
+ width: number;
+ echartOptions: EChartsOption;
+ emitFilter: boolean;
+ setDataMask: SetDataMaskHook;
+ labelMap: Record;
+ groupby: string[];
+ selectedValues: Record;
+}