@@ -52,6 +52,7 @@ import { useNestedProp } from "../useNestedProp";
5252import { useThemeCheck } from " ../useThemeCheck" ;
5353import { useUserOptionState } from " ../useUserOptionState" ;
5454import { useChartAccessibility } from " ../useChartAccessibility" ;
55+ import { useSmallArcLayoutsClassic } from " ../useSmallArcLayouts" ;
5556import img from " ../img" ;
5657import Shape from " ../atoms/Shape.vue" ;
5758import Title from " ../atoms/Title.vue" ; // Must be ready in responsive mode
@@ -968,230 +969,21 @@ function isSmallArc(arc, seriesIndex) {
968969 );
969970}
970971
971- const smallArcLayoutsClassic = computed (() => {
972- if (FINAL_CONFIG .value .type !== " classic" ) return {};
973-
974- const layouts = {};
975- const arcs = noGhostDonut .value || [];
976- if (! arcs .length ) return layouts;
977-
978- const configObject = FINAL_CONFIG .value ;
979- const centerX = svg .value .width / 2 ;
980- const centerY = svg .value .height / 2 ;
981-
982- const topPadding = padding .value .top + 16 ;
983- const bottomPadding = svg .value .height - padding .value .bottom - 16 ;
984-
985- // Base height for a single small label (percentage + one name line)
986- const baseLineHeight = labels_inline_fontSize .value * 1.5 ;
987-
988- const markerTextGap = 8 ;
989- const radialOffset = 6 ;
990-
991- const leftBandMarkerX = centerX - (minSize .value + radialOffset);
992- const rightBandMarkerX = centerX + (minSize .value + radialOffset);
993-
994- const isCurved = !! configObject .style .chart .layout .curvedMarkers ;
995-
996- function makeConnectorPathForSmallArcs ({ midX, midY, bandX, bandY }) {
997- if (! isCurved) {
998- const elbowX = midX;
999- const elbowY = bandY;
1000- return ` M ${ midX} ${ midY} L ${ elbowX} ${ elbowY} L ${ bandX} ${ bandY} ` ;
1001- }
1002-
1003- const side = bandX < centerX ? - 1 : 1 ;
1004- const offset = 12 ;
1005-
1006- const controlPointX = (midX + bandX) / 2 + side * offset;
1007- const controlPointY = midY + (bandY - midY) * 0.5 ;
1008-
1009- return ` M ${ midX} ${ midY} Q ${ controlPointX} ${ controlPointY} ${ bandX} ${ bandY} ` ;
1010- }
1011-
1012- // All small arcs (top and bottom), excluding segregated / animating ones
1013- const candidates = arcs
1014- .map ((arc , index ) => {
1015- const { x: midX , y: midY } = findArcMidpoint (arc .path );
1016- const inlineMarkerX = calcMarkerOffsetX (arc).x ;
1017- const inlineMarkerY = calcMarkerOffsetY (arc) - 3.5 ;
1018-
1019- const nameLines = String (arc .name ?? " " ).split (/ \n / g );
1020- const extraNameLines = Math .max (0 , nameLines .length - 1 );
1021-
1022- const lineHeight = labels_inline_fontSize .value * 1.2 ;
1023- const extraHeight = extraNameLines * lineHeight;
1024-
1025- const labelHeight = baseLineHeight + extraHeight;
1026-
1027- return {
1028- arc,
1029- index,
1030- midX,
1031- midY,
1032- inlineMarkerX,
1033- inlineMarkerY,
1034- labelHeight,
1035- };
1036- })
1037- .filter (({ arc }) => {
1038- const seriesIndex = arc .seriesIndex ?? 0 ;
1039-
1040- // Do not include arcs that are being animated or are segregated
1041- if (animatingIndex .value === seriesIndex) return false ;
1042- if (segregated .value .includes (seriesIndex)) return false ;
1043-
1044- return isSmallArc (arc, seriesIndex);
1045- });
1046-
1047- const topLeftCandidates = [];
1048- const topRightCandidates = [];
1049- const bottomLeftCandidates = [];
1050- const bottomRightCandidates = [];
1051-
1052- candidates .forEach (candidate => {
1053- const isTop = candidate .inlineMarkerY < centerY;
1054- const isLeft = candidate .inlineMarkerX < centerX;
1055-
1056- if (isTop && isLeft) topLeftCandidates .push (candidate);
1057- else if (isTop && ! isLeft) topRightCandidates .push (candidate);
1058- else if (! isTop && isLeft) bottomLeftCandidates .push (candidate);
1059- else bottomRightCandidates .push (candidate);
1060- });
1061-
1062- const sortByVerticalPositionAscending = (a , b ) =>
1063- a .inlineMarkerY - b .inlineMarkerY || a .index - b .index ;
1064-
1065- const sortByVerticalPositionDescending = (a , b ) =>
1066- b .inlineMarkerY - a .inlineMarkerY || a .index - b .index ;
1067-
1068- topLeftCandidates .sort (sortByVerticalPositionAscending);
1069- topRightCandidates .sort (sortByVerticalPositionAscending);
1070- bottomLeftCandidates .sort (sortByVerticalPositionDescending);
1071- bottomRightCandidates .sort (sortByVerticalPositionDescending);
1072-
1073- // TOP LEFT BAND (always clustered if any)
1074- let currentTopLeftY = topPadding;
1075- topLeftCandidates .forEach (candidate => {
1076- const { index , midX , midY , labelHeight } = candidate;
1077-
1078- const labelY = currentTopLeftY;
1079- const bandMarkerX = leftBandMarkerX;
1080- const bandMarkerY = labelY;
1081-
1082- const connectorPath = makeConnectorPathForSmallArcs ({
1083- midX,
1084- midY,
1085- bandX: bandMarkerX,
1086- bandY: bandMarkerY,
1087- });
1088-
1089- layouts[index] = {
1090- side: " left" ,
1091- labelX: bandMarkerX - markerTextGap,
1092- labelY: labelY + labels_inline_fontSize .value / 3 ,
1093- textAnchor: " end" ,
1094- markerX: bandMarkerX,
1095- markerY: bandMarkerY,
1096- connectorPath,
1097- };
1098-
1099- currentTopLeftY += labelHeight;
1100- });
1101-
1102- // TOP RIGHT BAND (always clustered if any)
1103- let currentTopRightY = topPadding;
1104- topRightCandidates .forEach (candidate => {
1105- const { index , midX , midY , labelHeight } = candidate;
1106-
1107- const labelY = currentTopRightY;
1108- const bandMarkerX = rightBandMarkerX;
1109- const bandMarkerY = labelY;
1110-
1111- const connectorPath = makeConnectorPathForSmallArcs ({
1112- midX,
1113- midY,
1114- bandX: bandMarkerX,
1115- bandY: bandMarkerY,
1116- });
1117-
1118- layouts[index] = {
1119- side: " right" ,
1120- labelX: bandMarkerX + markerTextGap,
1121- labelY: labelY + labels_inline_fontSize .value / 3 ,
1122- textAnchor: " start" ,
1123- markerX: bandMarkerX,
1124- markerY: bandMarkerY,
1125- connectorPath,
1126- };
1127-
1128- currentTopRightY += labelHeight;
1129- });
1130-
1131- // BOTTOM LEFT BAND
1132- if (bottomLeftCandidates .length > 1 ) {
1133- let currentBottomLeftY = bottomPadding;
1134- bottomLeftCandidates .forEach (candidate => {
1135- const { index , midX , midY , labelHeight } = candidate;
1136-
1137- currentBottomLeftY -= labelHeight;
1138- const labelY = currentBottomLeftY;
1139- const bandMarkerX = leftBandMarkerX;
1140- const bandMarkerY = labelY;
1141-
1142- const connectorPath = makeConnectorPathForSmallArcs ({
1143- midX,
1144- midY,
1145- bandX: bandMarkerX,
1146- bandY: bandMarkerY,
1147- });
1148-
1149- layouts[index] = {
1150- side: " left" ,
1151- labelX: bandMarkerX - markerTextGap,
1152- labelY: labelY + labels_inline_fontSize .value / 3 ,
1153- textAnchor: " end" ,
1154- markerX: bandMarkerX,
1155- markerY: bandMarkerY,
1156- connectorPath,
1157- };
1158- });
1159- }
1160-
1161- // BOTTOM RIGHT BAND
1162- if (bottomRightCandidates .length > 1 ) {
1163- let currentBottomRightY = bottomPadding;
1164- bottomRightCandidates .forEach (candidate => {
1165- const { index , midX , midY , labelHeight } = candidate;
1166-
1167- currentBottomRightY -= labelHeight;
1168- const labelY = currentBottomRightY;
1169- const bandMarkerX = rightBandMarkerX;
1170- const bandMarkerY = labelY;
1171-
1172- const connectorPath = makeConnectorPathForSmallArcs ({
1173- midX,
1174- midY,
1175- bandX: bandMarkerX,
1176- bandY: bandMarkerY,
1177- });
1178-
1179- layouts[index] = {
1180- side: " right" ,
1181- labelX: bandMarkerX + markerTextGap,
1182- labelY: labelY + labels_inline_fontSize .value / 3 ,
1183- textAnchor: " start" ,
1184- markerX: bandMarkerX,
1185- markerY: bandMarkerY,
1186- connectorPath,
1187- };
1188- });
1189- }
1190-
1191- return layouts
972+ const { smallArcLayoutsClassic } = useSmallArcLayoutsClassic ({
973+ FINAL_CONFIG ,
974+ noGhostDonut,
975+ svg,
976+ padding,
977+ labels_inline_fontSize,
978+ minSize,
979+ findArcMidpoint,
980+ calcMarkerOffsetX,
981+ calcMarkerOffsetY,
982+ animatingIndex,
983+ segregated,
984+ isSmallArc
1192985});
1193986
1194-
1195987function displayArcPercentage (arc , stepBreakdown ) {
1196988 const p = arc .value / sumValues (stepBreakdown);
1197989 return isNaN (p) ? 0 : applyDataLabel (
0 commit comments