canvas.geo

canvas.geoNode

A geographic environment on the scene's canvas. Its in: names the boundary pack the environment is drawn over — canvas.geo:world is a world map on its own, with no child node needed — and every overlay inside it inherits that pack. The body holds those overlays: a choropleth joined to an @data dataset, marker points, great-circle arc routes, further region layers, and the parameters that drive them. The frame is a camera rather than a pair of domains — centre: (latitude first) and zoom: (web-map doubling levels) — and both are animatable with !cue.to, so one keyframe on each is a fly-to. The committed packs ship with the library and an attached .geojson travels with the document, so nothing is ever fetched from a tile server: a map renders offline, under both themes, and identically in ten years.

Writing with it:Selecting canvas content

#The geographic environment

canvas.geo is a map surface. Its implicit attribute is in, the boundary pack the environment is drawn over, so canvas.geo:world{ } is already a world map with nothing inside it. The committed packs are world (Natural Earth 110m countries) and six admin-1 sets — us-states, canada-provinces, australia-states, uk-counties, nz-regions and europe-admin1. Every overlay in the body inherits that pack unless it names its own. title captions the environment, projection chooses how the sphere is laid flat, and show-scale (on by default) says what a length on screen is worth on the ground.

canvas.geo:world{
    marker(at: 48.85, 2.35; label: Paris)
}(
    title: Where the survey ran
    projection: equal-earth
)

Boundaries a pack does not carry — electorates, catchments, council wards — arrive as an attached .geojson file, and in names it the same way it names a pack: in: #wards, pointing at an @data declaration. Either way the geometry is drawn as SVG, exactly as a graph mark is, and nothing is fetched from a tile server — which is what lets a map render offline, under both themes, and identically in ten years.

The projection is a claim, not a decoration. equal-earth keeps areas honest and is the right world framing; mercator keeps angles honest and is the frame on which a great-circle route visibly bends; conic tunes its standard parallels to whatever the environment contains and is the right choice for one country; albers-usa carries Alaska and Hawaii in as insets; globe is the sphere itself, the far hemisphere behind the horizon. Choose the one whose distortion your claim can afford.

#centre, zoom and the fly-to

A geographic environment is framed by a camera rather than by a pair of domains: centre — latitude first — and zoom, in web-map doubling levels, where 1 fits the world's width and each level halves the span. Omit them both and the environment fits its own contents across the whole choreography, so a marker that travels stays framed at every point of its journey.

Both are animatable, and a !cue.to on each with the same anchor is the fly-to. A centre may also be written as #id naming a marker the environment already draws, so a map framed on Tokyo that also pins Tokyo states the place once; a keyframe onto a reference snaps rather than gliding, because a reference has no midpoint to interpolate through.

canvas.geo#tour:world{
    region(in: world; only: France, Germany; fill: teal)
}(
    centre: 20, 0 !cue.to{50.5, 15}(at: 1; over: 900ms)
    zoom: 1 !cue.to{4}(at: 1; over: 900ms)
)

interactive: true hands the camera to the reader — scroll to zoom, drag to pan, double-click to reset. Exploration stays on the environment's subject: the zoom floor is the frame that fits its packs, markers and arcs, so a reader cannot lose the map. Their camera then overrides the authored one for the session, and continue: #that-environment carries it into a later environment along with their parameter values.

#Overlays

Everything drawn over the basemap is an overlay, and the four share opacity and hidden (declared but not drawn, and animatable, so hidden: true !cue.to{false}(at: 2) is a reveal). Cues wrap overlays exactly as they wrap graph marks. The attention cues do not reach this environment: a highlight ring and a spotlight are plot machinery, and a geographic environment admits only cue and cue.draw.

#Boundary layers

The environment's in: already draws its pack, so a region is for what a basemap cannot say: a subset in its own colours, a second pack layered over the first, an attached.geojson over a committed pack, or boundaries that arrive on a cue. only and except narrow it by name or ISO alpha-3 code, matched case-insensitively — France, DEU and Ivory Coast all read — and fill, stroke and width paint what is left.

canvas.geo:world{
    region(in: world; except: Antarctica)
    region(in: world; only: France, Germany, Italy; fill: teal)
}

#Joining a dataset to regions

choropleth joins a @data column onto the pack's regions — the geographic sibling of the graph's heatmap. regions names the column of region names and values the column to shade by; join keys match pack names and short codes case-insensitively, and a region the join does not cover keeps the neutral land fill rather than disappearing.

What values holds decides the reading. A numeric column shades through a ramp — multicolour, temperature, or a single hue's own intensity — with range pinning the window. A text column enumerates instead: each distinct value becomes a category with its own flat hue, named by key, and intensity gives depth within that hue. That pair is the electoral map's own grammar — the winner names the colour, the margin names how solid it is.

canvas.geo:australia-states{
    choropleth(
        regions: #vote.state
        values: #vote.party
        intensity: #vote.margin
        key: Labor red, Coalition blue, Greens green
    )
}(
    title: Who led each state
)
A choropleth publishes derived min, max, mean and count over the values the join actually covered, so prose can cite the spread the shading spans instead of a number typed in beside it: give the mark an id — choropleth#gdp() — and read {{#gdp.max}}.

#Places

marker is a point on the earth, in two forms. Singly, at takes a coordinate, latitude first, with an optional label; either half may be a parameter expression, so a marker rides a slider or a keyframe. Data-bound, lat and lon name columns and the one node draws every row — add size-by and symbol area scales with that column, which is the proportional-symbol map.

canvas.geo:world{
    marker(lat: #quakes.lat; lon: #quakes.lon; size-by: #quakes.magnitude; colour: red; opacity: 0.6)
}

#Great-circle routes

arc draws the great-circle path between two places — the shortest route over the earth, drawn the way the earth actually is. On a mercator environment the London–Tokyo arc bends far north of the straight line between them, and that one element is the whole projection-distortion lesson. Either end may be #id naming a marker in the same environment, and an anchored end follows its marker. An arc publishes derived distance in kilometres — the number the arc is.

canvas.geo:world{
    marker#london(at: 51.47, -0.46; label: London)
    marker#tokyo(at: 35.55, 139.78; label: Tokyo)
    cue.draw{
        arc#route(from: #london; to: #tokyo; label: {{round(#route.distance, 0)}} km)
    }(
        in: 1
        in-duration: 1200ms
    )
}(
    projection: mercator
)

#Moving a map through time

A map moves through time in one of two ways, and which one is a fact about the data. For a handful of periods, chain the columns: values is animatable, so #y1990.rate !cue.to{#y2000.rate}(at: 1) eases every region between its own two readings. Pin range when you do, or the ramp re-normalises each frame and the change erases itself.

For long-format data — one row per region per period — parameter.time is the better tool. It is an ordinary parameter under the skin, but it takes its range from the data rather than being told one, reads as a period, and can play. Name the period column and the environment gains a scrubber over exactly that span; marks carrying a period slice themselves against it, interpolating between the two periods that bracket wherever the reader is standing.

canvas.geo:world{
    parameter.time#years(period: #rates.year; name: Year; duration: 12s)
    choropleth(regions: #rates.country; values: #rates.rate; period: #rates.year; range: [0, 220])
}

Parameters and cues gives the rule the choice actually turns on: a change belongs to a scrubber when one sentence holds across its whole range, and to a cue otherwise.

#Derived attributes

Two overlays compute something and publish it for reading: a choropleth's min, max, mean and count, and an arc's distance. A parameter.time publishes value, the period the reader is standing on. Reach one by member access on the node's id — #route.distance in an attribute, {{#route.distance}} in prose. Derived attributes covers how they behave.


#Attributes

inImplicit
worldus-statescanada-provincesaustralia-statesuk-countiesnz-regionseurope-admin1reference
Also written: basemapThe environment's basemap — the boundary pack it draws, and the one every overlay inside it inherits. world is Natural Earth 110m countries; the other six are admin-1 packs. It may instead be #id naming an attached .geojson dataset — in: #wards — which makes the attachment itself the boundaries. Normally written in the head, canvas.geo:world, the way canvas.code:python is.Can be written as shorthand: canvas.geo:value
names
For an attached .geojson only: the feature property holding each region's name, which is what a choropleth joins on. Absent, the first of name, NAME, title or id the features carry.
codes
For an attached .geojson only: a feature property holding a short join code — the custom equivalent of a pack region's ISO alpha-3.
simplify
For an attached .geojson only: Douglas–Peucker tolerance in degrees, thinning boundary detail as the file is read. 0 keeps every point; a large file wants about 0.01.
Default0
The caption set over the top-left of the environment. A map carries no axis labels to name its own subject, so unlike a graph's title: this one is visible content and not merely an accessible name. Rich text, so $maths$ and inline formatting render.
projection
equal-earthmercatorequirectangularconicalbers-usaglobe
How the sphere is laid flat — which decides what the map tells the truth about. equal-earth, the default, keeps AREAS honest and is the right world framing. mercator keeps ANGLES honest: the frame every slippy map uses, and the one on which a great-circle arc visibly bends away from the straight line. equirectangular plots latitude and longitude straight. conic is an Albers equal-area conic that tunes its standard parallels to whatever the environment contains — what national atlases use, and the right choice for a single country or region. albers-usa is the United States composite, the lower 48 on an equal-area conic with Alaska and Hawaii carried in as insets, so a US environment spends no width on the Pacific. globe is the sphere itself, orthographic, with the far hemisphere behind the horizon.
Defaultequal-earth
centreAnimatable
Also written: centerWhere the camera looks: a coordinate written LATITUDE FIRST — centre: 48.85, 2.35 — or #id naming a marker this environment already draws, centre: #tokyo, so the place is stated once. Either half of a coordinate may be a parametised expression. Animatable: centre: 20, 0 !cue.to{50.5, 15}(at: 2; over: 900ms) pans the frame, and writing the same anchor on zoom: makes the two move as one fly-to. A keyframe targeting a #id snaps rather than gliding, because a reference has no midpoint to interpolate through. Omitted, the environment frames its own contents.
zoomAnimatable
Web-map doubling levels: 1 fits the world's width, and each level halves the span. Omitted, the zoom is AUTOMATIC — the environment fits what is inside it across the whole choreography, so a marker that travels stays framed at every point of its journey; writing zoom: 1 asks for exactly 1 instead. Parametised, and animatable with !cue.to.
interactive
Let the reader explore: scroll to zoom about the cursor, drag to pan, double-click to reset, with zoom and reset controls in the environment's toolbar. Exploration stays on the environment's subject — the zoom floor is the frame that fits its packs, markers and arcs, and panning cannot leave them behind. Once touched, the explored camera overrides the authored one for the rest of the session.
Defaultfalse
show-scaleAnimatable
Also written: scaleThe scale bar in the corner, saying what a length on the screen is worth on the ground. On by default, because every projection here changes scale across the frame and a reader has no other way to judge distance. It is measured at the middle of the environment, the only place the reading is honest. Turn it off for an environment where distance is beside the point.
Defaulttrue
show-graticuleAnimatable
Also written: graticuleDraw the latitude and longitude grid over the environment. Animatable, so a cue can bring the grid in at the step where the projection itself becomes the subject.
Defaultfalse
graticule-spacing
Degrees between graticule lines. Does nothing without show-graticule:.
Default15
continue
Inherit an earlier geographic environment's state: continue: #that-environment. The reader's explored camera and their parameter values carry forward; every authored attribute is the new environment's own.

#Allowed content

#Allowed in

#Examples

canvas.geo:world(title: The world)
canvas.geo:australia-states{
    choropleth(regions: #vote.state; values: #vote.party; key: Labor red, Coalition blue)
}(
    title: Who led each state
)
canvas.geo#tour:world{
    region(in: world; only: France, Germany; fill: teal)
}(
    centre: 20, 0 !cue.to{50.5, 15}(at: 1; over: 900ms)
    zoom: 1 !cue.to{4}(at: 1; over: 900ms)
)
canvas.geo:canada-provinces{
    choropleth(regions: #d.province; values: #d.rate)
}(
    projection: conic
)

#Notes

  • An environment's own id is written on the node — canvas.geo#europe{} — and continue: points back at it with #europe.
  • Cues wrap overlays exactly as they wrap graph marks, and cue.draw traces an arc along its length.
  • The cursor's coordinates, and the name of the region beneath it, read out in the corner of the environment.