Fix axis spine corner overextension - #5765
Open
thchr wants to merge 3 commits into
Open
Conversation
…etry The four `Axis` spine segments (plus the opposite and mirrored spines) were each extended by half a spine width past the axis corners so that, with the default butt linecap, the segments would overlap into a clean corner instead of leaving a notch. `create_linepoints` similarly padded trimmed spines by half a tick width. Because that extension was baked into the vertex positions as a fixed pixel distance computed from the spine width at render time, it only lined up at that exact stroke width. Re-stroking a CairoMakie-exported SVG/PDF in an external editor (a common step when composing figures) moved every corner out of alignment. Instead, end each spine exactly at the corner (or the outer tick, when trimmed) and set `linecap = :square` on the spine `linesegments!` calls. A square cap extends the stroke by half its width automatically and is written into the SVG/PDF as `stroke-linecap="square"`, so the corner coverage now scales with the stroke when the figure is restyled. At any given stroke width the rendered result is unchanged. Tick marks keep their butt linecap and are untouched here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tick marks were placed relative to the *outer edge* of the spine stroke:
the mark started at `0.5 * spinewidth` outside the centerline and ran
`ticksize` outward from there (shifted inward by `tickalign * ticksize`).
That couples the tick geometry to the spine's stroke width, which is exactly
what breaks when a figure is exported to SVG/PDF and the spine is restroked
in a vector editor -- widening the stroke swallows the tick, narrowing it
opens a gap, and a mark meant to point outward can end up poking into the
plot area.
Anchor the mark on the spine centerline instead, which no stroke width can
uncover:
outer_len = (1 - tickalign) * ticksize
inner_len = tickalign * ticksize
The drawn mark is exactly `ticksize` long for every `tickalign`, and
`tickalign` (0 = out, 1 = in) simply slides it across the centerline.
`spinewidth` drops out of the tick geometry entirely, here and in
`mirror_ticks`.
At `tickalign = 0` the mark now starts on the centerline rather than the
outer spine edge, so half a spine width of it is covered by the spine. To
keep default axes looking exactly as they did, the default tick sizes are
raised by half the default spine width: `Axis` `ticksize` 5 -> 5.5 and
`minorticksize` 3 -> 3.5, likewise for `Colorbar` and standalone `LineAxis`.
Figures that set `ticksize` explicitly get marks that appear
`spinewidth / 2` shorter than before.
`tickspace`, which feeds the tick label gap and the axis protrusion, now
means "how far the mark reaches past the outer spine edge" and subtracts
that same half spine width, so tick labels and the plot area stay put.
Verified pixel-identical to master on CairoMakie for default, mirrored,
trimmed, flipped and Colorbar axes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thchr
force-pushed
the
fix-axis-spine-corner-overextension
branch
from
August 28, 2026 08:19
1d0974d to
40f7c76
Compare
Dropping the geometry extensions from `create_linepoints` also dropped the
`0.5 * tickwidth` that a trimmed spine end used to carry, so the end landed on
the outermost tick's centerline and the `:square` linecap then pushed it half a
spine width further out. With `spinewidth = 8` and a default `tickwidth = 1`
that is a 3.5px stub sticking out past the last tick.
A trimmed end should finish flush with the outer edge of that tick, i.e. half a
tick width past its center, as it did before. The cap already contributes half a
spine width, so the geometry only has to make up the difference:
trim = 0.5 * (tickwidth - spinewidth)
Untrimmed ends are unaffected -- they still stop at the axis corner and let the
cap fill it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thchr
force-pushed
the
fix-axis-spine-corner-overextension
branch
from
August 28, 2026 11:04
6bb16eb to
98e4d14
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🙋♂️ As discussed on Slack with @jkrumbiegel, this PR seeks to fix two interrelated things:
spinewidth / 2to avoid the appearance of a "notch" in the corner. A more elegant way to do this is just to draw the axis lines with a:squarecap instead of a:buttcap.spinewidth / 2away from the actual axis centerline. This gives some real surprises, especially withtickalign ≠ 0, since the stroke is then not placed in a natural position at e.g.,tickalign = 0.5or1. Fix this by starting/anchoring thetickalign = 0(andtickalign = 1) strokes to the axis' centerline.These changes are - for me - chiefly motivated by the fact that the existing drawing approach gives headaches if one wants to restyle an exported CairoMakie figure manually in e.g. Illustrator or Inkscape afterwards: if any axis or tick linewidth is changed, or if the figure itself scaled, the "absolute" positioning hacks lead to very visible artifacts (that I have to admit to historically spending way to much time on fixing, by manually realigning all these little lines).
A fuller Claude-generated description below (especially of the ticks-change).
🤖 CairoMakie-exported axes don't survive being restroked in Illustrator/Inkscape. Two separate causes, one commit each — the first is a straightforward fix, the second involves a judgement call where input/agreement is needed.
1. Spines overshoot the corners (
a48775e)The spine geometry was extended by
0.5 * spinewidthpast each axis corner to fill the corner join. That extension is baked in at axis creation, so restroking the exported figure at a different width leaves the overshoot wrong in both directions.Now the spine ends exactly at the corner and uses
linecap = :square, which fills the corner and scales with whatever stroke width the file is rendered at. Rendering is unchanged.2. Ticks are anchored to the spine's outer edge (
1d0974d)Tick marks started at
0.5 * spinewidthoutside the spine centerline and ranticksizeoutward from there (shifted inward bytickalign * ticksize). Same coupling problem: widening the spine's stroke in an editor swallows the tick, narrowing it opens a gap.Ticks are now anchored on the spine centerline — the one place no stroke width can uncover:
The drawn mark is exactly
ticksizelong for everytickalign, which simply slides it across the centerline.spinewidthdrops out of the tick geometry entirely.Both changes live in
Makie/, so this fixes all backends, not just Cairo.The judgement call
Because the mark now starts on the centerline rather than the outer spine edge, half a spine width of it is covered by the spine. To keep existing figures looking the same, default tick sizes are raised by half the default spine width —
ticksize5 → 5.5 andminorticksize3 → 3.5, forAxis,ColorbarandLineAxis— andtickspace(which drives the tick-label gap and the axis protrusion) subtracts that same half width so labels and the plot area stay put.Default axes therefore render byte-identically to master (verified on CairoMakie: default, mirrored, trimspine, flipped, and
Colorbar, majors and minors). But a figure that setsticksizeexplicitly gets marks that appearspinewidth / 2shorter than before.Open question: the
5 → 5.5default is a literal, so it only cancels the change whenspinewidthis also default. An axis withspinewidth = 8and untouchedticksizegets marks appearing1.5long (=8/2 - 5.5) instead of5.0. One alternative isticksize = automatic, resolved as5f0 + 0.5f0 * spinewidth, which cancels it for any spine width. I went with the literal because the point of this change is to maketicksizean unconditional stroke length, and a spinewidth-coupled default reintroduces exactly the coupling being removed — but there's a definite trade-off.What changing
tickalignlooks like before/afterspinewidth = 12,ticksize = 18. After: the drawn mark is a constant length andtickalignslides it across the centerline; at1.0it sits fully inside with its outer tip on the centerline. Before: the mark hung off the spine's outer edge and the buried fraction varied withtickalign.🤖 Generated with Claude Code
Checklists
Type of change
Checklist