# ggsketch 2.0.0

The 2.0 series turns ggsketch from a *line style* into a *drawing-medium*
simulator. This first piece is the engine that makes it possible.

* **Chicklet charts.** New `geom_sketch_chicklet()` -- a hand-drawn take on
  `ggchicklet::geom_chicklet()`. It stacks rounded "pill" segments with a small
  `segment_gap` between them and a solid fill by default; add `coord_flip()` for
  the classic horizontal layout. Built on `geom_sketch_col()`, so it inherits
  every fill style, drawing medium, and sketch parameter.

* **Repelled labels escape panel corners.** The `repel_layout()` solver now
  picks its separation axis by the room left inside the panel bounds, so
  labels pressed into a corner fan out along the edge instead of being
  clamped back on top of each other (previously e.g. three overlapping boxes
  in a scatter's corner).
* **Calendar axes read as calendars.** `geom_sketch_calendar()` now labels
  the y axis with weekday names (honouring `week_start`) and the x axis with
  month names instead of raw grid numbers; set `labels = FALSE` to supply
  your own scales.
* **Saner default hachure pitch.** When `hachure_gap` is `NULL`,
  `sketch_polygon_grob()` now picks the gap at draw time in device inches --
  15% of the shape's smaller drawn extent, clamped to \[0.04, 0.4\] -- instead
  of geoms guessing in data units. Wide flat rectangles (Gantt/funnel/pyramid
  bars, long tiles) no longer degenerate into a few huge strokes escaping the
  outline, and `geom_sketch_gantt()`/`funnel()`/`pyramid()` drop their fixed
  0.12 workaround pitch. An explicit `hachure_gap` is honoured unchanged, but
  default-gap output shifts slightly.
* **Rect geoms bend under nonlinear coords.** `geom_sketch_rect()`,
  `geom_sketch_tile()`, and `geom_sketch_col()`/`bar()` now densify their
  boundaries before the coordinate transform, so bars under `coord_polar()` /
  `coord_sketch_polar()` curve into proper wedges and rings instead of
  straight-edged quads.
* **Radar legend fix.** `geom_sketch_radar()` no longer leaks an `NA` key into
  a mapped discrete `fill` scale, so its colour and fill legends merge into one
  (previously two legends, one with a spurious `NA` entry).
* **Edge labels are never clipped.** `geom_sketch_bump()`,
  `geom_sketch_arc_diagram()`, `geom_sketch_parallel()`, and
  `geom_sketch_dendrogram()` now reserve panel room for their end/leaf/axis
  labels (and the dendrogram's rotated labels hang cleanly away from the
  tree), so nothing is cut off at the plot edge under `theme_void()`.
* **The lightest engraved region always draws.** `scale_tone_continuous()`'s
  default band is now `c(0.15, 0.95)` (was `c(0.1, 0.95)`), keeping the
  faintest mapped tone above the engraving ladder's blank-paper threshold --
  previously the lightest `geom_sketch_shade()` region could disappear
  entirely.
* **Alluvial strata gaps.** `geom_sketch_alluvial()` gains `stratum_gap`
  (default 0.02): a small vertical gap between adjacent strata so roughened
  boxes never overlap at their shared edge; `0` restores flush stacking.

* **Variable-width strokes (Layer 1 + Layer 2).** `stroke_ribbon()` builds a
  hand-drawn stroke as a filled polygon ribbon offset around a centreline, so a
  line can taper to a point, swell with pressure, or vary like a broad
  calligraphic nib -- effects `grid` cannot produce with a constant-`lwd`
  polyline. `stroke_profile()` supplies ready-made width profiles (`"taper_in"`,
  `"taper_out"`, `"belly"`, `"flat"`), and `sketch_stroke_grob()` renders a path
  as variable-width ribbons, re-roughening the centreline at device resolution
  (with round or butt caps, optional width jitter for a dry edge, and a
  `nib_angle` for calligraphy). This is the foundation the forthcoming media
  (ink, brush, pencil, charcoal, calligraphy) are built on.
* **Drawing media on the path geoms.** A new `medium` argument selects how a line
  is laid down: `"pen"` (the default -- the classic constant-width double stroke,
  unchanged), or `"ink"`, `"brush"`, `"pencil"`, `"charcoal"`, `"marker"`,
  `"crayon"`, which render through the variable-width engine -- tapered ink,
  brushy belly swells, grainy multi-pass pencil/charcoal, translucent marker,
  waxy crayon. Available on `geom_sketch_line()`, `geom_sketch_path()`,
  `geom_sketch_segment()`, and `geom_sketch_step()`; `sketch_media()` lists the
  options. Existing plots are unaffected (the default stays `"pen"`).
* **Three more drawing media.** `"fountain_pen"` (a crisp, wet line that pools
  slightly at the ends), `"ballpoint"` (a thin, even, faintly skipping stroke),
  and `"pastel"` (a broad, soft, grainy and translucent mark, lighter than
  charcoal) join the `medium` family and the `scale_medium_discrete()` palette.
* **Waterfall charts.** `geom_sketch_waterfall()` floats each step's delta
  (`y`) from the running total before it to the running total after it across
  the categories (`x`), with dotted hand-drawn connectors carrying the level
  over the gaps. Bars colour by direction (`fill_increase` / `fill_decrease` /
  `fill_total`, or map `fill` to `after_stat(change)` yourself), an optional
  `measure = "total"` aesthetic draws closing-total bars from zero, and any
  `fill_style` works (including `"watercolor"`).
* **Gantt / timeline charts.** `geom_sketch_gantt()` draws one roughened bar
  per task from `x` (start) to `xend` (end) on a discrete `y` -- the whiteboard
  project-planning look. An optional `progress` aesthetic (0-1) overlays a
  slimmer, darker solid bar over the completed fraction; bars take rounded
  corners (`corner_radius`), dates on x and any `fill_style`.
* **Funnel and population-pyramid charts.** `geom_sketch_funnel()` centres one
  bar per stage on zero with width equal to the stage's value, so the shrinking
  bars read as drop-off, with translucent trapezoid connectors carrying each
  stage into the next. `geom_sketch_pyramid()` draws back-to-back bars mirrored
  about zero by a two-level `side` aesthetic -- the population pyramid. Both
  take every `fill_style`.
* **Font-aware export helper.** `ggsketch_save()` is a drop-in
  `ggplot2::ggsave()` wrapper that picks a device able to see fonts registered
  with `register_sketch_font()`: ragg for raster formats, svglite for SVG,
  `cairo_pdf` for PDF (the base `pdf` device misses registered fonts), and it
  warns PostScript users towards PDF -- ending the "my handwriting font
  disappeared in the saved file" footgun.
* **One-call style presets.** `sketch_style()` bundles a paper ground, a
  qualitative colour/fill palette tuned to it and (on ggplot2 >= 4.0) matching
  default geom ink into a single `+`-able object: `p +
  sketch_style("chalkboard")`. Presets: `"notebook"`, `"chalkboard"`,
  `"blueprint"`, `"field_notes"` and `"graphite"`; `sketch_styles()` lists
  them, and `palette = FALSE` keeps your own scales.
* **Highlighter swipes and hand-drawn underlines.**
  `annotate_sketch_highlight()` lays a wide translucent chisel-tip band (the
  new highlighter medium) over a region of interest in one call, and
  `annotate_sketch_underline()` draws a quick wobbly underline
  (`strokes > 1` re-draws it for an emphatic scrawl).
* **Chalk and highlighter media.** `medium = "chalk"` draws a broad, dry,
  flat-ended stroke with a faint halo of settled dust either side of the line --
  made for `theme_sketch(paper = "chalkboard")` with a light stroke colour.
  `medium = "highlighter"` lays a single very translucent chisel-tip band with
  crisp edges, so a swipe drawn under (or over) a pen line reads as a
  fluorescent emphasis rather than a line of its own.
* **Airbrush / spray medium.** A new `medium = "spray"` renders a line as a soft
  cloud of dots scattered around the centreline with no hard outline -- the
  spray-can / airbrush look. Powered by a new Layer-1 sampler `spray_scatter()`
  (Gaussian perpendicular offset, density falloff and feathered edges) and a
  Layer-2 `sketch_spray_grob()` that re-roughens and scatters at device
  resolution. Selectable per group via `aes(medium = )` like the other media.
* **Paper / canvas grounds.** `theme_sketch(paper = )` paints a simulated paper
  texture behind the data: `"notebook"` (blue rules + red margin), `"graph"`,
  `"dotted"`, `"aged"` (warm ground with soft blotches), `"blueprint"` and
  `"chalkboard"` (dark grounds that flip the text light), and `"kraft"`. The
  ruling is spaced in physical inches so it looks right on any panel aspect, and
  everything is drawn as vector primitives, so it reproduces on every device.
  `element_sketch_paper()` is the underlying panel-background element (usable
  directly in `theme()`), and `sketch_papers()` lists the grounds. The default
  `"none"` leaves the theme unchanged.
* **Watercolour fill.** A new `fill_style = "watercolor"` paints a region with
  stacked translucent washes instead of stroked fill lines: where the jittered
  boundary copies overlap the colour deepens toward the interior, the irregular
  edges feather like pigment bleeding into wet paper, and a scatter of darker
  specks reads as pigment granulation. Overlapping shapes blend wet-on-wet. It
  works on every polygon-fill geom (`geom_sketch_polygon()`, `geom_sketch_area()`,
  `geom_sketch_col()` / `bar()`, `geom_sketch_rect()` / `tile()`,
  `geom_sketch_violin()`, `geom_sketch_boxplot()`, `geom_sketch_ridgeline()`,
  ...), on the ellipse/circle geoms (`geom_sketch_circle()`, the `mark_circle` /
  `mark_ellipse` family), and on the hole-aware band geoms
  (`geom_sketch_contour_filled()`, `geom_sketch_density_2d_filled()`) where the
  washes respect holes via a new Layer-1 `watercolor_wash_multi()`. Powered by a
  new Layer-1 `watercolor_wash()`. Everything is vector, so it reproduces on
  every device.
* **Watercolour media physics: wet-on-wet bleed and ink-into-paper grain.** Two
  couplings make the washes behave more like real pigment. Where two watercolour
  regions overlap, the pigments now mingle: a new Layer-1 `wash_bleed()` samples
  the shared area and lays down soft specks tinted with the *blended* colour, so
  the overlap reads as mixed paint (toggle with `options(ggsketch.wash_bleed =)`).
  And the wash edge now feathers along the paper tooth: `watercolor_wash()` gains
  a `grain` argument that wicks the boundary in coherent capillary channels, and
  `theme_sketch(paper = )` couples it automatically through a new `paper_grain()`
  (smooth notebook/graph grounds wick little; aged and kraft wick a lot). `grain
  = 0` is the historical look and draws no extra randomness, so existing seeds
  reproduce exactly.
* **`medium` is now a mappable aesthetic.** On the path-like geoms
  (`geom_sketch_line()`, `geom_sketch_path()`, `geom_sketch_segment()`,
  `geom_sketch_step()`) you can map a discrete variable with `aes(medium = )` to
  draw each group (or each segment) in a different medium, and
  `scale_medium_discrete()` chooses which media the levels map to. The legend
  keys render in their own medium. Setting `medium` as a constant still works
  exactly as before, and the default is unchanged (`"pen"`).
* **`pressure` is a mappable aesthetic.** On `geom_sketch_line()` /
  `geom_sketch_path()`, map `aes(pressure = )` to a variable to make the stroke
  swell and thin *along* the line, like a pen pressed harder in places. The line
  then renders through the variable-width `stroke_ribbon()` engine even under the
  default `medium = "pen"`, and combines with any non-`pen` medium (the width
  profiles multiply). Values are rescaled by the new
  `scale_pressure_continuous()` (default band `c(0.2, 1.6)`); wrap in `I()` for
  raw multipliers. Per-vertex pressure is carried as an interpolated arc-length
  profile, so it stays correct when the centreline is re-roughened at device
  resolution.
* **The sketch colour palette now interpolates.** `scale_colour_sketch()` /
  `scale_fill_sketch()` (and `sketch_palette()`) used to recycle once a factor
  had more than eight levels. They now interpolate the eight ink-on-paper
  anchors into one distinct colour per level via a `colorRampPalette()` ramp, so
  the discrete scales keep working for large factors and quasi-continuous use.
  The first eight colours are unchanged, so small plots look identical; pass
  `interpolate = FALSE` for the old recycling behaviour.
* **`coord_sketch()`.** A drop-in replacement for `coord_cartesian()` that draws
  the *frame* hand-drawn -- the panel gridlines and axis ticks become roughened
  sketch grobs -- under *any* theme, not only `theme_sketch()`. It reuses
  ggplot2's own gridline and axis layout and only swaps how those elements are
  drawn, so limits, expansion, and clipping behave exactly like
  `coord_cartesian()`. `rough_grid` / `rough_ticks` toggle each element; combine
  with `theme_sketch(rough_frame = TRUE)` to roughen the panel border too.
* **`coord_sketch_polar()`.** The polar companion to `coord_sketch()` -- a
  drop-in replacement for `coord_polar()` that draws the circular grid
  hand-drawn (wobbly concentric rings and radial spokes), so rose / circular bar
  charts get a frame that matches the marks under any theme. Like
  `coord_sketch()` it reuses ggplot2's polar layout and only swaps how the grid
  is drawn (`theta`, `start`, `direction` behave as in `coord_polar()`).
* **New chart families: `geom_sketch_dumbbell()` and `geom_sketch_slope()`.** Two
  hand-drawn comparison charts. `geom_sketch_dumbbell()` draws a roughened
  connector from `x` to `xend` on a shared `y`, capped with a sketch dot at each
  end (separate `colour_x` / `colour_xend`), for showing the gap between two
  paired values per category. `geom_sketch_slope()` draws one roughened line per
  `group` across an ordered x with a dot at each vertex -- a sketch slopegraph
  for before/after rank changes. Both reuse the existing stroke and point grobs,
  so they need no new dependencies.
* **New chart family: `geom_sketch_beeswarm()`.** A hand-drawn beeswarm
  (dot-strip) plot -- at each categorical `x` the points are nudged sideways so
  they no longer overlap, so the width of the swarm reads as the local density of
  `y`. The offset is computed deterministically in data space (no device metrics,
  fully reproducible) and the dots are drawn with `geom_sketch_point()`, so they
  keep the usual wobble. `width` widens the swarm; `binwidth` / `nbins` control
  the `y` rows. No new dependencies (cf. `ggbeeswarm::geom_beeswarm()`).
* **New chart family: `geom_sketch_ridgeline()`.** A hand-drawn ridgeline
  (joyplot) -- a kernel density of `x` for each category on a discrete `y`,
  raised to its own baseline so the ridges overlap and the changing shape of the
  distribution is easy to compare. `scale` sets how far the tallest ridge rises
  (values above 1 overlap), `rel_min_height` trims the tails, and the fill
  honours every `fill_style` including `"watercolor"`. Ridges are drawn
  back-to-front so nearer ones sit on top. Powered by a new
  `StatSketchDensityRidges`; no new dependencies (cf.
  `ggridges::geom_density_ridges()`).
* **New chart family: `geom_sketch_waffle()`.** A hand-drawn waffle chart -- a
  square grid (default 10x10 = 100 cells) where the number of cells of each
  colour reads as that category's share of the whole. Map the category to `fill`
  and (for summarised data) a count to `weight`; cells are tallied by a new
  `StatSketchWaffle` (largest-remainder rounding) and drawn with
  `geom_sketch_tile()`, so they take the roughened outline and any `fill_style`
  (including `"watercolor"`). No new dependencies (cf. `waffle::geom_waffle()`).
* **New chart family: `geom_sketch_treemap()`.** A hand-drawn treemap -- nested
  rectangles tiling a square, each with area proportional to a value, so the
  biggest categories take the most space. Map the value to `area`, the category
  to `fill`, and optionally `label` to write a name in each tile. Rectangles are
  placed by a new Layer-1 squarified algorithm (`treemap_layout()`, exported) and
  drawn with the roughened rect look, so they take any `fill_style`. No new
  dependencies (cf. `treemapify::geom_treemap()`).
* **New chart family: `geom_sketch_streamgraph()`.** A hand-drawn streamgraph
  (ThemeRiver) -- a stacked area whose baseline floats so the coloured bands flow
  around a moving centre. Map `x`, `y`, and `fill`; a new `StatSketchStream`
  stacks the values and offsets the baseline (`offset = "silhouette"` centred,
  `"zero"` for a normal stacked area, or `"wiggle"` to minimise the slope), and
  each band is drawn as a roughened ribbon (any `fill_style`, including
  `"watercolor"`). No new dependencies (cf. `ggstream::geom_stream()`).
* **New chart family: `geom_sketch_calendar()`.** A hand-drawn calendar heatmap
  in the GitHub-contributions style -- one roughened tile per day, laid out as
  weeks (columns) and weekdays (rows), coloured by a value. Map `date` and
  `fill`; a new `StatSketchCalendar` builds the week/weekday grid (`week_start`
  chooses Sunday or Monday on top), and tiles are drawn with `geom_sketch_tile()`
  (default `fill_style = "solid"` so the gradient reads). Facet on a year column
  for multiple years. No new dependencies.
* **Flagship: hand-drawn networks.** `geom_sketch_node()` and
  `geom_sketch_edge()` draw a graph the sketch way -- roughened node markers with
  optional handwriting labels, and roughened (optionally curved, via `curvature`)
  edges. `sketch_graph()` is the bridge: give it a two-column edge data frame (or
  an `igraph` object) and it returns ready-to-plot `nodes` and `edges` frames,
  positioning nodes with `force_layout()`, a from-scratch pure-R
  Fruchterman-Reingold force-directed layout -- so the whole flagship needs **no
  graph dependency**. `igraph` is an optional, guarded convenience for ingestion
  only; extra edge/node columns are carried through as attributes you can map.
  The layout is seeded and reproducible and never disturbs the global RNG.
* **Flagship: hand-drawn maps (`sf`).** `geom_sketch_sf()` is a sketch take on
  `ggplot2::geom_sf()`: it roughens the boundaries of simple-features geometry in
  one call -- `(MULTI)POLYGON` features get a hole-aware hachure (or any
  `fill_style`), `(MULTI)LINESTRING` features become sketch paths, and
  `(MULTI)POINT` features become sketch points. It extracts coordinates from the
  `sf` object up front and draws ordinary sketch layers, so it sidesteps
  `coord_sf()`/`stat_sf()` and plots in **planar coordinates** -- pre-project
  lon/lat data with `sf::st_transform()` for a faithful map. `sf` is an optional,
  guarded dependency (Suggests); it is only needed when you call this geom.
* **Radar / spider charts.** `geom_sketch_radar()` draws each series as a closed
  roughened polygon over evenly spaced angular axes, behind a hand-drawn web of
  concentric rings, radial spokes and axis labels. Map `axis`, `value` and
  `group` (plus `colour`/`fill`); values are scaled to a common outer ring
  (`rmax`) and each polygon takes any `fill_style`, including `"watercolor"`.
  Like the pie geoms it lives in its own square space -- pair with
  `coord_equal()`. No new dependencies (cf. `ggradar`, `fmsb::radarchart()`).
* **Chord diagrams.** `geom_sketch_chord()` lays nodes on a circle, each given a
  rim arc proportional to its total flow, and draws every weighted relation as a
  ribbon whose ends are sub-arcs joined by curves through the centre. Give it an
  edge table and the `from`, `to` and `value` columns; ribbons are coloured by
  source node (add [scale_fill_sketch()] or any fill scale), take any
  `fill_style`, and self-loops are dropped. Pair with `coord_equal()`. No new
  dependencies (cf. `circlize::chordDiagram()`).
* **Alluvial / Sankey diagrams.** `geom_sketch_alluvial()` draws two or more
  categorical axes as stacks of strata, joined by flows whose thickness is the
  frequency of each category combination. Give it a wide data frame, the `axes`
  columns (in order), an optional `value` weight, and an optional `fill` column
  (default: the first axis); flows get raised-cosine edges and any `fill_style`.
  It draws in ordinary x/y space -- pair with `theme_void()`. No new
  dependencies (cf. `ggalluvial`, `ggsankey`).
* **Motion.** `animate_sketch()` animates a sketch plot two ways. `type =
  "boil"` (default) re-renders it while shifting every roughening seed per frame,
  so the whole drawing shimmers and re-draws itself like a hand-animated cel (the
  "boiling line" effect); the shift rides on a new global `ggsketch.seed_jitter`
  option that `resolve_seed()` adds to *every* resolved seed -- explicit or
  inherited -- so a plot boils with no change to its code, and frame 1 reproduces
  the static render exactly. `type = "draw_on"` instead reveals the finished
  drawing progressively, as if a hand were drawing it on. The reveal can be a
  straight wipe (`direction` `"lr"`/`"rl"`/`"bt"`/`"tb"`), a diagonal wipe
  (`"diag"`), or a circular iris opening from the centre (`"radial"`), and its
  pacing across frames follows an `easing` curve (`"linear"`, `"ease_in"`,
  `"ease_out"`, `"ease_in_out"`). Frames are stitched into a GIF when
  `gifski` or `magick` is installed (guarded Suggests, no new hard dependency);
  otherwise the frame paths are returned. Fully reproducible from `seed`.
* **`rough_frame` now reaches facet strips and the colourbar.**
  `theme_sketch(rough_frame = TRUE)` already drew the gridlines, panel border and
  axis ticks hand-drawn; it now also roughens facet **strip backgrounds** and the
  continuous-scale **colourbar frame and ticks**, so a faceted or colour-mapped
  plot's whole frame matches the marks. These reuse the existing
  `element_sketch_rect()` / `element_sketch_line()` elements (which work directly
  as `strip.background`, `legend.frame` and `legend.ticks` in any theme), so no
  new function or fragile facet/guide subclass is involved.
* **Two new tonal fills: `"stipple"` and `"pencil_shade"`.** `fill_style =
  "stipple"` scatters fine rough dots at random interior points (a pointillist
  tone with no visible lattice, unlike the grid-aligned `"dots"`); `fill_style =
  "pencil_shade"` lays trimmed graphite strokes plus a sparser cross set at a
  small angle for soft directional shading. Both work on every polygon-fill geom
  and respect holes on the band geoms (via new multi-ring variants). Density /
  texture follow `hachure_gap` and `roughness` as usual.
* **Ink-into-paper grain now reaches the line media too.** The paper-tooth
  coupling that feathers watercolour washes (`theme_sketch(paper = )`) now also
  roughens the variable-width stroke media (ink, brush, pencil, charcoal, ...):
  on a toothy ground (`"aged"`, `"kraft"`) their centreline and wet edge pick up
  extra grain, so a drawing reads consistently across line and fill. A no-op on
  plain ground, so existing plots are unchanged.
* **Repelled labels (the sketch answer to ggrepel).**
  `geom_sketch_text_repel()` and `geom_sketch_label_repel()` place text / boxed
  labels near their points but nudged apart so they no longer overlap each other
  or cover the data, each tied back to its anchor by a hand-drawn leader. A new
  Layer-1 force solver `repel_layout()` does the placement (run in device inches
  at draw time, so the repulsion is even on any panel aspect): overlapping boxes
  shove apart, boxes slide off the points they cover, and a weak spring keeps
  each label near what it names. The boxes and leaders are roughened like the
  rest of ggsketch; the glyphs stay a handwriting font (ADR-0007). No new
  dependency (cf. `ggrepel`).
* **Callout leader routing.** `geom_sketch_callout()` (and
  `annotate_sketch_callout()`) gain `leader =`: `"straight"` (default),
  `"elbow"` (horizontal then vertical, flowchart style) or `"curved"` (a bowed
  Bezier, sized by `curvature`). The arrowhead re-orients to the leader's end
  tangent for every route. Backed by a new Layer-1 `leader_path()`.
* **Arrowhead vocabulary.** Arrows and callout leaders now take a `head =`
  argument choosing the head style: `"triangle_open"` (the classic two-stroke V),
  `"triangle_filled"`, `"barb"` (swept-back harpoon barbs), `"fishtail"` (a
  forked swallowtail), `"dot"` (a blob) or `"bar"` (a perpendicular tick), and
  `geom_sketch_arrow()` gains `ends =` (`"last"` / `"first"` / `"both"`) for
  double-headed arrows. One Layer-1 generator `arrowhead()` (listed by
  `sketch_arrowheads()`) drives `geom_sketch_arrow()`, `annotate_sketch_arrow()`
  and `geom_sketch_callout()` alike. The old `arrow_type` (`"open"`/`"closed"`)
  still works and maps onto the new styles.
* **Faster boil animations (frame caching).** `animate_sketch(type = "boil")`
  now computes the plot's build (statistics, scales, layout) once and only
  re-tables / re-draws it per frame, instead of rebuilding the whole plot every
  frame -- the boil only ever varied at draw time. Output is unchanged (frame 1
  still equals the static render); heavier plots (stats, large data, contours)
  see the biggest speed-up.
* **gganimate bridge.** `boil_gganimate()` renders a gganimate animation --
  any ggsketch plot plus a `transition_*()` -- so that, on top of gganimate's
  data tweening (bars growing, points flying between states, a line drawing
  itself along `x`), the hand-drawn lines *boil*: the roughening seed steps once
  per frame as gganimate draws it, so the marks shimmer while the data moves. It
  is the moving-data companion to `animate_sketch()` (which boils a static plot),
  keyed to a per-frame counter so frame 1 is un-boiled and the run reproduces.
  gganimate is an optional Suggests; frames are stitched with the same
  `gifski` / `magick` backend, or returned as paths.
* **Parallel-coordinates plots.** `geom_sketch_parallel()` draws several numeric
  columns as vertical axes and every observation as a roughened polyline
  crossing them at its values. Give it the `axes` columns (in order); axes scale
  independently to a common height by default, and `colour` maps a column to the
  lines. No new dependencies (cf. `GGally::ggparcoord()`, `MASS::parcoord()`).
* **Mosaic plots.** `geom_sketch_mosaic()` splits the unit square into columns by
  the marginal counts of one categorical variable, then each column vertically by
  the conditional counts of a second, so every roughened tile's area is the joint
  frequency. Give it `x`, `y` and an optional `value` weight; colour by `y`
  (default) or `x`. No new dependencies (cf. `graphics::mosaicplot()`,
  `ggmosaic`).
* **Sunburst charts.** `geom_sketch_sunburst()` draws a hierarchy as nested rings
  of roughened annular sectors: the columns in `levels` define the hierarchy from
  the inner root ring outward, and each deeper ring splits its parent's angular
  span by the children's summed `value`, so a child always nests inside its
  parent. Colour by the top-level ancestor (`fill_by = "root"`, the classic look),
  by each node, or by ring. Pair with `coord_equal()` + `theme_void()`. No new
  dependencies (cf. `sunburstR`, `plotly`).
* **Arc diagrams.** `geom_sketch_arc_diagram()` lays nodes along a horizontal
  line and draws every weighted relation as a roughened semicircle arching over
  (or under, `side = "bottom"`) the axis between its endpoints - a linear cousin
  of the chord diagram. Edges are coloured by source and their width scales with
  `value`; node points scale with incident weight. No new dependencies
  (cf. `arcdiagram`, `ggraph`).
* **Bump / ranking charts.** `geom_sketch_bump()` draws each series' rank at
  every time point and joins it across adjacent times with smooth roughened
  curves, so a crossing reads as one series overtaking another. Give it long data
  with `x` (time), `group` (series) and `value` (ranked within each time); colour
  by series and label the ends. No new dependencies (cf. `ggbump`).
* **Dendrograms.** `geom_sketch_dendrogram()` draws a hierarchical-clustering
  tree with the right-angle elbows roughened into a hand-drawn wobble. Pass a
  ready `stats::hclust()` object or a numeric data frame (a tree is then computed
  via `hclust(dist(...))`, with `method`/`distance` controls). Four orientations
  (`up`/`down`/`left`/`right`). Pure base stats; no new dependencies
  (cf. `ggdendro`).
* **Marimekko charts.** `geom_sketch_marimekko()` draws variable-width stacked
  bars: each column's width is one category's (`x`) share of the total and the
  stacked segments are a second category's (`fill`) shares, so every tile's area
  is the joint value. The value-weighted, segment-coloured cousin of the mosaic
  plot (and reuses its layout); shows each column's width percent on top. No new
  dependencies.
* **Coxcomb / Nightingale rose charts.** `geom_sketch_rose()` gives each category
  an equal angular wedge whose radius encodes `value`; with `area_true = TRUE` the
  sector *area* (radius square-root) encodes value, as in Florence Nightingale's
  mortality roses. An optional `fill` category stacks radially within each wedge.
  Pair with `coord_equal()` + `theme_void()`. No new dependencies.

* **Engraving: tonal shading by line density.** A new module shades by the
  *density* of hand-drawn hatch lines, the way an etcher or banknote engraver
  builds a gradient -- light areas stay near-blank, shadows accumulate dense
  cross-hatch. Unlike the fill-pattern packages (which tile a motif), the tone
  is *computed* from geometry. `geom_sketch_engrave()` shades an `x`/`y`/`z`
  surface (high `z` = dark); `geom_sketch_shade()` shades each polygon region
  with a uniform density set by a `tone` aesthetic, so a mapped value reads as
  darkness. Powered by a new Layer-1 engine (`engrave_fill()` /
  `engrave_ladder()`, exposed at Layer 1) that lays down a ladder of hatch
  layers and keeps each only where the tone reaches its threshold, reusing the
  hole-aware scan-line. A pitch floor (`min_gap_in`) keeps the darkest tones
  from exploding into a runaway number of strokes.
  * `scale_tone_continuous()` (alias `scale_engrave()`) rescales a mapped
    variable to a legible tone band, so `aes(tone = z)` works the way
    `scale_size()` does for size; wrap in `I()` to use raw tone.
  * `geom_sketch_engrave()` takes an `x`/`y`/`z` grid directly, or shades raw
    points through a density stat, e.g.
    `geom_sketch_engrave(stat = "density_2d", contour = FALSE, aes(z = after_stat(density)))`.

* **Dot plots.** `geom_sketch_dotplot()` draws a hand-drawn Wilkinson-style dot
  plot: the data are binned along `x` and one roughened circular dot per
  observation is stacked upward in each bin. Dots stay round on any panel
  aspect (the diameter is taken from the bin width and converted to device
  inches at draw time). The sketch analogue of `ggplot2::geom_dotplot()`.
* **Filled contour and 2-D density bands.** `geom_sketch_contour_filled()` and
  `geom_sketch_density_2d_filled()` fill the *bands* between contour levels (not
  just the lines). Each band is a region that may contain holes (the next level
  up, cut out); a new hole-aware scan-line filler (`hachure_fill_multi()` /
  `sketch_fill_multi()`, exposed at Layer 1) keeps the holes empty, so
  `fill_style = "hachure"` and `"cross_hatch"` work on multi-ring regions as
  well as `"solid"`. This completes the filled `*_filled` family planned since
  1.3.0.
* **Bounding marks.** `geom_sketch_mark_circle()`, `geom_sketch_mark_ellipse()`,
  and `geom_sketch_mark_rect()` draw a roughened bounding shape around each
  group of points -- the sketch analogues of `ggforce::geom_mark_circle()` /
  `geom_mark_ellipse()` / `geom_mark_rect()`, completing the mark family started
  by `geom_sketch_mark_hull()`. Shade them with a mapped `fill` or leave them
  outline-only.
* **Lollipop charts.** `geom_sketch_lollipop()` draws a roughened stem from a
  `baseline` to each value, capped with a sketch point -- a tidy alternative to
  bars for ranked or sparse values (cf. `ggalt::geom_lollipop()`). Set
  `horizontal = TRUE` for horizontal stems.
* **Empirical CDF.** `geom_sketch_ecdf()` draws the empirical cumulative
  distribution as a hand-drawn stairstep (the sketch analogue of
  `ggplot2::stat_ecdf()`).
* **Content-aware arrows.** `geom_sketch_arrow()` (and the one-off
  `annotate_sketch_arrow()`) draw a hand-drawn arrow from `(x, y)` to
  `(xend, yend)` with an optional handwriting label. They are "content-aware":
  the shaft curvature defaults to an automatic, pleasing bow whose side follows
  the direction of travel; the roughened arrowhead orients itself to the curve's
  *end tangent*, so it always points at the target however the shaft bends; and
  the label justifies itself away from the target so it never sits under the
  shaft. `arrow_type = "closed"` gives a filled rough arrowhead.
* **Callouts.** `geom_sketch_callout()` (and `annotate_sketch_callout()`) draw a
  handwriting label in a roughened rounded box, optionally with a leader arrow to
  a target. The box auto-sizes to the label and the leader leaves from the box
  edge nearest the target -- a sketch speech-bubble / callout.
* **Hull marks.** `geom_sketch_mark_hull()` draws a roughened convex hull around
  each group of points (the sketch analogue of `ggforce::geom_mark_hull()`), for
  circling or grouping clusters; shade it with a `fill` or leave it outline-only.
* **Hand-drawn pie and donut charts.** `geom_sketch_pie()` draws a sketchy pie
  (one slice per row, sized by the `amount` aesthetic and coloured by `fill`);
  `geom_sketch_donut()` is the same with a hole. Slices stay circular on any
  panel shape (radii are taken from the smaller panel dimension and assembled in
  device space), so they look right without `coord_fixed()`. Slices are solid by
  default, with a rough edge; pass any `fill_style` (`"hachure"`, ...) to hatch
  them instead.
* **Arc geometry (Layer 1).** New `rough_arc()` roughens an elliptical arc into
  open hand-drawn strokes (the open-arc sibling of `rough_ellipse()`), and a new
  `sketch_wedge_grob()` draws roughened pie/donut sectors. These are the arc
  sampler that powers the pie/donut geoms.
* **Rounded rectangles and bars.** `geom_sketch_rect()`, `geom_sketch_tile()`,
  and `geom_sketch_col()` / `geom_sketch_bar()` gain a `corner_radius` argument
  (a fraction `[0, 1]` of each half-side) for rounded corners. Default `0`
  keeps sharp corners, so existing plots render identically.

# ggsketch 1.6.0

* **`roughness` is now a mappable aesthetic on the per-shape geoms.** Following
  `geom_sketch_point()`, you can map `roughness` per shape with
  `aes(roughness = )` (rescaled by `scale_roughness_continuous()`) or set a
  constant on: `geom_sketch_col()` / `geom_sketch_bar()`, `geom_sketch_rect()` /
  `geom_sketch_tile()`, `geom_sketch_circle()` / `geom_sketch_ellipse()`,
  `geom_sketch_segment()` / `geom_sketch_spoke()`, and the point-based
  `geom_sketch_jitter()` / `geom_sketch_count()`. `sketch_ellipse_grob()` now
  takes a per-shape roughness vector. Defaults are unchanged, so existing plots
  render identically. Path-like geoms (line, path, area, density, smooth, step,
  reference lines, ...) keep `roughness` as a layer parameter, since per-row
  roughness is ill-defined for a single path.
* **Independent fill roughness and seed.** Every fill-bearing geom now accepts
  `fill_roughness` and `fill_seed`, so the fill texture can be controlled
  separately from the outline. Previously the fill roughness was hardwired to a
  fraction of the outline's (`roughness * 0.5` for polygons, `* 0.4` for
  ellipses) and shared the outline's seed. The defaults are unchanged
  (`NULL` keeps the historical coupling), so existing plots render identically;
  set the values for a scratchier fill under a clean edge, or to reshuffle the
  fill pattern without moving the outline. Exposed on `sketch_polygon_grob()` /
  `sketch_ellipse_grob()` and threaded through `geom_sketch_col()`/`bar()`,
  `rect()`/`tile()`, `polygon()`, `area()`/`ribbon()`, `density()`, `violin()`,
  `circle()`/`ellipse()`, `crossbar()`, `boxplot()`, `hex()`, and `smooth()`.

# ggsketch 1.5.0

Annotation toolkit (first piece), roughness-as-an-aesthetic, and a real solid
fill.

* **`geom_sketch_bracket()`** draws a hand-drawn significance / comparison
  bracket spanning `xmin` to `xmax` at height `y`, with short end tips and an
  optional handwriting `label` (e.g. a p-value or "n.s.") centred above. The
  sketch counterpart of a `ggsignif` bracket, for marking pairwise comparisons
  on boxplots, bars, and violins.
* **`roughness` is now a mappable aesthetic on `geom_sketch_point()`.** Map it to
  a variable (`aes(roughness = z)`) so each point wobbles more or less, or set a
  constant. A mapped variable is rescaled to a legible roughness band by the new
  **`scale_roughness_continuous()`** (default range `c(0.01, 0.75)`), applied
  automatically just like `scale_size()`; wrap values in `I()` to use them as raw
  roughness. Rolling the mappable treatment out to the other geoms is planned.

### Bug fixes

* `fill_style = "solid"` now actually paints the shape with its fill colour
  instead of leaving the interior empty. Previously "solid" drew the outline
  only, so the fill colour was computed and then never used and the shape stayed
  transparent (most visible on solid bars/columns). The fill follows the
  roughened boundary so the hand-drawn edge is kept. Shapes with no fill
  (`fill = NA`) stay outline-only as before.
* `geom_sketch_boxplot()` now defaults to `fill = NA`, so the box stays
  outline-only (as it effectively was before solid fill started painting). Pass a
  `fill` for a solid box, or `fill_style = "hachure"` with a `fill` for a shaded
  one.
* `theme_sketch(base_family = )` now defaults to
  `getOption("ggsketch.base_family", "")`, so `options(ggsketch.base_family =
  "auto")` makes every sketch plot's text (titles, axes, legend) use a
  handwriting font, not only the labels drawn by `geom_sketch_text()` /
  `geom_sketch_bracket()`.

# ggsketch 1.4.0

The frame can now be roughened too, plus a matching colour palette, a new fill
style, and reproducible fonts.

* **Rough theme elements.** `element_sketch_line()` and `element_sketch_rect()`
  render gridlines, panel borders, and axis ticks as roughened sketch grobs.
  `theme_sketch(rough_frame = TRUE)` turns them on so the frame matches the marks.
  Works on ggplot2 3.5 and 4.0 (S3 `element_grob` methods; sketch params stored as
  attributes that survive theme merging).
* **Sketch colour scales.** `scale_colour_sketch()` / `scale_fill_sketch()` use a
  qualitative palette (`sketch_palette()`) chosen to suit the hand-drawn look; the
  `*_sketch_c()` variants give a continuous blue gradient.
* **New fill style `"scribble"`** — one continuous winding stroke that overshoots
  the boundary, like scribbling to fill a shape. Available everywhere a
  `fill_style` is accepted.
* **Reproducible fonts.** `register_sketch_font()` registers a handwriting font
  file (via systemfonts) so the result reproduces on any machine or CI runner
  without a system install; the font resolver now also finds registered fonts.

### Bug fixes

* `geom_sketch_point()` (and `geom_sketch_circle()` / `geom_sketch_ellipse()`)
  now draw each mark in its own colour when the colour/fill aesthetic varies
  within a single group — e.g. a continuous scale such as `scale_colour_sketch_c()`.
  Previously every mark took the first point's colour, so a gradient looked flat.
* The double-stroke outline now shares its vertices across passes, so it reads as
  one hand-drawn line gone over twice rather than two parallel lines. Previously
  each pass jittered the vertices independently and the strokes could drift apart
  on short or flat edges (most visible on outlines like violins).
* `geom_sketch_boxplot()` draws the median with much less bowing so the thick
  median line reads as one firm line rather than a bowed lens.
* The handwriting-font resolver (`base_family = "auto"`, `geom_sketch_text()`)
  now handles variable fonts. A face such as Caveat that ships as a variable font
  cannot be drawn by name on ragg/svglite (the device falls back to the default),
  so the resolver pins a renderable instance automatically. This is why a
  handwriting face now shows up out of the box, with no manual
  `register_sketch_font()` call needed.

# ggsketch 1.3.0

New geoms (Tier 3, first batch — 2-D density and text):

* `geom_sketch_contour()` — hand-drawn contour lines of a surface (via
  `stat_contour`).
* `geom_sketch_density2d()` / `geom_sketch_density_2d()` — 2-D kernel-density
  contour lines (via `stat_density_2d`).
* `geom_sketch_hex()` — hexagonal binning heatmap (needs the optional `hexbin`
  package).
* `geom_sketch_text()` and `geom_sketch_label()` — text in a handwriting font
  (the sketch of text is the font, not roughened glyphs). They now honour
  `nudge_x`/`nudge_y` like `ggplot2::geom_text()`, and the font resolver falls
  back to handwriting faces preinstalled on Windows/macOS (Segoe Print, Ink
  Free, Bradley Hand, Chalkboard, Comic Sans MS, …) so a sketchy face is found
  even when Caveat is not installed. Use a font-aware device
  (ragg, svglite, cairo) to render the handwriting font; the base GDI `png()`
  device may fall back to the default family.

Still to come in Tier 3: filled contour / 2-D density bands
(`*_filled`, which need multi-ring hole-aware hachure) and `geom_sketch_dotplot()`.

# ggsketch 1.2.0

New geoms (Tier 2 coverage — more stats and connectors; reuse existing grobs,
one new optional dependency):

* `geom_sketch_count()` — points sized by overplot count (via `stat_sum`).
* `geom_sketch_function()` — sketch a function curve (via `stat_function`).
* `geom_sketch_qq()` and `geom_sketch_qq_line()` — quantile-quantile points and
  reference line.
* `geom_sketch_quantile()` — quantile regression lines (needs the optional
  `quantreg` package).
* `geom_sketch_rug()` — marginal ticks along the panel edges (`sides`).
* `geom_sketch_spoke()` — segments from `(x, y)` by `angle` + `radius`.
* `geom_sketch_curve()` — hand-drawn curved connector (quadratic Bezier).
* `geom_sketch_bin2d()` / `geom_sketch_bin_2d()` — rectangular 2-D bin heatmap.

# ggsketch 1.1.0

New geoms (Tier 1 coverage — all reuse the existing grobs and base ggplot2
stats; no new hard dependencies):

* `geom_sketch_histogram()` and `geom_sketch_freqpoly()` — binned distributions
  (via `stat_bin`).
* `geom_sketch_jitter()` — `geom_sketch_point()` with a jitter position.
* `geom_sketch_violin()` — mirrored kernel density with hachure fill (via
  `stat_ydensity`).
* Interval family: `geom_sketch_linerange()`, `geom_sketch_pointrange()`,
  `geom_sketch_errorbar()`, `geom_sketch_crossbar()`.
* Reference lines: `geom_sketch_abline()`, `geom_sketch_hline()`,
  `geom_sketch_vline()`.

# ggsketch 1.0.0

First public release. A grammar-native, pure-R implementation of the rough.js
hand-drawn aesthetic for ggplot2 — no JavaScript, works on every graphics device.

### Geoms

* Lines & points: `geom_sketch_line()`, `geom_sketch_path()`,
  `geom_sketch_point()`.
* Bars & tiles: `geom_sketch_col()`, `geom_sketch_bar()`, `geom_sketch_rect()`,
  `geom_sketch_tile()`.
* Areas & curves: `geom_sketch_polygon()` (concave-safe fill),
  `geom_sketch_ribbon()`, `geom_sketch_area()`, `geom_sketch_density()`,
  `geom_sketch_smooth()`.
* Circular & composed: `geom_sketch_circle()`, `geom_sketch_ellipse()`,
  `geom_sketch_segment()`, `geom_sketch_step()`, `geom_sketch_boxplot()`.
* Helpers: `annotate_sketch()` and `theme_sketch()` (light + dark presets).

### Fill styles

* `hachure`, `cross_hatch`, `zigzag`, `zigzag_line`, `dots`, `dashed`, `solid`,
  via a full Active-Edge-Table scan-line filler that handles concave polygons.

### Design

* Three strict layers: pure geometry (Layer 1), grid grobs with `makeContent()`
  (Layer 2), ggproto geoms (Layer 3). Roughening happens in device-inch space.
* Reproducible: every randomized routine draws from a seeded local RNG stream and
  never mutates the user's global `.Random.seed`. Set `options(ggsketch.seed=)`.
* Sketchy legend keys for every geom.

### Notes

* Algorithms reimplemented in original R from published descriptions; no rough.js
  source is included (see `inst/NOTICE`). Independent of, and not affiliated with,
  rough.js or ggrough.

### Development history (internal milestones)

* 0.0.1 — Layer 1 sketch core (roughen, ellipse, Bézier, hachure, fill styles).
* 0.1.0 — Layer 2 grobs + first geoms (line/path/point) + `theme_sketch()`.
* 0.2.0 — Filled rectangular geoms (col/bar/rect/tile) + fill-style coverage.
* 0.3.0 — Polygons, areas, ribbons, density, smooth.
* 1.0.0 — Circular & composed geoms, annotations, theming, docs, release.
