Summary
When the X-axis label population has a wide length spread (e.g. "W1"
through "W21", or single-digit months mixed with longer ones), the
currently visible labels appear at uneven pixel cadence even when the
data size lines up for a perfectly integer-stride pick (e.g. 21 data points
with count = 6 produces stride 4 — [0, 4, 8, 12, 16, 20] — but only
[1, 5, 8, 12, 15, 19] makes it past the safe range).
Repro
- Render a chart with ≥ 21 X-axis categories at 1080 wide / tablet-landscape.
- Use a category set whose label lengths vary by 1-2 characters (e.g.
W1 ... W21).
- Default
xLabels.count = 6.
Observed: labels at indices [1, 5, 8, 12, 15, 19] instead of the mathematically
ideal [0, 4, 8, 12, 16, 20]. Pixel stride between adjacent labels is 4, 3, 4, 3, 4
instead of a clean 4, 4, 4, 4, 4.
Root Cause
AxisXPlanner.kt:7 defines AXIS_X_EDGE_PADDING_PX = 4f, and AxisHelpers.kt's
estimateXAxisLabelFootprintPx uses the longest label for width.
centeredLabelIndexRange trims each side by labelHalfWidth + edgePaddingPx,
which is over-aggressive for label sets dominated by shorter entries.
Rough math at 1080 wide with default 11sp label and 34° tilt:
labelWidth ≈ 50-60 px (rotated longest label)
unitWidth ≈ 95 px (1080 / 21)
minCenterX ≈ 30 px → loses one index on each side
Suggested Direction
- Replace the longest-label width estimate with a percentile (e.g. 90th)
so a single outlier label does not collapse the safe range.
- Or expose
edgePadding as a per-chart style parameter so callers (docs
generation, low-density data) can opt out.
- Or, for known-clean datasets where
dataSize - 1 is divisible by
count - 1, skip the planner blending and emit the simple even
stride directly.
This issue is for follow-up PR work; not in scope of the current
docs GIF / screenshot work in branch
ci/pr-gif-required-and-landscape.
Summary
When the X-axis label population has a wide length spread (e.g. "W1"
through "W21", or single-digit months mixed with longer ones), the
currently visible labels appear at uneven pixel cadence even when the
data size lines up for a perfectly integer-stride pick (e.g. 21 data points
with
count = 6produces stride 4 —[0, 4, 8, 12, 16, 20]— but only[1, 5, 8, 12, 15, 19]makes it past the safe range).Repro
W1...W21).xLabels.count = 6.Observed: labels at indices
[1, 5, 8, 12, 15, 19]instead of the mathematicallyideal
[0, 4, 8, 12, 16, 20]. Pixel stride between adjacent labels is4, 3, 4, 3, 4instead of a clean
4, 4, 4, 4, 4.Root Cause
AxisXPlanner.kt:7definesAXIS_X_EDGE_PADDING_PX = 4f, andAxisHelpers.kt'sestimateXAxisLabelFootprintPxuses the longest label for width.centeredLabelIndexRangetrims each side bylabelHalfWidth + edgePaddingPx,which is over-aggressive for label sets dominated by shorter entries.
Rough math at 1080 wide with default 11sp label and 34° tilt:
labelWidth≈ 50-60 px (rotated longest label)unitWidth≈ 95 px (1080 / 21)minCenterX≈ 30 px → loses one index on each sideSuggested Direction
so a single outlier label does not collapse the safe range.
edgePaddingas a per-chart style parameter so callers (docsgeneration, low-density data) can opt out.
dataSize - 1is divisible bycount - 1, skip the planner blending and emit the simple evenstride directly.
This issue is for follow-up PR work; not in scope of the current
docs GIF / screenshot work in branch
ci/pr-gif-required-and-landscape.