Open Source Adventures: Episode 30: Using D3 and Parcel to visualize Russian Tank Losses
Tags: javascript, d3
Published: 2022-04-05 21:13:58

In the previous episode I used D3 to make a simple graph, without using any tooling. Let's do a more modern take.

### Create a new app

There's a lot of different bundlers, and most of them require some painful configuration.

Let's try [`parcel`](https://parceljs.org/) this time, as it promises to just work out of the box.

```
$ npm init -y
$ npm install d3 parcel
```

It doesn't quite do what it promises, but it's still a lot less configuration that webpack or rollup.

### Parcel configuration for GitHub Pages

The first issue with Parcel is that it outputs everything with absolute path, so your app will only work if you host it on top level of a domain.

Which is not how GitHub Pages are setup, and is overall a terrible default. The default should be relative paths, so it can be served anywhere. To make it work, we need to passs `--public-url .` to `parcel`.

### `package.json`

We need to change two things. Set `source` entry with our entrypoint. And tell Parcel that we want relative URLs, so it works with GitHub Pages.

```json
{
  "name": "episode-30",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "parcel:build": "parcel build --public-url ."
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "d3": "^7.4.2",
    "parcel": "^2.4.1"
  },
  "source": "src/index.html"
}
```

That's almost the end of our Parcel issues.

### `src/index.html`

Here we can reduce two scripts to one. We need to add `type="module"` annotation to make it work.

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="app.css">
  </head>
  <body>
    <h1>Russian Tank Losses</h1>
    <script src="app.js" type="module"></script>
  </body>
</html>
```

### `src/app.css`

Unchanged from previous version.

```css
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
```

### `src/app.js`

And one more Parcel issue. By default it won't copy static assets like our `.csv` files to the build directory. There are many solutions to this - the way we'll be using it, is by importing `url:` of the asset we want.

Overall, only three lines changed from previous episode's version.

First we need to import d3 with `import * as d3 from "d3"`. We could also just import specific functions like `import {csv, scaleLinear, scaleTime, extent, select, axisBottom, axisLeft, line} from d3`, but D3 API really wasn't created for it, and I wouldn't recommend it.

Second we need to inform Parcel that our `csv` needs bundling. `csvUrl` will become the Parcel-bundled URL, with appropriate hash. This isn't the only way to do assets, but it works well enough.

And then we need to use that `csvUrl` with `let data = await d3.csv(csvUrl, parseRow)`.

Nothing else needed changing.

```javascript
import * as d3 from "d3"
import csvUrl from 'url:./russia_losses_equipment.csv'

let parseRow = ({date,tank}) => ({date: new Date(date), tank: +tank})

let main = async () => {
  let data = await d3.csv(csvUrl, parseRow)
  data.unshift({date: new Date("2022-02-24"), tank: 0})

  let xScale = d3.scaleTime()
    .domain(d3.extent(data, d => d.date))
    .range([0, 600])

  let yScale = d3.scaleLinear()
    .domain(d3.extent(data, d => d.tank))
    .range([400, 0])

  let svg = d3.select("body")
    .append("svg")
      .attr("width", 800)
      .attr("height", 600)
    .append("g")
      .attr("transform", "translate(100, 100)")

  svg.append("g")
    .call(d3.axisLeft(yScale))

  svg.append("g")
    .attr("transform", "translate(0, 400)")
    .call(d3.axisBottom(xScale))

  svg.append("path")
    .datum(data)
    .attr("fill", "none")
    .attr("stroke", "red")
    .attr("stroke-width", 1.5)
    .attr("d", d3.line()
      .x(d => xScale(d.date))
      .y(d => yScale(d.tank)))
}

main()
```

### Should you use Parcel?

[Parcel](https://parceljs.org/) didn't quite do zero-config bundling, and there are some annoying things, like it's impossible to turn off hashing (`--no-content-hash` just replaces content hashes with static hashes), but that's still a big improvement over other JavaScript bundlers.

If you use a framework like Svelte or React, you already have a bundler setup, so you should probably just use that. But if you don't, Parcel might be the best low-config solution right now.

### Story so far

I deployed this on GitHub Pages, [you can see it here](https://taw.github.io/open-source-adventures/episode-30/).

### Coming next

In the next episode, we'll port this app to Svelte. And after that we'll try to figure out how long until Russia runs out of tanks.