Skip to content

Coverage

Randy Zwitch edited this page Sep 13, 2026 · 4 revisions

Primitive coverage

Is the set of primitives this package builds the right set? The Backlog's "Foundation review" answers yes, but argues it from capability alone: canvas can fill any shape it can describe, so a charting layer never has to drop to raw pixels. That's a real argument and it's still true, but it's an internal one — it says the package can draw anything, not that it draws the things visualizations actually need.

This page checks the same claim from outside, against what real visualizations use and what an independently designed graphics grammar chose to ship. Both point the same way, which is the useful part: the conclusion doesn't rest on this project's own taste.

What real visualizations actually use

Beagle (Battle et al., CHI 2018) is the largest corpus study to hand. It scraped 41,000+ SVG visualizations from the web across five tools and repositories (D3, Plotly, ChartBlocks, FusionCharts, Graphiq) and classified them into 24 types at 86% accuracy.

The distribution is heavily unbalanced. Most visualizations fall under four types — bar, line, scatter, and geographic maps — with simple bar charts and histograms making up the largest share by a wide margin.

Two findings are worth keeping in mind because they cut against intuition:

  • Pie charts are rare in practice. They occupy far more space in chart-type debates than in real corpora.
  • The long tail is genuinely long but genuinely thin. 24 types were classifiable; four dominate. Breadth of chart vocabulary is not what most real work needs.

What an independent grammar chose

Vega-Lite settled on a small, closed set of primitive marks: area, bar, circle, line, point, rect, rule, square, text, tick, geoshape, plus arc. Everything more complex is a composite mark — boxplot, errorbar, errorband are macros expanding to layered primitives, not new geometry.

That is a research group that studied this space arriving at roughly a dozen primitives and deriving the rest. It matches Wilkinson's original decomposition in The Grammar of Graphics and ggplot2's geom set, so the convergence isn't coincidental.

The mapping

Every Vega-Lite primitive mark, against what this package already exports:

Mark canvas_mojo
bar, rect, square fill_rect
circle fill_circle_aa
ellipse (not a Vega-Lite mark; error ellipses, confidence regions) fill_ellipse_aa, draw_ellipse_aa
line, rule, tick draw_line_aa, draw_polyline_aa
area fill_path_aa
arc (pie, donut) fill_arc_aa, fill_ring_sector_aa
text draw_text
geoshape fill_path_aa
boxplot, errorbar, errorband (composite) fill_rect + draw_line_aa
point with a non-circular shape fill_path_aa, no symbol vocabulary
surface (not a Vega-Lite mark; 3D surface and heightmap plots) fill_mesh, fill_mesh_shaded

The vocabulary is covered, and the two dominant corpus types — bar and line — land on the two cheapest primitives in the package.

Where coverage was incomplete

Two gaps, different in kind — one still open by choice, one closed.

Shaped point markers (triangle, diamond, cross, star) have no preset. This is deliberate and already recorded in the Backlog; the external evidence only strengthens the existing reasoning. Each marker is 3–6 Path calls with no new geometry behind it, and a marker set is chart vocabulary — the layer that knows which markers a scatter plot wants is the charting layer, not this one. d3-shape makes the same split, shipping seven symbols in a module above the rendering layer.

Ellipse was not reachable through DrawTarget — and unlike the markers, this was a real gap rather than a deferral, which is the one thing this review turned up that the internal capability argument had missed. fill_ellipse_aa/draw_ellipse_aa existed as raster primitives, but neither was on the trait and SvgCanvas had no ellipse method at all. Normally "use fill_path_aa" covers a missing shape — but Path.arc_to takes a single radius, so it builds circular arcs only, and an ellipse can therefore only be approximated through Path, with cubics.

Both halves were added to the trait as a result (see the Changelog): fill_ellipse_aa, then draw_ellipse_aa once it was clear the outline hits the identical limitation. That makes the ellipse the only shape on the trait carrying a fill and an outline, where circles and arcs carry only a fill — an asymmetry that is exactly right, since a circle outline is stroke_path_aa over a one-arc_to Path and an ellipse outline has no equivalent.

The general lesson is worth more than the fix: "you can always build it as a Path" is the standing justification for keeping the trait narrow, and it holds for every shape except the ones needing a curve Path can't express exactly. Circular arcs it has; elliptical arcs it does not. Anything else in that category would be a real gap too.

Why not simply add more shapes

Because shape is a weak encoding channel, and adding shapes has sharply diminishing returns.

Shape has no numerical ordering — it is selective but not quantitative, in Bertin's terms — so there is no clean capacity number the way there is for position or length. Shape It Up (Tseng et al., TVCG 2025) evaluated 39 shapes across three tasks and found that performance "does not map well to classical features of shape such as angles, fill, or convex hull" — the intuitive ways of reasoning about which shapes pair well simply don't predict how they perform. Their recommendations come from measured pairwise relations between shapes instead. So a library cannot pick a good shape set by reasoning from shape properties; that has to come from perceptual data.

The binding constraint in practice is also not availability but discriminability under overlap: shapes have to be relatively large to tell apart, which worsens occlusion exactly in the dense scatterplots where a categorical encoding is most wanted (van Onzenoodt et al., 2020). Existing libraries bracket the reasonable range — d3 ships 7 symbols, ggplot2 ships 25 — and the corpus evidence above argues for spending effort on bar/line/scatter quality over shape breadth.

What this evidence does not tell you

Worth stating plainly, so this page isn't over-read:

  • Corpus frequency is not your requirement. fill_arc_aa and fill_ring_sector_aa are on the trait despite pie charts being rare in Beagle's corpus, because a real downstream donut chart needed them. A concrete caller beats a population statistic every time; the corpus tells you where the mass is, not what you're building.
  • Beagle sampled the web, and SVG specifically. Scientific plotting, print, and canvas-rendered visualizations are underrepresented. A library aimed at those would weigh error bars, contours, and small-multiple layouts more heavily than this distribution suggests.
  • Coverage is not quality. That every mark maps to a primitive says nothing about whether the output looks right at chart sizes. That question is answered by the antialiasing and text work, not by this page.

Sources

Clone this wiki locally