Build your own TARDIS with D3
If you're interested in learning more about D3.js and understanding how to build custom charts with it (or if you just like Doctor Who!), then this blog is for you! It's a step-by-step guide to creating an interactive TARDIS from scratch with D3.
June 2, 2025
If you’ve been following these blog posts for a little while, you’ll know that I’m a big fan of TidyTuesday, a weekly social data project where participants explore a new dataset, create something from it (often a visualisation), and share the result and code with the community. Back in 2023, one of the datasets was all about Doctor Who episodes, and the chart I created that week compared viewership across each seasons and featured an image of a TARDIS:
What you might not immediately notice, is that the TARDIS section of the chart is not an image, it’s actually a data visualisation built with ggplot2
in R:
Earlier this year, to gain more experience in working with D3.js, I decided to recreate the TARDIS chart. So in this blog post, we’ll do two things:
- Recreate the static version using D3 instead of R
- Add interactivity which shows users fun Doctor Who facts
New to D3? The D3 in Depth book is a good tutorial for getting started. I also recommend checking out the D3 Graph Gallery for some inspiration (with editable code)!
Building a static version
We’ll start by creating an HTML file which loads the D3 library, a JavaScript file called chart.js
where we’ll put our plotting code, and a CSS file to apply styling. Importantly, the HTML file should also include a div
with an id
of chart
, as this is where our chart will appear. You can use a different id
if you want, just make sure to also change it in the corresponding chart code later.
|
|
Now, in the chart.js
file, we’re going to set up the basic for our chart, and define a function called tardis()
that will draw the TARDIS. It has an argument called data
which we’ll come back to in a little bit. We define a width and height for the chart. Here, I’ve used a 4 by 6 ratio because it’s quite a standard size and roughly matches the aspect ratio of a TARDIS. We also set up the x
and y
axis scales, to define the grid that we’ll be drawing on. Here, we set the x axis to go between 0 and 80, and the y-axis to go between 0 and 120. Note that these also use a 4 by 6 ratio, so that our chart doesn’t end up squashed in one direction. You could just use 0-400 and 0-600 as your grid, but I find it easier to work with slightly smaller numbers and this makes it simpler if you want to change the size later.
We also set up our chart container, with the id
of chart
to match the HTML file, and set up the SVG with the correct width and height.
|
|
Drawing the base
To draw the main base of the TARDIS i.e. the blue shape in the background, we could create a complex polygon with lots of corners. However, it’s actually easier to create this shape by overlaying multiple rectangles. And we’re going to start by creating a CSV file that contains the data to draw these rectangles.
We have two columns x
and y
that define the starting corner of each rectangle, and the rWidth
and rHeight
defines how wide and tall each rectangle is. We also create a column, fillCol
, with the hex code that defines what colour the rectangle should be filled with (the rectangle will also be outlined with the same colour), and two columns for the transparency. Although we don’t really need the fillCol
, fillOpacity
, or strokeOpacity
columns for just the base, we’ll make use of them later when we draw more elements. The data initial data looks like this:
|
|
We then add the following code inside the tardis()
function to draw the rectangles based on this data we’ve created. We use the attr()
function to set the x
, y
, width
and height
values based on the columns in the data, using the xScale()
and yScale()
to make sure they scale correctly to the chart area. We then apply the style()
function to set the fill and stroke colours, alongside the opacity of each, again based on the columns in the data. In this example, I’ve set the stroke to "red"
just so that you can see how the rectangles fit together to create the shape. In the real version, the stroke is set to the fillCol
column using the commented out line of code below:
|
|
We also need to make sure the input data is connected to the tardis()
function. After the function definition, we read in the data and call the function with that data. We need to make sure that D3 interprets the columns we read in from the CSV file in the correct way (either character or numeric), before passing it to our tardis()
function:
|
|
Adding some more rectangles
We now need to add more rectangles to our chart to draw the windows, panels, and similar elements. We add these to the same data file for the base rectangles, again specifying the x
, y
, rHeight
, and rWidth
columns. However, for these new rectangles, we change the colours and the opacity.
|
|
The windows at the top also have some lines on them. Although we could follow a similar process of creating a data file to draw some lines as a path
, we’re going to cheat slightly and consider lines as just very thin rectangles. This means we can just add a few more lines to this data file.
Show full CSV file
|
|
Since the columns in the CSV file specify everything about how the rectangles look, we don’t need to make any further adjustments to the JavaScript in the tardis()
function, as our existing code will draw these additional elements.
Adding text and circles
We now want to draw some text and circles to add the final details. We could take a similar approach as before in using a csv file, but we have only a few circles and text elements to draw, so we could just draw these individually:
For example, to add the POLICE
text to the top of the base, we use:
|
|
We then repeat this process for the word BOX
, PUBLIC CALL
, and the notice on the door. You could treat POLICE BOX
as one element, but I found it easier to position it the way I wanted by doing them separately.
We can also add the following code to draw a small circle, forming part of the door handle:
|
|
We can repeat a few times for the other circular elements.
Adding interactivity
We’re going add some tooltips to the TARDIS chart which display interesting facts about Doctor Who.
Tooltips data
We’ll add 8 different tooltips that are activated by hovering over the squares on the TARDIS doors. One slight issue with this approach is that some of the squares are currently underneath other elements e.g. the bars on the windows. This means that if we just go straight ahead and attach the tooltips to the squares, the hover effects will be quite odd and will be switching on and off where there are other elements on top of the squares.
To get around this problem, we’re simply going to draw new transparent squares on top of the ones that already exist, and add the tooltips to these. We don’t necessarily need to do this for all 8 squares, but I found it easier to keep all the tooltips data together. We could add these transparent squares to the CSV file that draws the rectangles, but I’m going to create a new CSV file that has the x
, y
, rWidth
, and rHeight
columns as before. We don’t need any of the colour-related columns as everything will be fully transparent. However, we will add a new column, tooltipText
, which as the name suggests contains the text to be included in the tooltip.
In that tooltipText
, we write out the 8 facts we want to appear. The examples used here are adapted from some of the facts at
www.saga.co.uk/magazine/entertainment/doctor-who-25-fascinating-facts. You might notice that we use HTML to apply styling to the text e.g. italics tags, <i></i>
, for episode names. Our tooltips data looks like this:
|
|
Implementing tooltips
We need to adapt the code that reads in the data and runs the tardis()
function because we now have a second data set to read in. Again, we need to make sure the columns are read in with the correct type. Notice that the tardis()
function now has two arguments: data
and tooltipsData
:
|
|
We can now use D3 to draw these transparent rectangles, using code that is very similar to that used to draw the base rectangles. We again use .attr()
to set the x
, y
, and width
, and height
based on the tooltipsData
. However, we hard code the fill and transparency colours to fully transparent rather than using columns in the data. We also add a class
to these new rectangles, to enable them to be linked to the tooltips. Note that we use rect2
to differentiate these rectangles from rect
.
|
|
Finally, we add some JavaScript to define when the tooltips appear. We create elements with the class tooltip
that are fully transparent by default. When a user hovers over any element with the class tooltipText
(any of our transparent rectangles), the opacity changes to 0.9. Upon hover, the text in the tooltipText
column of the data is also added. When a user stops hover that element, the opacity is returned to 0 and the tooltip becomes invisible.
|
|
We then add some CSS to style all object with the tooltip
class i.e. all of the tooltips. This sets the background colour to the same shade of blue as the TARDIS, uses white for the text and tooltip border, and sets the maximum width to 300px
to force the text to wrap for long facts.
|
|
Optionally, we can add a summary and details element in the HTML file which displays the facts shown on the tooltips as plain text, for people who dislike or can’t use interactive elements like tooltips. Here’s a screen recording of the final product:
Now you’re ready to go ahead, and create your very own TARDIS!
Source: giphy.com
For attribution, please cite this work as:
Build your own TARDIS with D3.
Nicola Rennie. June 2, 2025.
nrennie.rbind.io/blog/build-your-own-tardis-d3
BibLaTeX Citation
@online{rennie2025, author = {Nicola Rennie}, title = {Build your own TARDIS with D3}, date = {2025-06-02}, url = {https://nrennie.rbind.io/blog/build-your-own-tardis-d3} }
Licence: creativecommons.org/licenses/by/4.0