Skip to content

Commit d4af0d9

Browse files
committed
fix(ui): Implement mathematically stable label alignment to eliminate vertical jitter and overlapping
1 parent fe2dd53 commit d4af0d9

1 file changed

Lines changed: 25 additions & 26 deletions

File tree

lib/flutter_circle_pack_chart_painter.dart

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -164,18 +164,29 @@ class FlutterCirclePackChartPainter extends CustomPainter {
164164
) {
165165
if (!showValue && !showLabels) return;
166166

167-
final double valueFontSize = (baseFontSize / cameraScale).clamp(0.1, 100.0);
168-
final double labelFontSize = ((baseFontSize * 0.75) / cameraScale).clamp(0.1, 100.0);
169-
final double maxWidth = radius * 2.5;
167+
// --- MATHEMATICALLY STABLE FONT SIZING & SPACING ---
168+
// Instead of using TextPainter.height (which jitters), we use a fixed multiplier
169+
// of the baseFontSize for vertical positioning.
170+
171+
final double valueSize = (baseFontSize / cameraScale).clamp(0.1, 100.0);
172+
final double labelSize = ((baseFontSize * 0.75) / cameraScale).clamp(0.1, 100.0);
173+
174+
// Total visual vertical height block (in canvas units)
175+
// 1.2 and 1.0 are stable line-height multipliers.
176+
final double valueHeight = showValue ? (valueSize * 1.2) : 0.0;
177+
final double labelHeight = showLabels ? (labelSize * 1.0) : 0.0;
178+
final double totalHeight = valueHeight + labelHeight;
179+
final double maxWidth = radius * 2.2; // Tighter constraint to avoid edges
180+
181+
double currentY = center.dy - (totalHeight / 2);
170182

171-
TextPainter? valuePainter;
172183
if (showValue) {
173-
valuePainter = TextPainter(
184+
final valuePainter = TextPainter(
174185
text: TextSpan(
175186
text: node.displayValue ?? node.value.toStringAsFixed(0),
176187
style: TextStyle(
177188
color: Colors.white.withValues(alpha: opacity),
178-
fontSize: valueFontSize,
189+
fontSize: valueSize,
179190
fontWeight: FontWeight.bold,
180191
),
181192
),
@@ -184,16 +195,21 @@ class FlutterCirclePackChartPainter extends CustomPainter {
184195
maxLines: 1,
185196
ellipsis: '...',
186197
)..layout(maxWidth: maxWidth);
198+
199+
valuePainter.paint(
200+
canvas,
201+
Offset(center.dx - (valuePainter.width / 2), currentY),
202+
);
203+
currentY += valueHeight; // Advance by the FIXED multiplier, not dynamic height
187204
}
188205

189-
TextPainter? labelPainter;
190206
if (showLabels) {
191-
labelPainter = TextPainter(
207+
final labelPainter = TextPainter(
192208
text: TextSpan(
193209
text: node.label,
194210
style: TextStyle(
195211
color: Colors.white.withValues(alpha: opacity),
196-
fontSize: labelFontSize,
212+
fontSize: labelSize,
197213
fontWeight: FontWeight.normal,
198214
),
199215
),
@@ -202,24 +218,7 @@ class FlutterCirclePackChartPainter extends CustomPainter {
202218
maxLines: 1,
203219
ellipsis: '...',
204220
)..layout(maxWidth: maxWidth);
205-
}
206-
207-
// Calculate total vertical height and center
208-
double totalHeight = 0;
209-
if (valuePainter != null) totalHeight += valuePainter.height;
210-
if (labelPainter != null) totalHeight += labelPainter.height;
211-
212-
double currentY = center.dy - (totalHeight / 2);
213-
214-
if (valuePainter != null) {
215-
valuePainter.paint(
216-
canvas,
217-
Offset(center.dx - (valuePainter.width / 2), currentY),
218-
);
219-
currentY += valuePainter.height;
220-
}
221221

222-
if (labelPainter != null) {
223222
labelPainter.paint(
224223
canvas,
225224
Offset(center.dx - (labelPainter.width / 2), currentY),

0 commit comments

Comments
 (0)