block by boeric 8b34abda1d33b983b09b

D3 Key Function Demo

Full Screen

D3 Key Function Demo

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

index.html

<!doctype html>
<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("&nbsp;");

// 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("&nbsp;");

  // 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>