Skip to content

Commit

Permalink
extending datawrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
nicucalcea committed May 13, 2024
1 parent a0598a5 commit 893825e
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 160 deletions.
39 changes: 38 additions & 1 deletion _observable/docs/src/datawrapper.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
export class DataWrapper {
export class Datawrapper {
constructor(id, containerName) {
this.id = id;
this.viz = undefined;
this.containerName = containerName;
this.container = document.getElementById(containerName);
this.observer = null;
this.listeners = {};

this.setupObserver();
this.renderDataWrapper();
this.setupEventListener();
}

setupObserver() {
Expand Down Expand Up @@ -52,4 +55,38 @@ export class DataWrapper {
this.viz.patch(key, value);
}
}

setupEventListener() {
window.addEventListener('message', this.receiveMessage.bind(this), false);
}

receiveMessage = (event) => {
if (event.data && event.data.source === 'datawrapper' && event.data.chartId && this.listeners[event.data.type]) {
this.listeners[event.data.type].forEach((cb) => {
if (typeof cb === 'function') cb(event.data);
});
}
}

on(event, callback, once = false) {
if (typeof event !== 'string') throw new Error('event name must be a string');
if (typeof callback !== 'function') throw new Error('callback must be a function');
if (!this.listeners[event]) this.listeners[event] = [];
const wrappedCallback = once ? (...args) => {
callback(...args);
this.off(event, wrappedCallback);
} : callback;
this.listeners[event].push(wrappedCallback);
return this;
}

off(event, callback) {
if (!this.listeners[event]) return this;
if (!callback) this.listeners[event].length = 0;
const i = this.listeners[event].indexOf(callback);
if (i > -1) {
this.listeners[event].splice(i, 1);
}
return this;
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
---
title: Patching Datawrapper
toc: false
title: Extending Datawrapper
toc: true
---

You can set up Datawrapper to update a chart based on some in-browser triggers.
# Update Datawrapper charts with user input

<div class="note">These notes are mostly inspired from the <a href="https://youtu.be/uKMVKarlgI4?feature=shared&t=10688" target="_blank">How we ditched D3 and mostly used Datawrapper</a> talk.</div>

Let's take a simple scatterplot.

<div style="min-height:436px"><script type="text/javascript" defer src="https://datawrapper.dwcdn.net/hhddC/embed.js?v=11" charset="utf-8"></script><noscript><img src="https://datawrapper.dwcdn.net/hhddC/full.png" alt="" /></noscript></div>

While this is nice and tells a story, it's not ideal for exploring data. Can you easily find your country in the chart? Can you change one of the axis? Can you see how the data changes over time?

# Patching Datawrapper
While it's a virtually undocumented feature, you can set up Datawrapper to update a chart based on some in-browser triggers, like a button or dropwdown menu.

Instead of the traditional iframes, you can [embed Datawrapper charts with Web Components](https://blog.datawrapper.de/web-component-embedding/). This has several advantages, mainly in terms of performance, but for the purposes of this tutorial, it allows us to make changes to the chart based on user input.
Instead of the traditional iframes, you need to [embed Datawrapper charts with Web Components](https://blog.datawrapper.de/web-component-embedding/). This has several advantages, mainly in terms of performance, but for the purposes of this tutorial, it allows us to make changes to the chart based on user input.

First, we'll load some JS code that makes everything possible. Add the following to your HTML file (you can find the full code [here](../src/datawrapper.js)):
First, we'll load some JS code that makes everything possible. Add the following to your HTML file (you can [find the full code here](https://gist.github.com/nicucalcea/576bb8c351d3c0337942432ccb6dda8f)):

```js echo
import {DataWrapper} from '../src/datawrapper.js';
import {Datawrapper} from '../src/datawrapper.js';
```

Next, we add the ID of our Datawrapper chart and import it. We'll display the chart later.
Next, we add the ID of our Datawrapper chart and create it. We'll display the chart later.

```js echo
const scatterId = 'hhddC';
const scatter = new DataWrapper(scatterId, 'scatter-plot-1');
const scatter = new Datawrapper(scatterId, 'scatter-plot-updateable');
```

We'll need some input. I'm using the `Inputs` Observable module to create a nice dropdown menu, but you can crate it with whatever framework you like or in vanilla JS.
We'll need some form of an input. I'm using the `Inputs` Observable module to create a nice dropdown menu, but you can crate it with whatever framework you like or in vanilla JS.

```js echo run=false
import * as Inputs from "npm:@observablehq/inputs";
Expand All @@ -48,14 +50,14 @@ We also need a bit of code that listens to changes in the dropdown menu and push
```js echo
if (countrySelect) {
scatter.updateViz(`metadata.visualize.add-labels`, [countrySelect]);
scatter.updateViz(`title`, `Income vs life expectancy in ${countrySelect}`);
scatter.updateViz(`title`, `Income vs life expectancy in ${countrySelect} in 2021`);
}
```

Finally, let's add a `scatter-plot-1` div to our HTML. This is where the chart will be insterted. For convenience, we'll also copy the `countrySelect` dropdown menu again.
Finally, let's add a `scatter-plot-updateable` div to our HTML. This is where the chart will be insterted. For convenience, we'll also copy the `countrySelect` dropdown menu again.

```
<div id="scatter-plot-1"></div>
<div id="scatter-plot-updateable"></div>
```

```js
Expand Down Expand Up @@ -97,40 +99,58 @@ const countrySelect = view(
);
```

<div id="scatter-plot-1"></div>
<div id="scatter-plot-updateable"></div>

You can find a list of updateable properties [on the Datawrapper website](https://developer.datawrapper.de/docs/chart-properties) or [see the properties of your own chart here](https://api.datawrapper.de/v3/charts/hhddC) (replace the ID with your chart ID).

<!-- # Animating charts in Datawrapper
Theoretically, you can use this method to create scrollable stories or animated charts.

Let's build on the previous example.

First, let's build a slider.

<div id="time-slider">
</div>

```js echo
let timeSlider = view(Inputs.range([1960, 2021], {step: 1, value: 1960}));



# Listening to chart interactions

Similarly to how you can update a chart based on user input outside the chart, you can also listen to interactions within the chart to update other parts of the page.

If you haven't already done so, import the necessary code.

```js echo run=false
import {Datawrapper} from '../src/datawrapper.js';
```

And as before, we'll add the ID of our Datawrapper chart and display it. We'll also add a tooltip div, which I'll explain below.

```js echo
const scatterAnimated = new DataWrapper('hhddC', 'scatter-plot-2');
const scatterTooltips = new Datawrapper(scatterId, 'scatter-plot-tooltips');
```

Finally, let's add a `scatter-plot-2` div to our HTML. This is where the chart will be insterted. For convenience, we'll also copy the `countrySelect` dropdown menu again.
```
<div id="scatter-plot-2"></div>
```
<div id="scatter-plot-tooltips"></div>
<div id="tooltip"></div>
```

<div id="scatter-plot-tooltips"></div>

<div id="scatter-plot-2"></div>
<div id="tooltip" class="tip">Tooltips will show up here.</div>

Now, we'll add a bit of code that listens for mouse events on the chart and displays a tooltip with data from the chart.

```js echo
if (timeSlider) {
scatterAnimated.updateViz(`title`, `Income vs life expectancy in ${timeSlider}`);
scatterAnimated.updateViz('metadata.axes.x', `gdp_per_capita_current_${timeSlider}`);
scatterAnimated.updateViz('metadata.axes.y', `life_expectancy_${timeSlider}`);
scatterAnimated.updateViz('metadata.axes.size', `population_${timeSlider}`);
}
``` -->
const tooltip = document.querySelector('#tooltip');

scatterTooltips.on('symbol.mouseenter', (data) => {
tooltip.innerHTML = `<h3>Country: ${data.data.country}</h3>GDP per capita: ${data.data.gdp_per_capita_current_2021}<br>Life expectancy: ${data.data.life_expectancy_2021}`;
tooltip.style.display = 'block';
});

scatterTooltips.on('symbol.mouseleave', (data) => {
tooltip.style.display = 'none';
});
```

Now, when you hover over a scatterplot point, you should see a tooltip with the country name, GDP per capita, and life expectancy.

You can combine the two methods explained above to create a more interactive experience using just Datawrapper charts and a little bit of JavaScript.
4 changes: 2 additions & 2 deletions _observable/observablehq.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export default {
"open": true,
"pages": [
{
"name": "Patching Datawrapper",
"path": "viz/patching-datawrapper.html"
"name": "Extending Datawrapper",
"path": "viz/extending-datawrapper.html"
}
]
},
Expand Down
4 changes: 2 additions & 2 deletions _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ website:
href: sources/calendar.qmd
- section: "Dataviz"
contents:
- text: "Patching Datawrapper"
- text: "Extending Datawrapper"
icon: "bar-chart-steps"
href: viz/patching-datawrapper.html
href: viz/extending-datawrapper.html
- section: "AI"
# icon: "robot"
contents:
Expand Down
2 changes: 1 addition & 1 deletion city/index.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ format:
filters:
- filters/newpagelink.lua
- quarto
- filters/speaker-notes.lua
# - filters/speaker-notes.lua
href: city
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
export class DataWrapper {
export class Datawrapper {
constructor(id, containerName) {
this.id = id;
this.viz = undefined;
this.containerName = containerName;
this.container = document.getElementById(containerName);
this.observer = null;
this.listeners = {};

this.setupObserver();
this.renderDataWrapper();
this.setupEventListener();
}

setupObserver() {
Expand Down Expand Up @@ -52,4 +55,38 @@ export class DataWrapper {
this.viz.patch(key, value);
}
}

setupEventListener() {
window.addEventListener('message', this.receiveMessage.bind(this), false);
}

receiveMessage = (event) => {
if (event.data && event.data.source === 'datawrapper' && event.data.chartId && this.listeners[event.data.type]) {
this.listeners[event.data.type].forEach((cb) => {
if (typeof cb === 'function') cb(event.data);
});
}
}

on(event, callback, once = false) {
if (typeof event !== 'string') throw new Error('event name must be a string');
if (typeof callback !== 'function') throw new Error('callback must be a function');
if (!this.listeners[event]) this.listeners[event] = [];
const wrappedCallback = once ? (...args) => {
callback(...args);
this.off(event, wrappedCallback);
} : callback;
this.listeners[event].push(wrappedCallback);
return this;
}

off(event, callback) {
if (!this.listeners[event]) return this;
if (!callback) this.listeners[event].length = 0;
const i = this.listeners[event].indexOf(callback);
if (i > -1) {
this.listeners[event].splice(i, 1);
}
return this;
}
}
4 changes: 2 additions & 2 deletions docs/ai/google-sheets.html
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@
<ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth1 show">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../viz/patching-datawrapper.html" class="sidebar-item-text sidebar-link"><i class="bi bi-bar-chart-steps" role="img">
<a href="../viz/extending-datawrapper.html" class="sidebar-item-text sidebar-link"><i class="bi bi-bar-chart-steps" role="img">
</i>
<span class="menu-text">Patching Datawrapper</span></a>
<span class="menu-text">Extending Datawrapper</span></a>
</div>
</li>
</ul>
Expand Down
10 changes: 5 additions & 5 deletions docs/ai/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<script src="../site_libs/quarto-search/quarto-search.js"></script>
<meta name="quarto:offset" content="../">
<link href="../ai/google-sheets.html" rel="next">
<link href="../viz/patching-datawrapper.html" rel="prev">
<link href="../viz/extending-datawrapper.html" rel="prev">
<link href="../favicon.png" rel="icon" type="image/png">
<script src="../site_libs/quarto-html/quarto.js"></script>
<script src="../site_libs/quarto-html/popper.min.js"></script>
Expand Down Expand Up @@ -156,9 +156,9 @@
<ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth1 show">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../viz/patching-datawrapper.html" class="sidebar-item-text sidebar-link"><i class="bi bi-bar-chart-steps" role="img">
<a href="../viz/extending-datawrapper.html" class="sidebar-item-text sidebar-link"><i class="bi bi-bar-chart-steps" role="img">
</i>
<span class="menu-text">Patching Datawrapper</span></a>
<span class="menu-text">Extending Datawrapper</span></a>
</div>
</li>
</ul>
Expand Down Expand Up @@ -717,8 +717,8 @@ <h2 class="anchored" data-anchor-id="how-ai-works">How AI works</h2>
</script>
<nav class="page-navigation">
<div class="nav-page nav-page-previous">
<a href="../viz/patching-datawrapper.html" class="pagination-link" aria-label="Patching Datawrapper">
<i class="bi bi-arrow-left-short"></i> <span class="nav-page-text">Patching Datawrapper</span>
<a href="../viz/extending-datawrapper.html" class="pagination-link" aria-label="Extending Datawrapper">
<i class="bi bi-arrow-left-short"></i> <span class="nav-page-text">Extending Datawrapper</span>
</a>
</div>
<div class="nav-page nav-page-next">
Expand Down
2 changes: 1 addition & 1 deletion docs/ai/llm-comparison.html
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@
<details open>
<summary>Dataviz</summary>
<ol>
<li class="observablehq-link"><a href="../viz/patching-datawrapper.html">Patching Datawrapper</a></li>
<li class="observablehq-link"><a href="../viz/extending-datawrapper.html">Extending Datawrapper</a></li>
</ol>
</details>
<details open class="observablehq-section-active">
Expand Down
Loading

0 comments on commit 893825e

Please sign in to comment.