A very brief example of Observable Plot in action

A minimal example of Observable Plot embedded in a webpage

Posted by Joe O'Reilly on Sunday, September 19, 2021

A Very Brief Example Of Observable Plot In Action

Much like Vega-Lite, or ggplot2 Observable Plot offers a ‘grammar of graphics’ data visualisation framework. As it draws the visualisations in SVG it looks particularly sharp and is well suited for web-based purposes, such as dynamic visualisations based on data obtained through an API. The framework feels a bit more customisable than vega-lite as it is much easier to extend with other JavaScript libraries. While the framework is still in its infancy, I think that as the project develops it will be a really powerful tool to build quick, clean, data visualisations on the web. Here I show a really basic use of the Observable Plot framework and how it can be embedded in a web page using the TidyTuesday Olympics dataset as an example.

In this post I will build a really simple plot showing the total number of individual medals won by GBR Olympians over time. Note that the individual medal count includes one medal for each team member in a team event, such as 4 medals if GBR finished 1st, 2nd, or 3rd, in the 4x100 relay.

Data Preparation

The first step is to get the data into the format we need, which is JSON in this case. As this website is built using markdown and R this can be achieved in an Rmarkdown file. First, we download the data and extract only the elements we require using data.table.

library(data.table)
library(jsonlite)

# Download the data
olympics <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-07-27/olympics.csv')

# Convert to a data table
setDT(olympics)

# Keep only GBR, post 1950, summer olympics; Summarise by counting medal winners 
# at each Olympics
olympics <- olympics[
  year > 1950 & noc == 'GBR' & season == 'Summer' & !is.na(medal),
  .(`Number of Medals` = .N), 
  keyby = .(Year = year) ]

Next, we take the data.table object and turn it into a JSON object which is injected directly into the HTML document that the Rmarkdown is rendered as. This is done by using the jsonlite package and the cat() function. If you look at the HTML source of this page you will see the JSON object residing within a pair of <script> tags as a consequence of this process.

cat(
  paste(
  '<script>
    var data = ', toJSON(olympics),';
  </script>'
  , sep="")
)

Building The Plot

Next we load up the required JavaScript libraries, here these are d3.js and Plot, and define a figure element that the plot will reside within.

<head>
<script src="https://cdn.jsdelivr.net/npm/d3@6"></script>
<script src="https://cdn.jsdelivr.net/npm/@observablehq/plot@0.1"></script>
</head>

<figure id="fig1"></figure>

We then need to use the high-level Plot syntax to build the plot itself, and this is placed inside a pair of <script> tags, as we are using JavaScript. The plot itself is drawn with a call to Plot.plot() on the final line.

// Set Plot Options
var options = {
  inset: 10,
  padding: 10,
  width: 960,
  marginLeft: 75,
  height: 480,
  // set x axis options
  x: {
    label: "",
    grid: false,
    tickFormat: d3.format("d"),
    ticks: 6,
    domain: [1950, 2020]
  },
  // set y axis options
  y: {
    grid:true
  },
  // define the marks we will use, dots and and a line
  marks: [
    Plot.dot(data, {
      x: "Year",
      y: "Number of Medals"}), 
    Plot.line(data, {x: "Year", y: "Number of Medals", curve:"step-after"})
  ]
};
// Draw the plot
document.getElementById("fig1")
        .appendChild(Plot.plot(options));

Building On The Basic Plot

A benefit of web based data visualisation is access to the wider world of web graphics and JavaScript libraries to further style a plot. As a particularly garish example, here I use CSS to add a shadow to the step-wise line representing the change in medal count from year to year. This is achieved by using a single style tag, selecting the figure using the relevant id, and using CSS to style the path contained within by adding a filter effect. This is disgusting, but an example of what is possible.

#fig2 path{filter:drop-shadow(0px 4px 8px rgba(255, 0, 0, 80%));}