The script demonstrates the use of a key function when binding data to a D3 selection. The user will cycle through a sequence of steps that will mutate a data structure. For each step,
A div on the right shows the current content of the data structure. A div at the bottom shows the source code that is about to be executed (using a bit of code introspection).
For bl.ocks.org users, the script should be viewed in its own window. See the script in action here
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 Key Function Demo</title>
<!-- Author: Bo Ericsson, bo@boe.net -->
<link rel=stylesheet type=text/css href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap.min.css" media="all">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<style>
body {
margin: 10px;
}
h4 {
margin-bottom: 20px;
}
pre {
font-size: 10px;
line-height: 11px;
}
label {
margin-top: 10px;
margin-bottom: 10px;
}
</style>
<body>
<script>
'use strict';
var func;
var keyFunc = null;
var data;
var idCounter = 0;
// get reference to container
var body = d3.select("body").append("div")
.attr("id", "container");
body.append("h4").text("D3 Key Function Demo")
// create UI elements
var ctrlDiv = body.append("div")
.style("position", "relative");
// add checkbox key function
var useKeyFunction = false;
ctrlDiv.append("label")
.attr("class", "checkbox")
.text("Use key function")
.append("input")
.attr("type", "checkbox")
.property("checked", useKeyFunction)
.on("click", function() {
useKeyFunction = d3.select(this).property("checked")
})
// add checkbox for selection ordering
var reOrderSelection = false;
ctrlDiv.append("label")
.attr("class", "checkbox")
.text("Reorder DOM elements to match selection")
.append("input")
.attr("type", "checkbox")
.property("checked", reOrderSelection)
.on("click", function() {
reOrderSelection = d3.select(this).property("checked")
if (reOrderSelection && data != undefined) d3.selectAll(".testItem")
.data(data, keyFunc)
.order();
})
// add button for code execution
var btn = ctrlDiv.append("button")
.attr("class", "btn btn-small")
.style("width", "300px")
.text("Step 0: Create data")
.on("click", function() {
this.blur();
if (func != undefined) func();
})
// add comment field
var comment = ctrlDiv.append("label")
.html(" ");
// add elem to hold current data structure
var currData = ctrlDiv.append("pre")
.style("position", "absolute")
.style("width", "200px")
.style("top", "30px")
.style("left", "410px")
.style("display", "none")
.text("")
// add elem to hold code to be executed
var code = ctrlDiv.append("pre")
.style("position", "absolute")
.style("width", "600px")
.style("top", "570px")
.style("left", "10px")
.style("height", "200px")
.style("overflow", "scroll")
.text("")
// create div to hold the dynamically created labels
var div = body.append("div")
.style("margin-top", "30px")
// introspect this code
var source = d3.select("body").select("script").node().innerText;
code.text(source);
var lines = source.split("\n");
var lineNumbers = [];
lines.forEach(function(d, i) {
if (d.indexOf("function step") == 0 || d.indexOf("function start") == 0)
lineNumbers.push({ func: d.substring(0, 14), lineNumber: i})
})
function scrollCode(codeSegment) {
var found;
lineNumbers.some(function(d) {
if (d.func.indexOf("function " + codeSegment) == 0) {
found = d;
return true;
}
})
var lineNumber = found.lineNumber
code.node().scrollTop = lineNumber * 11;
}
// master data
var initialData = [
{ item: 0, name: "zero", value: 0, initial: true },
{ item: 1, name: "one", value: 1, initial: true },
{ item: 2, name: "two", value: 2, initial: true },
{ item: 3, name: "three", value: 3, initial: true },
{ item: 4, name: "four", value: 4, initial: true }
];
// called from each step
function processData(data) {
keyFunc = null;
if (useKeyFunction) keyFunc = function(d, i) { return d.item }
var updateSel = div.selectAll(".testItem")
.data(data, keyFunc)
// process exit selection
updateSel.exit().remove();
// process enter selection; add items
updateSel.enter().append("label")
.attr("id", function(d) { return idCounter++} )
.attr("class", "testItem")
.style("color", function(d) {
if (d.initial) return "red";
else return "black";
})
.style("display", "block");
// process update election (which now includes the enter selection)
updateSel.text(function(d, i) { return d.name + " (" + d.value + ")"; });
// un-comment this to see the order of items in the selection
updateSel[0].forEach(function(d) {
//console.log(d3.select(d).attr("id"));
})
// optionally reorder DOM elements to match selection order
// only relevant if our key function is used
if (reOrderSelection) updateSel.order();
}
// set initial function pointer, and code scroll position
func = step0;
scrollCode("step0");
function step0() {
// clear DOM
d3.selectAll(".testItem").remove();
comment.html(" ");
// clone data and reset counter
data = JSON.parse(JSON.stringify(initialData, null, 1));
//console.log('data.bla', data.bla())
idCounter = 0;
// update UI
currData.text(JSON.stringify(data, null, 1));
currData.style("display", "block");
// next
btn.text("Step 1: Load DOM...");
scrollCode("step1");
func = step1;
}
function step1() {
// inital data bind
processData(data);
// update UI
currData.text(JSON.stringify(data, null, 1));
comment.text(data.length + " items were added...");
// next
btn.text("Step 2: Remove 2nd item...");
scrollCode("step2");
func = step2;
}
function step2() {
// remove the 2nd item
data.splice(1, 1);
processData(data);
// update UI
currData.text(JSON.stringify(data, null, 1));
comment.text("The 2nd item was removed...");
// next
btn.text("Step 3: Move last item to 2nd position");
scrollCode("step3");
func = step3;
}
function step3() {
// move last item to 2nd position
var lastItem = data.pop();
lastItem.name = lastItem.name + "-modified";
lastItem.value = lastItem.value * 10;
data.splice(1, 0, lastItem);
processData(data);
// update UI
currData.text(JSON.stringify(data, null, 1));
comment.text("Was the new item moved to 2nd position?");
// next
btn.text("Step 4: Add three items to end of array...");
scrollCode("step4");
func = step4;
}
function step4() {
// add items
var initialLength = data.length;
data.push({ item: 4, name: "four", value: 400 });
data.push({ item: 5, name: "five", value: 500 });
data.push({ item: 6, name: "six", value: 600 });
var dataLength = data.length;
var itemsAdded = dataLength - initialLength;
processData(data);
// update UI
currData.text(JSON.stringify(data, null, 1));
var domLength = d3.selectAll(".testItem")[0].length;
var text = (domLength == dataLength) ?
"All " + itemsAdded + " new items were added to DOM" :
"Only " + (domLength - dataLength + itemsAdded) + " items were added to DOM!";
comment.text(text);
// next
btn.text("Step 5: Add one item at the beginning of array...");
scrollCode("step5");
func = step5;
}
function step5() {
data.unshift({ item: 7, name: "seven", value: 7 });
processData(data);
// update UI
currData.text(JSON.stringify(data, null, 1));
comment.text("Pay attention where the new item was added to the DOM");
// next
btn.text("Repeat Step 0: Create data")
scrollCode("step0");
func = step0;
}
</script>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js