Skip to content

Measuring How Readers Use Your Figures ​

Interactive figures raise a fair question: does anyone actually use them? Every embedded viewer can tell you. It reports what its reader does to the page that hosts it: where they look, which channels they switch, which frame they stop on, and how long the figure stays on screen.

This guide is for the people who run that page: a journal, a preprint server, a lab site. The viewer itself sends nothing anywhere, so everything here runs on your side, with tools you already have.

See it first

The reader interactions demo prints every event of three viewers as you use them. Every field is described in Reader Interactions.

Name Your Figures ​

Give each viewer an id that is unique on the page. Every event carries it, so your data says which figure it came from.

html
<find-nuclei-viewer id="fig-2-nuclei" url="https://example.org/nuclei.zarr"></find-nuclei-viewer>

In a MyST article, the same key goes into the directive:

md
:::{any:bundle} https://find-nuclei.github.io/embed/v1/widget.mjs
{
  "id": "fig-2-nuclei",
  "url": "https://example.org/nuclei.zarr"
}
:::

A Collector in 20 Lines ​

This sends the events to your own endpoint in small batches. sendBeacon delivers even while the reader is leaving, and the last batch goes out on pagehide. Put it near the top of the page, before the viewers load.

html
<script>
  (() => {
    const ENDPOINT = '/collect/figures';   // your own endpoint
    const SESSION = crypto.randomUUID();   // one per page load
    let batch = [];

    const send = () => {
      if (batch.length === 0) return;
      navigator.sendBeacon(ENDPOINT, JSON.stringify({ session: SESSION, events: batch }));
      batch = [];
    };

    document.addEventListener('fn-viewer:interaction', (e) => {
      batch.push(e.detail);
      if (batch.length >= 20) send();
    });
    setInterval(send, 10000);
    addEventListener('pagehide', send);
  })();
</script>

SESSION is yours to define. A random value per page load, as here, keeps readers anonymous and tells one visit's events apart from the next. If your site already runs its own sessions or consent, use those instead.

Google Analytics 4 ​

Google Analytics takes flat values, so pick the fields you need:

js
document.addEventListener('fn-viewer:interaction', (e) => {
  const d = e.detail;
  gtag('event', 'figure_' + d.action, {
    figure_id: d.id,
    figure_zoom: Math.round(d.view.zoom * 10) / 10,
    figure_x: Math.round(d.view.x),
    figure_y: Math.round(d.view.y),
    figure_t: d.t,
  });
});

Register the figure_ values as custom dimensions to see them in reports. Plausible, Matomo and most other tools have a similar call for custom events.

Reading the Data ​

  • Order: seq counts up on each viewer, starting at 0 with ready. A new url loads a new image and starts again at 0.
  • Starting view: the ready event. It is the view to compare when some figures open on an overview and others on a close-up.
  • Time on a figure: add up the time from each visible to the next hidden. A figure removed while on screen, for example when a reader follows a link inside a single-page site, ends without hidden, so close the interval at your own page-leave time.
  • Where readers look: view.bounds is the area on screen, in image units. Stack them for a map of attention across the image.
  • Accidental zooms: a mouse wheel over a figure zooms it instead of scrolling the page. A short burst of zoom right after visible is often a reader scrolling past.
  • Replaying a view: paste view.x, view.y and view.zoom into an embed's x, y and zoom to open on the same centre and magnification.

Privacy ​

Your page decides what to collect and where it goes, so the data, and the responsibility for it, are yours. The viewer sends nothing anywhere. Beyond loading itself from find-nuclei.github.io, it talks only to the store that holds the image.

The events carry no personal data of their own: no address, no cookie, no identifier beyond the id you gave the figure. Your endpoint sees each reader's IP address, as it does for any request, so drop it if you do not need it. The usual rules for analytics apply: say what you collect in your privacy notice, ask for consent where your readers' laws require it, and keep the data only as long as you need it.

MyST and Curvenote Sites ​

The listener belongs to the site that hosts the article, usually its page template or its analytics script, not to the article's Markdown. The events leave each widget's shadow root, so a listener on document hears every figure on the page. Elemental Microscopy and the other Curvenote sites work this way.

Free. Private. Browser-based.