diff --git a/frontend/src/container/PanelWrapper/PiePanelWrapper.tsx b/frontend/src/container/PanelWrapper/PiePanelWrapper.tsx
index a583d60d6b..948f62af3f 100644
--- a/frontend/src/container/PanelWrapper/PiePanelWrapper.tsx
+++ b/frontend/src/container/PanelWrapper/PiePanelWrapper.tsx
@@ -15,7 +15,7 @@ import { useRef, useState } from 'react';
import { PanelWrapperProps, TooltipData } from './panelWrapper.types';
import { lightenColor, tooltipStyles } from './utils';
-// refernce: https://www.youtube.com/watch?v=bL3P9CqQkKw
+// reference: https://www.youtube.com/watch?v=bL3P9CqQkKw
function PiePanelWrapper({
queryResponse,
widget,
@@ -60,6 +60,7 @@ function PiePanelWrapper({
}))
.filter((d) => d !== undefined) as never[]),
);
+
pieChartData = pieChartData.filter(
(arc) =>
arc.value && !isNaN(parseFloat(arc.value)) && parseFloat(arc.value) > 0,
@@ -76,13 +77,63 @@ function PiePanelWrapper({
width = offsetWidth;
height = offsetHeight;
}
- const half = size / 2;
+
+ // Adjust the size to leave room for external labels
+ const radius = size * 0.35;
+ // Add inner radius for donut chart
+ const innerRadius = radius * 0.6;
+
+ // Calculate total value for center display
+ const totalValue = pieChartData.reduce(
+ (sum, data) => sum + parseFloat(data.value || '0'),
+ 0,
+ );
+
+ // Format total for display with the same unit as segments
+ const formattedTotal = getYAxisFormattedValue(
+ totalValue.toString(),
+ widget?.yAxisUnit || 'none',
+ );
+
+ // Extract numeric part and unit separately for styling
+ const matches = formattedTotal.match(/([\d.]+[KMB]?)(.*)$/);
+ const numericTotal = matches?.[1] || formattedTotal;
+ const unitTotal = matches?.[2]?.trim() || '';
+
+ // Dynamically calculate font size based on text length to prevent overflow
+ const getScaledFontSize = ({
+ text,
+ baseSize,
+ innerRadius,
+ }: {
+ text: string;
+ baseSize: number;
+ innerRadius: number;
+ }): number => {
+ if (!text) return baseSize;
+
+ const { length } = text;
+ // More aggressive scaling for very long numbers
+ const scaleFactor = Math.max(0.3, 1 - (length - 3) * 0.09);
+
+ // Ensure text fits in the inner circle (roughly)
+ const maxSize = innerRadius * 0.9; // Don't use more than 90% of inner radius
+
+ return Math.min(baseSize * scaleFactor, maxSize);
+ };
+
+ const numericFontSize = getScaledFontSize({
+ text: numericTotal,
+ baseSize: radius * 0.3,
+ innerRadius,
+ });
+ const unitFontSize = numericFontSize * 0.5; // Unit size is half of numeric size
const getFillColor = (color: string): string => {
if (active === null) {
return color;
}
- const lightenedColor = lightenColor(color, 0.4); // Adjust the opacity value (0.7 in this case)
+ const lightenedColor = lightenColor(color, 0.4); // Adjust the opacity value (0.4 in this case)
return active.color === color ? color : lightenedColor;
};
@@ -101,10 +152,8 @@ function PiePanelWrapper({
value: string;
color: string;
}): number => parseFloat(data.value)}
- outerRadius={({ data }): number => {
- if (!active) return half - 3;
- return data.label === active.label ? half : half - 3;
- }}
+ outerRadius={radius}
+ innerRadius={innerRadius}
padAngle={0.01}
cornerRadius={3}
width={size}
@@ -113,36 +162,53 @@ function PiePanelWrapper({
{
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
(pie) =>
- pie.arcs.map((arc, index) => {
+ pie.arcs.map((arc) => {
const { label } = arc.data;
const [centroidX, centroidY] = pie.path.centroid(arc);
- const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.6;
const arcPath = pie.path(arc);
const arcFill = arc.data.color;
- // Calculate available space for label text
- const arcSize = arc.endAngle - arc.startAngle;
- const maxLabelLength = Math.floor(arcSize * 15);
- const labelText = arc.data.label;
- const displayLabel =
- labelText.length > maxLabelLength
- ? `${labelText.substring(0, maxLabelLength - 3)}...`
- : labelText;
+ // Calculate angle bisector for the arc (midpoint of the arc)
+ const angle = (arc.startAngle + arc.endAngle) / 2;
+
+ // Calculate outer point for the label
+ const labelRadius = radius * 1.3; // Label position
+ const labelX = Math.sin(angle) * labelRadius;
+ const labelY = -Math.cos(angle) * labelRadius;
+
+ // Calculate endpoint for the connecting line
+ const lineEndRadius = radius * 1.1;
+ const lineEndX = Math.sin(angle) * lineEndRadius;
+ const lineEndY = -Math.cos(angle) * lineEndRadius;
+
+ // Format the value for display
+ const displayValue = getYAxisFormattedValue(
+ arc.data.value,
+ widget?.yAxisUnit || 'none',
+ );
+
+ // Determine text anchor based on position in the circle
+ const textAnchor = Math.sin(angle) > 0 ? 'start' : 'end';
+
+ // Shorten label if too long
+ const shortenedLabel =
+ label.length > 15 ? `${label.substring(0, 12)}...` : label;
+
+ const shouldShowLabel =
+ parseFloat(arc.data.value) /
+ pieChartData.reduce((sum, d) => sum + parseFloat(d.value), 0) >
+ 0.03;
return (
{
showTooltip({
tooltipData: {
label,
- // do not update the unit in the data as the arc allotment is based on value
- // and treats 4K smaller than 40
- value: getYAxisFormattedValue(
- arc.data.value,
- widget?.yAxisUnit || 'none',
- ),
+ value: displayValue,
color: arc.data.color,
key: label,
},
@@ -157,24 +223,78 @@ function PiePanelWrapper({
}}
>
- {hasSpaceForLabel && (
-
- {displayLabel}
-
+
+ {shouldShowLabel && (
+ <>
+ {/* Connecting line */}
+
+
+ {/* Line from arc edge to label */}
+
+
+ {/* Label text */}
+
+ {shortenedLabel}
+
+
+ {/* Value text */}
+
+ {displayValue}
+
+ >
)}
);
})
}
+
+ {/* Add total value in the center */}
+
+
+ {numericTotal}
+
+ {unitTotal && (
+
+ {unitTotal}
+
+ )}
+
{tooltipOpen && tooltipData && (