diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Sunburst/Stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Sunburst/Stories.tsx
new file mode 100644
index 0000000000..7742f1ecfe
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Sunburst/Stories.tsx
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { SuperChart, getChartTransformPropsRegistry } from '@superset-ui/core';
+import { boolean, withKnobs } from '@storybook/addon-knobs';
+import {
+ EchartsSunburstChartPlugin,
+ SunburstTransformProps,
+} from '@superset-ui/plugin-chart-echarts';
+import { withResizableChartDemo } from '../../../../shared/components/ResizableChartDemo';
+import data from './data';
+
+new EchartsSunburstChartPlugin()
+ .configure({ key: 'echarts-sunburst' })
+ .register();
+
+getChartTransformPropsRegistry().registerValue(
+ 'echarts-sunburst',
+ SunburstTransformProps,
+);
+
+export default {
+ title: 'Chart Plugins/plugin-chart-echarts/Sunburst',
+ decorators: [withKnobs, withResizableChartDemo],
+};
+
+export const Sunburst = ({ width, height }) => (
+
+);
diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Sunburst/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Sunburst/data.ts
new file mode 100644
index 0000000000..35675465df
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Sunburst/data.ts
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+export default [
+ { genre: 'Adventure', platform: 'Wii', count: 84 },
+ { genre: 'Adventure', platform: 'N64', count: 14 },
+ { genre: 'Adventure', platform: 'XOne', count: 12 },
+ { genre: 'Adventure', platform: 'PS4', count: 19 },
+ { genre: 'Strategy', platform: 'Wii', count: 25 },
+ { genre: 'Strategy', platform: 'PS4', count: 15 },
+ { genre: 'Strategy', platform: 'N64', count: 29 },
+ { genre: 'Strategy', platform: 'XOne', count: 23 },
+ { genre: 'Simulation', platform: 'PS4', count: 15 },
+ { genre: 'Simulation', platform: 'XOne', count: 36 },
+ { genre: 'Simulation', platform: 'N64', count: 20 },
+ { genre: 'Simulation', platform: 'Wii', count: 50 },
+];
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/EchartsSunburst.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/EchartsSunburst.tsx
index 3dd8dc931a..390c830c45 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/EchartsSunburst.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/EchartsSunburst.tsx
@@ -94,22 +94,22 @@ export default function EchartsSunburst(props: SunburstTransformedProps) {
contextmenu: eventParams => {
if (onContextMenu) {
eventParams.event.stop();
+ const { data } = eventParams;
+ const { records } = data;
const treePath = extractTreePathInfo(eventParams.treePathInfo);
- if (treePath.length > 0) {
- const pointerEvent = eventParams.event.event;
- const filters: BinaryQueryObjectFilterClause[] = [];
- if (columns) {
- treePath.forEach((path, i) =>
- filters.push({
- col: columns[i],
- op: '==',
- val: path,
- formattedVal: path,
- }),
- );
- }
- onContextMenu(pointerEvent.clientX, pointerEvent.clientY, filters);
+ const pointerEvent = eventParams.event.event;
+ const filters: BinaryQueryObjectFilterClause[] = [];
+ if (columns?.length) {
+ treePath.forEach((path, i) =>
+ filters.push({
+ col: columns[i],
+ op: '==',
+ val: records[i],
+ formattedVal: path,
+ }),
+ );
}
+ onContextMenu(pointerEvent.clientX, pointerEvent.clientY, filters);
}
},
};
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx
index 7ad3618a5a..2c90dff452 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx
@@ -189,9 +189,10 @@ const config: ControlPanelConfig = {
controls?.secondary_metric?.value !== controls?.metric.value,
),
},
- groupby: {
+ columns: {
label: t('Hierarchy'),
- description: t('This defines the level of the hierarchy'),
+ description: t(`Sets the hierarchy levels of the chart. Each level is
+ represented by one ring with the innermost circle as the top of the hierarchy.`),
},
},
formDataOverrides: formData => ({
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/Sunburst1.png b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/Sunburst1.png
new file mode 100644
index 0000000000..87c140e7c3
Binary files /dev/null and b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/Sunburst1.png differ
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/Sunburst2.png b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/Sunburst2.png
new file mode 100644
index 0000000000..677b2a1966
Binary files /dev/null and b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/Sunburst2.png differ
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/thumbnail.png b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/thumbnail.png
index 7afef30bd4..87c140e7c3 100644
Binary files a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/thumbnail.png and b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/images/thumbnail.png differ
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/index.ts
index fe75a7916f..5ca8d5a8fc 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/index.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/index.ts
@@ -21,6 +21,8 @@ import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
import controlPanel from './controlPanel';
import buildQuery from './buildQuery';
+import example1 from './images/Sunburst1.png';
+import example2 from './images/Sunburst2.png';
export default class EchartsSunburstChartPlugin extends ChartPlugin {
constructor() {
@@ -29,13 +31,13 @@ export default class EchartsSunburstChartPlugin extends ChartPlugin {
controlPanel,
loadChart: () => import('./EchartsSunburst'),
metadata: new ChartMetadata({
- behaviors: [Behavior.INTERACTIVE_CHART],
+ behaviors: [Behavior.INTERACTIVE_CHART, Behavior.DRILL_TO_DETAIL],
category: t('Part of a Whole'),
credits: ['https://echarts.apache.org'],
description: t(
'Uses circles to visualize the flow of data through different stages of a system. Hover over individual paths in the visualization to understand the stages a value took. Useful for multi-stage, multi-group visualizing funnels and pipelines.',
),
- exampleGallery: [],
+ exampleGallery: [{ url: example1 }, { url: example2 }],
name: t('Sunburst Chart v2'),
tags: [
t('ECharts'),
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts
index 9873d1dc32..51e89f8c6c 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts
@@ -16,9 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
-
import {
CategoricalColorNamespace,
+ DataRecordValue,
getColumnLabel,
getMetricLabel,
getNumberFormatter,
@@ -26,21 +26,23 @@ import {
getTimeFormatter,
NumberFormats,
NumberFormatter,
+ SupersetTheme,
t,
} from '@superset-ui/core';
import { EChartsCoreOption } from 'echarts';
-import { SunburstSeriesNodeItemOption } from 'echarts/types/src/chart/sunburst/SunburstSeries';
import { CallbackDataParams } from 'echarts/types/src/util/types';
import { OpacityEnum } from '../constants';
-import { defaultGrid, defaultTooltip } from '../defaults';
+import { defaultGrid } from '../defaults';
import { Refs } from '../types';
import { formatSeriesName, getColtypesMapping } from '../utils/series';
import { treeBuilder, TreeNode } from '../utils/treeBuilder';
import {
EchartsSunburstChartProps,
EchartsSunburstLabelType,
+ NodeItemOption,
SunburstTransformedProps,
} from './types';
+import { getDefaultTooltip } from '../utils/tooltip';
export function getLinearDomain(
treeData: TreeNode[],
@@ -96,6 +98,7 @@ export function formatTooltip({
totalValue,
metricLabel,
secondaryMetricLabel,
+ theme,
}: {
params: CallbackDataParams & {
treePathInfo: {
@@ -109,9 +112,9 @@ export function formatTooltip({
totalValue: number;
metricLabel: string;
secondaryMetricLabel?: string;
+ theme: SupersetTheme;
}): string {
const { data, treePathInfo = [] } = params;
- treePathInfo.shift();
const node = data as TreeNode;
const formattedValue = numberFormatter(node.value);
const formattedSecondaryValue = numberFormatter(node.secondaryValue);
@@ -121,33 +124,43 @@ export function formatTooltip({
node.secondaryValue / node.value,
);
const absolutePercentage = percentFormatter(node.value / totalValue);
- const parentNode = treePathInfo[treePathInfo.length - 1];
+ const parentNode =
+ treePathInfo.length > 2 ? treePathInfo[treePathInfo.length - 2] : undefined;
+
const result = [
- `
${absolutePercentage} of total
`,
+ ``,
+ `
+ ${node.name}
+
`,
+ `
+ ${absolutePercentage} of total
+
`,
];
if (parentNode) {
const conditionalPercentage = percentFormatter(
node.value / parentNode.value,
);
result.push(`
-
- ${conditionalPercentage} of parent
+
+ ${conditionalPercentage} of ${parentNode.name}
`);
}
result.push(
- `
+ `
${metricLabel}: ${formattedValue}${
colorByCategory
? ''
: `, ${secondaryMetricLabel}: ${formattedSecondaryValue}`
}
-
`,
+
`,
colorByCategory
? ''
- : `
- ${metricLabel}/${secondaryMetricLabel}: ${compareValuePercentage}
-
`,
+ : `
${metricLabel}/${secondaryMetricLabel}: ${compareValuePercentage}
`,
);
+ result.push('
');
return result.join('\n');
}
@@ -247,9 +260,14 @@ export default function transformProps(
linearColorScale(totalSecondaryValue / totalValue);
}
- const traverse = (treeNodes: TreeNode[], path: string[]) =>
+ const traverse = (
+ treeNodes: TreeNode[],
+ path: string[],
+ pathRecords?: DataRecordValue[],
+ ) =>
treeNodes.map(treeNode => {
const { name: nodeName, value, secondaryValue, groupBy } = treeNode;
+ const records = [...(pathRecords || []), nodeName];
let name = formatSeriesName(nodeName, {
numberFormatter,
timeFormatter: getTimeFormatter(dateFormat),
@@ -258,10 +276,10 @@ export default function transformProps(
}),
});
const newPath = path.concat(name);
- let item: SunburstSeriesNodeItemOption = {
+ let item: NodeItemOption = {
+ records,
name,
value,
- // @ts-ignore
secondaryValue,
itemStyle: {
color: colorByCategory
@@ -270,7 +288,7 @@ export default function transformProps(
},
};
if (treeNode.children?.length) {
- item.children = traverse(treeNode.children, newPath);
+ item.children = traverse(treeNode.children, newPath, records);
} else {
name = newPath.join(',');
}
@@ -295,7 +313,7 @@ export default function transformProps(
...defaultGrid,
},
tooltip: {
- ...defaultTooltip,
+ ...getDefaultTooltip(refs),
show: !inContextMenu,
trigger: 'item',
formatter: (params: any) =>
@@ -306,6 +324,7 @@ export default function transformProps(
totalValue,
metricLabel,
secondaryMetricLabel,
+ theme,
}),
},
series: [
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/types.ts
index 91030fd7b5..37844addea 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/types.ts
@@ -20,10 +20,12 @@
import {
ChartDataResponseResult,
ChartProps,
+ DataRecordValue,
QueryFormColumn,
QueryFormData,
QueryFormMetric,
} from '@superset-ui/core';
+import { SunburstSeriesNodeItemOption } from 'echarts/types/src/chart/sunburst/SunburstSeries';
import {
BaseTransformedProps,
ContextMenuTransformedProps,
@@ -62,3 +64,8 @@ export type SunburstTransformedProps =
BaseTransformedProps
&
ContextMenuTransformedProps &
CrossFilterTransformedProps;
+
+export type NodeItemOption = SunburstSeriesNodeItemOption & {
+ records: DataRecordValue[];
+ secondaryValue: number;
+};
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts b/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts
index d76de5b53d..c5ada14932 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/defaults.ts
@@ -1,7 +1,3 @@
-import { CallbackDataParams } from 'echarts/types/src/util/types';
-import { LegendOrientation } from './types';
-import { TOOLTIP_POINTER_MARGIN, TOOLTIP_OVERFLOW_MARGIN } from './constants';
-
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -20,66 +16,12 @@ import { TOOLTIP_POINTER_MARGIN, TOOLTIP_OVERFLOW_MARGIN } from './constants';
* specific language governing permissions and limitations
* under the License.
*/
+import { LegendOrientation } from './types';
+
export const defaultGrid = {
containLabel: true,
};
-export const defaultTooltip = {
- position: (
- canvasMousePos: [number, number],
- params: CallbackDataParams,
- tooltipDom: HTMLElement,
- rect: any,
- sizes: { contentSize: [number, number]; viewSize: [number, number] },
- ) => {
- // algorithm copy-pasted from here:
- // https://github.com/apache/echarts/issues/5004#issuecomment-559668309
-
- // The chart canvas position
- const canvasRect = tooltipDom.parentElement
- ?.getElementsByTagName('canvas')[0]
- .getBoundingClientRect();
-
- // The mouse coordinates relative to the whole window
- // The first parameter to the position function is the mouse position relative to the canvas
- const mouseX = canvasMousePos[0] + (canvasRect?.x || 0);
- const mouseY = canvasMousePos[1] + (canvasRect?.y || 0);
-
- // The width and height of the tooltip dom element
- const tooltipWidth = sizes.contentSize[0];
- const tooltipHeight = sizes.contentSize[1];
-
- // Start by placing the tooltip top and right relative to the mouse position
- let xPos = mouseX + TOOLTIP_POINTER_MARGIN;
- let yPos = mouseY - TOOLTIP_POINTER_MARGIN - tooltipHeight;
-
- // The tooltip is overflowing past the right edge of the window
- if (xPos + tooltipWidth >= document.documentElement.clientWidth) {
- // Attempt to place the tooltip to the left of the mouse position
- xPos = mouseX - TOOLTIP_POINTER_MARGIN - tooltipWidth;
-
- // The tooltip is overflowing past the left edge of the window
- if (xPos <= 0)
- // Place the tooltip a fixed distance from the left edge of the window
- xPos = TOOLTIP_OVERFLOW_MARGIN;
- }
-
- // The tooltip is overflowing past the top edge of the window
- if (yPos <= 0) {
- // Attempt to place the tooltip to the bottom of the mouse position
- yPos = mouseY + TOOLTIP_POINTER_MARGIN;
-
- // The tooltip is overflowing past the bottom edge of the window
- if (yPos + tooltipHeight >= document.documentElement.clientHeight)
- // Place the tooltip a fixed distance from the top edge of the window
- yPos = TOOLTIP_OVERFLOW_MARGIN;
- }
-
- // Return the position (converted back to a relative position on the canvas)
- return [xPos - (canvasRect?.x || 0), yPos - (canvasRect?.y || 0)];
- },
-};
-
export const defaultYAxis = {
scale: true,
};
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts
index 32e0416a6b..97916997d4 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/treeBuilder.ts
@@ -16,17 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { DataRecord } from '@superset-ui/core';
+import { DataRecord, DataRecordValue } from '@superset-ui/core';
import _ from 'lodash';
export type TreeNode = {
- name: string;
+ name: DataRecordValue;
value: number;
secondaryValue: number;
groupBy: string;
children?: TreeNode[];
};
+function getMetricValue(datum: DataRecord, metric: string) {
+ return _.isNumber(datum[metric]) ? (datum[metric] as number) : 0;
+}
+
export function treeBuilder(
data: DataRecord[],
groupBy: string[],
@@ -37,7 +41,8 @@ export function treeBuilder(
const curData = _.groupBy(data, curGroupBy);
return _.transform(
curData,
- (result, value, name) => {
+ (result, value, key) => {
+ const name = curData[key][0][curGroupBy]!;
if (!restGroupby.length) {
(value ?? []).forEach(datum => {
const metricValue = getMetricValue(datum, metric);
@@ -81,7 +86,3 @@ export function treeBuilder(
[] as TreeNode[],
);
}
-
-function getMetricValue(datum: DataRecord, metric: string) {
- return _.isNumber(datum[metric]) ? (datum[metric] as number) : 0;
-}
diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js
index 88c1975d01..2bce5ae096 100644
--- a/superset-frontend/src/visualizations/presets/MainPreset.js
+++ b/superset-frontend/src/visualizations/presets/MainPreset.js
@@ -68,6 +68,7 @@ import {
EchartsTreemapChartPlugin,
EchartsMixedTimeseriesChartPlugin,
EchartsTreeChartPlugin,
+ EchartsSunburstChartPlugin,
} from '@superset-ui/plugin-chart-echarts';
import {
SelectFilterPlugin,
@@ -165,6 +166,7 @@ export default class MainPreset extends Preset {
new TimeColumnFilterPlugin().configure({ key: 'filter_timecolumn' }),
new TimeGrainFilterPlugin().configure({ key: 'filter_timegrain' }),
new EchartsTreeChartPlugin().configure({ key: 'tree_chart' }),
+ new EchartsSunburstChartPlugin().configure({ key: 'sunburst_v2' }),
new HandlebarsChartPlugin().configure({ key: 'handlebars' }),
...experimentalplugins,
],