CSCI-4830-002-2014 / mailinglist

A special repository to act like a mailing list, for announcement, general discussion, etc. Assignment-specific issues should still go to the individual repository.
2 stars 0 forks source link

Understanding D3 at a deeper level #10

Open wannabeCitizen opened 9 years ago

wannabeCitizen commented 9 years ago

A D3 Code Breakdown

Given what I noticed around class today, I wanted to create a write up to give a better breakdown of D3. First, if you're not familiar with the DOM as a tree, please look to this useful entry in W3C schools that explains the DOM as a tree or a DAG. http://www.w3schools.com/js/js_htmldom.asp

D3 is actually a pretty hard library to master, and without this understanding, it'll seem like complete magic and craziness. To help contextualize how this works in D3, I'm going to analyze some D3 code for you all. I'll use snippets from a pretty complicated piece of D3 that is used to build a calendar view: http://bl.ocks.org/mbostock/4063318

Here's a breakdown of what you'll find in this code that exemplifies a common D3 process.
The process is usually:

Establish variables you'll want to re-use (these can contain callbacks to your data or not).

Here is an example of setting some time/date variables that are constant and then a color variable that has a callback function that is bound to some static data (NOT your main dataset; remember, all data binding is not necessarily to your main dataset):

var day = d3.time.format("%w"),
    week = d3.time.format("%U"),
    percent = d3.format(".1%"),
    format = d3.time.format("%Y-%m-%d");

var color = d3.scale.quantize()
    .domain([-.05, .05])
    .range(d3.range(11).map(function(d) { return "q" + d + "-11"; }));

Set a variable that selects an SVG element on the body.

This will act as your canvas to place your graphical elements on. Notice again that we are binding these elements to some static data - a range from 1990-2011; however, this is not your main dataset:

var svg = d3.select("body").selectAll("svg")
    .data(d3.range(1990, 2011))
  .enter().append("svg")
    .attr("width", width)
    .attr("height", height)

Create the different graphical elements you need (e.g., lines, bubbles, rectangles, etc.).

Notice that you start with your svg variable that is a selector your HTML body that you made above, then within all children of the body, you're further selecting any element with the .day class and binding data to them (they don't actually exist yet, until you call .enter()). Then you use .enter() to move the context of this variable TO the edge of the selected HTML element, which is currently BODY > .day > [you are here]. Then you use .append() to add a new element (in this case 'rect' which is a type of svg element). From there you modify the classes for stylistics:

var rect = svg.selectAll(".day")
    .data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
  .enter().append("rect")
    .attr("class", "day")
    .attr("width", cellSize)
    .attr("height", cellSize)
    .attr("x", function(d) { return week(d) * cellSize; })
    .attr("y", function(d) { return day(d) * cellSize; })

Load your live data and use a callback function to add in all elements of the data that are actually driven by your main data set:

d3.csv("dji.csv", function(error, csv) {
  var data = d3.nest()
    .key(function(d) { return d.Date; })
    .rollup(function(d) { return (d[0].Close - d[0].Open) / d[0].Open; })
    .map(csv);

  rect.filter(function(d) { return d in data; })
      .attr("class", function(d) { return "day " + color(data[d]); })
    .select("title")
      .text(function(d) { return d + ": " + percent(data[d]); });
});

This final step is important because I noticed a lot of people confused by when data actually enters the canvas and why. You should realize that static data can be bound to elements and set up prior to loading your dataset. However, none of the elements you actually bind to data prior to loading your main data (your .csv or .json) will be responsive to this in any way. Once you actually call d3.json() or d3.csv() or whatever, you then bring this data into scope for your SVG elements. Thus, even if you set up a graphical element that you want to be responsive to this data, you should not enter it until that data is being loaded, and, again, you do this using .enter().append() within your callback to your loading function, which actually has the data in scope.

I hope this clarifies some stuff for you all. Keep working though, everyone was doing great stuff tonight.

All the best, Mike