A d3 experiment on my Tinkerer blog

warning

This post is more than 5 years old. While math doesn't age, code and operating systems do. Please use the code/ideas with caution and expect some issues due to the age of the content. I am keeping these posts up for archival purposes because I still find them useful for reference, even when they are out of date!

This blog uses Tinkerer (at the time this post was written; now it's actually 11ty), which is based on the Sphinx documentation framework, to create static html pages from rest (rst) markup. If you are familiar with the Python world you've probably created documentation using Sphinx, or at the very least, you have read documenation created in Sphinx. Because the setup is Python-focused, it is not straightforward to write posts that employ javascript libraries like d3js . This post describes my method for doing javascript examples in Tinkerer blog posts or Sphinx docs, as well as how to do some d3js.

Raw javascript

First, let's consider a simple javascript example without using d3js. I will create an html-div as well as two buttons that use javascript to insert some text. In the rst markup the div and buttons are created using

.. raw:: html

<div id="js_ex"
style="padding: 20px 0px;">

<p id="js_ex_text"
style="padding: 10px; background: #111; color: #eee;">
-- click write and clear ---
</p>

<button id="js_ex_write">
write
</button>
<button id="js_ex_clear">
clear
</button>
</div>

The result of the above code is shown below:

-- click write and clear ---

The raw:: html tag in the rst markup makes Tinkerer/Sphinx insert the html into the document. Next, we can use javascript to change the text using some click events. In the rst document I have:

.. raw:: html

<script type='text/javascript'>
p = document.getElementById('js_ex_text');

buttonWrite = document.getElementById('js_ex_write');
buttonWrite.onclick = function() {
p.innerHTML = 'Hello <strong>js</strong> world!';
};

buttonClear = document.getElementById('js_ex_clear');
buttonClear.onclick = function() {
p.innerHTML = ' -- nothing here, click write --';
};
</script>

Notice that the .. raw:: html tag is used again and the javascript is surrounded by the script tags -- just as you would have to do in normal, inline html markup.

Okay, now let's integrate some d3js into a Tinkerer post or Sphinx document.

Using d3

Linking d3

The first step in using d3js is to link to the library. I'm sure there are a variety of ways to do this, but I chose to link to the library on all pages.

Given this choice, there is a standard way to add external javascript files -- see Enabling Google Analytics for an example of adding google analytics js to a Tinkerer blog. In my case, I added d3.min.js (you can get the latest version here: d3 at github to the _static directory and modified the page.html template in my theme -- see page template example -- to include the following::

{%- extends "layout.html" -%}

{% set script_files = script_files + ["_static/d3.min.js"] %}

This tells Tinkerer to link the d3.min.js in the header of each page. If you are not working with a theme, create a directory _templates and add the file page.html with the above content. The organization would be something like this (where conf.py and master.rst are in the base directory of the blog):

├── conf.py
├── master.rst
├── _static
│ ├── d3.min.js
├── _templates
├── page.html

That should work as well.

Using d3 in a post

Finally, here's a little experiment using d3js that is motivated by the classic three little circles example. As with the simple javascript example we have to setup an html-div that will be used:

.. raw:: html

<div id='vizdiv' style="padding: 20px 0px;">
</div>

Let's put it here:

Next, we use the following d3 code to draw some circles using data:

var data = [{r: 5, cy: 100, cx: 100},
{r: 10, cy: 100, cx: 200},
{r: 15, cy: 100, cx: 300}];

var root = d3.select('#vizdiv').append('svg')
.attr('width', 400)
.attr('height', 200);

root.selectAll('circle')
.data(data).enter()
.append('circle')
.attr('r', function(d) {return d.r;})
.attr('cx', function(d) {return d.cx;})
.attr('cy', function(d) {return d.cy;})
.attr('fill', 'steelblue');

In the rst markup for the post, this actually has to look like this:

.. raw:: html

<script type='text/javascript'>
var data = [{r: 5, cy: 100, cx: 100},
{r: 10, cy: 100, cx: 200},
{r: 15, cy: 100, cx: 300}];

var root = d3.select('#vizdiv').append('svg')
.attr('width', 400)
.attr('height', 200);

root.selectAll('circle')
.data(data).enter()
.append('circle')
.attr('r', function(d) {return d.r;})
.attr('cx', function(d) {return d.cx;})
.attr('cy', function(d) {return d.cy;})
.attr('fill', 'steelblue');
</script>

Agina, notice that we have to use .. raw:: html and the script tags!

Okay, that's it. Now I'm setup to talk more about javascript and d3 on the blog -- very nice! If you have other ways to do this, again, please leave a comment!