Projections
Layout gives an element a rectangular drawing area. A projection maps its data points into that area. The same Arrow, Polyline, and Points can use Cartesian, polar, or geographic coordinates.
Polar coordinates in Graph
Give Graph a polar_projection() to map named
angle/radius records into Cartesian coordinates:
Each record supplies theta in radians and r as its radius. The helper
returns {x: r * cos(theta), y: r * sin(theta)} by default.
Graph maps those through its limits and flips into pixels.
Limits describe the projection's output space. Both limits must be explicit, or
provided with coord; inferring nonlinear bounds would require additional
sampling. Equal axis spans and a square frame preserve the polar disk's shape.
polar_projection({degrees, offset, clockwise}) defaults to radians, an offset
of 0, and counterclockwise angles from positive x. degrees: true makes both
theta and offset use degrees. The angle is offset + theta, or
offset - theta with clockwise: true. For compass bearings, use
polar_projection({degrees: true, offset: 90, clockwise: true}): zero points up
and 90° points right with Graph's default flips. Signed radii are supported.
Inputs require finite theta and r; extra finite numeric dimensions are
allowed. Tuples always expand to {x, y}, so use named records for polar input.
Custom projections can still be supplied as pure coordinate-record functions.
Parametric paths in source coordinates
SymLine,
SymSpline,
SymPoly,
SymPoints, and
SymArrow accept f(t) returning any numeric
coordinate record. Sampling produces source coordinates; the enclosing Graph
projects them into its Cartesian view. For example, put this spiral inside the
polar Graph above:
tlim sets the parameter range, while Graph's xlim and ylim describe the
projected output. Use tvals for explicit parameter samples. fx and fy remain
Cartesian conveniences; f lets the curve use the projection's own dimension
names, including {lon, lat} or {x, y, z}.
SymArrow uses the same sampling options as SymLine and the head controls of
Arrow. It draws an end head by default; add
start-head for both ends or set end-head={false} to omit the end head.
Directions follow the projected path, while head-size stays in layout units.
The runnable example uses SymLine for the polar rings and SymArrow for the spiral,
alongside ordinary lines, points, and labels sharing the same projection. Samples
are stored at construction; resizing does not call f again. Projection does not
add samples, so increase samples when a curve needs more detail. Null/nonfinite
samples and hidden projected points break the path, without adding heads at cuts.
Logarithmic coordinates and manual axes
log_projection({axes, base}) takes {x, y} records and returns logarithms on
the selected axes. Defaults are axes: 'both' and base: 10. Use axes: 'x'
or axes: 'y' for a semilog view; the other coordinate passes through unchanged.
The base must be finite, positive, and different from 1. Bases below 1
reverse the logarithmic direction. Extra finite numeric dimensions are allowed;
mark inputs can also use [x, y] tuples.
On a logged axis, values at or below zero return null, hiding the point and
splitting paths. An unlogged axis can still contain zero or negative values.
Nonfinite samples follow the usual gap rules.
Graph limits are projected values: [0, 3] covers data values from 1 to
1000 at base 10. HAxis,
VAxis, and meshes already use output coordinates
and do not apply the projection again. Supply explicit [position, label] ticks
to display the original data values:
Equal data ratios now occupy equal distances. You choose the tick labels and
positions; the projection carries no axis or tick metadata. Minor ticks can use
log10(value) positions between decades. The
logarithmic projection example includes
minor grid lines, two curves, and markers. Its f(t) samples uniformly in the
exponent, giving even detail across decades.
Both 2D helpers return immutable Projection objects and are available in JSX
and as imports from @gum-jsx/core. Their exported option types are
PolarProjectionOptions and LogProjectionOptions. Options are captured when
the helper is created. Use Graph for these views; Plot keeps its existing
Cartesian axes and does not accept a projection prop.
More than two input dimensions
Source records can carry any number of numeric dimensions. The
three-dimensional helix uses {x, y, z}
for a sampled curve, marker callbacks, axes, and label positions. Its Graph
maps every source position with one helper:
Only the final output used by Graph needs Cartesian x and y. Named records
also let intermediate projections use other names before that final mapping.
Standard 3D projections
These core helpers are available directly in JSX and as named imports from
@gum-jsx/core. Each returns an immutable Projection that can be shared among
Graphs. Inputs require finite numeric x, y, and z; extra numeric dimensions
are allowed. Camera positions and vectors use the exported Point3 record type.
Tuples keep their two-dimensional {x, y} meaning and cannot supply z.
| Helper | Options and defaults |
|---|---|
isometric_projection() | Fixed isometric view; each source unit axis projects to length 1, with 120° between axes. |
orthographic_projection({azimuth, elevation}) | Defaults to azimuth: 45, elevation: 30, in degrees. Projects onto a plane through the origin; depth does not change size. |
perspective_projection({eye, target, up, focal_length, near}) | Requires eye. Defaults to target: {x: 0, y: 0, z: 0}, up: {x: 0, y: 0, z: 1}, focal_length: 1, near: 0.01. |
Isometric and orthographic views
isometric_projection() uses x′ = (x − y) √3 / 2 and
y′ = z − (x + y) / 2. It is a uniformly scaled orthographic view with equal
foreshortening of the three axes.
For orthographic_projection, angles describe the direction of sight.
Azimuth turns from positive x toward positive y; elevation tilts the sight
direction toward positive z and must be between -90 and 90 degrees.
For example, azimuth: 90, elevation: 0 looks along positive y, displaying x
horizontally and z vertically. At either pole, azimuth still selects the page's
horizontal orientation. Projected x points right and projected y points up
with Graph's default flips. Distances within the projection plane retain their
source scale.
Perspective cameras
eye looks toward target; the camera uses up to orient the page. The default
is z-up. The eye and target must differ, and up must be nonzero and not parallel
to the direction of sight. A camera looking straight along z can use
up: {x: 0, y: 1, z: 0}. Camera options are copied when the helper is created;
later changes to the input objects do not affect it.
In the camera frame, output is {x: focal_length * horizontal / depth, y: focal_length * vertical / depth}. Depth is distance along the direction of
sight from the eye. focal_length and near must be positive finite numbers in
source coordinate units. Points with depth <= near return null, including
points behind the camera. This omits markers and annotations and splits sampled
paths using the existing visibility rules. Segments crossing the near plane
are not geometrically clipped; supply suitable samples or split them explicitly.
All three helpers return output coordinates for Graph's explicit limits. To preserve their proportions, match the Graph aspect to the ratio of its x and y limit spans. Projection affects positions; marker dimensions, stroke widths, text orientation, and child drawing order follow the usual rules. Depth sorting and surface visibility require separate handling.
Annotations inside GeoMap
GeoMap supplies its fitted projection to children:
Geographic records use {lon, lat} in degrees. [longitude, latitude] and
{x: longitude, y: latitude} are aliases; complete pairs are required, and
mixing the two naming schemes in one record is an error. Children share the map's
size, fitting, padding, center, rotation, and visibility. A point on the back of
an orthographic globe is omitted. Direct-child pos values project as a whole;
the placed element retains its normal size and orientation.
See Map routes for a sampled arrow.
Geometry stays with the element
Projection changes points. It does not warp completed fragments or automatically add path samples. An Arrow with two endpoints stays straight between their projected positions; supply more points for a curved route. Spline controls are computed from projected samples. Arrowheads, marker sizes, line widths, and text use ordinary layout lengths.
A projection can return null for an unavailable point. Markers and annotations
are omitted; line-like marks split into runs. Arrowheads stay on visible original
endpoints and are not added at cuts. This is point visibility, not geometric
clipping of a path: callers must sample and split routes appropriately at seams,
such as the antimeridian. GeoMap's geographic source geometry continues to use
its own spherical path renderer.
space="local" opts marks out of data mapping. A pair of tagged lengths such as
[px(20), px(30)] also stays local. A projected pair cannot mix one bare data
number with one tagged length. Line, HLine, VLine, Polyline, and Polygon use
ambient coordinates by default. A Line is omitted if either endpoint is hidden;
Polyline and Polygon break into visible runs, with Polygon closing each run. Path retains its
local geometry. Bars retain their rectangle
construction from projected opposite corners. Arc can project its center with
local px/em radii; use sampled Polyline points for a data-space arc.
Fill accepts named records on both explicit boundary arrays, splitting the
whole region when either side is missing or hidden. A scalar boundary requires
exactly {x, y} because it replaces one axis. Field and SymField also keep
Cartesian point and vector arithmetic before projection.
Plot axes and Network retain their existing Cartesian behavior.
The core contract
Projection wraps a pure (point: Coordinate) => Coordinate | null function,
where Coordinate is a readonly record of named numeric dimensions.
Graph accepts either this object or
the callback directly. Coordinates can carry a projection alongside
xlim, ylim, flip_x, and flip_y.
Custom elements use map_point(value, query.coordinates, size) for numeric
points, or coordinate_point(value, size, query.measure, query.coordinates) for
data and length-valued positions. Both return {x, y} in local pixels or null.
Pass coordinates through query.child to establish or inherit a frame; see
Coordinates. graph_children provides the same child
placement policy used by Graph and GeoMap.
These core helpers accept arbitrary numeric records, including {theta, r} and
{x, y, z}, and preserve every dimension until projection. Only the final result
used for viewport mapping needs x and y. Direct Projection.project calls
take records; read_coordinate expands source tuple shorthand to {x, y}.
copy_coordinate validates and snapshots a finite numeric record. A standalone
Projection may produce another named space for a later projection to consume.
Manual composition must pass through null results.
Annotation placement uses pos, accepting tuple shorthand or named numeric
records. Omitted positions stay at the local origin; an explicit position goes
through the projection. Projected marks preserve the same records in points,
from, to, segments, tip, origin, and Arc's center. Parametric f(t)
sampling also accepts arbitrary numeric records. For example,
<SymLine f={theta => ({theta, r: 0.8})} tlim={[0, tau]} /> draws a polar ring
inside the Graph above. Null samples and any nonfinite dimension create gaps.
Projection callbacks run during layout and must not depend on mutable captured
state. The immutable Projection object retains behavior by identity, and that
identity participates in layout caching. Construct a new projection when its
behavior changes. Arbitrary functions remain disallowed in source props.
Inputs and non-null outputs must be nonempty records of finite numbers.
Local lengths require Cartesian x and y; arbitrary named dimensions must be
numeric. No inverse, path transformer,
or automatic sampling is required; unmap_point supports linear frames only.