Shirley Wu
(@sxywu)
SVG Paths
SVG Transforms
Selections
Data binding
Enter-append
Scales
Update & Exit
Transitions
Nesting elements
rect
x: x-coordinate of top-left
y: y-coordinate of top-left
width
height
circle
cx: x-coordinate of center
cy: y-coordinate of center
r: radius
text
x: x-coordinate
y: y-coordinate
dx: x-coordinate offset
dy: y-coordinate offset
text-anchor: horizontal text alignment
Hi!
path
d: path to follow
Moveto, Lineto, Curveto, Arcto
Create 3 unique petal shapes
<svg>
<rect />
<rect />
<rect />
<rect />
<rect />
</svg>
<script>
var data = [100, 250, 175, 200, 120];
d3.selectAll('rect')
.data(data)
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', 'blue')
.attr('stroke', '#fff');
</script>
5 rectangle elements
Select all rectangle elements that exist
<svg>
<rect />
<rect />
<rect />
<rect />
<rect />
</svg>
<script>
var data = [100, 250, 175, 200, 120];
d3.selectAll('rect')
.data(data)
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', 'blue')
.attr('stroke', '#fff');
</script>
"Bind" data to the selections
<svg>
<rect />
<rect />
<rect />
<rect />
<rect />
</svg>
<script>
var data = [100, 250, 175, 200, 120];
d3.selectAll('rect')
.data(data)
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', 'blue')
.attr('stroke', '#fff');
</script>
Loop through each rectangle selection
Get passed in (data, index)
<svg>
<rect />
<rect />
<rect />
<rect />
<rect />
</svg>
<script>
var data = [100, 250, 175, 200, 120];
d3.selectAll('rect')
.data(data)
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', 'blue')
.attr('stroke', '#fff');
</script>
Play with the code
(Change some numbers, add/remove a rect, console log all the things)
<svg></svg>
<script>
var rectWidth = 100;
var height = 300;
var data = [100, 250, 175, 200, 120];
var svg = d3.select('svg');
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', 'blue')
.attr('stroke', '#fff');
</script>
Wut, no rectangle elements?!
So what are we even selecting?
A: an empty selection
So how are those bars appearing?
<svg></svg>
<script>
var rectWidth = 100;
var height = 300;
var data = [100, 250, 175, 200, 120];
var svg = d3.select('svg');
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', 'blue')
.attr('stroke', '#fff');
</script>
Magic ✨
<svg></svg>
<script>
var rectWidth = 100;
var height = 300;
var data = [100, 250, 175, 200, 120];
var svg = d3.select('svg');
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('width', rectWidth)
.attr('height', d => d)
.attr('fill', 'blue')
.attr('stroke', '#fff');
</script>
Play with the code
(Try adding/removing some numbers, and make sure to console log all the things)
d3.scaleLinear()
.domain([min, max]) // input
.range([min, max]); // output
scale: mapping from
data attributes (domain)
to display (range)
date → x-value
value → y-value
value → opacity
etc.
// get min/max
var height = 600;
var data = [
{date: new Date('01-01-2015'), temp: 0},
{date: new Date('01-01-2017'), temp: 3}
];
var min = d3.min(data, d => d.date);
var max = d3.max(data, d => d.date);
// or use extent, which gives back [min, max]
var extent = d3.extent(data, d => d.date);
var yScale = d3.scaleLinear()
.domain([min, max])
.range([height, 0]);
// continuous
d3.scaleLinear()
d3.scaleLog()
d3.scaleTime()
// ordinal
d3.scaleBand()
Scales I use often:
Type of petal: parental guidance rating
Size of flower: IMDb rating out of 10
Number of petals: number of IMDb votes
Hint: use SVG transform's translate, rotate, and scale (in that order)
// bars includes update selection
var bars = svg.selectAll('rect')
.data(data, d => d);
// exit
bars.exit().remove();
// enter
var enter = bars.enter().append('rect')
.attr('width', rectWidth)
.attr('stroke', '#fff');
// enter + update
bars = enter.merge(bars)
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('height', d => d)
.attr('fill', d => colors(d));
key function: controls which datum is assigned to which element
// bars includes update selection
var bars = svg.selectAll('rect')
.data(data, d => d);
// exit
bars.exit().remove();
// enter
var enter = bars.enter().append('rect')
.attr('width', rectWidth)
.attr('stroke', '#fff');
// enter + update
bars = enter.merge(bars)
.attr('x', (d, i) => i * rectWidth)
.attr('y', d => height - d)
.attr('height', d => d)
.attr('fill', d => colors(d));
Enter selection: chain attributes that don't depend on data
Combines 2 selections into one
Enter+update selection: chain attrs dependent on data
Exit selection
Play with the code
At every second, draw the next flower.
Emphasize changes in state
(object constancy)
var t = d3.transition()
.duration(1000);
var svg = d3.select('svg');
var bars = svg.selectAll('rect')
.data(data, d => d);
// exit
bars.exit().transition(t)
.attr('y', height)
.attr('height', 0)
.remove();
// enter
var enter = bars.enter().append('rect')
.attr('width', rectWidth)
.attr('stroke', '#fff')
.attr('y', height);
// enter + update
bars = enter.merge(bars)
.attr('x', (d, i) => i * rectWidth)
.attr('fill', d => colors(d))
.transition(t)
.attr('y', d => height - d)
.attr('height', d => d);
Define transition, syncs animation everywhere it's used
Animate height down to 0 before removing
Animate remaining <rect>'s height to their next state
var t = d3.transition()
.duration(1000);
var svg = d3.select('svg');
var bars = svg.selectAll('rect')
.data(data, d => d);
// exit
bars.exit().transition(t)
.attr('y', height)
.attr('height', 0)
.remove();
// enter
var enter = bars.enter().append('rect')
.attr('width', rectWidth)
.attr('stroke', '#fff')
.attr('y', height);
// enter + update
bars = enter.merge(bars)
.attr('x', (d, i) => i * rectWidth)
.attr('fill', d => colors(d))
.transition(t)
.attr('y', d => height - d)
.attr('height', d => d);
go from state A to state B
Everything after .transition(): state B to transition to
Same attributes set before .transition(): state A to transition from
If attributes not specified for A, d3 extrapolates from default
Play with the code
Nest elements when you want to apply the same styles and transforms to a group of elements
<g> element that consist of petals and leaves, has translate and scale applied
<path> element for each petal with rotate applied
const data = [
{fill: 'red', circle: [2, 25, 50]},
{fill: 'blue', circle: [10, 100, 150]},
];
const svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const groups = svg.selectAll('g')
.data(data).enter().append('g')
.attr('fill', d => d.fill)
.attr('transform', 'translate(10,10)');
const circles = groups.selectAll('circle')
.data(d => d.circle).enter().append('circle')
.attr('cx', d => d)
.attr('r', 10);
const data = [
{fill: 'red', circle: [2, 25, 50]},
{fill: 'blue', circle: [10, 100, 150]},
];
const svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const groups = svg.selectAll('g')
.data(data).enter().append('g')
.attr('fill', d => d.fill)
.attr('transform', 'translate(10,10)');
const circles = groups.selectAll('circle')
.data(d => d.circle).enter().append('circle')
.attr('cx', d => d)
.attr('r', 10);
Use <g> element to nest child elements *in SVG, only <g> elements can have children
const data = [
{fill: 'red', circle: [2, 25, 50]},
{fill: 'blue', circle: [10, 100, 150]},
];
const svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const groups = svg.selectAll('g')
.data(data).enter().append('g')
.attr('fill', d => d.fill)
.attr('transform', 'translate(10,10)');
const circles = groups.selectAll('circle')
.data(d => d.circle).enter().append('circle')
.attr('cx', d => d)
.attr('r', 10);
const data = [
{fill: 'red', circle: [2, 25, 50]},
{fill: 'blue', circle: [10, 100, 150]},
];
const svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const groups = svg.selectAll('g')
.data(data).enter().append('g')
.attr('fill', d => d.fill)
.attr('transform', 'translate(10,10)');
const circles = groups.selectAll('circle')
.data(d => d.circle).enter().append('circle')
.attr('cx', d => d)
.attr('r', 10);
const data = [
{fill: 'red', circle: [2, 25, 50]},
{fill: 'blue', circle: [10, 100, 150]},
];
const svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
const groups = svg.selectAll('g')
.data(data).enter().append('g')
.attr('fill', d => d.fill)
.attr('transform', 'translate(10,10)');
const circles = groups.selectAll('circle')
.data(d => d.circle).enter().append('circle')
.attr('cx', d => d)
.attr('r', 10);
Play with the code