feat(echarts-pie): add string template support for labels (#28774)
This commit is contained in:
parent
cc492ffed4
commit
a067ffb92d
|
|
@ -123,11 +123,31 @@ const config: ControlPanelConfig = {
|
|||
['key_percent', t('Category and Percentage')],
|
||||
['key_value_percent', t('Category, Value and Percentage')],
|
||||
['value_percent', t('Value and Percentage')],
|
||||
['template', t('Template')],
|
||||
],
|
||||
description: t('What should be shown on the label?'),
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'label_template',
|
||||
config: {
|
||||
type: 'TextControl',
|
||||
label: t('Label Template'),
|
||||
renderTrigger: true,
|
||||
description: t(
|
||||
'Format data labels. ' +
|
||||
'Use variables: {name}, {value}, {percent}. ' +
|
||||
'\\n represents a new line. ' +
|
||||
'ECharts compatibility:\n' +
|
||||
'{a} (series), {b} (name), {c} (value), {d} (percentage)',
|
||||
),
|
||||
visibility: ({ controls }: ControlPanelsContainerProps) =>
|
||||
controls?.label_type?.value === 'template',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'number_format',
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ export default function transformProps(
|
|||
labelsOutside,
|
||||
labelLine,
|
||||
labelType,
|
||||
labelTemplate,
|
||||
legendMargin,
|
||||
legendOrientation,
|
||||
legendType,
|
||||
|
|
@ -242,6 +243,38 @@ export default function transformProps(
|
|||
{},
|
||||
);
|
||||
|
||||
const formatTemplate = (
|
||||
template: string,
|
||||
formattedParams: {
|
||||
name: string;
|
||||
value: string;
|
||||
percent: string;
|
||||
},
|
||||
rawParams: CallbackDataParams,
|
||||
) => {
|
||||
// This function supports two forms of template variables:
|
||||
// 1. {name}, {value}, {percent}, for values formatted by number formatter.
|
||||
// 2. {a}, {b}, {c}, {d}, compatible with ECharts formatter.
|
||||
//
|
||||
// \n is supported to represent a new line.
|
||||
|
||||
const items = {
|
||||
'{name}': formattedParams.name,
|
||||
'{value}': formattedParams.value,
|
||||
'{percent}': formattedParams.percent,
|
||||
'{a}': rawParams.seriesName || '',
|
||||
'{b}': rawParams.name,
|
||||
'{c}': `${rawParams.value}`,
|
||||
'{d}': `${rawParams.percent}`,
|
||||
'\\n': '\n',
|
||||
};
|
||||
|
||||
return Object.entries(items).reduce(
|
||||
(acc, [key, value]) => acc.replaceAll(key, value),
|
||||
template,
|
||||
);
|
||||
};
|
||||
|
||||
const formatter = (params: CallbackDataParams) => {
|
||||
const [name, formattedValue, formattedPercent] = parseParams({
|
||||
params,
|
||||
|
|
@ -262,6 +295,19 @@ export default function transformProps(
|
|||
return `${name}: ${formattedPercent}`;
|
||||
case EchartsPieLabelType.ValuePercent:
|
||||
return `${formattedValue} (${formattedPercent})`;
|
||||
case EchartsPieLabelType.Template:
|
||||
if (!labelTemplate) {
|
||||
return '';
|
||||
}
|
||||
return formatTemplate(
|
||||
labelTemplate,
|
||||
{
|
||||
name,
|
||||
value: formattedValue,
|
||||
percent: formattedPercent,
|
||||
},
|
||||
params,
|
||||
);
|
||||
default:
|
||||
return name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export type EchartsPieFormData = QueryFormData &
|
|||
innerRadius: number;
|
||||
labelLine: boolean;
|
||||
labelType: EchartsPieLabelType;
|
||||
labelTemplate: string | null;
|
||||
labelsOutside: boolean;
|
||||
metric?: string;
|
||||
outerRadius: number;
|
||||
|
|
@ -56,6 +57,7 @@ export enum EchartsPieLabelType {
|
|||
KeyPercent = 'key_percent',
|
||||
KeyValuePercent = 'key_value_percent',
|
||||
ValuePercent = 'value_percent',
|
||||
Template = 'template',
|
||||
}
|
||||
|
||||
export interface EchartsPieChartProps
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import {
|
|||
SqlaFormData,
|
||||
supersetTheme,
|
||||
} from '@superset-ui/core';
|
||||
import { LabelFormatterCallback, PieSeriesOption } from 'echarts';
|
||||
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
||||
import transformProps, { parseParams } from '../../src/Pie/transformProps';
|
||||
import { EchartsPieChartProps } from '../../src/Pie/types';
|
||||
|
||||
|
|
@ -101,3 +103,112 @@ describe('formatPieLabel', () => {
|
|||
).toEqual(['<NULL>', '1.23k', '12.34%']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pie label string template', () => {
|
||||
const params: CallbackDataParams = {
|
||||
componentType: '',
|
||||
componentSubType: '',
|
||||
componentIndex: 0,
|
||||
seriesType: 'pie',
|
||||
seriesIndex: 0,
|
||||
seriesId: 'seriesId',
|
||||
seriesName: 'test',
|
||||
name: 'Tablet',
|
||||
dataIndex: 0,
|
||||
data: {},
|
||||
value: 123456,
|
||||
percent: 55.5,
|
||||
$vars: [],
|
||||
};
|
||||
|
||||
const getChartProps = (form: Partial<SqlaFormData>): EchartsPieChartProps => {
|
||||
const formData: SqlaFormData = {
|
||||
colorScheme: 'bnbColors',
|
||||
datasource: '3__table',
|
||||
granularity_sqla: 'ds',
|
||||
metric: 'sum__num',
|
||||
groupby: ['foo', 'bar'],
|
||||
viz_type: 'my_viz',
|
||||
...form,
|
||||
};
|
||||
|
||||
return new ChartProps({
|
||||
formData,
|
||||
width: 800,
|
||||
height: 600,
|
||||
queriesData: [
|
||||
{
|
||||
data: [
|
||||
{ foo: 'Sylvester', bar: 1, sum__num: 10 },
|
||||
{ foo: 'Arnold', bar: 2, sum__num: 2.5 },
|
||||
],
|
||||
},
|
||||
],
|
||||
theme: supersetTheme,
|
||||
}) as EchartsPieChartProps;
|
||||
};
|
||||
|
||||
const format = (form: Partial<SqlaFormData>) => {
|
||||
const props = transformProps(getChartProps(form));
|
||||
expect(props).toEqual(
|
||||
expect.objectContaining({
|
||||
width: 800,
|
||||
height: 600,
|
||||
echartOptions: expect.objectContaining({
|
||||
series: [
|
||||
expect.objectContaining({
|
||||
avoidLabelOverlap: true,
|
||||
data: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'Arnold, 2',
|
||||
value: 2.5,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'Sylvester, 1',
|
||||
value: 10,
|
||||
}),
|
||||
]),
|
||||
label: expect.objectContaining({
|
||||
formatter: expect.any(Function),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const formatter = (props.echartOptions.series as PieSeriesOption[])[0]!
|
||||
.label?.formatter;
|
||||
|
||||
return (formatter as LabelFormatterCallback)(params);
|
||||
};
|
||||
|
||||
it('should generate a valid pie chart label with template', () => {
|
||||
expect(
|
||||
format({
|
||||
label_type: 'template',
|
||||
label_template: '{name}:{value}\n{percent}',
|
||||
}),
|
||||
).toEqual('Tablet:123k\n55.50%');
|
||||
});
|
||||
|
||||
it('should be formatted using the number formatter', () => {
|
||||
expect(
|
||||
format({
|
||||
label_type: 'template',
|
||||
label_template: '{name}:{value}\n{percent}',
|
||||
number_format: ',d',
|
||||
}),
|
||||
).toEqual('Tablet:123,456\n55.50%');
|
||||
});
|
||||
|
||||
it('should be compatible with ECharts raw variable syntax', () => {
|
||||
expect(
|
||||
format({
|
||||
label_type: 'template',
|
||||
label_template: '{b}:{c}\n{d}',
|
||||
number_format: ',d',
|
||||
}),
|
||||
).toEqual('Tablet:123456\n55.5');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue