scale_linewidth(): per-segment mapped line width - #189
Merged
Conversation
A mapped `linewidth` on `mark_line()`, `mark_step()` and `mark_segment()` now draws, via `segments_grob(lwd = <vector>)`: each segment gets one constant width, the mean of its two endpoint values. `scale_linewidth()` trains the data range onto an lwd range (default `c(0.5, 4)`) and draws its own legend, reusing the edge-width guide's key glyph. A constant `linewidth =` is untouched — still one `lines_grob`, byte for byte the same output, asserted in the tests rather than assumed. Edges keep their own scale: `linewidth` on `mark_edges()` still trains `scale_edge_width()`. The two are split by mark, so a plot can map both independently. Consequently `guides(linewidth = )` / `lims(linewidth = )` now address the line-width scale (`edge_width` for edges), and a mapped `linewidth` on a line no longer draws a stray edge-width legend. Also handled: polar/trans munching (each piece keeps its segment's width), flip, sketch, and the glow/outline halos, which widen each mapped width instead of collapsing to one constant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ng it The endpoint mean made one NA poison both segments meeting at that vertex, and a non-finite lwd reaches the backend as width 0 -- so a single missing value silently cost ~31% of a five-point line's ink. Each segment now falls back to whichever endpoint is known; only a segment with no known width at either end is dropped, and its provenance row goes with it. This matches how the other continuous aesthetics degrade: a mapped size with one NA costs one point, not two. Also flag the guides()/lims() redirection as a minor breaking change with its migration, lock it with a test, and document params$.lwd_add as an internal effect-copy channel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`compile-marks-helpers.R` carried the roxygen `@include classes.R` / `NULL` block twice, the second copy below the file comment. Collate is unchanged by the removal, so this is cosmetic: the header now matches its sibling compile-marks-*.R files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Closes #188.
A mapped
linewidthonmark_line(),mark_step()andmark_segment()nowdraws. It routes to
vellum::segments_grob(lwd = <vector>), so each segment getsone constant width — the mean of its two endpoint values, which is stated in the
docs rather than left implicit.
What's here
scale_linewidth()(+ ascale_linewidth_continuous()alias), mirroringscale_size():range(defaultc(0.5, 4), in lwd units),limits,breaks,name. Exported, in_pkgdown.yml's scales section..emit_line,.emit_stepand.emit_segmentswitch toa per-segment
segments_grobwhenlinewidthis mapped. A step's width holdsacross each tread and blends across the riser; a segment is already one element
per row, so it just takes the
lwdvector.the break's width).
linewidthstill emits onelines_grob— asserted in the tests, not assumed.Caveats, documented not hidden
The width changes in visible steps at each vertex and round joins do not fill a
sharp corner: inherent to per-segment width, and the reason the smooth-taper half
(#165 / r-vellum/vellum#47) still exists. No backend can stroke one path at
several widths in a single call, so a mapped
linewidthemits one path elementper segment in SVG/PDF — noted in
scale_linewidth(), NEWS and the vignette,since it matters for file size on dense data. The scale API is the one that a
future smooth taper would keep, so that lands as an emitter change.
Edge width vs line width
linewidthonmark_edges()still trainsscale_edge_width(); the two scalesare split by mark, so a graph can map edge width and a line's width to different
variables. Two visible consequences:
guides(linewidth = )andlims(linewidth = )nowaddress the line-width scale (
edge_widthfor edges) — they previouslycanonicalised to
edge_width, so on avgraph()they reached the edges. Theynow silently do not, because a declared scale for an untrained aesthetic is
simply unused. Migration is
guides(edge_width = )/lims(edge_width = ),now locked by a test.
linewidthonmark_line()/mark_step()/mark_segment()nolonger trains the edge-width scale, so those three stop drawing a stray
edge-width legend that changed nothing on screen. Note this is only those
three:
mark_rule(),mark_linerange()andmark_area()also document alinewidth, and a mapped one there still trainsedge_widthand still draws alegend advertising widths that do not render. Pre-existing, not touched here --
they emit
segments_grobalready, so extending them is the same one-linechange
.emit_segmentgot, and is left as a follow-up.Both are in NEWS.
Also handled
Polar /
coord_trans(each segment is munched exactly as.xy_path()wouldmunch the whole polyline, and every piece keeps that segment's width), flip,
sketch=, andeffects = list(glow())—.emit_copies()widens onlyparams$linewidth, which a mapped width ignores, so the widening is carriedseparately and added to each mapped width. Provenance rows are refined to the
drawn segments.
Review fixes
NA) width no longer erases two segments. The endpoint meanmade one
NApoison both segments meeting at that vertex, and a non-finitelwdreaches the backend as width 0 — so a single missing value silently cost~31% of a five-point line's ink. Each segment now falls back to whichever
endpoint is known, so a missing value thins the line instead; only a segment
with no known width at either end is dropped, and its provenance row goes with
it. This matches how the other continuous aesthetics degrade — a mapped
sizewith one
NAcosts one point, not two.NAtests (thinning, no ink lost, the both-ends-missing drop keepingprovenance aligned, all-missing draws nothing) and one locking the
guides()/lims()redirection plus its migration.params$.lwd_addis documented as an internal effect-copy channel.Verification
R CMD check: 0 errors / 0 warnings / 0 notes. Full suite green (3264 passing).New
tests/testthat/test-scale-linewidth.Rcovers the trained scale and itslegend, the endpoint-mean widths, the byte-identical constant path, style-group
splitting, step and segment, polar/flip/sketch, the glow halo, and the
edge-width/line-width split. Rendered and eyeballed in PNG, SVG and PDF.
Docs updated:
scale_linewidth(),mark_point/mark_area/mark_segmentRd,NEWS, README, the scales-and-guides vignette,
_pkgdown.yml, andinst/examples/06-size-shape-scales.R.🤖 Generated with Claude Code