Skip to content

Commit

Permalink
feat(echarts-pie): add string template support for labels (apache#28774)
Browse files Browse the repository at this point in the history
  • Loading branch information
hexcafe authored Jun 12, 2024
1 parent cc492ff commit a067ffb
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export default function transformProps(
labelsOutside,
labelLine,
labelType,
labelTemplate,
legendMargin,
legendOrientation,
legendType,
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type EchartsPieFormData = QueryFormData &
innerRadius: number;
labelLine: boolean;
labelType: EchartsPieLabelType;
labelTemplate: string | null;
labelsOutside: boolean;
metric?: string;
outerRadius: number;
Expand All @@ -56,6 +57,7 @@ export enum EchartsPieLabelType {
KeyPercent = 'key_percent',
KeyValuePercent = 'key_value_percent',
ValuePercent = 'value_percent',
Template = 'template',
}

export interface EchartsPieChartProps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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');
});
});

0 comments on commit a067ffb

Please sign in to comment.