Supporting D3 v3 and v4

Jan. 16, 2017

D3 is a popular javascript library for data visualizations. Last year they released version 4, which had a significant rewrite of the API. This introduced a problem for us because we had one project using D3 v3 and another project using d3 v4. These two projects shared some code and therefore we needed to be able to support both versions of D3 depending on which one was available.

Detecting current version

Detecting which version of d3 is being used is the easy part.

Method 1

Check the value of d3.version

// we use split instead of `d3.version.charAt(0)` to avoid a windows 9 like situation.
var majorVersion = d3.version.split('.')[0];
if(majorVersion === '3') {
    // version 3 code
} else if(majorVersion === '4') {
    // version 4 code
} else {
    // Are you from the future?
}

Method 2

Because the API has changed so much, in a lot of cases you can simply check for the existence of the methods you need.

For example, if the v3 method you need is d3.scale.linear and the v4 one is d3.scaleLinear you could do

var scaleLinear = d3.scaleLinear || d3.scale.linear;

and then call scaleLinear where ever it’s needed.

Using the brush

The biggest change we had to make was where we were using the brush for zooming

brush-selection

Version 3 code

function brushed(selection) {
    // handle selection
}

var brush = d3.svg.brush()
    .x(d3.scale.linear().range([0, width]))
    .on('brushend', function brushend() {
        var extent = brush.extent();
        wrapper.select('.brush').call(brush.clear());
        brushed(extent);
    });

To get this to work there were a few changes that needed to be made.

Version 3Version 4
d3.svg.brush()d3.brushX()
brush.x(d3.scale.linear().range([0, width]))brush.extent([[0, 0], [width, height]])
brush.extent()d3.event.selection
brush.on('brushend', func)brush.on('end', func)
wrapper.select('.brush').call(brush.clear())wrapper.select('.brush').call(brush.move, null)

Because v4 removed the brush.clear method, we had to replaced it with a brush.move call to null

This introduces another issue. The brush.clear method would not fire the brushend event, which meant it was fine to call it inside the brushend handler itself. Now that we were using the brush.move method, it would fire the end event and therefore cause a loop. To solve this we check d3.event.selection, If there is nothing there, return.

Version 4 code

function brushed(selection) {
    // handle selection
}

var brush = d3.brushX()
    .extent([[0, 0], [width, height]])
    .on('end', function() {
        var selection = d3.event.selection;
        if (!selection) return;
        wrapper.select('.brush').call(brush.move, null);
        brushed(selection);
    });

Complete code

function brushed(selection) {
    // handle selection
}

if(d3.svg) {
    var brush = d3.svg.brush()
        .x(d3.scale.linear().range([0, width]))
        .on('brushend', function brushend() {
            var extent = brush.extent();
            wrapper.select('.brush').call(brush.clear());
            brushed(extent);
        });
} else {
    var brush = d3.brushX()
        .extent([[0, 0], [width, height]])
        .on('end', function() {
            var selection = d3.event.selection;
            if (!selection) return;
            wrapper.select('.brush').call(brush.move, null);
            brushed(selection);
        });
}

Some other api changes

Version 3Version 4
d3.svg.axis().scale(x).orient('bottom')d3.axisBottom().scale(x)
d3.svg.area().interpolate('step-before')d3.area().curve(d3.curveStepBefore)

More information

If you need to use more function across both versions, https://github.com/d3/d3/blob/master/CHANGES.md has a lot of useful information you can use to figure out what is required.

Jeffrey Burt, Software Developer
Toronto based Front end developer who loves to make things. From handcrafted furniture to modern web apps he takes pride in learning from every experience and approaching problems in new ways.