This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 6a83b6fd87 fix(pie chart): Total now positioned correctly with all
Legend positions, and respects theming (#34435)
6a83b6fd87 is described below
commit 6a83b6fd8782758666655191f9c962692b3096fc
Author: Evan Rusackas <[email protected]>
AuthorDate: Fri Aug 1 12:00:23 2025 -0700
fix(pie chart): Total now positioned correctly with all Legend positions,
and respects theming (#34435)
---
.../plugin-chart-echarts/src/Pie/transformProps.ts | 29 ++--
.../test/Pie/transformProps.test.ts | 151 +++++++++++++++++++++
2 files changed, 166 insertions(+), 14 deletions(-)
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
index c0ec8f9242..e3980a02b3 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
@@ -95,27 +95,27 @@ function getTotalValuePadding({
top: donut ? 'middle' : '0',
left: 'center',
};
- const LEGEND_HEIGHT = 15;
- const LEGEND_WIDTH = 215;
if (chartPadding.top) {
padding.top = donut
- ? `${50 + ((chartPadding.top - LEGEND_HEIGHT) / height / 2) * 100}%`
- : `${((chartPadding.top + LEGEND_HEIGHT) / height) * 100}%`;
+ ? `${50 + (chartPadding.top / height / 2) * 100}%`
+ : `${(chartPadding.top / height) * 100}%`;
}
if (chartPadding.bottom) {
padding.top = donut
- ? `${50 - ((chartPadding.bottom + LEGEND_HEIGHT) / height / 2) * 100}%`
+ ? `${50 - (chartPadding.bottom / height / 2) * 100}%`
: '0';
}
if (chartPadding.left) {
- padding.left = `${
- 50 + ((chartPadding.left - LEGEND_WIDTH) / width / 2) * 100
- }%`;
+ // When legend is on the left, shift text right to center it in the
available space
+ const leftPaddingPercent = (chartPadding.left / width) * 100;
+ const adjustedLeftPercent = 50 + leftPaddingPercent * 0.25;
+ padding.left = `${adjustedLeftPercent}%`;
}
if (chartPadding.right) {
- padding.left = `${
- 50 - ((chartPadding.right + LEGEND_WIDTH) / width / 2) * 100
- }%`;
+ // When legend is on the right, shift text left to center it in the
available space
+ const rightPaddingPercent = (chartPadding.right / width) * 100;
+ const adjustedLeftPercent = 50 - rightPaddingPercent * 0.75;
+ padding.left = `${adjustedLeftPercent}%`;
}
return padding;
}
@@ -220,7 +220,7 @@ export default function transformProps(
name: otherName,
value: otherSum,
itemStyle: {
- color: theme.colors.grayscale.dark1,
+ color: theme.colorText,
opacity:
filterState.selectedValues &&
!filterState.selectedValues.includes(otherName)
@@ -368,7 +368,7 @@ export default function transformProps(
const defaultLabel = {
formatter,
show: showLabels,
- color: theme.colors.grayscale.dark2,
+ color: theme.colorText,
};
const chartPadding = getChartPadding(
@@ -403,7 +403,7 @@ export default function transformProps(
label: {
show: true,
fontWeight: 'bold',
- backgroundColor: theme.colors.grayscale.light5,
+ backgroundColor: theme.colorBgContainer,
},
},
data: transformedData,
@@ -445,6 +445,7 @@ export default function transformProps(
text: t('Total: %s', numberFormatter(totalValue)),
fontSize: 16,
fontWeight: 'bold',
+ fill: theme.colorText,
},
z: 10,
}
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/test/Pie/transformProps.test.ts
b/superset-frontend/plugins/plugin-chart-echarts/test/Pie/transformProps.test.ts
index 36fefc1f49..087f1f4929 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/test/Pie/transformProps.test.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/test/Pie/transformProps.test.ts
@@ -221,6 +221,157 @@ describe('Pie label string template', () => {
});
});
+describe('Total value positioning with legends', () => {
+ const getChartPropsWithLegend = (
+ showTotal = true,
+ showLegend = true,
+ legendOrientation = 'right',
+ donut = true,
+ ): EchartsPieChartProps => {
+ const formData: SqlaFormData = {
+ colorScheme: 'bnbColors',
+ datasource: '3__table',
+ granularity_sqla: 'ds',
+ metric: 'sum__num',
+ groupby: ['category'],
+ viz_type: 'pie',
+ show_total: showTotal,
+ show_legend: showLegend,
+ legend_orientation: legendOrientation,
+ donut,
+ };
+
+ return new ChartProps({
+ formData,
+ width: 800,
+ height: 600,
+ queriesData: [
+ {
+ data: [
+ { category: 'A', sum__num: 10, sum__num__contribution: 0.4 },
+ { category: 'B', sum__num: 15, sum__num__contribution: 0.6 },
+ ],
+ },
+ ],
+ theme: supersetTheme,
+ }) as EchartsPieChartProps;
+ };
+
+ it('should center total text when legend is on the right', () => {
+ const props = getChartPropsWithLegend(true, true, 'right', true);
+ const transformed = transformProps(props);
+
+ expect(transformed.echartOptions.graphic).toEqual(
+ expect.objectContaining({
+ type: 'text',
+ left: expect.stringMatching(/^\d+(\.\d+)?%$/),
+ top: 'middle',
+ style: expect.objectContaining({
+ text: expect.stringContaining('Total:'),
+ }),
+ }),
+ );
+
+ // The left position should be less than 50% (shifted left)
+ const leftValue = parseFloat(
+ (transformed.echartOptions.graphic as any).left.replace('%', ''),
+ );
+ expect(leftValue).toBeLessThan(50);
+ expect(leftValue).toBeGreaterThan(30); // Should be reasonable positioning
+ });
+
+ it('should center total text when legend is on the left', () => {
+ const props = getChartPropsWithLegend(true, true, 'left', true);
+ const transformed = transformProps(props);
+
+ expect(transformed.echartOptions.graphic).toEqual(
+ expect.objectContaining({
+ type: 'text',
+ left: expect.stringMatching(/^\d+(\.\d+)?%$/),
+ top: 'middle',
+ }),
+ );
+
+ // The left position should be greater than 50% (shifted right)
+ const leftValue = parseFloat(
+ (transformed.echartOptions.graphic as any).left.replace('%', ''),
+ );
+ expect(leftValue).toBeGreaterThan(50);
+ expect(leftValue).toBeLessThan(70); // Should be reasonable positioning
+ });
+
+ it('should center total text when legend is on top', () => {
+ const props = getChartPropsWithLegend(true, true, 'top', true);
+ const transformed = transformProps(props);
+
+ expect(transformed.echartOptions.graphic).toEqual(
+ expect.objectContaining({
+ type: 'text',
+ left: 'center',
+ top: expect.stringMatching(/^\d+(\.\d+)?%$/),
+ }),
+ );
+
+ // The top position should be adjusted for top legend
+ const topValue = parseFloat(
+ (transformed.echartOptions.graphic as any).top.replace('%', ''),
+ );
+ expect(topValue).toBeGreaterThan(50); // Shifted down for top legend
+ });
+
+ it('should center total text when legend is on bottom', () => {
+ const props = getChartPropsWithLegend(true, true, 'bottom', true);
+ const transformed = transformProps(props);
+
+ expect(transformed.echartOptions.graphic).toEqual(
+ expect.objectContaining({
+ type: 'text',
+ left: 'center',
+ top: expect.stringMatching(/^\d+(\.\d+)?%$/),
+ }),
+ );
+
+ // The top position should be adjusted for bottom legend
+ const topValue = parseFloat(
+ (transformed.echartOptions.graphic as any).top.replace('%', ''),
+ );
+ expect(topValue).toBeLessThan(50); // Shifted up for bottom legend
+ });
+
+ it('should use default positioning when no legend is shown', () => {
+ const props = getChartPropsWithLegend(true, false, 'right', true);
+ const transformed = transformProps(props);
+
+ expect(transformed.echartOptions.graphic).toEqual(
+ expect.objectContaining({
+ type: 'text',
+ left: 'center',
+ top: 'middle',
+ }),
+ );
+ });
+
+ it('should handle regular pie chart (non-donut) positioning', () => {
+ const props = getChartPropsWithLegend(true, true, 'right', false);
+ const transformed = transformProps(props);
+
+ expect(transformed.echartOptions.graphic).toEqual(
+ expect.objectContaining({
+ type: 'text',
+ top: '0', // Non-donut charts use '0' as default top position
+ left: expect.stringMatching(/^\d+(\.\d+)?%$/), // Should still adjust
left for right legend
+ }),
+ );
+ });
+
+ it('should not show total graphic when showTotal is false', () => {
+ const props = getChartPropsWithLegend(false, true, 'right', true);
+ const transformed = transformProps(props);
+
+ expect(transformed.echartOptions.graphic).toBeNull();
+ });
+});
+
describe('Other category', () => {
const defaultFormData: SqlaFormData = {
colorScheme: 'bnbColors',