Skip to content

Instantly share code, notes, and snippets.

@jimkang
Forked from anonymous/index.html
Last active July 6, 2016 13:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jimkang/d93ccb154a244f82f1b5a7bdddf4563f to your computer and use it in GitHub Desktop.
Save jimkang/d93ccb154a244f82f1b5a7bdddf4563f to your computer and use it in GitHub Desktop.
Rendering grouped data.
<!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 &lt;g&gt; and their subobjects correspond to SVG elements within that &lt;g&gt;. (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>
<div>
<button id="group-a-button">Render Group A</button>
<button id="group-b-button">Render Group B</button>
</div>
<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>
body {
font-family: 'Proxima Nova', 'Helvetica Neue', sans-serif;
}
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