What I wish someone had told me
when I was learning D3
Think about visualization –
it's all about binding data to visual elements.
var dataset = [20, 5, 10, 0, 50];
d3.select('body')
.selectAll('p') // selection
.data(dataset) // data binding
.enter()
.append('p') // dom manipulation
.attr('class', 'paragraph') // static property
.text(function(d, i) { // dynamic property
return i + ': my value is ' + d;
})
.style('font-size', function(d) {
return (d / 2 + 25) + 'px'; // maps to [25, 50]
});
Visual elements enter or exit the stage
// update
var p = d3.select('body')
.selectAll('p')
.data(newData) // elements that already are on the stage
.text(function(d) { return d + ' has been here all along'; });
// enter
p.enter() // elements that are about to enter the stage
.append('p')
.text(function(d) { return d + ' is new here'; });
// exit
p.exit() // elements that are about to exit the stage
.remove();
Scales are functions that map from an input domain to an output range.
var fontSize = d3.scale.linear()
.domain([d3.min(dataset), d3.max(dataset)])
.range([25, 50]);
d3.selectAll('p')
.style('font-size', function(d) {
return fontSize(d) + 'px';
});
Transition provides the visual cue that
resembles how real-world objects change.
d3.selectAll('p')
.transition()
.delay(function(d, i) {
return i * 50;
})
.style('padding-left', function() {
return Math.random() * 200 + 'px';
});
d3.max(array[, accessor]);
d3.min(array[, accessor]);
d3.extent(array[, accessor]);
d3.sum(array[, accessor]);
d3.mean(array[, accessor]);
d3.median(array[, accessor]);
d3.range([start, ]stop[, step]);
d3.nest()
.key(function(d) { return d.school })
.entries(array); // or `.map(array)`
var width = 840, height = 500, padding = 10;
var x = d3.scale.linear() // x scale
.domain([0, dataset.length - 1])
.range([padding, width - padding]);
var y = d3.scale.linear() // y scale
.domain(d3.extent(dataset))
.range([height - padding, padding]);
var line = d3.svg.line() // line generator
.x(function(d, i) { return x(i); })
.y(function(d) { return y(d); });
d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('path')
.datum(dataset)
.attr('d', line);
Based on http://bl.ocks.org/3048740 by Mike Bostock
var width = 840;
var height = 500;
var outerRadius = height / 2 - 10;
var innerRadius = 120;
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'stacked-radial')
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
d3.csv('data.csv', function(data) {
// play with `data`
});
// turn strings into numbers
data.forEach(function(d) {
d.time = +d.time;
d.value = +d.value;
});
// nest operator
var nest = d3.nest()
.key(function(d) { return d.key; });
// stack operator
var stack = d3.layout.stack()
.offset('zero') // stack from a baseline
.values(function(d) { return d.values; })
.x(function(d) { return d.time; })
.y(function(d) { return d.value; });
var layers = stack(nest.entries(data));
// [0, 7] -> [0, 2π]
var angle = d3.time.scale()
.domain([0, d3.max(data, function(d) {
return d.time + 1;
})])
.range([0, 2 * Math.PI]);
// value -> radius
var radius = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.y0 + d.y;
})])
.range([innerRadius, outerRadius]);
// ordinal scale of 20 colors
var color = d3.scale.category20c();
// area generator
var area = d3.svg.area.radial()
.interpolate('cardinal-closed')
.angle(function(d) { return angle(d.time); })
.innerRadius(function(d) { return radius(d.y0); })
.outerRadius(function(d) { return radius(d.y0 + d.y); });
svg.selectAll('.layer')
.data(layers)
.enter().append('path')
.attr('class', 'layer')
.attr('d', function(d) { return area(d.values); })
.style('fill', function(d, i) { return color(i); });
// date -> weekday name
var formatDate = d3.time.format('%a');
// day -> weekday name
var formatDay = function(d) {
return formatDate(new Date(2007, 0, d));
};
svg.selectAll('.axis')
.data(d3.range(
d3.max(data, function(d) { return d.time; }) -
d3.min(data, function(d) { return d.time; }) + 1))
.enter().append('g')
.attr('class', 'axis')
.attr('transform', function(d) {
return 'rotate(' + angle(d) * 180 / Math.PI + ')';
})
.call(d3.svg.axis()
.scale(radius.copy().range([-innerRadius, -outerRadius]))
.orient('left'))
.append('text') // weekdays
.attr('y', -innerRadius + 6)
.attr('dy', '.71em')
.attr('text-anchor', 'middle')
.text(function(d) { return formatDay(d); });
var selected = []; // ui state
layers.forEach(function() { selected.push(false); }); // initialize state
// add mouse click listener
svg.selectAll('.layer')
.on('click', function(d, i) {
selected[i] = !selected[i];
render();
});
// render current state
var render = function() {
svg.selectAll('.layer')
.transition()
.style('fill', function(d, i) {
if (d3.max(selected)) {
// something is selected
return selected[i] ? color(i) : '#eee';
} else {
// nothing is selected
return color(i);
}
});
};
var width = 840;
var height = 500;
var outerRadius = height / 2 - 10;
var innerRadius = 120;
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'stacked-radial')
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
d3.csv('data.csv', function(data) {
// data preprocessing
// turn strings into numbers
data.forEach(function(d) {
d.time = +d.time;
d.value = +d.value;
});
// nest operator
var nest = d3.nest()
.key(function(d) { return d.key; });
// stack operator
var stack = d3.layout.stack()
.offset('zero') // stack from a baseline
.values(function(d) { return d.values; })
.x(function(d) { return d.time; })
.y(function(d) { return d.value; });
var layers = stack(nest.entries(data));
// draw stacked radial area chart
// [0, 7] -> [0, 2π]
var angle = d3.time.scale()
.domain([0, d3.max(data, function(d) {
return d.time + 1;
})])
.range([0, 2 * Math.PI]);
// value -> radius
var radius = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.y0 + d.y;
})])
.range([innerRadius, outerRadius]);
// ordinal scale of 20 colors
var color = d3.scale.category20c();
// area generator
var area = d3.svg.area.radial()
.interpolate('cardinal-closed')
.angle(function(d) { return angle(d.time); })
.innerRadius(function(d) { return radius(d.y0); })
.outerRadius(function(d) { return radius(d.y0 + d.y); });
svg.selectAll('.layer')
.data(layers)
.enter().append('path')
.attr('class', 'layer')
.attr('d', function(d) { return area(d.values); })
.style('fill', function(d, i) { return color(i); });
// draw axes
// date -> weekday name
var formatDate = d3.time.format('%a');
// day -> weekday name
var formatDay = function(d) {
return formatDate(new Date(2007, 0, d));
};
svg.selectAll('.axis')
.data(d3.range(
d3.max(data, function(d) { return d.time; }) -
d3.min(data, function(d) { return d.time; }) + 1))
.enter().append('g')
.attr('class', 'axis')
.attr('transform', function(d) {
return 'rotate(' + angle(d) * 180 / Math.PI + ')';
})
.call(d3.svg.axis()
.scale(radius.copy().range([-innerRadius, -outerRadius]))
.orient('left'))
.append('text') // weekdays
.attr('y', -innerRadius + 6)
.attr('dy', '.71em')
.attr('text-anchor', 'middle')
.text(function(d) { return formatDay(d); });
// interaction
var selected = []; // ui state
layers.forEach(function() { selected.push(false); }); // initialize state
// add mouse click listener
svg.selectAll('.layer')
.on('click', function(d, i) {
selected[i] = !selected[i];
render();
});
// render current state
var render = function() {
svg.selectAll('.layer')
.transition()
.style('fill', function(d, i) {
if (d3.max(selected)) {
// something is selected
return selected[i] ? color(i) : '#eee';
} else {
// nothing is selected
return color(i);
}
});
};
});