# Empty Pipes

### Panning and Zooming with D3v4

• 03 Jul 2016
• |
• javascript
• d3
• zooming
• |

All that’s necessary for panning and zooming is a translation [tx, ty] and a scale factor k. When a zoom transform is applied to an element at position [x0, y0], its new position becomes [tx + k × x0, ty + k × y0]. That’s it. Everything else is just sugar and spice on top of this simple transform.

The major difference between zooming in D3v3 and and D3v4 is that the behavior (dealing with events) and the transforms (positioning elements) are more separated. In v3, they used to be part of the behavior whereas in v4, they’re part of the element on which the behavior is called.

To illustrate, let’s plot 4 points. The rest of this post will only deal with data in one dimension. It should be trivial to expand to two dimensions. The points will represent the values 1, 1010, 1020 and 5000:

``````    var xScale = d3.scaleLinear()
.domain([0,5000])
.range([100,500])

var dataPoints = [1,1010,1020,5000];

gMain.selectAll('circle')
.data(dataPoints)
.enter()
.append('circle')
.attr('r', 7)
.attr('cx', function(d) { return xScale(d); });
``````

We can see that two of the points, 1010 and 1020, are virtually on top of each other. Using our `xScale`, we can determine that they’re less than 1 pixel apart.

``````    xScale(1010) //180.8
xScale(1020) //181.6
``````

What if we want to zoom in so that they’re 10 pixels apart? We’ll first need to calculate the scale factor, k:

``````    var k = 10 / (xScale(1020) - xScale(1010))  //~ 12.5
``````

Let’s say we want the point 1010 to be positioned at pixel 200. We need to determine tx such that 200 = tx + k × xScale(1010)

``````    var tx = 200 - k * xScale(1010) //-2600
``````

When we apply this to our plot.

``````    var k = 10 / (xScale(1020) - xScale(1010))
var tx = 200 - k * xScale(1010)
var t = d3.zoomIdentity.translate(tx, 0).scale(k)

gMain.selectAll('circle')
.data(dataPoints)
.enter()
.append('circle')
.attr('r', 7)
.attr('cx', function(d) { return t.applyX(xScale(d)); });
``````

We get two lovely separated circles.

Fantastic, right? But notice that the top axis still refers to the old domain. This is because we never changed it. In the old version of D3, we would attach the axis to the zoom behavior, set the `translate` and `scale` properties and be done with it. In v4, we have to rescale our linear scale manually and use the rescaled version to create the axis:

``````    var xNewScale = t.rescaleX(xScale)

var xTopAxis = d3.axisTop()
.scale(xNewScale)
.ticks(3)
``````

The examples above demonstrate how the zoom transforms work, but they don’t actually use the zoom behavior. For that we need to create a behavior and attach it to an element:

``````    var circles = svg.selectAll('circle');
var zoom = d3.zoom().on('zoom', zoomed);

function zoomed() {
var transform = d3.event.transform;

// rescale the x linear scale so that we can draw the top axis
var xNewScale = transform.rescaleX(xScale);
xTopAxis.scale(xNewScale)
gTopAxis.call(xTopAxis);

// draw the circles in their new positions
circles.attr('cx', function(d) { return transform.applyX(xScale(d)); });
}

gMain.call(zoom)
``````

Here we recompute the zoom transform every time there is a zoom event and reposition each circle. We also rescale the x-scale so that we can use it to create an axis. The astute observer will note that `transform.applyX(xScale(d))` is actually equivalent to `xNewScale(d)`. Automatic rescaling was possible using v3 by calling `zoom.x(xScale)`, but this has been done away with in favor of explicit rescaling using `transform.rescaleX(xScale)`.

The code above works but if we had programmatically zoomed in beforehand (as we did in the previous section by applying the transform), then applying the zoom behavior would remove that transform as soon as we start zooming.

Why?

Because in the `zoomed` function we obtain a `transform` from `d3.event.transform`. In previous versions of D3, this would come from the zoom behavior itself (`zoom.translate` and `zoom.scale`). In v4, it comes from the element on which the zoom behavior is called (`gMain`). To programmatically zoom in and then apply the zoom behavior starting from there, we need to set the zoom transform of the `gMain` element before we call the behavior:

``````var k = 10 / (xScale(1020) - xScale(1010))
var tx = 200 - k * xScale(1010)
var t = d3.zoomIdentity.translate(tx, 0).scale(k)

gMain.call(zoom.transform, t);
gMain.call(zoom)
``````

Now we start with an already zoomed in view and can zoom in and out using the mouse.

To wrap up this post, let’s combine these techniques to create a figure which automatically zooms between random data points (a la M. Bostock’s Zoom Transitions Block). How do we do this?

First, we need a function to call every time we want to jump to a point:

``````    let targetPoint = 1010;

function transition(selection) {
let n = dataPoints.length;
let prevTargetPoint = targetPoint;

// pick a new point to zoom to
while (targetPoint == prevTargetPoint) {
let i = Math.random() * n | 0
targetPoint = dataPoints[i];
}

selection.transition()
.delay(300)
.duration(2000)
.call(zoom.transform, transform)
.on('end', function() { circles.call(transition); });
}

circles.call(transition);
``````

This function picks a random point (`targetPoint`) and calls a transition on the selection. In our case, the selection will be the circles. When the transition is over, we simply call the function again to start it over.

Second, we need a transform to center the view on the target point:

``````    function transform() {
// put points that are 10 values apart 20 pixels apart
var k = 20 / (xScale(10) - xScale(0))
// center in the middle of the visible area
var tx = (xScale.range()[1] + xScale.range()[0])/2 - k * xScale(targetPoint)
var t = d3.zoomIdentity.translate(tx, 0).scale(k)
return t;
}

``````

And that’s all. Just remember, when zooming and panning the position of the transformed point [x1,y1] = [tx + k × x0, ty + k × y0]. Everything else is just window dressing.

### Isochrone Driving Maps of the World

• 04 Mar 2016
• |
• maps
• javascript
• d3.js
• leaflet
• |

One of my favorite things about maps is the context they provide for overlayed information. This can range from the mundane and orthodox (such as roads and boundaries) to the esoteric and abstract. Nearly a year ago I wrote a blog post which overlayed travel time (isochrone) data on top of a map of Europe. It showed how long it would take somebody to travel from a given city to any other point in Europe using only public transportation. In this post, I do the exact same thing using driving times.

Driving times starting in Vienna, Austria. Notice how the frontier of the contours is more rounded than in Lincoln, NE.

Driving times starting in Lincoln, Nebraska.

The wonderful thing about portraying driving times is that it's possible to make such maps for cities from all over the world. In doing so, we can see the how the transportation infrastructure of a region meshes with the natural features to create a unique accessibility profile. Lincoln (Nebraska) is centered in the USA and has a characteristic diamond shaped travel time profile. Why? Anyone that has looked at a map of the region will certainly have noticed that the roads are arranged in a grid pattern. Thus it takes much longer to travel along the diagonal than to travel straight north and south. Vienna, Austria, on the other hand, has a more circular accessibility profile due to the abundance of roads going in all directions.

The Andes mountains block driving to the east of Santiago, Chile

The Darien Gap separates Central America from South America

Looking at the isochrone map of Santiago, Chile, one can clearly see the how the Andes mountains block travel east of the city. Southeast of panama city, the odd fact that you can’t drive between North / Central America and South America becomes clear. Zooming in at identical levels, you can compare cities and see the difference in accessibility of the relatively wealthy South Africa with that of the less developed, wilder Congo. The differences in accessibility between different cities of the world can range from the trivial (Denver, CO vs Lincoln, NE) to the substantial (Perth, Australia vs. Sydney, Australia). Individual cities can have a wide automobile-reachible area (Moscow, Russia) or a narrow, geography, politics and infrastructure-constrained area (Irkutsk, Russia).

The accessibility profile of Kinshasa, Democratic Republic of the Congo is rugged and discontinuous.

Cape Town, South Africa has good links to the interior of the country as well as to Namibia in the north.

Whatever the case, the places that are most interesting are always those that are also most familiar. For this reason, I’ve provided overlays for most major cities around the world. The map currently shows Vienna, but clicking any of the links below will open a map for that particular city. The travel times were obtained using GraphHopper and OpenStreetMap data so don’t be surprised if they differ from those of Google maps.

Other Cities

 Atlanta Juneau Ottawa Tegucigalpa Belmopan Lincoln Panama City Toronto Charlotte Los Angeles Philadelphia Vancouver Chicago Managua Phoenix Whitehorse Dallas Mexico City Pittsburgh Wichita Denver Miami San Diego Winnipeg Edmonton Minneapolis San Francisco Yellowknife Fairbanks Montreal San Jose Guatemala City New York San Salvador Houston Nome Seattle
 Amsterdam Budapest London Rome Antwerp Copenhagen Luxembourg Sofia Athens Dublin Madrid Stockholm Barcelona Frankfurt Minsk Tallinn Belgrade Geneva Oslo Vienna Berlin Helsinki Paris Vilnius Bratislava Jena Podgorica Warsaw Brussels Lisbon Prague Zagreb Bucharest Ljubljana Riga Zurich
 Asuncion Cartagena Manaus Santiago Bogota Cayenne Montevideo Sao Paolo Brasilia Georgetown Paramaribo Ushuaia Buenos Aires La Paz Quito Caracas Lima Rio De Janeiro
 Abidjan Conakry Lagos Monrovia Accra Dakar Libreville N Djamena Algiers Dar Es Salaam Lilongwe Nairobi Antananarivo Freetown Lome Niamey Asmara Gaborone Luanda Nouakchott Banjul Johannesburg Lusaka Porto Novo Bissau Juba Maputo Rabat Brazzaville Kampala Marrakesh Tripoli Bujumbura Khartoum Maseru Tunis Cairo Kigali Mbabane Windhoek Cape Town Kinshasa Mogadishu Yaounde
 Abu Dhabi Dubai Moscow St Petersburg Almaty Dushanbe Mosul Tashkent Amman Hanoi Mumbai Tbilisi Ankara Irkutsk Muscat Tehran Ashgabat Islamabad Naypyidaw Tel Aviv Astana Jakarta New Delhi Thimphu Baghdad Jeddah Novosibirsk Tianjin Baku Jerusalem Phnom Penh Tokyo Bangkok Kabul Pyongyang Ulaanbaatar Beijing Karachi Riyadh Vientiane Beirut Kathmandu Sanaa Vladivostok Bishkek Kuwait City Seoul Yerevan Colombo Lahore Shanghai Damascus Manama Singapore Doha Manila Sochi
 Adelaide Christchurch Noumea Sydney Apia Hobart Perth Wellington Auckland Honiara Port Moresby Brisbane Melbourne Suva

Errata / Disclaimer

Everything is an estimate. Rounding errors abound. Don’t use this for anything but entertainment and curiosity. But you already know that.

Some data may be missing. There may be faster routes. Google Maps certainly finds some better ones. If you find issues, please let me know and I’ll do my best to fix them.

For each starting city, an area encompassing 30 degrees north, east, south and west was subdivided into a .1 degree grid. Directions from the starting city to each point were calculated using graphhopper and the OSM map files. From this, contours were extracted using matplotlib’s `contourf` function. These contour files are stored as JSONs and loaded as a D3.js overlay on top of a leaflet map.

All of the code used to create these maps is on github. If you have any questions, feel free to ask.

Background Information and Motivation

This project was a logical extension of the isochrone train maps of Europe project.

• 02 Mar 2016
• |
• javascript
• es6
• tutorial
• |

### Overview

One of the most common scenarios I run into when creating javascript applications is the following.

1. I start working on some application (let’s call it `foo`).

2. I start working on a different program (let’s call it `bar`) and need some of the functionality that I already implemented in `foo`.

In python, accomplishing this is trivial by placing the source files in the same directory and saying `import foo`. In javascript, it’s a little more complicated. Rather than recapitulate a lot of existing documentation about the different ways that one can implement modules in javascript (CommonJS, AMD, etc…), I’ll just dive into how I use `gulp` and `webpack` to take advantage of the ECMAScript 6 syntax for modules to create reusable javascript components.

#### Goal

The goal of this project is to create two components: `foo` and `bar`. `foo` will export a function called `drawCircle` that we can import into `bar`. `bar` will then draw a rectangle on top of the circle.

Here’s the code for `app/scripts/bar.js`

``````import d3 from 'd3';
import {drawCircle} from 'foo';

export function drawRectangleAndCircle(divName) {
drawCircle(divName);

d3.select('svg')
.append('rect')
.attr('x', 75)
.attr('y', 75)
.attr('width', 100)
.attr('height', 100)
.classed('bar-rectangle', true)
.attr('opacity', 0.7)
}
``````

And the relevant code from `app/index.html` which uses `bar.js`:

`````` <!-- build:js scripts/bar.js -->
<script src='scripts/bar.js'></script>
<!-- endbuild -->

<script type='text/javascript'>

bar.drawRectangleAndCircle('#circle');

</script>
``````

In order for all of this to function, we need some tools to manage which modules we have installed (`npm`), resolve the import statements (`webpack`), translate the es6 to more universally understandable es5 (`babel`) and to build everything into one package (`gulp`). The easiest way to get started is to use `yeoman` to scaffold out the application. To install yeoman, simply use `npm`:

``````npm install -g yo
``````

Then, install the generator for this tutorial:

``````npm install -g generator-gulp-webpack-es6
``````

#### Setting up foo

Create a directory to host the `foo` module and scaffold it out:

``````mkdir foo
cd foo
yo gulp-webpack-es6
``````

When yeoman asks for `Your project name:`, make sure to enter `foo`:

You can just use the default values for the other fields. The default values for author name and email address are taken from your `~/.gitconfig` if you have one.

That’s it. We now have a module which simply draws a circle in the browser. Witness it in all its glory by running:

``````gulp serve
``````

And pointing your browser to `127.0.0.1:9000`. The result should look like this:

<img src=’/img/es6_module/foo_browser.png’ width=320 />

Now, we want to install this package locally so that we can import it from our soon-to-be-created package `bar`. To do this we need to make one minor change to our `webpack.config.js` by adding the following lines:

``````  externals: {
"d3": "d3"
},
``````

These are necessary to tell webpack not to include d3 in the packaged file. Under normal circumstances this wouldn’t be necessary but if I omit it and use `npm link` as described below, I get the following error:

``````Uncaught TypeError: Cannot read property 'document' of undefined
``````

It’s seems like it happens either because `webpack` adds a `use strict;` somewhere in the bundled `foo` package, or because d3 is included in both `foo` and `bar`. In either case, specifying that it’s an external package seems to resolve the problem.

To build and install `foo` so that it can be imported by other modules, just run the following two commands.

``````gulp build
``````

The first will compile the code into a single javascript file and the second will install it as a global module on the local computer. From now on, whenever we need to make changes to `foo` we just need to run `gulp build` and they will be reflected in every local package that uses `foo`.

#### Setting up bar

To create the calling package `bar` we follow a very similar procedure:

``````mkdir bar
cd bar
yo gulp-webpack-es6
``````

When yeoman asks for `Your project name:`, make sure to enter `bar`. You can use the default values for the other fields.

Now because we want to use the functionality provided in `foo`, we need to install it:

``````npm link foo
``````

Adn then we need to modify `app/scripts/bar.js` and have it implement its own functionality:

``````import d3 from 'd3';
import {drawCircle} from 'foo';

export function drawRectangleAndCircle(divName) {
drawCircle(divName);

d3.select('svg')
.append('rect')
.attr('x', 75)
.attr('y', 75)
.attr('width', 100)
.attr('height', 100)
.classed('bar-rectangle', true)
.attr('opacity', 0.7)
}
``````

And finally we need to change the `<script>` code inside `index.html` to call the newly created function from `bar`:

``````<script type='text/javascript'>

bar.drawRectangleAndCircle('#circle');

</script>
``````

If everything worked as it should, running `gulp serve` should show a lovely circle / square combination:

<img src=”/img/es6_module/bar_browser.png” width=320 />

#### Generator directory structure

The directory structure of the componenets generated using `gulp-webpackes6` is organized so that the configuration files are in the root directory, all of the code is in the `app` subdirectory, unit tests are in `test` and the compiled javascript is in `dist`:

``````.
├── app
│   ├── index.html
│   ├── scripts
│   │   ├── foo.js
│   │   └── helper_module.js
│   └── styles
│       └── foo.css
├── bower.json
├── dist
│   ├── index.html
│   └── scripts
│       └── foo.js
├── gulpfile.babel.js
├── package.json
└── webpack.config.js
``````

#### Changing the module’s name

The default module is created so that the main file is in `app/scripts/module_name.js` where `module_name` is the name given in the generator questionnaire. To change it to something else, you can either re-run the generator or make the following changes:

• Rename `app/scripts/module_name.js`
• Change occurences of `module_name` in `app/index.html`
• Change the `entry` line in `webpack.config.js`