Created
July 6, 2016 13:13
-
-
Save anonymous/3af4827f9d99d7248c6708253d462834 to your computer and use it in GitHub Desktop.
Rendering grouped data // source http://jsbin.com/qiluce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>Rendering grouped data</title> | |
<style id="jsbin-css"> | |
body { | |
font-family: 'Proxima Nova', 'Helvetica Neue', sans-serif; | |
} | |
</style> | |
</head> | |
<body> | |
<p> | |
This is an example of rendering grouped data in which the outer objects in the array correspond to an SVG <g> and their subobjects correspond to SVG elements within that <g>. (I've worked out how to do this in the past, only to forget it again.) | |
</p> | |
<p> | |
The key techniques here are A) selecting subobjects using the outer group selection and B) joining the data to those subobjects by providing a <strong>function</strong> to <code>.data()</code> that gets the subobject data from a given group datum. That second data join will yield a flat selection for all of the subobjects, with that you can update them all in one go. | |
</p> | |
<button id="group-a-button">Render Group A</button> | |
<button id="group-b-button">Render Group B</button> | |
<svg id="board" width="640" height="640"></svg> | |
</svg> | |
<script src="https://d3js.org/d3-color.v1.min.js"></script> | |
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script> | |
<script src="https://d3js.org/d3-ease.v1.min.js"></script> | |
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script> | |
<script src="https://d3js.org/d3-selection.v1.min.js"></script> | |
<script src="https://d3js.org/d3-timer.v1.min.js"></script> | |
<script src="https://d3js.org/d3-transition.v1.min.js"></script> | |
<script id="jsbin-javascript"> | |
var transition = d3.transition(); | |
var t = d3.transition() | |
.duration(500); | |
// From https://www.npmjs.com/package/accessor | |
function accessor(prop) { | |
var property = 'id'; | |
if (prop && typeof prop === 'string') { | |
property = prop; | |
} | |
return function accessProperty(d) { | |
return d[property]; | |
}; | |
} | |
var groupSequenceA = [ | |
{ | |
id: 'group1', | |
transform: 'translate(200, 0)', | |
subobjects: [ | |
{ | |
id: 'objA', | |
cx: 100, | |
cy: 100, | |
r: 50, | |
fill: 'red' | |
}, | |
{ | |
id: 'objB', | |
cx: 200, | |
cy: 200, | |
r: 50, | |
fill: 'yellow' | |
}, | |
{ | |
id: 'objC', | |
cx: 150, | |
cy: 150, | |
r: 70, | |
fill: 'orange' | |
}, | |
{ | |
id: 'objZ', | |
cx: 150, | |
cy: 300, | |
r: 70, | |
fill: 'gray' | |
} | |
] | |
}, | |
{ | |
id: 'group2', | |
transform: 'translate(0, 100)', | |
subobjects: [ | |
{ | |
id: 'objD', | |
cx: 50, | |
cy: 300, | |
r: 50, | |
fill: 'green' | |
}, | |
{ | |
id: 'objE', | |
cx: 250, | |
cy: 250, | |
r: 50, | |
fill: 'purple' | |
}, | |
{ | |
id: 'objF', | |
cx: 155, | |
cy: 250, | |
r: 70, | |
fill: 'blue' | |
} | |
] | |
} | |
]; | |
var groupSequenceB = [ | |
{ | |
id: 'group1', | |
transform: 'translate(100, 50)', | |
subobjects: [ | |
{ | |
id: 'objA', | |
cx: 100, | |
cy: 100, | |
r: 80, | |
fill: 'hsl(0, 100%, 40%)' | |
}, | |
{ | |
id: 'objB', | |
cx: 200, | |
cy: 200, | |
r: 80, | |
fill: 'hsl(60, 100%, 40%)' | |
}, | |
{ | |
id: 'objC', | |
cx: 100, | |
cy: 150, | |
r: 100, | |
fill: 'hsl(30, 100%, 40%)' | |
} | |
] | |
}, | |
{ | |
id: 'group2', | |
transform: 'translate(100, 50)', | |
subobjects: [ | |
{ | |
id: 'objD', | |
cx: 50, | |
cy: 300, | |
r: 80, | |
fill: 'hsl(120, 100%, 15%)' | |
}, | |
{ | |
id: 'objE', | |
cx: 250, | |
cy: 250, | |
r: 80, | |
fill: 'hsl(300, 100%, 15%)' | |
}, | |
{ | |
id: 'objF', | |
cx: 155, | |
cy: 250, | |
r: 100, | |
fill: 'hsl(240, 100%, 30%)' | |
} | |
] | |
}, | |
{ | |
id: 'group3', | |
transform: 'translate(0, 0)', | |
subobjects: [ | |
{ | |
id: 'objㄱ', | |
cx: 50, | |
cy: 50, | |
r: 30, | |
fill: 'hsl(20, 50%, 15%)' | |
}, | |
{ | |
id: 'objㄴ', | |
cx: 80, | |
cy: 80, | |
r: 40, | |
fill: 'hsl(20, 50%, 75%)' | |
} | |
] | |
} | |
]; | |
function renderGroupSequence(seq) { | |
var board = d3.select('#board'); | |
var groups = board.selectAll('.group').data(seq, accessor()); | |
groups.exit().remove(); | |
var newGroups = groups.enter().append('g') | |
.classed('group', true) | |
.attr('id', accessor()); | |
var groupsToUpdate = newGroups.merge(groups); | |
groupsToUpdate | |
.transition(t) | |
.attr('transform', accessor('transform')); | |
// For each parent, select all children. | |
var subs = groupsToUpdate.selectAll('.subobject') | |
.data(accessor('subobjects'), accessor()); | |
subs.exit().remove(); | |
var newSubs = subs.enter().append('circle') | |
.classed('subobject', true) | |
.attr('id', accessor()); | |
var subsToUpdate = newSubs.merge(subs); | |
subsToUpdate | |
.transition(t) | |
.attr('cx', accessor('cx')) | |
.attr('cy', accessor('cy')) | |
.attr('r', accessor('r')) | |
.attr('fill', accessor('fill')); | |
} | |
function renderA() { | |
renderGroupSequence(groupSequenceA); | |
} | |
function renderB() { | |
renderGroupSequence(groupSequenceB); | |
} | |
((function go() { | |
d3.select('#group-a-button') | |
.on('click', function () { renderGroupSequence(groupSequenceA); }); | |
d3.select('#group-b-button') | |
.on('click', function () { renderGroupSequence(groupSequenceB); }); | |
// Auto-switch between frames at 10 fps. | |
// setInterval(renderNext, 1000/10); | |
// var frame = 0; | |
// function renderNext() { | |
// if (frame % 2 === 0) { | |
// setTimeout(renderB, 0); | |
// } | |
// else { | |
// setTimeout(renderA, 0); | |
// } | |
// frame += 1; | |
// } | |
})()); | |
</script> | |
<script id="jsbin-source-javascript" type="text/javascript">var transition = d3.transition(); | |
var t = d3.transition() | |
.duration(500); | |
// From https://www.npmjs.com/package/accessor | |
function accessor(prop) { | |
var property = 'id'; | |
if (prop && typeof prop === 'string') { | |
property = prop; | |
} | |
return function accessProperty(d) { | |
return d[property]; | |
}; | |
} | |
var groupSequenceA = [ | |
{ | |
id: 'group1', | |
transform: 'translate(200, 0)', | |
subobjects: [ | |
{ | |
id: 'objA', | |
cx: 100, | |
cy: 100, | |
r: 50, | |
fill: 'red' | |
}, | |
{ | |
id: 'objB', | |
cx: 200, | |
cy: 200, | |
r: 50, | |
fill: 'yellow' | |
}, | |
{ | |
id: 'objC', | |
cx: 150, | |
cy: 150, | |
r: 70, | |
fill: 'orange' | |
}, | |
{ | |
id: 'objZ', | |
cx: 150, | |
cy: 300, | |
r: 70, | |
fill: 'gray' | |
} | |
] | |
}, | |
{ | |
id: 'group2', | |
transform: 'translate(0, 100)', | |
subobjects: [ | |
{ | |
id: 'objD', | |
cx: 50, | |
cy: 300, | |
r: 50, | |
fill: 'green' | |
}, | |
{ | |
id: 'objE', | |
cx: 250, | |
cy: 250, | |
r: 50, | |
fill: 'purple' | |
}, | |
{ | |
id: 'objF', | |
cx: 155, | |
cy: 250, | |
r: 70, | |
fill: 'blue' | |
} | |
] | |
} | |
]; | |
var groupSequenceB = [ | |
{ | |
id: 'group1', | |
transform: 'translate(100, 50)', | |
subobjects: [ | |
{ | |
id: 'objA', | |
cx: 100, | |
cy: 100, | |
r: 80, | |
fill: 'hsl(0, 100%, 40%)' | |
}, | |
{ | |
id: 'objB', | |
cx: 200, | |
cy: 200, | |
r: 80, | |
fill: 'hsl(60, 100%, 40%)' | |
}, | |
{ | |
id: 'objC', | |
cx: 100, | |
cy: 150, | |
r: 100, | |
fill: 'hsl(30, 100%, 40%)' | |
} | |
] | |
}, | |
{ | |
id: 'group2', | |
transform: 'translate(100, 50)', | |
subobjects: [ | |
{ | |
id: 'objD', | |
cx: 50, | |
cy: 300, | |
r: 80, | |
fill: 'hsl(120, 100%, 15%)' | |
}, | |
{ | |
id: 'objE', | |
cx: 250, | |
cy: 250, | |
r: 80, | |
fill: 'hsl(300, 100%, 15%)' | |
}, | |
{ | |
id: 'objF', | |
cx: 155, | |
cy: 250, | |
r: 100, | |
fill: 'hsl(240, 100%, 30%)' | |
} | |
] | |
}, | |
{ | |
id: 'group3', | |
transform: 'translate(0, 0)', | |
subobjects: [ | |
{ | |
id: 'objㄱ', | |
cx: 50, | |
cy: 50, | |
r: 30, | |
fill: 'hsl(20, 50%, 15%)' | |
}, | |
{ | |
id: 'objㄴ', | |
cx: 80, | |
cy: 80, | |
r: 40, | |
fill: 'hsl(20, 50%, 75%)' | |
} | |
] | |
} | |
]; | |
function renderGroupSequence(seq) { | |
var board = d3.select('#board'); | |
var groups = board.selectAll('.group').data(seq, accessor()); | |
groups.exit().remove(); | |
var newGroups = groups.enter().append('g') | |
.classed('group', true) | |
.attr('id', accessor()); | |
var groupsToUpdate = newGroups.merge(groups); | |
groupsToUpdate | |
.transition(t) | |
.attr('transform', accessor('transform')); | |
// For each parent, select all children. | |
var subs = groupsToUpdate.selectAll('.subobject') | |
.data(accessor('subobjects'), accessor()); | |
subs.exit().remove(); | |
var newSubs = subs.enter().append('circle') | |
.classed('subobject', true) | |
.attr('id', accessor()); | |
var subsToUpdate = newSubs.merge(subs); | |
subsToUpdate | |
.transition(t) | |
.attr('cx', accessor('cx')) | |
.attr('cy', accessor('cy')) | |
.attr('r', accessor('r')) | |
.attr('fill', accessor('fill')); | |
} | |
function renderA() { | |
renderGroupSequence(groupSequenceA); | |
} | |
function renderB() { | |
renderGroupSequence(groupSequenceB); | |
} | |
((function go() { | |
d3.select('#group-a-button') | |
.on('click', function () { renderGroupSequence(groupSequenceA); }); | |
d3.select('#group-b-button') | |
.on('click', function () { renderGroupSequence(groupSequenceB); }); | |
// Auto-switch between frames at 10 fps. | |
// setInterval(renderNext, 1000/10); | |
// var frame = 0; | |
// function renderNext() { | |
// if (frame % 2 === 0) { | |
// setTimeout(renderB, 0); | |
// } | |
// else { | |
// setTimeout(renderA, 0); | |
// } | |
// frame += 1; | |
// } | |
})());</script></body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
font-family: 'Proxima Nova', 'Helvetica Neue', sans-serif; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var transition = d3.transition(); | |
var t = d3.transition() | |
.duration(500); | |
// From https://www.npmjs.com/package/accessor | |
function accessor(prop) { | |
var property = 'id'; | |
if (prop && typeof prop === 'string') { | |
property = prop; | |
} | |
return function accessProperty(d) { | |
return d[property]; | |
}; | |
} | |
var groupSequenceA = [ | |
{ | |
id: 'group1', | |
transform: 'translate(200, 0)', | |
subobjects: [ | |
{ | |
id: 'objA', | |
cx: 100, | |
cy: 100, | |
r: 50, | |
fill: 'red' | |
}, | |
{ | |
id: 'objB', | |
cx: 200, | |
cy: 200, | |
r: 50, | |
fill: 'yellow' | |
}, | |
{ | |
id: 'objC', | |
cx: 150, | |
cy: 150, | |
r: 70, | |
fill: 'orange' | |
}, | |
{ | |
id: 'objZ', | |
cx: 150, | |
cy: 300, | |
r: 70, | |
fill: 'gray' | |
} | |
] | |
}, | |
{ | |
id: 'group2', | |
transform: 'translate(0, 100)', | |
subobjects: [ | |
{ | |
id: 'objD', | |
cx: 50, | |
cy: 300, | |
r: 50, | |
fill: 'green' | |
}, | |
{ | |
id: 'objE', | |
cx: 250, | |
cy: 250, | |
r: 50, | |
fill: 'purple' | |
}, | |
{ | |
id: 'objF', | |
cx: 155, | |
cy: 250, | |
r: 70, | |
fill: 'blue' | |
} | |
] | |
} | |
]; | |
var groupSequenceB = [ | |
{ | |
id: 'group1', | |
transform: 'translate(100, 50)', | |
subobjects: [ | |
{ | |
id: 'objA', | |
cx: 100, | |
cy: 100, | |
r: 80, | |
fill: 'hsl(0, 100%, 40%)' | |
}, | |
{ | |
id: 'objB', | |
cx: 200, | |
cy: 200, | |
r: 80, | |
fill: 'hsl(60, 100%, 40%)' | |
}, | |
{ | |
id: 'objC', | |
cx: 100, | |
cy: 150, | |
r: 100, | |
fill: 'hsl(30, 100%, 40%)' | |
} | |
] | |
}, | |
{ | |
id: 'group2', | |
transform: 'translate(100, 50)', | |
subobjects: [ | |
{ | |
id: 'objD', | |
cx: 50, | |
cy: 300, | |
r: 80, | |
fill: 'hsl(120, 100%, 15%)' | |
}, | |
{ | |
id: 'objE', | |
cx: 250, | |
cy: 250, | |
r: 80, | |
fill: 'hsl(300, 100%, 15%)' | |
}, | |
{ | |
id: 'objF', | |
cx: 155, | |
cy: 250, | |
r: 100, | |
fill: 'hsl(240, 100%, 30%)' | |
} | |
] | |
}, | |
{ | |
id: 'group3', | |
transform: 'translate(0, 0)', | |
subobjects: [ | |
{ | |
id: 'objㄱ', | |
cx: 50, | |
cy: 50, | |
r: 30, | |
fill: 'hsl(20, 50%, 15%)' | |
}, | |
{ | |
id: 'objㄴ', | |
cx: 80, | |
cy: 80, | |
r: 40, | |
fill: 'hsl(20, 50%, 75%)' | |
} | |
] | |
} | |
]; | |
function renderGroupSequence(seq) { | |
var board = d3.select('#board'); | |
var groups = board.selectAll('.group').data(seq, accessor()); | |
groups.exit().remove(); | |
var newGroups = groups.enter().append('g') | |
.classed('group', true) | |
.attr('id', accessor()); | |
var groupsToUpdate = newGroups.merge(groups); | |
groupsToUpdate | |
.transition(t) | |
.attr('transform', accessor('transform')); | |
// For each parent, select all children. | |
var subs = groupsToUpdate.selectAll('.subobject') | |
.data(accessor('subobjects'), accessor()); | |
subs.exit().remove(); | |
var newSubs = subs.enter().append('circle') | |
.classed('subobject', true) | |
.attr('id', accessor()); | |
var subsToUpdate = newSubs.merge(subs); | |
subsToUpdate | |
.transition(t) | |
.attr('cx', accessor('cx')) | |
.attr('cy', accessor('cy')) | |
.attr('r', accessor('r')) | |
.attr('fill', accessor('fill')); | |
} | |
function renderA() { | |
renderGroupSequence(groupSequenceA); | |
} | |
function renderB() { | |
renderGroupSequence(groupSequenceB); | |
} | |
((function go() { | |
d3.select('#group-a-button') | |
.on('click', function () { renderGroupSequence(groupSequenceA); }); | |
d3.select('#group-b-button') | |
.on('click', function () { renderGroupSequence(groupSequenceB); }); | |
// Auto-switch between frames at 10 fps. | |
// setInterval(renderNext, 1000/10); | |
// var frame = 0; | |
// function renderNext() { | |
// if (frame % 2 === 0) { | |
// setTimeout(renderB, 0); | |
// } | |
// else { | |
// setTimeout(renderA, 0); | |
// } | |
// frame += 1; | |
// } | |
})()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment