Skip to main content

This article is brought to you by Datawrapper, a data visualization tool for creating charts, maps, and tables. Learn more.

All Blog Categories

Give me a (flashing) sign: How to make animated visualizations

Portrait of Michael Do Thoi
Michael Do Thoi

Hi, this is Michi from the support team! This week, I wanted to explore a workaround for a user request we receive every now and then: animated visualizations.

A while back, my colleague Elana shared a scatterplot that updates as you click, and ever since, I'd been meaning to see if the same method can be used to create animations that update on their own.

Turns out, by adding a little twist to the script, you can create visualizations that automatically apply changes to metadata without the need for user interaction. During testing, one thing led to another, and I ended up drawing a neon sign to visualize a very binary dataset: the vacancy status of an imaginary motel.


Click here to create your own copy of this visualization.

From a drawing to a locator map

The above visualization is a locator map with area markers alternating between being visible and being hidden. The area markers started off as drawings created in Adobe Illustrator, which were then converted into geodata using QGIS.

This method first came to my attention during Patrick Stotz's Unwrapped talk, where he revealed a number of very cool Datawrapper workarounds he and his team use at DER SPIEGEL, including how they created a custom choropleth map in the shape of an American football pitch (if you're curious, he talks about it at 19:23 of the talk).

My colleague Ceren (herself an Unwrapped speaker) went to great lengths to help me transfer this method to a locator map. Broadly, these were the five steps:

  1. export the Illustrator paths as DXFs from Illustrator,

  2. open them in QGIS,

  3. scale and shift the polygons so that they are small and close to 0°N 0°E (to avoid shape distortions),

  4. reproject to EPSG:4326/WGS 84 (the requisite projection for custom Datawrapper markers), and finally

  5. export each polygon as an individual GeoJSON file (so I can upload them as individual layers in Datawrapper).

Writing the script

📚

If you haven't yet read Elana's weekly chart, I highly recommend doing so now, as she explains the underlying PATCH method.

The next bit involved creating a script that would automatically alternate the visibility status of select markers, switching from visible to invisible and back every second. Using Claude, I generated the following code:

The script
(function () {
const CHART_ID = 'gKfPx';
const STEPS = [
    {
        duration: 1000,
        changes: {
            'dataset.markers.3.visibility.enabled': false,
            'dataset.markers.5.visibility.enabled': false,
            'dataset.markers.20.visibility.enabled': false
        }
    },
    {
        duration: 1000,
        changes: {
            'dataset.markers.3.visibility.enabled': true,
            'dataset.markers.5.visibility.enabled': true,
            'dataset.markers.20.visibility.enabled': true

        }
    }
];

function applyStep(vis, changes) {
    for (const [path, value] of Object.entries(changes)) {
        vis.patch(path, value);
    }
}

async function runLoop(vis) {
    while (true) {
        for (const step of STEPS) {
            applyStep(vis, step.changes);
            await new Promise(r => setTimeout(r, step.duration));
        }
    }
}

function findVis(containerId) {
    const container = document.getElementById(containerId);
    if (!container) return null;
    return [...container.children].find(el =>
        el.tagName.toLowerCase().startsWith('datawrapper-visualization')
    ) || null;
}

function waitFor(containerId, tries = 20) {
    return new Promise((resolve, reject) => {
        (function poll(n) {
            const el = findVis(containerId);
            if (el) return resolve(el);
            if (n <= 0) return reject(new Error(`datawrapper-visualization element not found in #${containerId}`));
            setTimeout(() => poll(n - 1), 100);
        })(tries);
    });
}

let started = false;
window.addEventListener('message', async e => {
    if (
        !started &&
        e.data?.source === 'datawrapper' &&
        e.data?.chartId === CHART_ID &&
        e.data?.type === 'vis.rendered'
    ) {
        started = true;
        const vis = await waitFor(`datawrapper-vis-${CHART_ID}`);
        runLoop(vis);
    }
});
})();

The STEPS array is the heart of the script, acting as the storyboard for our animation. It defines which steps — or frames, if we continue with the moviemaking analogy — should exist, which properties should be set within which step, and how long a given step should be held. The storyboard for our neon sign, for example, consists of two steps, each set to last 1,000 milliseconds: turn the lights off, then turn the lights on. Rinse and repeat.

In this specific case, the keys in the changes object refer to marker settings which are saved in the map's marker array, which is in the dataset. Likewise, it is also possible to update metadata settings in the same way: for example, if you want a step to change the x-axis grid to range from 2010 to 2020, you would replace the current keys in the script with metadata.visualize.custom-range-x: [2010, 2020].1

🎓

If you're not familiar with the different metadata properties a Datawrapper visualization can hold, I'd recommend this article in our API guide.

Finally, apart from defining your steps, you'll also have to change the chart ID variable in the first line so that the script targets the correct chart. Once that's done, your code is ready to be inserted as a <script> on the embed page. If you have multiple charts you'd like to animate on a page, you can insert the script multiple times, targeting a different chart each time.

So… why a neon sign?

While there wasn't a very meaningful thought process behind my "dataset," there are many very useful applications for this workaround. You could map out weather patterns or ship movements, show changes in traffic during an event, or progressively build up an interesting story like in Elana's Weekly Chart. I did recently read an interesting history of vacuum-formed signs by Beth Mathews2, and after spending so much time testing the script on some arrow markers, I must've just really had arrows stuck on my mind. And perhaps some of you might spot a loose homage to a particular TV show? ☕️ 🍂 📚


Anyway, have fun trying this out! As always, if you have any questions, feedback, or thoughts on this, please send us a message at support@datawrapper.de.

A thank you to Shaylee for the great blog title, Elana for her technical support, and a special shout-out to Ceren, who provided much help and guidance for this Weekly Chart.

1.
Datawrapper vis properties are almost exclusively saved in the metadata, with locator map markers being the exception that is saved in the dataset JSON. The script supports a mixing and matching of properties in both.
2.
Two additional sources of visual inspiration came from Instagram via @rolandopujol and @everything_signage.
Portrait of Michael Do Thoi

Michael Do Thoi (he/him) is on the support and customer success team. If you’re trying to figure something out in Datawrapper, he’s all ears (and eyes, if you message support@datawrapper.de). When not talking or typing, Michi’s probably thinking deeply about food or music. He lives in Berlin.

Sign up to our newsletters to get notified about everything new on our blog.