{"id":18713,"date":"2018-05-23T11:30:40","date_gmt":"2018-05-23T11:30:40","guid":{"rendered":"https:\/\/www.heartinternet.uk\/blog\/?p=18713"},"modified":"2018-05-23T11:30:40","modified_gmt":"2018-05-23T11:30:40","slug":"create-beautiful-test-driven-data-visualisations-with-d3-js","status":"publish","type":"post","link":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/","title":{"rendered":"Create beautiful test-driven data visualisations with D3.js"},"content":{"rendered":"<p><a href=\"https:\/\/d3js.org\/\" target=\"_blank\" rel=\"noopener\">D3.js<\/a> is the de facto library to create dynamic, interactive and engaging data visualisations on the web. D3 development frequently starts by working from one of the community\u2019s many examples. These examples are helpful as a way to jumpstart a project, but by working this way developers struggle to establish solid foundations for further development.<\/p>\n<p>How can we create maintainable and extendable D3 charts that all developers enjoy working with?<\/p>\n<p>Creating D3 visualisations with Test Driven Development is one way of producing code that is easy to extend, refactor and change. In this post, we will go through the process of creating a heatmap chart using a Test Driven approach. The result is a great looking chart with a complete set of tests that has code good enough to be used in a production environment. Let\u2019s get going!<\/p>\n<h2>Why D3 and TDD?<\/h2>\n<p>D3 is a compelling library based on web standards such as HTML, SVG, CSS, and Canvas and shines when using its interactivity capabilities and animations. D3 is a low-level library and offers a large number of operations on its different modules, making it a powerful yet complex library with a steep learning curve.<\/p>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/Test-driven_development\" target=\"_blank\" rel=\"noopener\">Test Driven Development<\/a> (or TDD) is a software development process in which developers create the tests for the functionality first, then write the minimum amount of code necessary to make those tests pass.<a href=\"http:\/\/butunclebob.com\/ArticleS.UncleBob.TheThreeRulesOfTdd\" target=\"_blank\" rel=\"noopener\"> The Three Laws of TDD<\/a> and the<a href=\"http:\/\/blog.cleancoder.com\/uncle-bob\/2014\/12\/17\/TheCyclesOfTDD.html\" target=\"_blank\" rel=\"noopener\"> Red-Green-Refactor cycle<\/a> establish the general rules of this methodology. TDD creates short feedback loops, produces excellent quality code, and is really fun to practice.<\/p>\n<p>So why do this library and methodology work together so well?<\/p>\n<p>First, because the example-based charts are not reusable, extendable or even testable, and they ultimately produce a big ball of mud that only the original creator can handle. Also, D3 development is hard, so there is a high chance that our initial approach to a problem will not be the best one.<\/p>\n<p>We will benefit from having a reliable suite of tests that allows us to iterate over our code. Combining D3.js and TDD will force us to create testable and modular code that can be shipped to production, integrated into your continuous integration system and extended when needed without drama.<\/p>\n<h2>Creating a heatmap<\/h2>\n<p>A <a href=\"https:\/\/datavizcatalogue.com\/methods\/heatmap.html\" target=\"_blank\" rel=\"noopener\">heatmap<\/a> is a data visualisation that shows data variations by using a colour scale. You can find some examples of it on your profile page of GitHub (a yearly heatmap), or in the home dashboard of Google Analytics (a weekly heatmap). We will build this chart to give you an idea of what it\u2019s like to work with D3 in the TDD way.<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-18716 size-full\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/github-yearly-heatmap-e1525786626166.png\" alt=\"Yearly heatmap in a GitHub user's profile\" width=\"650\" height=\"111\" \/><br \/>\n<small>Yearly heatmap in GitHub\u2019s user profile<\/small><\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-18718\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/google-analytics-weekly-heatmap.png\" alt=\"Weekly heatmap as used in Google Analytics\" width=\"414\" height=\"600\" \/><br \/>\n<small>Weekly heatmap in Google Analytics\u2019 Home dashboard<\/small><\/p>\n<h2>Technology setup<\/h2>\n<p>The first thing we need to do is to set up the environment to test our code. On this occasion, we will use<a href=\"http:\/\/karma-runner.github.io\/2.0\/index.html\" target=\"_blank\" rel=\"noopener\"> Karma<\/a> as the test runner and <a href=\"https:\/\/github.com\/jasmine\/jasmine\" target=\"_blank\" rel=\"noopener\">Jasmine<\/a> as the testing framework. As we do not want to spend time on the setup, we will use<a href=\"https:\/\/github.com\/developit\/karmatic\" target=\"_blank\" rel=\"noopener\"> Karmatic<\/a>, a wrapper project that allows for an almost magical plug and play installation of both tools.<\/p>\n<p>You can skip these initial steps by forking the repo with the code using<a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/tree\/step-setup\" target=\"_blank\" rel=\"noopener\"> this branch<\/a>. If you want to set it up yourself, I will be assuming you have a recent version of <a href=\"https:\/\/nodejs.org\/en\/download\/\" target=\"_blank\" rel=\"noopener\">node<\/a> and <a href=\"https:\/\/yarnpkg.com\/en\/docs\/getting-started\" target=\"_blank\" rel=\"noopener\">yarn<\/a> installed on your machine.<\/p>\n<p>Let\u2019s have a look at the specific steps we need to follow:<\/p>\n<p>First, we will create a new folder and set up a new package with &#8220;yarn init&#8221;, filling the prompted fields with the information you think necessary.<\/p>\n<p>Next, we will install Karmatic by typing &#8220;yarn add karmatic -D&#8221;, using the &#8220;-D&#8221; flag to add it to our devDependencies (you can learn more about the type of dependencies in the<a href=\"https:\/\/yarnpkg.com\/lang\/en\/docs\/dependency-types\/\" target=\"_blank\" rel=\"noopener\"> yarn docs<\/a>).<\/p>\n<p>Installing D3.js is also a requirement, so we will do it by running &#8220;yarn add d3&#8221;.<\/p>\n<p>Once that\u2019s done, we should enable to run npm tasks by creating the &#8220;scripts&#8221; object in the &#8220;package.json&#8221; file:<\/p>\n<pre><code>\"scripts\": {\n    \"test\": \"karmatic\",\n    \"test:watch\": \"karmatic watch --no-headless\"\n}<\/code><\/pre>\n<p>Then, running &#8220;yarn test&#8221; should give you a message such as:<\/p>\n<pre><code>$ karmatic\nExecuted 0 of 0 ERROR (0.001 secs \/ 0 secs)\n\nerror Command failed with exit code 1.<\/code><\/pre>\n<p>This is fine, as we have not created our first test.<\/p>\n<h2>Creating the chart structure<\/h2>\n<p>We are going to use the Reusable API code pattern to build our heatmap chart. This code pattern has been used in several D3 libraries and was first mentioned in a <a href=\"https:\/\/bost.ocks.org\/mike\/chart\/\" target=\"_blank\" rel=\"noopener\">2012 post by Mike Bostock<\/a> \u2013 the creator of D3.js.<\/p>\n<p>Let\u2019s have a look at an example of this structure:<\/p>\n<pre><code>const d3 = require('d3');\n\n\/\/ Reusable chart\nfunction heatmap() {\n    const width = 600;\n    const height = 400;\n    const margin = {\n        top: 10,\n        right: 10,\n        bottom: 10,\n        left: 10\n    };\n\n    let svg;\n    let chartWidth;\n    let chartHeight;\n    \/\/ Rest of variables\n\n    function exports(_selection) {\n        _selection.each(function(_data) {\n            chartWidth = width - margin.left - margin.right;\n            chartHeight = height - margin.top - margin.bottom;\n\n   \t     \/\/Chart creation code here\n        });\n    }\n\n    return exports;\n};\n\nexport default heatmap;<\/code><\/pre>\n<p>The previous code returns a function that will accept one or several <a href=\"https:\/\/bost.ocks.org\/mike\/selection\/\" target=\"_blank\" rel=\"noopener\">D3 selections<\/a> as input. Then it will extract the data from that selection to build a chart, using the D3 selection as a container. This pattern also allows us to configure the visualisation, as we will see in the next sections.<\/p>\n<p>Using the Reusable API to create charts differs a lot from what we usually see in the regular<a href=\"http:\/\/bl.ocksplorer.org\/\" target=\"_blank\" rel=\"noopener\">block examples<\/a>. I have written before <a href=\"https:\/\/www.eventbrite.com\/engineering\/leveling-up-d3-the-reusable-chart-api\/\" target=\"_blank\" rel=\"noopener\">about this pattern<\/a> and its benefits for the modularity, composability, simplicity, reusability, and testability of our charts. It will be the Reusable API structure that enables us to build visualisations on a TDD basis.<\/p>\n<h2>Creating our first test<\/h2>\n<p>Once our environment is ready, we can start writing a failing test. We will create a folder labeled \u201csrc\u201d and inside produce a file called &#8220;heatmap.test.js&#8221;. Let\u2019s see how we will set up the test suite:<\/p>\n<pre><code>const d3 = require('d3');\nconst heatmap = require('.\/heatmap').default;\nconst data = [...];\n\ndescribe('Heatmap', () => {\n    let container;\n    let heatmapChart;\n\n    \/\/ adds an html fixture to the DOM\n    beforeEach(() => {\n        const fixture = '<div id=\"fixture\"><div class=\"container\"><\/div><\/div>';\n\n        document.body.insertAdjacentHTML('afterbegin', fixture);\n    });\n\n    \/\/ remove the html fixture from the DOM\n    afterEach(function() {\n        document.body.removeChild(document.getElementById('fixture'));\n    });\n});<\/code><\/pre>\n<p>In the first snippet, we are adding and cleaning a fixture &#8220;div&#8221; from the DOM that we will use to hold our test chart. Once this is in place, we will be able to start with the test rendering suite setup:<\/p>\n<pre><code>describe('rendering the chart', () => {\n    beforeEach(() => {\n        heatmapChart = heatmap();\n        container = d3.select('.container');\n\n        container.datum(data).call(heatmapChart);\n    });\n});<\/code><\/pre>\n<p>Here we see how developers will use the Reusable API. First, we create an instance of the heatmap; then we create the container selector that will hold the chart and finally, we mix them by adding the data to the container and calling the heatmap on it.<\/p>\n<p>The data for our chart will be an array of 168 arrays (the hours in a week), each one containing three elements that represent the day of the week, the hour of the day and finally the value. A schema such as:<\/p>\n<pre><code>[\n  [dayOfWeek, hourOfDay, value],\n  [dayOfWeek, hourOfDay, value],\n  ...\n]<\/code><\/pre>\n<p>And now that we are ready to write our first failing test, we\u2019ll find an element classed &#8220;heatmap&#8221; in the DOM:<\/p>\n<pre><code>it('should render a heat map', () => {\n    let expected = 1;\n    let actual = container.select('.heatmap').nodes().length;\n\n    expect(actual).toEqual(expected);\n});<\/code><\/pre>\n<p>Our test will not find anything until we create our new chart file in &#8220;src\/heatmap.js&#8221; and fill the chart code comment in the Reusable API code example from before with the following code:<\/p>\n<pre><code>if (!svg) {\n    svg = d3.select(this)\n        .append('svg')\n        .classed('heatmap', true);\n}\n\nsvg\n    .attr('width', width)\n    .attr('height', height);<\/code><\/pre>\n<p>And we will get our first green test! You can examine the resulting code for this setup step in <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/tree\/step-setup\" target=\"_blank\" rel=\"noopener\">the \u2018step-setup\u2019 branch<\/a> of the repository.<\/p>\n<h2>Drawing a basic heatmap<\/h2>\n<p>Let\u2019s move forward and draw the boxes on our chart. As we want to create an hourly heatmap over a whole week, we will need to draw 24 * 7 different boxes. We\u2019ll use this fact in our test:<\/p>\n<pre><code>it('should render a box for each hour in the week', () => {\n    let expected = 24 * 7;\n    let actual = container.selectAll('.box').nodes().length;\n\n    expect(actual).toEqual(expected);\n});<\/code><\/pre>\n<p>This test fails as expected. Before we jump into creating the code that passes this test, I\u2019d like to create some SVG group elements &#8220;g&#8221; that will help us organise and position the SVG markup we are going to write. For this, we will include the following tests:<\/p>\n<pre><code>it('should render a container-group', () => {\n  let expected = 1;\n    let actual = container.select('g.container-group').nodes().length;\n\n    expect(actual).toEqual(expected);\n});\n\nit('should render a chart-group', () => {\n    let expected = 1;\n    let actual = container.select('g.chart-group').nodes().length;\n\n    expect(actual).toEqual(expected);\n});<\/code><\/pre>\n<p>To make these tests pass, we will need to create those elements, and we will do it only when creating the initial SVG root node. Let&#8217;s take a look at the code:<\/p>\n<pre><code>function buildContainerGroups() {\n    let container = svg\n          .append('g')\n            .classed('container-group', true)\n            .attr('transform', `translate(${margin.left}, ${margin.top})`);\n\n    container\n      .append('g')\n        .classed('chart-group', true);\n    container\n      .append('g')\n        .classed('metadata-group', true);\n}<\/code><\/pre>\n<p>This code applies&lt; =&#8221;https:\/\/bl.ocks.org\/mbostock\/3019563&#8243; target=&#8221;_blank&#8221;&gt;the Margin Convention of D3 charts and creates the group elements. We will also refactor our previous code to encapsulate within a \u2018buildSVG\u2019 function:<\/p>\n<pre><code>function buildSVG(container) {\n    if (!svg) {\n        svg = d3.select(container)\n          .append('svg')\n            .classed('heatmap', true);\n\n        buildContainerGroups();\n    }\n\n    svg\n        .attr('width', width)\n        .attr('height', height);\n}<\/code><\/pre>\n<p>We will call &#8220;buildSVG&#8221; from the main module thread passing \u2019this\u2019 as the container.<\/p>\n<p>So now, we should only have one test failing, the one checking for the boxes. A key element in a heatmap is <a href=\"https:\/\/medium.com\/@mbostock\/introducing-d3-scale-61980c51545f\" target=\"_blank\" rel=\"noopener\">the colour scale<\/a> that helps us figure out the colour that corresponds to each value in our data set. As this scale is not public, we will not test it, and this is the code:<\/p>\n<pre><code>function buildScales() {\n    colorScale = d3.scaleLinear()\n        .range([colorSchema[0], colorSchema[colorSchema.length - 1]])\n        .domain(d3.extent(data, function (d) { return d[2] }))\n        .interpolate(d3.interpolateHcl);\n}<\/code><\/pre>\n<p>The &#8220;colorSchema&#8221; is an array of colours that range from the lightest to the darkest. Now, we have all we need to write the code for drawing the boxes:<\/p>\n<pre><code>function drawBoxes() {\n    boxes = svg.select('.chart-group').selectAll('.box').data(data);\n\n    boxes.enter()\n      .append('rect')\n        .attr('width', boxSize)\n        .attr('height', boxSize)\n        .attr('x', function (d) { return d[1] * boxSize; })\n        .attr('y', function (d) { return d[0] * boxSize; })\n        .style('fill', function (d) { return colorScale(d[2]); })\n        .classed('box', true);\n\n    boxes.exit().remove();\n}<\/code><\/pre>\n<p>In the preceding code, we are using D3\u2019s <a href=\"https:\/\/bl.ocks.org\/mbostock\/3808218\" target=\"_blank\" rel=\"noopener\">enter\/update\/exit pattern<\/a> to create one rectangle element with class \u2018box\u2019 for each entry in our dataset. We are also giving the boxes a fixed size &#8220;boxSize&#8221; and position them in regards to the day of the week (d[0]) and the hour (d[1]). Lastly, we are using our newly created &#8220;colorScale&#8221; to style the box with a colour that relates to its value (d[2]).<\/p>\n<p>If everything is right, all of our tests should be passing now \u2014 you can check the code at this step in <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/tree\/step-basic\" target=\"_blank\" rel=\"noopener\">this branch<\/a>. We will also have the first render of our heatmap! We can take a glimpse at it by placing a &#8220;debugger&#8221; at the end of our test, opening the \u2018debug\u2019 tab and the browser dev tools in our test runner window:<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-18722 size-full\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/heatmap-initial-render-e1525786862591.png\" alt=\"The initial render of the heatmap\" width=\"650\" height=\"207\" \/><br \/>\n<small>Initial render of our heatmap<\/small><\/p>\n<h2>Adding axes<\/h2>\n<p>Next, let\u2019s spend some time adding the labels for the days \u2014 vertical axis \u2014 and the hours \u2014 horizontal axis \u2014 of our heatmap. We are going to code both axes at once, and we\u2019ll start by creating two tests:<\/p>\n<pre><code>it('should render the day labels', () => {\n    let expected = 7;\n    let actual = container.selectAll('.day-label').nodes().length;\n\n    expect(actual).toEqual(expected);\n});\n\nit('should render the hour labels', () => {\n    let expected = 24;\n    let actual = container.selectAll('.hour-label').nodes().length;\n\n    expect(actual).toEqual(expected);\n});<\/code><\/pre>\n<p>We are looking for seven elements with a &#8220;day-label&#8221; class and 24 elements with an &#8220;hour-label&#8221; class. These tests fail, so let\u2019s set up the elements we need to make them pass. First, we will need to create variables with the actual labels and their sizes:<\/p>\n<pre><code>\/\/ Day labels\nconst daysHuman = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];\nconst dayLabelWidth = 25;\n\n\/\/ Hour labels\nconst hoursHuman = [\n    '00h', '01h', '02h', '03h', '04h', '05h', '06h', '07h', '08h',\n    '09h', '10h', '11h', '12h', '13h', '14h', '15h', '16h', '17h',\n    '18h', '19h', '20h', '21h', '22h', '23h'\n];\nconst hourLabelHeight = 20;<\/code><\/pre>\n<p>Now, we should add some container groups to hold the labels, adding this code to the &#8220;buildContainerGroups&#8221; method:<\/p>\n<pre><code>container\n  .append('g')\n    .classed('day-labels-group', true);\ncontainer\n  .append('g')\n    .classed('hour-labels-group', true);<\/code><\/pre>\n<p>Finally, we can create the labels with the following code for the day labels:<\/p>\n<pre><code>function drawDayLabels() {\n  let dayLabelsGroup = svg.select('.day-labels-group');\n\n    dayLabels = svg.select('.day-labels-group').selectAll('.day-label')\n        .data(daysHuman);\n\n    dayLabels.enter()\n      .append('text')\n        .text((d) => d)\n        .attr('x', 0)\n        .attr('y', (d, i) => i * boxSize)\n        .style('text-anchor', 'start')\n        .style('dominant-baseline', 'central')\n        .attr('class', 'day-label');\n\n    dayLabelsGroup.attr('transform', `translate(-${dayLabelWidth}, ${boxSize\/2})`);<\/code><\/pre>\n<p>And for the hour labels:<\/p>\n<pre><code>function drawHourLabels() {\n  let hourLabelsGroup = svg.select('.hour-labels-group');\n    \n  hourLabels = svg.select('.hour-labels-group')\n      .selectAll('.hour-label')\n        .data(hoursHuman);\n\n    hourLabels.enter()\n      .append('text')\n        .text((d) => d)\n        .attr('y', 0)\n        .attr('x', (d, i) => i * boxSize)\n        .style('text-anchor', 'middle')\n        .style('dominant-baseline', 'central')\n        .attr('class', 'hour-label');\n\n     hourLabelsGroup.attr('transform', `translate(${boxSize\/2}, -${hourLabelHeight})`);\n}<\/code><\/pre>\n<p>In both snippets, we are using the &#8220;boxSize&#8221; variable to position and center the labels, along with the &#8220;text-anchor&#8221; and &#8220;dominant-baseline&#8221; properties. At the end of this step, the code of which you can find in <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/tree\/step-axis\" target=\"_blank\" rel=\"noopener\">the \u2018step-axis\u2019 branch<\/a>, we will render a chart such as:<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-18860\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/basic-heatmap-with-labels2.png\" alt=\"\" width=\"777\" height=\"268\" \/><br \/>\n<small>Basic heatmap with labels<\/small><\/p>\n<h2>Styling and adding animations<\/h2>\n<p>Our heatmap has potential, but it doesn\u2019t look great yet. Let\u2019s add some styles to make it more appealing!<\/p>\n<p>For that, we will attach a simple <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/blob\/step-styles-and-animations\/src\/css\/styles.css\" target=\"_blank\" rel=\"noopener\">stylesheet<\/a> that loads a Google font and adds some spacing and some colour to our labels. We can make use of a <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/blob\/step-styles-and-animations\/index.html\" target=\"_blank\" rel=\"noopener\">simple HTML file<\/a> and a <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/blob\/step-styles-and-animations\/webpack.config.js\" target=\"_blank\" rel=\"noopener\">basic Webpack configuration<\/a> that will use an <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/blob\/step-styles-and-animations\/src\/index.js\" target=\"_blank\" rel=\"noopener\">&#8220;index.js&#8221; file<\/a> to create our chart. Also, we will add a coloured stroke to our boxes when we draw them:<\/p>\n<pre><coode>function drawBoxes() {\n  boxes.enter()\n      .append('rect')\n        .classed('box', true)\n        .attr('width', boxSize)\n        .attr('height', boxSize)\n        .attr('x', function (d) { return d[1] * boxSize; })\n        .attr('y', function (d) { return d[0] * boxSize; })\n        .style('fill', function (d) { return colorScale(d[2]); })\n        .style('stroke', \"#FFFFFF\")\n        .style('stroke-width', 2);\n}<\/code><\/pre>\n<p>Then, if we change the &#8220;colorSchema&#8221; to be:<\/p>\n<pre><code>let colorSchema = [\n    '#C0FFE7',\n    '#95F6D7',\n    '#6AEDC7',\n    '#59C3A3',\n    '#479980'\n];<\/code><\/pre>\n<p>We will render something like this:<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-18728\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/hourly-heatmap-chart-e1525787104226.png\" alt=\"Styled hourly heatmap chart\" width=\"650\" height=\"277\" \/><br \/>\n<small>Styled heatmap<\/small><\/p>\n<p>Much better, right? However, I feel we are not making the most of D3, so let\u2019s use one of the more celebrated features of the library: its animations!<\/p>\n<p>We could set an opacity of 0.2 and set an initial gray colour when drawing the boxes. Later, we will raise the opacity to 1 and animate the boxes colour to its proper value. This is how we do it:<\/p>\n<pre><code>boxes.enter()\n  .append('rect')\n    .classed('box', true)\n    .attr('width', boxSize)\n    .attr('height', boxSize)\n    .attr('x', function (d) { return d[1] * boxSize; })\n    .attr('y', function (d) { return d[0] * boxSize; })\n    .style('opacity', 0.2)\n    .style('fill', '#BBBBBB')\n    .style('stroke', boxBorderColor)\n    .style('stroke-width', boxBorderSize)\n    .transition()\n        .duration(animationDuration)\n        .style('fill', function (d) { return colorScale(d[2]); })\n        .style('opacity', 1);<\/code><\/pre>\n<p>As you can see, we are setting the opacity and gray fill with &#8220;.style&#8221; calls, then marking the beginning of the animation by calling &#8220;transition&#8221;. We are setting the duration of the transition \u2014 we could also add an <a href=\"https:\/\/github.com\/d3\/d3-transition#transition_ease\" target=\"_blank\" rel=\"noopener\">easing function<\/a> or a <a href=\"https:\/\/github.com\/d3\/d3-transition#transition_delay\" target=\"_blank\" rel=\"noopener\">delay<\/a> \u2014 to two seconds, and then set the opacity to 1 and the fill colour to the one provided by our colour scale. Here are the results:<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-18862\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/animating-color-and-opacity.gif\" alt=\"heatmap with fade in\" width=\"791\" height=\"343\" \/><br \/>\n<small>Heatmap with fade-in and colour animation<\/small><\/p>\n<p>You can find this code in <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/tree\/step-styles-and-animations\" target=\"_blank\" rel=\"noopener\">the \u2018step-styles-and-animations\u2019 branch<\/a> of the repository.<\/p>\n<h2>Chart accessors<\/h2>\n<p>The Reusable API pattern allows us to configure our chart by creating \u2018property accessors.\u2019 They are essentially getter and setter functions, but all in one. For the sake of brevity, we have omitted this code and tests, and you can find them in <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/tree\/step-configuration-accessors\" target=\"_blank\" rel=\"noopener\">this branch<\/a>. There you can see how to add accessors to the colour schema, height, width, and margin or our heatmap.<\/p>\n<p>Now, in the &#8220;index.js&#8221; file, if we change the colour schema this way\u2026<\/p>\n<pre><code>const heatmapChart = heatmap();\nlet container = d3.select('.container');\n\nheatmapChart\n    .colorSchema([\n        '#ffd8d4',\n        '#ff584c',\n        '#9c1e19'\n    ]);\ncontainer.datum(data).call(heatmapChart);<\/code><\/pre>\n<p>\u2026 we will see what our chart looks like:<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-18863\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/red-color.gif\" alt=\"heat map with colour schema\" width=\"784\" height=\"336\" \/><br \/>\n<small>Heatmap with configured colour schema<\/small><\/p>\n<h2>Loading real data<\/h2>\n<p>Until now, we have been using fake data to render our heat map. Let\u2019s change this by loading some actual weather data. We are going to use data from the <a href=\"https:\/\/www.weatherbit.io\/\" target=\"_blank\" rel=\"noopener\">Weatherbit API<\/a>.<\/p>\n<p>I have always thought that the wind in San Francisco regularly picks up around 2pm. We are going to check if that\u2019s true. For that, we will find a meteorological station in San Francisco, for example, the one at 3221 20th Street (with coordinates &#8220;37.7585,-122.4137&#8221;).<\/p>\n<p>Then, checking the <a href=\"https:\/\/darksky.net\/dev\/docs#time-machine-request\" target=\"_blank\" rel=\"noopener\">documentation<\/a>, we see that the proper URL schema to get the info is:<\/p>\n<pre><code>https:\/\/api.weatherbit.io\/v2.0\/history\/hourly?lat=X,lon=Y&amp;start_date=2018-03-23&amp;end_date=2018-03-29&amp;key={API_KEY}<\/code><\/pre>\n<p>To get the API key, we will need to create a free account using <a href=\"https:\/\/www.weatherbit.io\/account\/create\" target=\"_blank\" rel=\"noopener\">this link<\/a>. After we get the key, we will need to type the date we want in the format &#8220;[YYYY]-[MM]-[DD]&#8221; and pass the units as Imperial units \u2018I&#8217;. This results in the following URL:<\/p>\n<pre><code>https:\/\/api.weatherbit.io\/v2.0\/history\/hourly?key=&lt;yourKey&gt;&amp;lat=37.7585&amp;lon=-122.4137&amp;start_date=2018-03-23:00&amp;end_date=2018-03-23:23&amp;units=I<\/code><\/pre>\n<p>As we want to show a week\u2019s data of hourly wind speeds, and the free tier of the API only allows one day of hourly data, we will need to do seven calls to get the data we need. For this, we will use <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Promise\/all\" target=\"_blank\" rel=\"noopener\">Promise.all<\/a> and the new <a href=\"https:\/\/github.com\/d3\/d3-fetch\" target=\"_blank\" rel=\"noopener\">d3-fetch library<\/a> added to D3 version 5. Here is how we create the requests:<\/p>\n<pre><code>\/\/ Fetch data\nconst requestURLs = [\n'https:\/\/api.weatherbit.io\/v2.0\/history\/hourly?key=<yourKey>&lat=37.7585&lon=-122.4137&start_date=2018-03-23:00&end_date=2018-03-23:23&units=I',    'https:\/\/api.weatherbit.io\/v2.0\/history\/hourly?key=<yourKey>&lat=37.7585&lon=-122.4137&start_date=2018-03-24:00&end_date=2018-03-24:23&units=I',\n...\n];\nconst requests = requestURLs.map((url) => d3.json(url));\n\nPromise.all(requests)\n    .then(function(values) {\n        let dataByHour = getFormattedWindSpeed(values);\n\n        console.log('dataByHour', dataByHour)\n\n        container.datum(dataByHour).call(heatmapChart);\n    });<\/code><\/pre>\n<p>Note how we will need to do some data formatting to adapt the shape of the API output into the format our heatmap. As we mentioned earlier, this is a flattened list of values with this shape \u2018[weekDayNumber, hour, value]\u2019. I will omit this code here, but you can find the code in <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/blob\/master\/src\/index.js#L858\" target=\"_blank\" rel=\"noopener\">the repository<\/a>.<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-18734\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/windspeed-heatmap-e1525787974517.png\" alt=\"Heatmap of wind speed in San Francisco from 19 to 25 March\" width=\"650\" height=\"279\" \/><\/p>\n<p>Hmm\u2026 It seems I was not right about my 2pm hypothesis. Or at least that did not happen in the week from the 19 to the 25 of March. What about the previous week? This is what I got:<\/p>\n<p align=\"center\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-18737\" src=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/windspeed-heatmap-2-e1525788194359.png\" alt=\"heatmap of wind speed in San Francisco from 12 to 18 March\" width=\"650\" height=\"291\" \/><\/p>\n<p>Well, here we see a block of moderate wind starting at 1 or 2pm, but it\u2019s not quite sharp. What we can see is that wind is not an explosive condition in San Francisco. It grows progressively until reaching the top speeds; then it fades gradually as well. This kind of analysis is what heatmaps are suitable for: to show variance on the values and highlight patterns.<\/p>\n<h2>Summary<\/h2>\n<p>In this post, we have set up an environment for creating D3.js visualisations with Test Driven Development. We created a heatmap visualisation by writing code to pass one test at a time, following the three rules of TDD. We also used a public API to load real data, apply it to our heatmap and interpret the resulting chart to extract insights from the data.<\/p>\n<p>This visualisation is far from complete. All heatmaps should include a legend that shows the values represented by the range of colours. You could implement that legend as part of this same heatmap or by creating an independent visualisation that could be reused with other charts. Our heatmap will also benefit from having a tooltip, making it more interactive and engaging.<\/p>\n<p>I want to encourage you to fork <a href=\"https:\/\/github.com\/Golodhros\/heart-internet-d3-tdd\/tree\/master\" target=\"_blank\" rel=\"noopener\">the repo<\/a> \u2013 or continue developing your code \u2013 to build these and other features! Share your code in the comments and let\u2019s start a conversation!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Everyone loves data visualisation &#8211; it&#8217;s a quick and easy way to turn stats into something that&#8217;s good looking and understandable. Find out how you can use D3.js to create visualisations that look great.<\/p>\n","protected":false},"author":2,"featured_media":18759,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12,24],"tags":[],"class_list":{"0":"post-18713","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-guest-posts","8":"category-web-design"},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Create beautiful test-driven data visualisations with D3.js - Heart Internet<\/title>\n<meta name=\"description\" content=\"Everyone loves data visualisation - it&#039;s a quick and easy way to turn stats into something that&#039;s good looking and understandable. Find out how you can use D3.js to create visualisations that look great.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Create beautiful test-driven data visualisations with D3.js - Heart Internet\" \/>\n<meta property=\"og:description\" content=\"Everyone loves data visualisation - it&#039;s a quick and easy way to turn stats into something that&#039;s good looking and understandable. Find out how you can use D3.js to create visualisations that look great.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/\" \/>\n<meta property=\"og:site_name\" content=\"Heart Internet\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/heartinternet\/\" \/>\n<meta property=\"article:published_time\" content=\"2018-05-23T11:30:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1450\" \/>\n\t<meta property=\"og:image:height\" content=\"966\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Eliot Chambers-Ostler\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@heartinternet\" \/>\n<meta name=\"twitter:site\" content=\"@heartinternet\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Eliot Chambers-Ostler\" \/>\n\t<meta name=\"twitter:label2\" content=\"Estimated reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/\"},\"author\":{\"name\":\"Eliot Chambers-Ostler\",\"@id\":\"https:\/\/heartblog.victory.digital\/#\/schema\/person\/58ed7f27cc0f3ab6e69135742a5eee28\"},\"headline\":\"Create beautiful test-driven data visualisations with D3.js\",\"datePublished\":\"2018-05-23T11:30:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/\"},\"wordCount\":2581,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/heartblog.victory.digital\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg\",\"articleSection\":[\"Guest Posts\",\"Web Design\"],\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/\",\"url\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/\",\"name\":\"Create beautiful test-driven data visualisations with D3.js - Heart Internet\",\"isPartOf\":{\"@id\":\"https:\/\/heartblog.victory.digital\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg\",\"datePublished\":\"2018-05-23T11:30:40+00:00\",\"description\":\"Everyone loves data visualisation - it's a quick and easy way to turn stats into something that's good looking and understandable. Find out how you can use D3.js to create visualisations that look great.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage\",\"url\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg\",\"contentUrl\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg\",\"width\":1450,\"height\":966},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.heartinternet.uk\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Create beautiful test-driven data visualisations with D3.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/heartblog.victory.digital\/#website\",\"url\":\"https:\/\/heartblog.victory.digital\/\",\"name\":\"Heart Internet\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/heartblog.victory.digital\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/heartblog.victory.digital\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/heartblog.victory.digital\/#organization\",\"name\":\"Heart Internet\",\"url\":\"https:\/\/heartblog.victory.digital\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\/\/heartblog.victory.digital\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/02\/HeartInternet_Logo_Colour.webp\",\"contentUrl\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/02\/HeartInternet_Logo_Colour.webp\",\"width\":992,\"height\":252,\"caption\":\"Heart Internet\"},\"image\":{\"@id\":\"https:\/\/heartblog.victory.digital\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/heartinternet\/\",\"https:\/\/x.com\/heartinternet\",\"https:\/\/www.linkedin.com\/company\/heart-internet-ltd\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/heartblog.victory.digital\/#\/schema\/person\/58ed7f27cc0f3ab6e69135742a5eee28\",\"name\":\"Eliot Chambers-Ostler\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\/\/heartblog.victory.digital\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/08\/cropped-Eliot-96x96.jpg\",\"contentUrl\":\"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/08\/cropped-Eliot-96x96.jpg\",\"caption\":\"Eliot Chambers-Ostler\"},\"url\":\"https:\/\/www.heartinternet.uk\/blog\/author\/eliot-chambers-ostler\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Create beautiful test-driven data visualisations with D3.js - Heart Internet","description":"Everyone loves data visualisation - it's a quick and easy way to turn stats into something that's good looking and understandable. Find out how you can use D3.js to create visualisations that look great.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/","og_locale":"en_GB","og_type":"article","og_title":"Create beautiful test-driven data visualisations with D3.js - Heart Internet","og_description":"Everyone loves data visualisation - it's a quick and easy way to turn stats into something that's good looking and understandable. Find out how you can use D3.js to create visualisations that look great.","og_url":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/","og_site_name":"Heart Internet","article_publisher":"https:\/\/www.facebook.com\/heartinternet\/","article_published_time":"2018-05-23T11:30:40+00:00","og_image":[{"width":1450,"height":966,"url":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg","type":"image\/jpeg"}],"author":"Eliot Chambers-Ostler","twitter_card":"summary_large_image","twitter_creator":"@heartinternet","twitter_site":"@heartinternet","twitter_misc":{"Written by":"Eliot Chambers-Ostler","Estimated reading time":"17 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#article","isPartOf":{"@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/"},"author":{"name":"Eliot Chambers-Ostler","@id":"https:\/\/heartblog.victory.digital\/#\/schema\/person\/58ed7f27cc0f3ab6e69135742a5eee28"},"headline":"Create beautiful test-driven data visualisations with D3.js","datePublished":"2018-05-23T11:30:40+00:00","mainEntityOfPage":{"@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/"},"wordCount":2581,"commentCount":0,"publisher":{"@id":"https:\/\/heartblog.victory.digital\/#organization"},"image":{"@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage"},"thumbnailUrl":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg","articleSection":["Guest Posts","Web Design"],"inLanguage":"en-GB","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/","url":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/","name":"Create beautiful test-driven data visualisations with D3.js - Heart Internet","isPartOf":{"@id":"https:\/\/heartblog.victory.digital\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage"},"image":{"@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage"},"thumbnailUrl":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg","datePublished":"2018-05-23T11:30:40+00:00","description":"Everyone loves data visualisation - it's a quick and easy way to turn stats into something that's good looking and understandable. Find out how you can use D3.js to create visualisations that look great.","breadcrumb":{"@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/"]}]},{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#primaryimage","url":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg","contentUrl":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2018\/05\/heatmap-header.jpg","width":1450,"height":966},{"@type":"BreadcrumbList","@id":"https:\/\/www.heartinternet.uk\/blog\/create-beautiful-test-driven-data-visualisations-with-d3-js\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.heartinternet.uk\/blog\/"},{"@type":"ListItem","position":2,"name":"Create beautiful test-driven data visualisations with D3.js"}]},{"@type":"WebSite","@id":"https:\/\/heartblog.victory.digital\/#website","url":"https:\/\/heartblog.victory.digital\/","name":"Heart Internet","description":"","publisher":{"@id":"https:\/\/heartblog.victory.digital\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/heartblog.victory.digital\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"},{"@type":"Organization","@id":"https:\/\/heartblog.victory.digital\/#organization","name":"Heart Internet","url":"https:\/\/heartblog.victory.digital\/","logo":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/heartblog.victory.digital\/#\/schema\/logo\/image\/","url":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/02\/HeartInternet_Logo_Colour.webp","contentUrl":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/02\/HeartInternet_Logo_Colour.webp","width":992,"height":252,"caption":"Heart Internet"},"image":{"@id":"https:\/\/heartblog.victory.digital\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/heartinternet\/","https:\/\/x.com\/heartinternet","https:\/\/www.linkedin.com\/company\/heart-internet-ltd"]},{"@type":"Person","@id":"https:\/\/heartblog.victory.digital\/#\/schema\/person\/58ed7f27cc0f3ab6e69135742a5eee28","name":"Eliot Chambers-Ostler","image":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/heartblog.victory.digital\/#\/schema\/person\/image\/","url":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/08\/cropped-Eliot-96x96.jpg","contentUrl":"https:\/\/www.heartinternet.uk\/blog\/wp-content\/uploads\/2025\/08\/cropped-Eliot-96x96.jpg","caption":"Eliot Chambers-Ostler"},"url":"https:\/\/www.heartinternet.uk\/blog\/author\/eliot-chambers-ostler\/"}]}},"_links":{"self":[{"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/posts\/18713","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/comments?post=18713"}],"version-history":[{"count":0,"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/posts\/18713\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/media\/18759"}],"wp:attachment":[{"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/media?parent=18713"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/categories?post=18713"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.heartinternet.uk\/blog\/wp-json\/wp\/v2\/tags?post=18713"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}