Fewer Lambdas in D3.js: How to type less and make your code simpler.

posted 2012-Feb-29

In D3.js you see a lot of functions that take an argument and return a property or method invocation on that argument, or even just the argument itself. Calls that look like this:

.data(function(d) { return d; }) 
.attr('title', function(d) { return d.value; })
.text(function(d) { return d.toFixed(); }) 

Rubyists have the sweet Symbol#to_proc method for converting a symbol into a function that accesses that method/property; they could write the latter two as:

.attr('title',&:value)
.text(&:toFixed) 

We can make similar—and even more powerful—shortcuts in JavaScript so that our D3 code is easier to type, easier to read, and easier to maintain.

// Create a function that returns a particular property of its parameter.
// If that property is a function, invoke it (and pass optional params).
function ƒ(name){ 
  var v,params=Array.prototype.slice.call(arguments,1);
  return function(o){
    return (typeof (v=o[name])==='function' ? v.apply(o,params) : v );
  };
}
 
// Return the first argument passed in
function I(d){ return d } 

Note that the name of the first function—ƒ—is a legal JavaScript identifier that is also easy-to-type on OS X (option-f) and unlikely to collide with other names.

With this the original code can be written far more simply:

.data(I) 
.attr('title', ƒ('value'))
.text(ƒ('toFixed'))       // round to integer
.text(ƒ('toFixed',2))     // round to 2 decimal places
Gavin Kistner
05:13PM ET
2012-May-21

Note that if you just want to round a number to the nearest integer you can more simply use .text(Math.round).

David Turgeon
09:24PM ET
2013-Dec-27

This function combines both ƒ & I doing double-duty.
It also allows an args array override and tolerates missing names.

function F(name) {
    var args, prop;
    args = Array.prototype.slice.call(arguments, 1);
    return function (obj, arr) {
        prop = obj && name ? obj[name] : obj;
        arr = arr && arr.length ? arr : args;
        return (typeof prop === 'function' ? prop.apply(obj, arr) : prop);
    };
}

var test;

test = F('toFixed', 2);
test(3.6) === “3.60”;

test = F('toFixed');
test(3.6) === “4” &&
test(3.6, [1]) === “3.6”;

test = F('console');
test(window) === window.console && 
test(window.console) === undefined;

function mock() {
    var args = Array.prototype.slice.call(arguments);
    return 'STUB' + (args[0] ? ':' + args : '');
}

test = F();
test(3) === 3 && 
test(this) === this && 
test(mock) === 'STUB' && 
test(mock, [3,4,5]) === 'STUB:3,4,5';
David Turgeon
11:51AM ET
2013-Dec-28

Thanks for fixing the code format!

(BTW: the double quoted strings got curled on lines 14,17,18 of code block.)

net.mind details other résumé contact
Phrogz.net