d3.js

data-driven documents

an introduction to

ivan loughman-pawelko

 - A Javascript library for manipulating the DOM based on data

- Declarative and (usually) readable syntax

- Data is bound to DOM elements and used to control them

- Exceptional control over how the DOM transitions when the data changes

Data Driven Documents

<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>

d3 is not...

- good with old browsers and doesn't try to be

-  a web map api

-  the solution to uninformative data

(in some ways it may make it worse)

d3.selectAll('div')
    .text("Little we see in Nature that is ours")
    .style("color","magenta")
    .classed("thatClass", true)
    .on("click", function(){
        // do something
        console.log("now what?")
    });
var div = document.querySelector('div')
div.textContent = "Little we see in Nature that is ours";
div.style.color = "magenta";
div.classList.add("thatClass");
div.addEventListener('click', function(){
    alert("now what?")
});

Selections

d3

just Javascript

var poem = ["Once in a golden hour", 
    "I cast to earth a seed.", 
    "Up there came a flower,", 
    "The people said, a weed."
];

d3.selectAll('.line')
   .data(poem)
   .text(function(d,i){
       return "Line (" + i + "): " + d;
   })
   .style("color", function(d,i){
       if(d.indexOf("flower") > -1){     
           return "magenta";
       }
   });   
<h2 class="line">Line</h2>
<h2 class="line">Line</h2>
<h2 class="line">Line</h2>
<h2 class="line">Line</h2>

Data Binding

var poem = ["Once in a golden hour", 
    "I cast to earth a seed.", 
    "Up there came a flower,", 
    "The people said, a weed."
];

d3.selectAll('.line')
var poem = ["Once in a golden hour", 
    "I cast to earth a seed.", 
    "Up there came a flower,", 
    "The people said, a weed."
];

d3.selectAll('.line')
   .data(poem) 

Appending to the DOM

var width = 600;
var height = 300;

var svg = d3.select(".graphic")
            .append("svg")
            .attr("width", width)
            .attr("height", height);
<body>
    <div class="graphic"></div>
</body>

SVG

 - specifications for creating two dimensional vector graphics 

<div class="graphic">
    <svg>
        <rect></rect>
        <circle></circle>
        <elipse></elipse>
        <path></path>
        <g></g>
    </svg>
</div>
var dataset = [{letter:"A", value:1},
            {letter:"B", value:3},
            {letter:"C", value:3},
            {letter:"D", value:2},
            ...
            {letter:"Y", value:4},
            {letter:"Z", value:10}]
svg.selectAll('rect')
    .data(dataset)
    .enter().append('rect')
    .attr('class','bar')
svg.selectAll("rect")
    .data(dataset)
    .enter().append("rect")
    .attr("class","bar")
    .attr("x", "20px")
    .attr("y", function(d,i) {
        return yScale(d.letter);
    })
    .attr("width", function(d,i){
        return xScale(d.value);
    })  
    .attr("height", "10px")
    .attr("fill", function(d) {
        return "rgb(0,"+(d.value*30)+
                ", " + (d.value * 50) + ")";
    });

Appending with Data

var yScale = d3.scale.ordinal()
                .domain(dataset.map(function(d){return d.letter}))
                .rangeRoundBands([0, height], 0.2)

Scales

\{A, B, C, ... , Z\} \to \{0, 23, 46, ..., 574, 600\}
{A,B,C,...,Z}{0,23,46,...,574,600}\{A, B, C, ... , Z\} \to \{0, 23, 46, ..., 574, 600\}
var xScale = d3.scale.linear()
                .domain([0, d3.max(dataset,function(d){return d.value})])
                .range([0,width])
\text{width} = 60*\text{d.letter}
width=60d.letter\text{width} = 60*\text{d.letter}

Linear

Ordinal

Transitions

- selections where the operators apply smoothly over time

- attributes and styles are interpolated between values

- easing functions, durations, and delays control interpolation

    var circle = svg.append('circle')
        .attr('cx', 20)
        .attr('cy', 20)
        .attr('r', 10)

    circle.on('click', function(){
        d3.select(this)
        .transition()
        .duration(3000)
        .delay(1000)
        .ease('linear')
        .attr('cx', 150)
        .attr('cy', 150)
        .attr('r', 20)
        .style('fill', 'red')
    })



    var x = 4;

Selections and Filtering

var subset = dataset.filter(
    function(datum){return vowels.indexOf(datum.letter) > -1}
);
// update subset
selection.transition()
    .duration(2000)
    .attr('width', function(d,i){
        return xScale(d.value);
    })
    .attr('y', function(d,i){
        return yScale(d.letter);
    });
// remove unwanted elements
selection.exit()
    .transition()
    .duration(2000)
    .attr('width', '0px')
    .remove();
// selection from subset
var selection = svg.selectAll('rect')
    .data(subset, function(d){
        return d.letter;
    });




selection.transition();
yScale.domain(
    dataset.sort(function(a,b){return b.value-a.value})
    .map(function(d){return d.letter})
)
svg.selectAll('rect')
    .transition()
    .duration(2000)
    .delay(function(d,i){
        return i*100
    })
    .attr('y', function(d,i){
        return yScale(d.letter)
    })

Sorting

d3 + angular

angular.module('d3Angular').directive('barChart', function(){
	return {
		restrict: 'E',
		template: '<svg></svg>',
		scope: {data: '=chartData'},
		link: function(scope,elem,attr){
                    // d3 stuff here    
		}
	}
});
angular.module('d3Angular', [])
<!DOCTYPE html>
<html>
    <head>
        <script src="angular.min.js"></script>
        <script src="d3.min.js"></script>
        <script src="app.js"></script>
    </head>
    <body ng-app="d3Angular">
      <div ng-controller='data'>
        <bar-chart chart-data="chartData"></bar-chart>
      </div>
    </body>
</html>

d3 directives

//d3 stuff here
// define svg + scales
...

// create chart
svg.selectAll("rect")
    .data(scope.data)
    .enter()
    .append("rect")
    .attr('class','bar')
    .attr("x", function(d, i) {
        return xScale(i);
    })
    .attr("y", function(d) {
        return height - yScale(d);
    })
    .attr("width", xScale.rangeBand())
    .attr("height", function(d) {
        return yScale(d);
    })
    .attr("fill", function(d) {
        return "rgb(" + (d * 10) + ",0,0)";
    });
angular.module('d3Angular').controller('data', function($scope) {
    $scope.chartData = [5,9,7,12,2,6];
});
<!DOCTYPE html>
<html>
    <head>
        <script src="/bower_components/angular/angular.min.js"></script>
        <script src="d3.min.js"></script>
        <script src="app.js"></script>
    </head>
    <body ng-app="d3Angular">
      <div ng-controller='data'>
        <bar-chart chart-data="chartData"></bar-chart>
        <input type="text" ng-model="newChartData">
        <button ng-click="update()">Draw Chart</button>
      </div>
    </body>
</html>
angular.module('d3Angular').controller('data', function($scope) {
	$scope.chartData = [0]; // this could be avoided but I'm lazy
	$scope.update = function(){
		$scope.chartData = $scope.newChartData.split(',')
                                         .map(function(e){return +e})
	}
});

updating

var drawChart = function(){
	xScale.domain(d3.range(scope.data.length));
	yScale.domain([0,d3.max(scope.data)]);
        // update existing data
	var bars = svg.selectAll('rect')
	    .data(scope.data);
        // remove old data
	bars.exit().remove();
        // append new data
	bars.enter().append('rect')
	        .attr("x", width)
	        .attr("y", function(d) {
	            return height - yScale(d); 
	        })
	        .attr("width", xScale.rangeBand()) 
	        .attr("height", function(d) {
	            return yScale(d); 
	        })
	        .attr("fill", function(d) {
	            return "rgb(" + (d * 10) + ",0,0)";
	        });
        // define transition
        // ...
}
scope.$watch('data', function(newValue,oldValue){
	scope.data = newValue;
	drawChart()
})

thanks

deck

By Ivan Loughman-Pawelko