Observable Notebooks 2.0

Some experiments with geoviz, geotoolbox, and the R data loaders…

Source code available on GitHub.

A data set

let world = FileAttachment("world.json").json();

Let’s see what’s inside.

1 - geoviz

geoviz is a JavaScript library for designing thematic maps. The library provides a set of d3 compatible functions that you can mix with the usual d3 syntax. The library is designed to be intuitive and concise.

Source: https://github.com/riatelab/geoviz
Documentation: https://riatelab.github.io/geoviz/

const geoviz = await import("geoviz");

An simple map

{
  let svg = geoviz.create({
    projection: "NaturalEarth1",
    zoomable: true,
  });
  svg.plot({ type: "outline" });
  svg.plot({ type: "graticule", stroke: "white" });
  svg.plot({ data: world, fill: "#38896F", tip: true });
  svg.plot({ type: "header", text: "Hello geoviz" });
  display(svg.render());
}

Typology

{
  let svg = geoviz.create({
    projection: "Hill",
    zoomable: true,
  });
  svg.plot({ type: "outline" });
  svg.plot({ type: "graticule", stroke: "white" });
  svg.plot({ type: "typo", data: world, var: "region", tip: true });
  svg.plot({ type: "header", text: "Hello geoviz" });
  display(svg.render());
}

Choropleth

{
  let svg = geoviz.create({
    projection: "InterruptedHomolosine",
    zoomable: true,
  });
  svg.plot({ type: "outline" });
  svg.plot({ type: "graticule", stroke: "white" });
  svg.plot({
    type: "choro",
    data: world,
    var: "gdppc",
    tip: true,
    leg_type: "horizontal",
    leg_title: "GDP per capita",
    leg_pos: [0, 360],
  });
  svg.plot({ type: "header", text: "Hello geoviz" });
  display(svg.render());
}

Proportional symbols

{
  let svg = geoviz.create({
    projection: "PolyhedralWaterman",
  });
  svg.plot({ type: "outline" });
  svg.plot({ type: "graticule", stroke: "white" });
  svg.plot({ datum: world, fill: "white", fillOpacity: 0.3 });
  svg.plot({
    type: "prop",
    data: world,
    k,
    symbol,
    var: "pop",
    fill: "#38896F",
    tip: true,
  });
  svg.plot({ type: "header", text: "Hello geoviz" });
  display(svg.render());
}

Combination of both

{
  let svg = geoviz.create({
    projection: "Loximuthal",
  });
  svg.plot({ type: "outline" });
  svg.plot({ type: "graticule", stroke: "white" });
  svg.plot({ datum: world, fill: "white", fillOpacity: 0.3 });
  svg.plot({
    type: "propchoro",
    data: world,
    var1: "pop",
    var2: "gdppc",
    colors: "PinkYl",
    tip: true,
  });
  svg.plot({ type: "header", text: "Hello geoviz" });
  display(svg.render());
}

Another nice example with tiles

2 - geotoolbox

geotoolbox is a javascript tool for geographers. It allows one to manage GeoJSON properties (attribute data) and provides several useful GIS operations for thematic cartography. The aim of geotoolbox is to offer functions designed to handle geoJSON directly, not just single features or geometries. As a result, the library is particularly user-friendly for users with little experience of javascript development. From a technical point of view, geotoolbox is largely based on geos-wasm GIS operators (a big thanks to Christoph Pahmeyer 🙏), but also on d3.geo and topojson. Geotoolbox also works well with other cartographic libraries such as geoviz and bertin.js. Note that there are other GIS libraries like turf.js, which is really great.

Source: https://github.com/riatelab/geotoolbox
Documentation: https://riatelab.github.io/geotoolbox/

const geo = await import("geotoolbox");

Union

const merge = geo.union(world, { id: "region" });

Simplify

const simple = geo.simplify(world, { k: k_simpl });

Buffer

const buffer = geo.buffer(africa, {
  dist: distance,
  each: true,
});

Clip

const chn = geo.filter(world, { func: "ISO3 = CHN" });
const ind = geo.filter(world, { func: "ISO3 = IND" });
const buff_chn = geo.buffer(chn, { dist: dist2 });
const buff_ind = geo.buffer(ind, { dist: dist2 });
const clipped = geo.clip(buff_chn, { clip: buff_ind });

3 - R data loaders

Since notebooks 1.5, it is possible to execute R code during build time. This is very convenient. It allows you to combine data preparation (in R) and data representation (in JS) in the same document.

The R code is executed at build time. The JavaScript code is executed on the fly when the page is opened.

You can write the R code directly in a R cell, or, to benefit from syntax highlighting, in a sourced R file.

In this R script, I open a CSV file, filter it, and save it again as a CSV.

source("../R/script1.R")

Then, I display it in Observable JavaScript.

let data = FileAttachment("../R/cities_CHN.csv").csv();
display(Inputs.table(data));

Now, let’s load a geopackage using the sf package.

library(sf)
AFG <- st_read("../R/afghanistan.gpkg")
st_write(AFG, dsn = "../R/afghanistan.geojson", driver = "GeoJSON")

Then, we can display it using geoviz.

let afg = await FileAttachment("../R/afghanistan.geojson").json();
display(geoviz.view(afg));

You can also generate maps with R and display them on your page. Here’s an example using the mapsf package.

library(mapsf)
png("../R/plot.png", width = 800, height = 600)
mtq <- mf_get_mtq()
mf_map(x = mtq)
mf_map(x = mtq, var = "POP", type = "prop", leg_pos = "topright")
mf_layout(
  title = "Population in Martinique",
  credits = "T. Giraud; Sources: INSEE & IGN, 2018"
)
dev.off()
display(await FileAttachment("../R/plot.png").image());

Conclusion

  • Notebooks 2.0 works perfectly!
  • You can now work offline.
  • Notebooks can be hosted and deployed wherever you want.
  • R data loaders allow you to chain together complex data import and handling operations at build time and document the process in a single notebook. This is great for traceability and reproducibility.

But

  • Syntax highlighting is missing for R cells.
  • An appropriate IDE would be needed to easily add chucks (cells). At this stage, Observable Desktop is only available on Mac OS.
  • Observable Notebooks 1.0 (on the web plateform) remain extremely important for sharing your work and building a community around data visualization.