ggsql is a grammar of graphics for SQL: you
describe a plot inside a SQL query and it renders in the
database (DuckDB), returning a web-ready Vega-Lite widget — no ggplot2
or sf runtime required. Version 0.4.1 added a spatial layer
(DRAW spatial) that reads WKB geometry.
countryatlas and ggsql fit together cleanly:
country_overrides()), and
join World Bank indicators onto geometry.So countryatlas becomes the data layer and ggsql the renderer.
world_query() is a pure string builder — it needs
nothing installed — so you can see exactly what will be sent to
ggsql:
as_ggsql_source() exports a curated countryatlas table
(with sf geometry WKB-encoded) to a DuckDB connection, a
Parquet file, or a nanoarrow stream that ggsql can read. The one-call
path is interactive_map(engine = "ggsql"):
# needs: ggsql, duckdb, DBI, sf
world_data(2020, geometry = "sf") |>
interactive_map(gdp_per_capita, engine = "ggsql", transform = "log10")Under the hood that is just the two building blocks, which you can also drive yourself for full control over the query:
Loading ggsql registers a chunk engine. Export the
source once, then chart it in a ```{ggsql} block,
referencing the registered table by name:
```{r}
library(ggsql)
reader <- duckdb_reader()
ggsql_register(reader, countryatlas:::ggsql_wkb_frame(world_data(2020, geometry = "sf")),
"countryatlas_world")
```
```{ggsql connection=reader}
VISUALISE gdp_per_capita AS fill
FROM countryatlas_world
DRAW spatial
PROJECT TO equal_earth
SCALE fill TO magma VIA log10
LABEL title => 'GDP per capita, 2020'
```The win is the same as ggsql’s everywhere else: only the rendered result leaves the database. For a single world map that is minor, but the moment your country panel lives in a warehouse — millions of rows, many years — pushing the aggregation and rendering down to where the data already is, rather than pulling it into R, is the whole point. countryatlas makes sure what you push down is keyed on an honest, reconciled ISO spine. ```