ergm-quarto

A Quarto extension embedding the ergm-js interactive network widget.

ergm-js is a small, dependency-light interactive Exponential Random Graph Model (ERGM) simulator built for teaching and presentations. This extension lets you drop it into any HTML-based Quarto output – html documents, websites, books, revealjs decks – with a single shortcode.

Quick start

quarto add gvegayon/ergm-quarto

Then, anywhere in a .qmd:

{{< ergm-widget n=60 theta-nodematch=2 height=420 >}}

About ergm-js and npm

The simulator is published on npm as ergm-js, so you can also use it outside Quarto in any JavaScript project:

npm install ergm-js

This extension vendors a copy of a published npm release (currently 0.3.0) into _extensions/, so nothing is fetched from npm or a CDN at render or view time – decks work offline and with embed-resources: true. The extension’s version number mirrors it: X.Y.Z-N means ergm-js X.Y.Z, release N of this extension. See the ergm-js README for the library’s own API.

Offline use and embed-resources

Nothing is loaded from a CDN, so no network is needed at render or view time. The widget’s JavaScript and CSS are registered as a Quarto HTML dependency, which Quarto handles in one of two ways:

  • Default: the files are copied to site_libs/quarto-contrib/ergm-js-<version>/ and linked from the page. Publish site_libs/ alongside the HTML.
  • embed-resources: true: Quarto inlines the scripts and stylesheet into the single HTML file, which works offline when emailed or opened from disk.
format:
  html:
    embed-resources: true

tools/check-render.py checks the second case on every CI run: it renders examples/embed-resources.qmd and fails if the output still references site_libs or any external <script>/<link>.

A live widget

Learn more

Why no inline <script>?

The obvious implementation – emit a <div> plus an inline <script>ERGMWidget.mount(...)</script> next to it – breaks inside reveal.js. reveal.js itself loads at the end of <body>, after all slide markup, so an inline script sitting inside a slide runs while window.Reveal is still undefined. ergm-widget.js’s own lazy-init logic checks for Reveal first and falls back to a plain IntersectionObserver when it’s missing – which, inside a reveal.js deck (where every slide is already in the DOM), is the wrong behavior: it can’t tell which slide is “current”.

Instead, this extension emits only a <div> with a JSON options payload in a data-ergm-options attribute, and ships a small bootstrap script (ergm-quarto.js) that mounts every such div at DOMContentLoaded. By then reveal.js has already run Reveal.initialize() (also an end-of-body, parser-blocking script), so window.Reveal exists and ergm-widget.js correctly takes its per-slide show/hide branch instead.

Known limitations

  • Live theme toggling doesn’t recolor the graph. Chrome (panes, buttons, stats) re-themes live; node/edge colors are baked in at mount time. See Theming.
  • ?print-pdf needs a nudge. reveal.js’s print mode reports only one “current” slide, which would otherwise hide every widget except that one. The bootstrap force-shows all widgets when it detects print-pdf in the URL.
  • Colors are restricted to hex forms (#rgb, #rrggbb, …) or bare CSS color keywords – rgb(...)/hsl(...) aren’t accepted, mainly so a color value can never contain the comma used to separate list options.
  • A widget outside its documented slider range (e.g. n=500) still simulates correctly, but its on-screen <input type="range"> will show clamped. The extension warns about this at render time.
  • Loading several extensions that each inject <head> dependencies can, in rare cases, push <meta charset="utf-8"> past the browser’s fixed pre-scan window for local file:// previews of non-ASCII documents. Serving over HTTP (e.g. GitHub Pages) always avoids this.