36  Globe

What does the data look like on the earth itself? The map chapter flattened the sphere to fit a page. This chapter keeps it round, and turns it toward you.

Here is the map chapter’s first sentence, and beside it the same sentence with the space swapped:

quakes_fiji: first 5 of 1000 rows
east north elevation magnitude slab
181.62 -20.42 -562 4.8 540-630 km
181.03 -20.62 -650 4.2 630-720 km
184.10 -26.00 -42 5.4 0-90 km
181.66 -17.97 -626 4.1 540-630 km
181.96 -20.42 -649 4.0 630-720 km
(data(quakes_fiji) + point + x(east) + y(north) + map() +
   title("map()")) |
  (data(quakes_fiji) + point + x(east) + y(north) +
     globe(turn = 178, tilt = -18) + title("globe(turn = 178, tilt = -18)"))
((data(quakes_fiji) + point + x(col.east) + y(col.north) + map() +
   title("map()")) |
  (data(quakes_fiji) + point + x(col.east) + y(col.north) +
     globe(turn = 178, tilt = -18) + title("globe(turn = 178, tilt = -18)")))
(data(quakes_fiji) + point + x(:east) + y(:north) + map() +
  title("map()")) |
  (data(quakes_fiji) + point + x(:east) + y(:north) +
  globe(turn = 178, tilt = -18) + title("globe(turn = 178, tilt = -18)"))
beside(plot(data(quakes_fiji), point, x(col.east), y(col.north), map(),
  title("map()")),
  plot(data(quakes_fiji), point, x(col.east), y(col.north),
  globe({ turn: 178, tilt: -18 }),
  title("globe(turn = 178, tilt = -18)")))
170° 180° -30° -20° map() North East globe(turn = 178, tilt = -18)

“Given quakes Fiji: points, x is east, y is north, on the globe.”

x is longitude and y is latitude, exactly as on the map. What changed is what a position means on the page. Each point now stands at its place on the sphere, and the sphere is drawn as the disk you would see looking at it.

36.1 The view

A globe shows one half of the earth, so which half matters. turn and tilt name the place the view faces, in the words space() already uses: turn is the longitude at the center of the disk, and tilt is the latitude. The plot above faces Fiji. Leave them out and the view faces zero and zero, where the equator crosses the prime meridian.

turn is a bearing, so any number is a view: 190 and -170 face the same place. tilt is a latitude, and it stops at the poles.

In the web edition the written angle is only where the globe starts. Drag it and it turns, the way the cube in Space turns, and the far half of the earth comes around; reset returns to the view the sentence asked for. On paper a plot keeps its one view, which is why this chapter also draws some data at a second angle.

Half the earth faces away, and data on that half is hidden behind the sphere. gog counts what a view hides rather than dropping it in silence:

world_borders: first 5 of 4150 rows
lon lat country continent piece
66.519 37.363 Afghanistan Asia p001
69.196 37.151 Afghanistan Asia p001
70.807 38.486 Afghanistan Asia p001
71.845 36.738 Afghanistan Asia p001
73.260 37.495 Afghanistan Asia p001
data(world_borders) + path + x(lon) + y(lat) + group(piece) +
  style(color = "#c9ccd6") +
  data(quakes_fiji) + point + x(east) + y(north) +
  style(color = "tomato", opacity = 0.35, size = 3) +
  globe(turn = 178, tilt = -18) + title("The map chapter's layers, kept round")
(data(world_borders) + path + x(col.lon) + y(col.lat) + group(col.piece) +
  style(color = "#c9ccd6") +
  data(quakes_fiji) + point + x(col.east) + y(col.north) +
  style(color = "tomato", opacity = 0.35, size = 3) +
  globe(turn = 178, tilt = -18) + title("The map chapter's layers, kept round"))
data(world_borders) + path + x(:lon) + y(:lat) + group(:piece) +
  style(color = "#c9ccd6") + data(quakes_fiji) + point + x(:east) +
  y(:north) + style(color = "tomato", opacity = 0.35, size = 3) +
  globe(turn = 178, tilt = -18) +
  title("The map chapter's layers, kept round")
plot(data(world_borders), path, x(col.lon), y(col.lat), group(col.piece),
  style({ color: "#c9ccd6" }), data(quakes_fiji), point, x(col.east),
  y(col.north), style({ color: "tomato", opacity: 0.35, size: 3 }),
  globe({ turn: 178, tilt: -18 }),
  title("The map chapter's layers, kept round"))
gog: 3192 of 4150 row(s) face away from `globe(turn = 178, tilt = -18)` and are hidden behind the sphere. Turn to face them, or put a second view beside this one with `|`.
The map chapter's layers, kept round

“Given the world borders: paths, x is lon, y is lat, grouped by piece; then given quakes Fiji: points, x is east, y is north, on the globe.”

Read the message above the plot. The coastline table covers the whole earth, so most of its rows face away from any one view, while every earthquake faces this one. Turning the globe brings the hidden rows around:

data(world_borders) + path + x(lon) + y(lat) + group(piece) +
  style(color = "#c9ccd6") +
  globe(turn = 20, tilt = 5) + title("The other side")
(data(world_borders) + path + x(col.lon) + y(col.lat) + group(col.piece) +
  style(color = "#c9ccd6") +
  globe(turn = 20, tilt = 5) + title("The other side"))
data(world_borders) + path + x(:lon) + y(:lat) + group(:piece) +
  style(color = "#c9ccd6") + globe(turn = 20, tilt = 5) +
  title("The other side")
plot(data(world_borders), path, x(col.lon), y(col.lat), group(col.piece),
  style({ color: "#c9ccd6" }), globe({ turn: 20, tilt: 5 }),
  title("The other side"))
The other side

36.2 The graticule

The gray lines on the sphere are the graticule: a meridian every thirty degrees of longitude, and a parallel every thirty degrees of latitude. They are this space’s gridlines, the same lines every panel draws, bent onto the sphere the way polar bends them into rings and spokes. There are no axes beside the disk, because a sphere has no edge to write one on; the graticule is the reference instead. theme(grid = ) reaches it like any other grid:

data(quakes_fiji) + point + x(east) + y(north) +
  globe(turn = 178, tilt = -18) + theme(grid = "none") +
  title("No graticule")
(data(quakes_fiji) + point + x(col.east) + y(col.north) +
  globe(turn = 178, tilt = -18) + theme(grid = "none") +
  title("No graticule"))
data(quakes_fiji) + point + x(:east) + y(:north) +
  globe(turn = 178, tilt = -18) + theme(grid = "none") +
  title("No graticule")
plot(data(quakes_fiji), point, x(col.east), y(col.north),
  globe({ turn: 178, tilt: -18 }), theme({ grid: "none" }),
  title("No graticule"))
No graticule

36.3 A route bends with the sphere

On the map, path joins places with straight strokes. On the globe a straight route does not exist. The shortest way between two places bends with the surface, along a great circle, and a path here follows it:

flight: all 2 rows
lon lat
139.69 35.69
-118.24 34.05
data(world_borders) + path + x(lon) + y(lat) + group(piece) +
  style(color = "#c9ccd6") +
  data(flight) + path + x(lon) + y(lat) +
  style(color = "#3d5a80", arrow = "end") +
  globe(turn = 178, tilt = -18) + title("Tokyo to Los Angeles")
(data(world_borders) + path + x(col.lon) + y(col.lat) + group(col.piece) +
  style(color = "#c9ccd6") +
  data(flight) + path + x(col.lon) + y(col.lat) +
  style(color = "#3d5a80", arrow = "end") +
  globe(turn = 178, tilt = -18) + title("Tokyo to Los Angeles"))
data(world_borders) + path + x(:lon) + y(:lat) + group(:piece) +
  style(color = "#c9ccd6") + data(flight) + path + x(:lon) + y(:lat) +
  style(color = "#3d5a80", arrow = "end") +
  globe(turn = 178, tilt = -18) + title("Tokyo to Los Angeles")
plot(data(world_borders), path, x(col.lon), y(col.lat), group(col.piece),
  style({ color: "#c9ccd6" }), data(flight), path, x(col.lon), y(col.lat),
  style({ color: "#3d5a80", arrow: "end" }),
  globe({ turn: 178, tilt: -18 }), title("Tokyo to Los Angeles"))
Tokyo to Los Angeles

Two rows describe that route, and the sphere sets the curve between them. The arc bends toward the pole because the shortest way across an ocean bends there too.

36.4 Many routes are a network

One table can hold every route at once. Give each route a name, give each of its two rows a place, and group() splits the table into one arc per route, exactly as it split the coastline into pieces. A point layer marks the cities and a text layer names them. The picture this draws is a route network, and it needs no new word, because every position in it is a place the data already knows:

routes: first 5 of 14 rows
city route lon lat
Seoul Seoul-Tokyo 126.98 37.57
Seoul Seoul-Singapore 126.98 37.57
Seoul Seoul-Delhi 126.98 37.57
Tokyo Seoul-Tokyo 139.69 35.68
Tokyo Tokyo-Anchorage 139.69 35.68
cities: first 5 of 8 rows
city lon lat
Seoul 126.98 37.57
Tokyo 139.69 35.68
Singapore 103.82 1.35
Delhi 77.21 28.61
Cairo 31.24 30.04
data(world_borders) + path + x(lon) + y(lat) + group(piece) +
  style(color = "#c9ccd6") +
  data(routes) + path + x(lon) + y(lat) + group(route) +
  style(color = "#3d5a80", opacity = 0.6) +
  data(cities) + point + x(lon) + y(lat) +
  data(cities) + text + x(lon) + y(lat) + label(city) + style(nudge = "up") +
  globe(turn = 95, tilt = 30) + title("Routes between cities")
(data(world_borders) + path + x(col.lon) + y(col.lat) + group(col.piece) +
  style(color = "#c9ccd6") +
  data(routes) + path + x(col.lon) + y(col.lat) + group(col.route) +
  style(color = "#3d5a80", opacity = 0.6) +
  data(cities) + point + x(col.lon) + y(col.lat) +
  data(cities) + text + x(col.lon) + y(col.lat) + label(col.city) + style(nudge = "up") +
  globe(turn = 95, tilt = 30) + title("Routes between cities"))
data(world_borders) + path + x(:lon) + y(:lat) + group(:piece) +
  style(color = "#c9ccd6") + data(routes) + path + x(:lon) + y(:lat) +
  group(:route) + style(color = "#3d5a80", opacity = 0.6) + data(cities) +
  point + x(:lon) + y(:lat) + data(cities) + text + x(:lon) + y(:lat) +
  label(:city) + style(nudge = "up") + globe(turn = 95, tilt = 30) +
  title("Routes between cities")
plot(data(world_borders), path, x(col.lon), y(col.lat), group(col.piece),
  style({ color: "#c9ccd6" }), data(routes), path, x(col.lon), y(col.lat),
  group(col.route), style({ color: "#3d5a80", opacity: 0.6 }),
  data(cities), point, x(col.lon), y(col.lat), data(cities), text,
  x(col.lon), y(col.lat), label(col.city), style({ nudge: "up" }),
  globe({ turn: 95, tilt: 30 }), title("Routes between cities"))
gog: 897 of 4150 row(s) face away from `globe(turn = 95, tilt = 30)` and are hidden behind the sphere. Turn to face them, or put a second view beside this one with `|`.
Seoul Tokyo Singapore Delhi Cairo London Anchorage Sydney Routes between cities

“Given the world borders: paths, x is lon, y is lat, grouped by piece; then given the routes: paths, x is lon, y is lat, grouped by route; then given the cities: points, x is lon, y is lat; then given the cities: text, x is lon, y is lat, label by city, on the globe.”

Looking up each city’s longitude and latitude is the host language’s job, the same join a later section does for country centers. What the sentence adds is only the layers: the routes, then the cities, then their names.

Every arc bends with the surface. The count above the plot reports what this view hides: the coastlines on the far side, and no city. Dragging the globe in the web edition brings those coastlines around. A network between places asks nothing new of the grammar. The places carry the positions, group() carries the connections, and the sphere carries the shortest ways between them.

36.5 Meridians and parallels

A rule draws a line at one position and spans the axis it does not name. On the map that drew the equator as a straight line. Here the same sentence bends it around the sphere:

equator: its one row
lat
0
data(world_borders) + path + x(lon) + y(lat) + group(piece) +
  style(color = "#c9ccd6") +
  data(equator) + rule + y(lat) + style(color = "#d4674f") +
  globe(turn = 20, tilt = 30) + title("The equator, bent around the sphere")
(data(world_borders) + path + x(col.lon) + y(col.lat) + group(col.piece) +
  style(color = "#c9ccd6") +
  data(equator) + rule + y(col.lat) + style(color = "#d4674f") +
  globe(turn = 20, tilt = 30) + title("The equator, bent around the sphere"))
data(world_borders) + path + x(:lon) + y(:lat) + group(:piece) +
  style(color = "#c9ccd6") + data(equator) + rule + y(:lat) +
  style(color = "#d4674f") + globe(turn = 20, tilt = 30) +
  title("The equator, bent around the sphere")
plot(data(world_borders), path, x(col.lon), y(col.lat), group(col.piece),
  style({ color: "#c9ccd6" }), data(equator), rule, y(col.lat),
  style({ color: "#d4674f" }), globe({ turn: 20, tilt: 30 }),
  title("The equator, bent around the sphere"))
The equator, bent around the sphere

“Given the world borders: paths, x is lon, y is lat, grouped by piece; then given the lines of table: a rule, y is lat, on the globe.”

A rule on y is a parallel, a circle of constant latitude. A rule on x is a meridian, and it runs pole to pole.

36.6 The choropleth, kept round

Filling countries is the map’s most familiar use, and the edge of the disk is the hard case for a fill. The map chapter filled each country by its continent. The same sentence fills the countries on the sphere:

data(world_borders) + zone + x(lon) + y(lat) + group(country) +
  color(continent) + globe(turn = 20, tilt = 5) +
  title("Every country, filled by continent")
(data(world_borders) + zone + x(col.lon) + y(col.lat) + group(col.country) +
  color(col.continent) + globe(turn = 20, tilt = 5) +
  title("Every country, filled by continent"))
data(world_borders) + zone + x(:lon) + y(:lat) + group(:country) +
  color(:continent) + globe(turn = 20, tilt = 5) +
  title("Every country, filled by continent")
plot(data(world_borders), zone, x(col.lon), y(col.lat),
  group(col.country), color(col.continent), globe({ turn: 20, tilt: 5 }),
  title("Every country, filled by continent"))
Every country, filled by continent Continent Asia Europe Africa South America Oceania North America Seven seas (open ocean)

“Given the world borders: zones, x is lon, y is lat, grouped by country, color by continent, on the globe.”

Look at the countries the edge of the disk cuts through. gog closes each cut shape along that edge, so a half-visible country is still a filled region rather than an open outline.

36.7 A measure stands on the radius

The flattened map has no axis to spare. The sphere has exactly one: the radius, which runs straight out from the surface at every place. A bar here stands at its place and measures outward along it, a spike from the surface, and z names its measure:

population_spikes: first 5 of 120 rows
country lon lat people continent reach
Afghanistan 67.366280 34.59696 31889923 Asia 5647.116
Albania 20.165857 41.23771 3600523 Europe 1897.504
Algeria 2.743348 29.27604 33333216 Africa 5773.493
Angola 16.961080 -10.70456 12420476 Africa 3524.270
Argentina -65.436810 -38.39190 40301927 Americas 6348.380
data(world_borders) + path + x(lon) + y(lat) + group(piece) +
  style(color = "#c9ccd6") +
  data(population_spikes) + bar + x(lon) + y(lat) + z(reach) + color(continent) +
  globe(turn = 20, tilt = 12) +
  title("World population, standing on its places")
(data(world_borders) + path + x(col.lon) + y(col.lat) + group(col.piece) +
  style(color = "#c9ccd6") +
  data(population_spikes) + bar + x(col.lon) + y(col.lat) + z(col.reach) + color(col.continent) +
  globe(turn = 20, tilt = 12) +
  title("World population, standing on its places"))
data(world_borders) + path + x(:lon) + y(:lat) + group(:piece) +
  style(color = "#c9ccd6") + data(population_spikes) + bar + x(:lon) +
  y(:lat) + z(:reach) + color(:continent) + globe(turn = 20, tilt = 12) +
  title("World population, standing on its places")
plot(data(world_borders), path, x(col.lon), y(col.lat), group(col.piece),
  style({ color: "#c9ccd6" }), data(population_spikes), bar, x(col.lon),
  y(col.lat), z(col.reach), color(col.continent),
  globe({ turn: 20, tilt: 12 }),
  title("World population, standing on its places"))
World population, standing on its places Continent Asia Europe Africa Americas Oceania

“Given the world borders: paths, x is lon, y is lat, grouped by piece; then given the spikes: bars, x is lon, y is lat, z is reach, color by continent, on the globe.”

Finding a country’s center and joining the population are the host language’s job, as the join was on the map. Shaping the measure is its job too. The square root keeps China’s spike from making the rest look tiny, and a scale parameter would only be a second way of writing that one line.

Look at the edge of the disk. The sphere itself clips a spike, so one standing just behind that edge still shows its top when it is tall enough. The tallest spikes there are seen from the side. A spike facing you foreshortens toward a point, which is why a plot like this is read by turning it.

36.8 The depth goes to the cube

Each earthquake here carries a third number: elevation, its depth below the surface. That is not a length to raise from the surface but a third place coordinate, and on the globe z belongs to the spike alone. So gog refuses the sentence below rather than guessing at it. The refusal names three ways forward: add a bar layer, drop z(), or draw the same table in the cube.

data(quakes_fiji) + point + x(east) + y(north) + z(elevation) +
  globe(turn = 178, tilt = -18)
(data(quakes_fiji) + point + x(col.east) + y(col.north) + z(col.elevation) +
  globe(turn = 178, tilt = -18))
data(quakes_fiji) + point + x(:east) + y(:north) + z(:elevation) +
  globe(turn = 178, tilt = -18)
plot(data(quakes_fiji), point, x(col.east), y(col.north),
  z(col.elevation), globe({ turn: 178, tilt: -18 }))
Error:
! gog: `z(...)` on a globe is the radius, and only a `bar` reads it — a spike standing at its place, measuring outward from the surface. Add a `bar` layer to raise spikes, drop `z(...)` to keep the surface marks, or drop `globe()` for the cube, where every position mark reads `z`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A place with a depth belongs in the cube, the space with a third position. space() names the view with the words globe() uses:

data(quakes_fiji) + point + x(east) + y(north) + z(elevation) +
  color(slab) + space(turn = 130, tilt = 25) +
  title("The same earthquakes, read by depth")
(data(quakes_fiji) + point + x(col.east) + y(col.north) + z(col.elevation) +
  color(col.slab) + space(turn = 130, tilt = 25) +
  title("The same earthquakes, read by depth"))
data(quakes_fiji) + point + x(:east) + y(:north) + z(:elevation) +
  color(:slab) + space(turn = 130, tilt = 25) +
  title("The same earthquakes, read by depth")
plot(data(quakes_fiji), point, x(col.east), y(col.north),
  z(col.elevation), color(col.slab), space({ turn: 130, tilt: 25 }),
  title("The same earthquakes, read by depth"))
180 170 -10 -20 -30 -200 -400 -600 East North Elevation The same earthquakes, read by depth Slab 0-90 km 90-180 km 180-270 km 270-360 km 360-450 km 450-540 km 540-630 km 630-720 km

“Given quakes Fiji: points, x is east, y is north, z is elevation, color by slab.”

Drag this one too: the globe turns the earth, and the cube turns the cloud, one gesture for both. slab names the depth band each quake falls in. The earlier plots looked straight down on these quakes; this one shows the sinking sheet of ocean floor they sit on.

36.9 What a globe refuses

You may try a line through the earthquakes, as you would on a flat page. Both positions carry the place, exactly as on the map, so a mark that measures along an axis has no axis left:

data(quakes_fiji) + line + x(east) + y(north) + globe()
data(quakes_fiji) + line + x(col.east) + y(col.north) + globe()
data(quakes_fiji) + line + x(:east) + y(:north) + globe()
plot(data(quakes_fiji), line, x(col.east), y(col.north), globe())
Error:
! gog: `line` measures along an axis, and a `globe()` plot has none to spare — longitude and latitude use both. Drop `globe()` to draw `line` flat, or use `point`, `bar`, `text`, `path`, `rule`, or `zone`. To carry a quantity on the globe, put it on a channel instead of an axis: `point + size(<column>)` sizes each place by it, and `color(<column>)` shades it.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

The quantity moves to a channel instead, size() or color(), or it stands on the radius as a spike, the way the population spikes above do.

A latitude past a pole names no place, so a view cannot face that latitude:

data(quakes_fiji) + point + x(east) + y(north) + globe(tilt = 100)
data(quakes_fiji) + point + x(col.east) + y(col.north) + globe(tilt = 100)
data(quakes_fiji) + point + x(:east) + y(:north) + globe(tilt = 100)
plot(data(quakes_fiji), point, x(col.east), y(col.north),
  globe({ tilt: 100 }))
Error:
! gog: `globe(tilt = 100)` is outside -90 to 90, which is where a latitude lives. At 90 the view faces straight down on the north pole and at -90 on the south; there is no place past either. Use `globe(tilt = 90)` for the nearest view, or `globe(turn = )` to swing around the earth instead — a bearing wraps and a latitude does not.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

And a globe draws no axes at all, so naming one is refused rather than accepted and dropped:

data(quakes_fiji) + point + x(east) + y(north) + globe() + x_label("Longitude")
data(quakes_fiji) + point + x(col.east) + y(col.north) + globe() + x_label("Longitude")
data(quakes_fiji) + point + x(:east) + y(:north) + globe() +
  x_label("Longitude")
plot(data(quakes_fiji), point, x(col.east), y(col.north), globe(),
  x_label("Longitude"))
Error:
! gog: an axis label names an axis, and a `globe()` plot draws none — a sphere has no edge to write one on, and the graticule is the reference instead. Drop it; `title()` still names the plot.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

The map and the globe are one meaning with two pictures of it. The map trades the sphere for a page a reader sees whole. The globe keeps the sphere and trades away the far half. Moving between them costs one word.