Creating D3 components
a journey of pain, joy, frustration and enlightenment
Chris Price / @100pxls
d3
data join
components
our experience
d3
the basics
DOM abstraction
selection
var selection = d3.select('#id');
selection.select('div')
.selectAll('.class-name');
DOM abstraction
manipulation
selection.text('inner text')
.attr('class', 'class-name')
.style({
height: 'auto'
})
.each(function(data) {
var element = this;
// ...
})
.append('span');
DOM abstraction
events
selection.on('click.foo',
function(data) {
var element = this;
// ...
});
DOM abstraction
XHR
d3.json('/some/url',
function(error, data) {
// ...
});
DOM abstraction
animation
selection.style('background', 'blue')
.transition()
.duration(100)
.ease('linear')
.style('background', 'red');
layout algorithms
scales
var scale = d3.scale.linear()
.domain([-10, 10])
.range([0, 500]);
scale(-5); // 125
scale.invert(125); // -5
scale.ticks(3); // [-10, 0, 10]
layout algorithms
specialised layouts
var layout = d3.layout.pie()
.padAngle(Math.PI/10);
layout([3,1,2]);
// [ {
// "data":3,
// "value":3,
// "padAngle":0.314...,
// "startAngle":0,
// "endAngle":2.984...
// }, ... ]
layout algorithms
geometric primitives
var polygon = d3.geom.polygon(
[[0,0],[0,1],[1,1],[1,0]]);
polygon.area(); // 1
polygon.centroid(); // [0.5,0.5]
layout algorithms
geographic projections
var mercator = d3.geo.projection(
function(λ, φ) {
return [
λ,
Math.log(Math.tan(π / 4 + φ / 2))
];
});
SVG helpers
axis renderers
var scale = d3.linear.scale()
.domain([-10, 10])
.range([0, 100]);
var axis = d3.svg.axis()
.scale(scale);
d3.select('svg')
.call(axis);
SVG helpers
path data generators
var line = d3.svg.line();
line([[0,0],[50,50]]);
// "M0,0L50,50"
d3.select('svg')
.append('path')
.datum([[0,0],[50,50]])
.attr('d', line);
// <path d="M0,0L50,50"></path>
miscellaneous
data/time formatting
var format = d3.time.format("%a, %e %B %Y");
format(new Date(2015, 4, 7));
// "Thu, 7 May 2015"
var d = format.parse("Fri, 8 May 2015");
d.toString();
// "Fri May 08 2015 00:00:00 GMT+0000 (GMT)"
d3.time.year.ceil(d, 1);
// "Fri Jan 01 2016 00:00:00 GMT+0000 (GMT)"
examples
d3
- DOM abstraction
- layout algorithms
- SVG helpers
- miscellaneous
- data binding
data join
contains spoilers
data join
the HTML
<h1>USS Missouri Crew List</h1>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
data join
the data
var data = [
{
name: 'Adams', rank: 'Captain'
},
{
name: 'Krill', rank: 'Commander'
},
{
name: 'Ryback', rank: 'Chief Petty Officer'
}
];
data join
render loop
// ...
function render() {
requestAnimationFrame(render);
}
requestAnimationFrame(render);
data join
selection
// ...
function render() {
var container = d3.select('ul');
container.selectAll('li');
requestAnimationFrame(render);
}
// ...
data join
the join
// ...
var container = d3.select('ul');
container.selectAll('li')
.data(data);
requestAnimationFrame(render);
// ...
data join
update selection
// ...
var updateSelection = container.selectAll('li')
.data(data);
updateSelection.text(function(d) {
return d.name + ' (' + d.rank + ')';
});
// ...
data join
update selection
<h1>USS Missouri Crew List</h1>
<ul>
<li>Adams (Captain)</li>
<li>Krill (Commander)</li>
<li>Ryback (Chief Petty Officer)</li>
</ul>

data join
add an item to the data
var data = [
{
name: 'Adams', rank: 'Captain'
},
{
name: 'Krill', rank: 'Commander'
},
{
name: 'Ryback', rank: 'Chief Petty Officer'
},
{
name: 'Strannix', rank: 'Civilian'
}
];
data join
...no change
<h1>USS Missouri Crew List</h1>
<ul>
<li>Adams (Captain)</li>
<li>Krill (Commander)</li>
<li>Ryback (Chief Petty Officer)</li>
</ul>
data join
enter selection
// ...
var updateSelection = container.selectAll('li')
.data(data);
updateSelection.enter()
.append('li');
updateSelection.text(function(d) {
return d.name + ' (' + d.rank + ')';
});
// ...
data join
a change!
<h1>USS Missouri Crew List</h1>
<ul>
<li>Adams (Captain)</li>
<li>Krill (Commander)</li>
<li>Ryback (Chief Petty Officer)</li>
<li>Strannix (Civilian)</li>
</ul>
data join
remove an item from the data
var data = [
{
name: 'Krill', rank: 'Commander'
},
{
name: 'Ryback', rank: 'Chief Petty Officer'
},
{
name: 'Strannix', rank: 'Civilian'
}
];
data join
a change...
<h1>USS Missouri Crew List</h1>
<ul>
<li>Krill (Commander)</li>
<li>Ryback (Chief Petty Officer)</li>
<li>Strannix (Civilian)</li>
<li>Strannix (Civilian)</li>
</ul>
data join
exit selection
// ...
updateSelection.enter()
.append('li');
updateSelection.text(function(d) {
return d.name + ' (' + d.rank + ')';
});
updateSelection.exit()
.remove();
// ...
data join
a change!
<h1>USS Missouri Crew List</h1>
<ul>
<li>Krill (Commander)</li>
<li>Ryback (Chief Petty Officer)</li>
<li>Strannix (Civilian)</li>
</ul>
data join
spoilers
<h1>USS Missouri Crew List</h1>
<ul>
<li>Ryback (Chief Petty Officer)</li>
</ul>
var data = [
{
name: 'Ryback', rank: 'Chief Petty Officer'
}
];
data join
- idempotent transformation of data into nodes
demo
components
(functions)
components
just functions
function caseyRyback(selection) {
selection.text('Another cold day in Hell.');
}
function dramaticEffect(selection) {
selection.style('fontWeight', 'bold');
}
var selection = d3.select('span');
caseyRyback(selection);
dramaticEffect(selection);
components
invoked with call
function caseyRyback(selection) {
selection.text('Another cold day in Hell.');
}
function dramaticEffect(selection) {
selection.style('fontWeight', 'bold');
}
var selection = d3.select('span')
.call(caseyRyback)
.call(dramaticEffect);
components
factories
function caseyRybackFactory() {
function caseyRyback(selection) {
selection.text('Another cold day in Hell.');
}
return caseyRyback;
}
var component = caseyRybackFactory();
d3.select('span')
.call(component);
components
configurable
function caseyRybackFactory() {
var quote = 'Another cold day in Hell.';
function caseyRyback(selection) {
selection.text(quote);
}
caseyRyback.quote = function(value) {
if (!arguments.length) { return quote; }
quote = value;
return caseyRyback;
};
return caseyRyback;
}
components
configurable
var component = caseyRybackFactory()
.quote('Keep the faith, Strannix.');
d3.select('span')
.call(component);
components
- convention based unit of re-use
demo
our experience
d3fc
d3fc
- a toolkit for rapidly developing bespoke charts
- embraces d3
- small and composable
- prioritises simplicity
- a curated and consistent set of examples
our experience
8 rules
#1
function componentFactory() {
function component(selection) {
// ...
}
d3.rebind(component, strober,
'oddColor', 'evenColor');
return component;
}
var component = componentFactory();
d3.select('rect')
.oddColor('pink')
.evenColor('white')
.call(component);
function componentFactory() {
var event = d3.dispatch("customevent");
function component(selection) {
selection.each(function(data) {
d3.select(this)
.on('click.component', function() {
event.customevent.apply(
this, arguments);
});
});
}
d3.rebind(component, event, 'on');
return component;
}
react ripped off d3
idempotent DOM operations
const root = <ul className="my-list">
<li>Text Content</li>
</ul>;
ReactDOM.render(root,
document.getElementById('example'));
const root = function(container) {
const ul = container.selectAll('ul')
.data([null])
.enter()
.append('ul')
.classed('my-list')
.append('li')
.text('Text Content');
};
d3.select('#element')
.call(root);
component trees of props/data
class Avatar extends React.Component {
render() {
return (
<div>
<Pic username={this.props.user} />
{this.props.user}
</div>
);
}
}
ReactDOM.render(
<Avatar user="pwh" />,
document.getElementById('example')
);
const avatar = function(selection) {
const div = selection.selectAll('div')
.data((data) => [data]);
div.enter()
.append('div');
div.datum((d) => { user: d.user })
.call(pic)
.text((d) => d.user);
};
d3.select('#element')
.datum({ user: 'pwh' })
.call(avatar);
...

flux

https://facebook.github.io/flux/docs/overview.html
immutable.js + d3 = ?
react didn't rip off d3
(probably)
Creating D3 components
Chris Price / @100pxls
d3fc.io
slides.com/chrisprice/creating-d3-components-1-3
Creating D3 components - Bristech
By Chris Price
Creating D3 components - Bristech
The examples that went along with this deck are available on GitHub - https://github.com/chrisprice/creating-d3-components
- 1,267