Commit 0868fb0
Decoration for polar panels (#418)
* introduce ProjectionRenderer trait for modular projection handling
Replace CoordKind match arms throughout the Vega-Lite writer with a
ProjectionRenderer trait. Each projection type (cartesian, polar) now
owns its channel mapping and spec transformation logic, making it
straightforward to add map projections in the future.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add Projection::cartesian() and Projection::polar() constructors
Reduces boilerplate at call sites that use default aesthetics and
empty properties.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* remove spurious Send + Sync from traits
* Add panel decoration plumbing to ProjectionRenderer
background_layers() and foreground_layers() let projections prepend/append
VL layers around the data layers (e.g. grid lines, axis ticks). Both
receive resolved scales and the theme config so implementations can
derive decoration from break positions and style tokens.
Also moves apply_project_transforms and apply_panel_decor from free
functions into default methods on the trait (apply_transforms,
apply_panel_decor), removing the redundant renderer construction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Tag decoration layers with description and filter them in tests
Decoration layers inserted by apply_panel_decor() now get
"description": "background" or "foreground" automatically. Tests use a
new data_layer() helper that filters these out by index, so they remain
stable regardless of whether a projection adds decoration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Compute polar point marks in pixel space to align with arc marks
Non-arc marks in polar projection (point, line) now compute x/y in the
same pixel coordinate space arc marks use: center at (width/2, height/2)
with outerRadius = min(width,height)/2. Encodings use scale:null so
Vega-Lite treats values as raw positions. Also filters null position
values via isValid(), since scale:null bypasses VL's implicit null
handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Centralise polar coordinate math into shared expression helpers
Extracts five VL expression builders (expr_normalize_radius,
expr_normalize_theta, expr_polar_x, expr_polar_y, expr_polar_radius)
that are now used by data-layer transforms, arc mark radius ranges,
and decoration layers. Introduces POLAR_OUTER const for the normalised
outer radius. Also extracts polar_properties() from the inline parsing
that was duplicated in apply_polar_project.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add polar panel background, grid rings, and grid spokes
Implements background decoration layers for polar projections: a filled
panel arc, concentric grid rings at radius breaks, and radial grid
spokes at theta breaks. Moves numeric break/domain extraction to
Scale methods for reuse across the codebase.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add radial axis to polar foreground decoration
Draws axis line, tick marks, and labels along the start angle for
the radius (pos1) scale. Ticks are centered on full circles and
extend outward on partial arcs. Fixes operator precedence in
expr_polar_x/y by parenthesising the radius expression.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add angular axis to polar foreground decoration
Draws axis arc along the outer edge, radial tick marks at each theta
break, and centered text labels beyond the ticks. Label alignment
uses center/middle for now — per-datum alignment needs a different
approach in Vega-Lite.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use nested layers for angular axis labels to set per-label text alignment
Vega-Lite text marks only support a single align/baseline per layer.
Bucket breaks by their computed (align, baseline) in Rust, tag each
data row with an _ab field, and emit a sub-layer per unique tag that
filters on it and sets the correct mark properties.
Also fix clippy warnings (unused variable, unused import, unused mut).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Consolidate projection trait into single apply_projection() entry point
Replaces separate apply_transforms() and apply_panel_decor() calls with
one apply_projection() method. Moves faceting before projection so
decoration layers work correctly in faceted specs. Renames the unclear
apply() to transform_layers().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add PolarPanel struct to centralise polar geometry and expression helpers
PolarProjection now holds a PolarPanel with pre-computed angular range,
radius bounds, and VL expression strings (signal-based for non-faceted,
literal pixels for faceted). Expression helpers are methods on PolarPanel,
replacing the free functions. All private methods read from self.panel
instead of taking Projection parameters.
Also adds is_faceted() to the ProjectionRenderer trait with a default
panel_size() that returns container sizing, letting the call site in
mod.rs delegate sizing entirely to the projection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add numeric_breaks() and numeric_domain() to ScaleTypeTrait
Discrete and ordinal scales now synthesize numeric positions from their
categorical input ranges (breaks [1..n], domain [0.5, n+0.5]) so that
polar grid decoration can work uniformly across all scale types.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Support discrete scales, secondary channels, and offsets in polar→cartesian conversion
Extends convert_polar_to_cartesian for non-arc polar marks:
- Discrete theta/radius domains generate indexof() VL expressions
- radius2/theta2 channels converted to x2/y2 using primary domain
- Offset channels (radiusOffset/thetaOffset) normalized into polar
space when they carry a scale domain, or applied as raw pixel
displacements along the radial/tangential directions otherwise
- Discrete offsets narrowed by band fraction (0.9) to leave angular
gaps between adjacent categories, matching VL band scale padding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add break_labels() for display-ready axis labels in polar coordinates
Adds break_labels() to ScaleTypeTrait, returning (position, label)
pairs. Discrete and ordinal scales pair integer positions with
input-range category names; continuous scales format break values
as strings. Scale.break_labels() applies label_mapping overrides
on top (renaming or suppressing to empty string).
Polar radial and angular axes now use break_labels() so discrete
categories show their names instead of numeric positions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix panel size bug
* Fix discrete polar indexof: escape quotes and map OOB values to null
indexof() returns -1 for values not in the domain array, which
previously produced position 0 (outside the synthesized domain).
Now maps to null so VL drops the row. Also escapes single quotes
in category names to prevent broken VL expressions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add some missing tests
* Suppress polar axes and grid lines for dummy scales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Suppress polar decorations for free facet scales
Polar grid lines and axes are drawn as manual VL layers positioned from
the global scale domain. With free scales each panel has its own domain,
so the global positions would be wrong. Suppress them rather than render
misleading decorations.
Also adds Facet::is_free() and removes the free_scales plumbing from
EncodingContext/ScaleContext in favour of reading spec.facet directly.
get_projection_renderer() now takes Option<&Facet> instead of a bool.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add radar property to polar projection
Nullable boolean `radar` setting on PROJECT TO polar. When null
(default), auto-detects from theta scale discreteness. When
explicitly true, validates that the angle scale is discrete.
Resolved after scale resolution in resolve_projection_properties()
so downstream code can read it as a plain boolean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Render radar polygons for polar decorations
When radar mode is active (discrete theta), panel background, grid
rings, and angular axis outline use straight-segment polygons instead
of circular arcs. Shared helpers: arc_ring, polygon_ring, theta_breaks.
Donut panels (inner > 0) trace outer vertices forward then inner
reversed so the fill rule leaves the centre hole empty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Close partial-arc radar polygons with radial edges
For partial circles (start != end - 360), polygon_ring now adds
vertices at the start and end angles and traces back through the
centre (or inner radius) to form a closed wedge shape.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Correct radial axis placement for full-circle radar polygons
The start angle bisects a polygon face, which sits at cos(half_span)
of the circumscribed radius. Scale the axis line, ticks, and labels
inward so they land on the polygon edge rather than beyond it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Lerp theta offsets along polygon edges in radar mode
In radar mode, theta offsets (e.g. jitter) now interpolate linearly
toward the adjacent spoke instead of following a circular arc. This
keeps displaced points inside the polygon panel boundaries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Correct radar polygon and axis radii at partial-arc boundaries
Partial-arc start/end vertices are now pulled inward by
cos(angle_to_nearest_break) so boundary faces are flush with
inter-break edges. The radial axis correction is extended from
full-circle-only to all radar panels. Theta offset lerp targets
are clamped to [start, end] so boundary spokes lerp toward the
panel edge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add to docs
* Require >2 angle categories for radar mode
Radar plots with only 1–2 categories degenerate into a line or single
axis, so suppress auto-detection and reject explicit `radar => true`
when the theta scale has ≤2 levels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* bit of polishing
* Centralise scale info on PolarContext via AxisInfo struct
Introduce AxisInfo (domain, breaks, labels, is_free) built from scales
at construction time. Zero-range domains normalize to None upfront,
eliminating downstream guards. Add is_full_circle and
angle_breaks_radians as derived fields on PolarContext.
This simplifies expr_normalize_radius/theta, all decoration methods
(grid_rings, grid_spokes, radial_axis, angular_axis, panel_arc),
convert_polar_to_cartesian, and polygon_ring — which no longer need
scale/domain/thetas parameters passed through call chains.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove dead DataFrame clone from apply_polar_project
The polar projection never transforms the DataFrame — it only modifies
the VL spec. Drop the data parameter and Option<DataFrame> return from
transform_layers and apply_projection. Also fix 7 unnecessary mut
bindings in tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use shared escape_vega_string for all Vega expression escaping
encoding.rs escaped single quotes but not backslashes in label
remap expressions. Consolidate all three call sites to use the
existing escape_vega_string helper, which handles both.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Deduplicate discrete/ordinal scale methods into shared helpers
Extract categorical_numeric_breaks, categorical_numeric_domain, and
categorical_break_labels into scale_type/mod.rs. Both Discrete and
Ordinal now delegate to these instead of duplicating the logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* cargo fmt + clippy warnings
* relax angle tolerance
* add news bullets
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>1 parent 6bdf2a9 commit 0868fb0
19 files changed
Lines changed: 3344 additions & 513 deletions
File tree
- doc
- get_started
- syntax/coord
- src
- execute
- plot
- facet
- projection
- coord
- scale
- scale_type
- reader
- writer/vegalite
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
3 | 8 | | |
4 | 9 | | |
5 | 10 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
87 | | - | |
| 87 | + | |
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
33 | 37 | | |
34 | 38 | | |
35 | 39 | | |
| |||
70 | 74 | | |
71 | 75 | | |
72 | 76 | | |
| 77 | + | |
73 | 78 | | |
74 | 79 | | |
75 | 80 | | |
76 | 81 | | |
| 82 | + | |
77 | 83 | | |
78 | 84 | | |
79 | 85 | | |
| |||
97 | 103 | | |
98 | 104 | | |
99 | 105 | | |
| 106 | + | |
100 | 107 | | |
101 | 108 | | |
102 | 109 | | |
103 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
| |||
1420 | 1421 | | |
1421 | 1422 | | |
1422 | 1423 | | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
1423 | 1431 | | |
1424 | 1432 | | |
1425 | 1433 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
67 | 84 | | |
68 | 85 | | |
69 | 86 | | |
| |||
221 | 238 | | |
222 | 239 | | |
223 | 240 | | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
739 | 739 | | |
740 | 740 | | |
741 | 741 | | |
742 | | - | |
| 742 | + | |
743 | 743 | | |
744 | 744 | | |
745 | | - | |
746 | | - | |
747 | | - | |
748 | | - | |
749 | | - | |
| 745 | + | |
750 | 746 | | |
751 | 747 | | |
752 | 748 | | |
| |||
827 | 823 | | |
828 | 824 | | |
829 | 825 | | |
830 | | - | |
| 826 | + | |
831 | 827 | | |
832 | 828 | | |
833 | | - | |
834 | | - | |
835 | | - | |
836 | | - | |
837 | | - | |
| 829 | + | |
838 | 830 | | |
839 | 831 | | |
840 | 832 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
45 | 50 | | |
46 | 51 | | |
47 | 52 | | |
| |||
75 | 80 | | |
76 | 81 | | |
77 | 82 | | |
78 | | - | |
| 83 | + | |
| 84 | + | |
79 | 85 | | |
80 | 86 | | |
81 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
0 commit comments