Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active June 7, 2023 18:33
Show Gist options
  • Save mbostock/3934356 to your computer and use it in GitHub Desktop.
Save mbostock/3934356 to your computer and use it in GitHub Desktop.
Underscore’s Equivalents in D3

Collections

each(array)

Underscore example:

_.each([1, 2, 3], function(num) { alert(num); });

Vanilla equivalent:

[1, 2, 3].forEach(function(num) { alert(num); });

each(object)

Underscore example:

_.each({one: 1, two: 2, three: 3}, function(num, key) { alert(num); });

D3 equivalent using d3.values (d3.entries and d3.map forEach would also work):

d3.values({one: 1, two: 2, three: 3}).forEach(function(num) { alert(num); });

map(array)

Underscore example:

_.map([1, 2, 3], function(num) { return num * 3; });

Vanilla equivalent:

[1, 2, 3].map(function(num) { return num * 3; });

map(object)

Underscore example:

_.map({one: 1, two: 2, three: 3}, function(num, key) { return num * 3; });

D3 equivalent with d3.values (d3.entries and d3.map values.map would also work):

d3.values({one: 1, two: 2, three: 3}).map(function(num) { return num * 3; });

reduce

Underscore example:

var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }, 0);

Vanilla equivalent:

var sum = [1, 2, 3].reduce(function(memo, num) { return memo + num; }, 0);

reduceRight

Given:

var list = [[0, 1], [2, 3], [4, 5]];

Underscore example:

var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);

Vanilla equivalent:

var flat = list.reduceRight(function(a, b) { return a.concat(b); }, []);

find

Underscore example:

var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });

There’s no proper Vanilla equivalent of Underscore’s find method, but you can approximate it with array.filter. This is typically slower because it doesn’t stop once the first match is found:

var even = [1, 2, 3, 4, 5, 6].filter(function(num) { return num % 2 == 0; })[0];

A closer approximate is array.some, but this is less convenient because it requires the closure to set an enclosing variable:

var even; [1, 2, 3, 4, 5, 6].some(function(num) { return num % 2 == 0 && (even = num, true); });

contains

Underscore example:

_.contains([1, 2, 3], 3);

Vanilla equivalent:

[1, 2, 3].indexOf(3) >= 0;

invoke

Underscore example:

_.invoke([[5, 1, 7], [3, 2, 1]], "sort"]);

Vanilla equivalent:

[[5, 1, 7], [3, 2, 1]].forEach(function(array) { array.sort(); });

Note: JavaScript’s built-in array.sort sorts lexicographically rather than numerically; you almost always want to say array.sort(d3.ascending), or array.sort(function(a, b) { return a - b; }) if you know you’re sorting numbers or dates.

pluck

Given:

var stooges = [{name: "moe", age: 40}, {name: "larry", age: 50}, {name: "curly", age: 60}];

Underscore example:

_.pluck(stooges, "name");

Vanilla equivalent:

stooges.map(function(stooge) { return stooge.name; });

max

Given:

var stooges = [{name: "moe", age: 40}, {name: "larry", age: 50}, {name: "curly", age: 60}];

Underscore example:

_.max(stooges, function(stooge) { return stooge.age; });

Vanilla equivalent. You can omit the null argument if you know that stooges is non-empty or you’d prefer a TypeError:

stooges.reduce(function(p, v) { return v.age > p.age ? v : p; }, null);

Using d3.max is not exactly equivalent because it returns the maximum value, rather than the corresponding object. Also, d3.max ignores null, undefined and NaN values. Often, this is what you want:

d3.max(stooges, function(stooge) { return stooge.age; });

min

Given:

var numbers = [10, 5, 100, 2, 1000];

Underscore example:

_.min(numbers);

Vanilla equivalent:

numbers.reduce(function(p, v) { return Math.min(p, v); }, Infinity);

Using d3.min (not equivalent; see above regarding d3.max):

d3.min(numbers);

sortBy

Underscore example:

_.sortBy([1, 2, 3, 4, 5, 6], function(num) { return Math.sin(num); });

Vanilla equivalent:

[1, 2, 3, 4, 5, 6].sort(function(a, b) { return Math.sin(a) - Math.sin(b); });

In the vanilla equivalent, the comparator is invoked once per comparison rather than once per element; thus the vanilla version is potentially slower. More critically, array.sort doesn’t support nondeterministic comparators. You could fix that with a temporary array holding the mapped values, a second temporary array holding the indexes, and d3.permute:

var array = [1, 2, 3, 4, 5, 6], sin = array.map(Math.sin);
d3.permute(array, d3.range(array.length).sort(function(i, j) { return sin[i] - sin[j]; }));

groupBy

Underscore example:

_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });

D3 equivalent:

d3.nest().key(Math.floor).map([1.3, 2.1, 2.4]);

Underscore example:

_.groupBy(["one", "two", "three"], "length");

D3 equivalent:

d3.nest().key(function(d) { return d.length; }).map(["one", "two", "three"]);

D3’s nest operator supports more than one level of grouping, and can also return nested entries that preserve order.

countBy

Underscore example:

_.countBy([1, 2, 3, 4, 5], function(num) {
  return num % 2 == 0 ? "even" : "odd";
});

D3 equivalent using nest.rollup:

d3.nest()
    .key(function(num) { return num % 2 == 0 ? "even" : "odd"; })
    .rollup(function(values) { return values.length; })
    .map([1, 2, 3, 4, 5]);

shuffle

Underscore example:

_.shuffle([1, 2, 3, 4, 5, 6]);

D3 equivalent:

d3.shuffle([1, 2, 3, 4, 5, 6]);

toArray

Underscore example:

(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);

Vanilla equivalent:

(function(){ return [].slice.call(arguments, 1); })(1, 2, 3, 4);

size

Underscore example:

_.size({one: 1, two: 2, three: 3});

Vanilla equivalent using Object.keys:

Object.keys({one: 1, two: 2, three: 3}).length;

D3 equivalent using d3.keys:

d3.keys({one: 1, two: 2, three: 3}).length;

D3 equivalent using d3.map, if you want to perform other operations:

d3.map({one: 1, two: 2, three: 3}).keys().length;

Array Functions

first

Given:

var numbers = [5, 4, 3, 2, 1];

Underscore example:

_.first(numbers);

Vanilla equivalent when no n is specified:

numbers[0];

Vanilla equivalent when n is specified:

numbers.slice(0, n);

initial

Given:

var numbers = [5, 4, 3, 2, 1];

Underscore example:

_.initial(numbers);

Vanilla equivalent when no n is specified:

numbers.slice(0, numbers.length - 1);

Vanilla equivalent when n is specified:

numbers.slice(0, numbers.length - n);

last

Given:

var numbers = [5, 4, 3, 2, 1];

Underscore example:

_.last(numbers);

Vanilla equivalent when no n is specified:

numbers[numbers.length - 1];

Vanilla equivalent when n is specified:

numbers.slice(numbers.length - n);

rest, tail, drop

Given:

var numbers = [5, 4, 3, 2, 1];

Underscore example:

_.rect(numbers);

Vanilla equivalent when no n is specified:

numbers.slice(1);

Vanilla equivalent when n is specified:

numbers.slice(n);

compact

Underscore example:

_.compact([0, 1, false, 2, '', 3]);

Vanilla equivalent:

[0, 1, false, 2, '', 3].filter(function(d) { return d; });

flatten

TODO …

_.flatten([1, [2], [3, [[4]]]]);
_.flatten([1, [2], [3, [[4]]]], true);

without

TODO …

_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);

union

TODO …

_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);

intersection

TODO …

_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);

difference

TODO …

_.difference([1, 2, 3, 4, 5], [5, 2, 10]);

uniq

Underscore example:

_.uniq([1, 2, 1, 3, 1, 4]);

D3 equivalent for strings:

d3.set(["1", "2", "1", "3", "1", "4"]).values();

TODO equivalent for non-string values

zip

Underscore example:

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

D3 equivalent:

d3.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

object

TODO …

_.object(['moe', 'larry', 'curly'], [30, 40, 50]);

indexOf

TODO … Vanilla array.indexOf

lastIndexOf

TODO … Vanilla array.lastIndexOf

sortIndex

TODO … d3.bisect

range

Underscore example:

_.range(10);
_.range(1, 11);
_.range(0, 30, 5);
_.range(0, -10, -1);
_.range(0);

D3 equivalent:

d3.range(10);
d3.range(1, 11);
d3.range(0, 30, 5);
d3.range(0, -10, -1);
d3.range(0);

Function (uh, ahem) Functions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment