mirror of https://github.com/apache/superset.git
feat: Adds the ECharts Sankey chart
This commit is contained in:
parent
fe3ba12801
commit
d14da22e75
|
@ -33,7 +33,7 @@ const metadata = new ChartMetadata({
|
||||||
{ url: example1, description: t('Demographics') },
|
{ url: example1, description: t('Demographics') },
|
||||||
{ url: example2, description: t('Survey Responses') },
|
{ url: example2, description: t('Survey Responses') },
|
||||||
],
|
],
|
||||||
name: t('Sankey Diagram'),
|
name: t('Sankey Diagram (legacy)'),
|
||||||
tags: [
|
tags: [
|
||||||
t('Categorical'),
|
t('Categorical'),
|
||||||
t('Directional'),
|
t('Directional'),
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* 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 { SankeyTransformedProps } from './types';
|
||||||
|
import Echart from '../components/Echart';
|
||||||
|
|
||||||
|
export default function Sankey(props: SankeyTransformedProps) {
|
||||||
|
const { height, width, echartOptions, refs } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Echart
|
||||||
|
refs={refs}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
echartOptions={echartOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
import { buildQueryContext } from '@superset-ui/core';
|
||||||
|
import { SankeyFormData } from './types';
|
||||||
|
|
||||||
|
export default function buildQuery(formData: SankeyFormData) {
|
||||||
|
const { metric, sort_by_metric, source, target } = formData;
|
||||||
|
const groupby = [source, target];
|
||||||
|
return buildQueryContext(formData, baseQueryObject => [
|
||||||
|
{
|
||||||
|
...baseQueryObject,
|
||||||
|
groupby,
|
||||||
|
...(sort_by_metric && { orderby: [[metric, false]] }),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/**
|
||||||
|
* 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 { t, validateNonEmpty } from '@superset-ui/core';
|
||||||
|
import {
|
||||||
|
ControlPanelConfig,
|
||||||
|
dndGroupByControl,
|
||||||
|
} from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
|
const config: ControlPanelConfig = {
|
||||||
|
controlPanelSections: [
|
||||||
|
{
|
||||||
|
label: t('Query'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'source',
|
||||||
|
config: {
|
||||||
|
...dndGroupByControl,
|
||||||
|
label: t('Source'),
|
||||||
|
multi: false,
|
||||||
|
description: t(
|
||||||
|
'The column to be used as the source of the edge.',
|
||||||
|
),
|
||||||
|
validators: [validateNonEmpty],
|
||||||
|
freeForm: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'target',
|
||||||
|
config: {
|
||||||
|
...dndGroupByControl,
|
||||||
|
label: t('Target'),
|
||||||
|
multi: false,
|
||||||
|
description: t(
|
||||||
|
'The column to be used as the target of the edge.',
|
||||||
|
),
|
||||||
|
validators: [validateNonEmpty],
|
||||||
|
freeForm: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['metric'],
|
||||||
|
['adhoc_filters'],
|
||||||
|
['row_limit'],
|
||||||
|
['sort_by_metric'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Chart Options'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [['color_scheme']],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* regardin
|
||||||
|
* g 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 { Behavior, ChartMetadata, ChartPlugin, t } from '@superset-ui/core';
|
||||||
|
import buildQuery from './buildQuery';
|
||||||
|
import controlPanel from './controlPanel';
|
||||||
|
import transformProps from './transformProps';
|
||||||
|
import thumbnail from './images/thumbnail.png';
|
||||||
|
import example1 from './images/example1.png';
|
||||||
|
import example2 from './images/example2.png';
|
||||||
|
import { SankeyChartProps, SankeyFormData } from './types';
|
||||||
|
|
||||||
|
export default class EchartsSankeyChartPlugin extends ChartPlugin<
|
||||||
|
SankeyFormData,
|
||||||
|
SankeyChartProps
|
||||||
|
> {
|
||||||
|
/**
|
||||||
|
* The constructor is used to pass relevant metadata and callbacks that get
|
||||||
|
* registered in respective registries that are used throughout the library
|
||||||
|
* and application. A more thorough description of each property is given in
|
||||||
|
* the respective imported file.
|
||||||
|
*
|
||||||
|
* It is worth noting that `buildQuery` and is optional, and only needed for
|
||||||
|
* advanced visualizations that require either post processing operations
|
||||||
|
* (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
buildQuery,
|
||||||
|
controlPanel,
|
||||||
|
loadChart: () => import('./Sankey'),
|
||||||
|
metadata: new ChartMetadata({
|
||||||
|
behaviors: [Behavior.InteractiveChart],
|
||||||
|
credits: ['https://echarts.apache.org'],
|
||||||
|
category: t('TODO'),
|
||||||
|
description: t(`TODO`),
|
||||||
|
exampleGallery: [{ url: example1 }, { url: example2 }],
|
||||||
|
name: t('Sankey Chart'),
|
||||||
|
tags: [],
|
||||||
|
thumbnail,
|
||||||
|
}),
|
||||||
|
transformProps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/**
|
||||||
|
* 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 { EChartsOption, SankeySeriesOption } from 'echarts';
|
||||||
|
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
||||||
|
import {
|
||||||
|
CategoricalColorNamespace,
|
||||||
|
NumberFormats,
|
||||||
|
getColumnLabel,
|
||||||
|
getMetricLabel,
|
||||||
|
getNumberFormatter,
|
||||||
|
tooltipHtml,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { SankeyChartProps, SankeyTransformedProps } from './types';
|
||||||
|
import { Refs } from '../types';
|
||||||
|
import { getDefaultTooltip } from '../utils/tooltip';
|
||||||
|
import { getPercentFormatter } from '../utils/formatters';
|
||||||
|
|
||||||
|
type Link = { source: string; target: string; value: number };
|
||||||
|
|
||||||
|
export default function transformProps(
|
||||||
|
chartProps: SankeyChartProps,
|
||||||
|
): SankeyTransformedProps {
|
||||||
|
const refs: Refs = {};
|
||||||
|
const { formData, height, hooks, queriesData, width } = chartProps;
|
||||||
|
const { onLegendStateChanged } = hooks;
|
||||||
|
const { colorScheme, metric, source, target } = formData;
|
||||||
|
const { data } = queriesData[0];
|
||||||
|
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||||
|
const metricLabel = getMetricLabel(metric);
|
||||||
|
const valueFormatter = getNumberFormatter(NumberFormats.FLOAT_2_POINT);
|
||||||
|
const percentFormatter = getPercentFormatter(NumberFormats.PERCENT_2_POINT);
|
||||||
|
|
||||||
|
const links: Link[] = [];
|
||||||
|
const set = new Set<string>();
|
||||||
|
data.forEach(datum => {
|
||||||
|
const sourceName = String(datum[getColumnLabel(source)]);
|
||||||
|
const targetName = String(datum[getColumnLabel(target)]);
|
||||||
|
const value = datum[metricLabel] as number;
|
||||||
|
set.add(sourceName);
|
||||||
|
set.add(targetName);
|
||||||
|
links.push({
|
||||||
|
source: sourceName,
|
||||||
|
target: targetName,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const seriesData: NonNullable<SankeySeriesOption['data']> = Array.from(
|
||||||
|
set,
|
||||||
|
).map(name => ({
|
||||||
|
name,
|
||||||
|
itemStyle: {
|
||||||
|
color: colorFn(name),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// stores a map with the total values for each node considering the links
|
||||||
|
const nodeValues = new Map<string, number>();
|
||||||
|
links.forEach(link => {
|
||||||
|
const { source, target, value } = link;
|
||||||
|
const sourceValue = nodeValues.get(source) || 0;
|
||||||
|
const targetValue = nodeValues.get(target) || 0;
|
||||||
|
nodeValues.set(source, sourceValue + value);
|
||||||
|
nodeValues.set(target, targetValue + value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const tooltipFormatter = (params: CallbackDataParams) => {
|
||||||
|
const { name, data } = params;
|
||||||
|
const value = params.value as number;
|
||||||
|
const rows = [[metricLabel, valueFormatter.format(value)]];
|
||||||
|
const { source, target } = data as Link;
|
||||||
|
if (source && target) {
|
||||||
|
rows.push([
|
||||||
|
`% (${source})`,
|
||||||
|
percentFormatter.format(value / nodeValues.get(source)!),
|
||||||
|
]);
|
||||||
|
rows.push([
|
||||||
|
`% (${target})`,
|
||||||
|
percentFormatter.format(value / nodeValues.get(target)!),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return tooltipHtml(rows, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const echartOptions: EChartsOption = {
|
||||||
|
series: {
|
||||||
|
animation: false,
|
||||||
|
data: seriesData,
|
||||||
|
lineStyle: {
|
||||||
|
color: 'source',
|
||||||
|
},
|
||||||
|
links,
|
||||||
|
type: 'sankey',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
...getDefaultTooltip(refs),
|
||||||
|
formatter: tooltipFormatter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
refs,
|
||||||
|
formData,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
echartOptions,
|
||||||
|
onLegendStateChanged,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
QueryFormColumn,
|
||||||
|
QueryFormData,
|
||||||
|
QueryFormMetric,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { BaseChartProps, BaseTransformedProps } from '../types';
|
||||||
|
|
||||||
|
export type SankeyFormData = QueryFormData & {
|
||||||
|
colorScheme: string;
|
||||||
|
metric: QueryFormMetric;
|
||||||
|
source: QueryFormColumn;
|
||||||
|
target: QueryFormColumn;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SankeyChartProps extends BaseChartProps<SankeyFormData> {
|
||||||
|
formData: SankeyFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SankeyTransformedProps = BaseTransformedProps<SankeyFormData> & {};
|
|
@ -41,6 +41,7 @@ export {
|
||||||
} from './BigNumber';
|
} from './BigNumber';
|
||||||
export { default as EchartsSunburstChartPlugin } from './Sunburst';
|
export { default as EchartsSunburstChartPlugin } from './Sunburst';
|
||||||
export { default as EchartsBubbleChartPlugin } from './Bubble';
|
export { default as EchartsBubbleChartPlugin } from './Bubble';
|
||||||
|
export { default as EchartsSankeyChartPlugin } from './Sankey';
|
||||||
export { default as EchartsWaterfallChartPlugin } from './Waterfall';
|
export { default as EchartsWaterfallChartPlugin } from './Waterfall';
|
||||||
|
|
||||||
export { default as BoxPlotTransformProps } from './BoxPlot/transformProps';
|
export { default as BoxPlotTransformProps } from './BoxPlot/transformProps';
|
||||||
|
@ -58,6 +59,7 @@ export { default as SunburstTransformProps } from './Sunburst/transformProps';
|
||||||
export { default as BubbleTransformProps } from './Bubble/transformProps';
|
export { default as BubbleTransformProps } from './Bubble/transformProps';
|
||||||
export { default as WaterfallTransformProps } from './Waterfall/transformProps';
|
export { default as WaterfallTransformProps } from './Waterfall/transformProps';
|
||||||
export { default as HistogramTransformProps } from './Histogram/transformProps';
|
export { default as HistogramTransformProps } from './Histogram/transformProps';
|
||||||
|
export { default as SankeyTransformProps } from './Sankey/transformProps';
|
||||||
|
|
||||||
export { DEFAULT_FORM_DATA as TimeseriesDefaultFormData } from './Timeseries/constants';
|
export { DEFAULT_FORM_DATA as TimeseriesDefaultFormData } from './Timeseries/constants';
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ import {
|
||||||
EchartsHistogramChartPlugin,
|
EchartsHistogramChartPlugin,
|
||||||
EchartsRadarChartPlugin,
|
EchartsRadarChartPlugin,
|
||||||
EchartsFunnelChartPlugin,
|
EchartsFunnelChartPlugin,
|
||||||
|
EchartsSankeyChartPlugin,
|
||||||
EchartsTreemapChartPlugin,
|
EchartsTreemapChartPlugin,
|
||||||
EchartsMixedTimeseriesChartPlugin,
|
EchartsMixedTimeseriesChartPlugin,
|
||||||
EchartsTreeChartPlugin,
|
EchartsTreeChartPlugin,
|
||||||
|
@ -112,6 +113,7 @@ export default class MainPreset extends Preset {
|
||||||
new DistBarChartPlugin().configure({ key: 'dist_bar' }),
|
new DistBarChartPlugin().configure({ key: 'dist_bar' }),
|
||||||
new EventFlowChartPlugin().configure({ key: 'event_flow' }),
|
new EventFlowChartPlugin().configure({ key: 'event_flow' }),
|
||||||
new EchartsFunnelChartPlugin().configure({ key: 'funnel' }),
|
new EchartsFunnelChartPlugin().configure({ key: 'funnel' }),
|
||||||
|
new EchartsSankeyChartPlugin().configure({ key: 'sankey_v2' }),
|
||||||
new EchartsTreemapChartPlugin().configure({ key: 'treemap_v2' }),
|
new EchartsTreemapChartPlugin().configure({ key: 'treemap_v2' }),
|
||||||
new EchartsGaugeChartPlugin().configure({ key: 'gauge_chart' }),
|
new EchartsGaugeChartPlugin().configure({ key: 'gauge_chart' }),
|
||||||
new EchartsGraphChartPlugin().configure({ key: 'graph_chart' }),
|
new EchartsGraphChartPlugin().configure({ key: 'graph_chart' }),
|
||||||
|
|
Loading…
Reference in New Issue