PlotCat

plotcat is a Quarto extension for plot-recreation exercises. Authors provide a target plot and optional starter code. Students write R or Python in the browser, render their plot with WebR or Pyodide, and compare it with the target.

PlotCat compares SVG structure and plot text for static plots. It also supports a focused comparison of Plotly traces and layout. Plotly exercises support side-by-side comparison only; overlay and wipe apply to SVG plots. The target source is removed from the rendered HTML, but this is not a security boundary: do not put secrets in an exercise.

Requirements

PlotCat publishes interactive HTML only. Its browser runtimes need network access on first use and package availability in WebR or Pyodide. The included examples cover base R, ggplot2, tinyplot, lattice, matplotlib, plotnine, seaborn, and Plotly.

Installation

Add the extension to a Quarto project:

quarto add VisruthSK/PlotCat

Then enable the filter in a document or project config:

filters:
  - plotcat

Example

Write the target plot as the first chunk inside a .plotcat Div. Set #| eval: false: PlotCat renders both the target and a student’s submission in the browser. This must be set on each PlotCat chunk because Quarto decides whether to execute it before PlotCat’s filter runs.

::: {.plotcat}
```{r}
#| eval: false
tinyplot::tinyplot(
  dist ~ speed,
  data = cars,
  main = "Stopping distance by speed",
  xlab = "Speed",
  ylab = "Stopping distance"
)
```
:::

Add a second chunk to give students starter code. Also set #| eval: false on the starter chunk.

::: {.plotcat}
```{r}
#| eval: false
tinyplot::tinyplot(
  dist ~ speed,
  data = cars,
  main = "Stopping distance by speed",
  xlab = "Speed",
  ylab = "Stopping distance"
)
```

```{r}
#| eval: false
tinyplot::tinyplot(
  dist ~ speed,
  data = cars
)
```
:::

The first chunk becomes the browser-rendered target, and its source is omitted from the rendered HTML. The second chunk appears in the editor.

PlotCat accepts R and Python chunks. Both chunks in an exercise must use the same language.

Limitations

  • Font and text styling: SVG comparison checks text content, but ignores font families, font sizes, font weights, and text positioning.
  • Coarse geometry matching: Coordinates are rounded to ~1% relative precision and matched as unordered marks. Minor position shifts or point jittering may still yield high scores.
  • No code inspection: PlotCat evaluates rendered plot output (SVG or Plotly JSON), not R or Python code structure. It cannot check for specific function calls or idioms.
  • Plotly support: Plotly exercises only support side-by-side comparison and evaluate a subset of trace data, mark attributes, and titles.
  • Runtime sandbox: WebR and Pyodide run in the browser WebAssembly sandbox, so code cannot access files on the local filesystem (e.g., read.csv("C:/data.csv")). However, remote data files can be fetched directly using standard functions like read.csv("https://...") in R or pd.read_csv("https://...") in Python. Required packages must be available in WebAssembly.

Design Inspiration

PlotCat was inspired by ggplot2 Battles, brought to my attention by Emily Robinson.