D3.js – Data-Driven Documents – Web Standards



D3.js – Data-Driven Documents – Web Standards

1 1


sfhtml5-d3-talk

Introduction to D3.js for SFHTML5- 2015 http://www.meetup.com/sfhtml5/events/219966838/

On Github Yonet / sfhtml5-d3-talk

D3.js

Data-Driven Documents

Slides : bit.ly/sfhtml5-d3

Aysegul Yonet / @AysegulYonet

AnnieCannons.com

What is D3?

D3 allows you to bind data to the DOM, and then apply data-driven transformations to the document. Manupulates attributes of DOM elements with the attributes of data items.

What d3 is not?

Google Charts Example

D3 Scatterplot Example

Not a prototyping tool. For ex: Google charts has chart.draw(data, options). With d3, you have to define how the chart is rendered with the use of d3 methods.

Examples

D3 is very Flexible, It allows you to not just create basic charts but create anything you like with the use of svg. Open Sourced, Great community.

  • SVG
  • Selections
  • Update Pattern
  • Scales
  • Loading External Data
  • Working with Data

I will talk to you about...

Web Standards

  • HTML
  • CSS
  • SVG

SVG

  • Another HTML element, except you can not put another HTML element inside an SVG.
  • Does not support common HTML attributes or styles such as position, left, top, right, bottom or float.
  • Default svg size is browser-dependant.
d3 works with html and css and shines through with the use of SVGs. What is an SVG? SVG default size is implemented differently by various browsers. Make sure to define the Width and Height when you are creating an SVG.

Circle

<circle cx="250" cy="100" r="30" fill="#25B0B0">
</circle>
Let's take a look at circle. Notice we are calculating an area of a circle each time we draw a circle using these attributes. That means we have to be aware of the performance issues.

Rectangle

<rect x="200" y="50" width="100" height="100" rx="10" ry="0" fill="#25B0B0">
</rect>
Interesting fact: if a properly specified value is provided for rx but not for ry (or the opposite), then the browser will consider the missing value equal to the defined one.

Group

<g transform="translate(100, 100)">
	<circle cx="20" cy="10" r="30" fill="rgb(255, 0, 0)">
	</circle>
	<circle cx="100" cy="10" r="30" fill="#25B0B0">
	</circle>
</g>

jsFiddle

Any transformations (positioning, scaling, rotating) that are applied to the group element will also be applied to each of the child elements.

Line

<line x1="10" y1="10" x2="100" y2="100" stroke="blue" stroke-width="100">
</line>

Path

<path d="
	M50,100
	L131,66
	L259,115
	L339,50
	L400,98
	M350,150
	L100,150" fill="none" stroke="#25B0B0">
</path>
  • M - move.
  • L - line.
  • z - close path.

Text

<svg>
	<text x="100" y="100" fill="red" font-family="arial" font-size="16">
		Look at me, I am a text.
	</text>
</svg>
Unless you set the style of the SVG text, it inherits font-styles from CSS.

Selection

jQuery

var paragraphs = $("div p");

D3

var paragraphs = d3.select("div").selectAll("p");
jsFiddle D3 uses css selectors to define a selection. The selection can be based on tag, class, identifier, attribute, or place in the hierarchy. Child nodes might not exist at the time of our selection. One thing to note, D3 selector object only contains the elements that matched the selection rule when the selection was first created.
var p = d3.select("body").append('p');
p.html('Hello SFHTML5!').attr('style', 'color: red;');

jsFiddle

Once we have a selection, we can operate on our nodes.

Data

d3.selectAll("p")
	.data([0, 1, 1, 2, 3, 5, 8, 13, 21, 34])

JSFiddle

Most importantly we can bind data to our selections. When data is bound to a selection, each element in the data array is paired with the corresponding DOM node in the selection. We can use the data to define the attributes on that node

Enter()

d3.select("body").selectAll("p")
	.data([3, 7, 21, 31, 35, 42])
	.enter();
If there are fewer nodes than data, the extra data elements form the enter selection, which you can instantiate by appending to the enter selection.

Append()

d3.select("body").selectAll("p")
	.data([3, 7, 21, 31, 35, 42])
	.enter().append("p")
	.text(function(d) { return "I’m number " + d + "!"; });

D3 methods returns the selection they act upon, and we can apply multiple operations to the same selection. Here we are able to add text to our newly appended selection.

JSFiddle

Our new paraghraph's text is created using the data bound data points.

Exit() and Remove()

var bars = d3.select('body').selectAll('p').data(newData);

//if we have more data, add new 'p' for those data items
bars.enter().append('p')

//if we have less data points, remove the 'p' that no longer have a data pairing. 
bars.exit().remove();
What if we have less data points? Using D3’s exit and remove, you can get rid of outgoing nodes that are no longer needed. Like the enter method, exit selects the extra nodes that doesn't have data matching and remove deletes the nodes from our DOM.

Update Pattern

// Update…
var p = d3.select("body").selectAll("p")
	.data([3, 7, 21, 31, 35, 42]);

// Enter…
p.enter().append("p")

// Exit…
p.exit().remove();

JSFiddle

This is what we call the update pattern. If you forget about the enter and exit selections, you will automatically select only the elements for which there is corresponding data. By handling these three cases separately, you specify precisely which operations run on which nodes.

Chaining

d3.select("body").selectAll("p")
	.data([3, 7, 21, 31, 35, 42])
	.enter().append("p")
	.exit().remove();

jsFiddle

If you have been to the d3's website, I am sure you came accross the chained methods. Every chained method operates on the previous selection, and most operations return the same selection. Some methods return a new one! For example, selection.append returns a new selection containing the new elements. This conveniently allows you to chain operations into the new elements. Since method chaining can only be used to descend into the document hierarchy, use var to keep references to selections and go back up.

Operating on selection

  • Setting attributes or styles
  • Registering event listeners
  • Adding, removing or sorting nodes
  • Changing HTML or text content

JSFiddle

You can operate over the nodes in a declarative way using selector methods.

Transition

circles.attr("r", "0")
	.attr({
			'cx': function(d) {return (d * 2); },
			'cy': 10
	})
	.transition()
	.duration(750)
	.delay(function(d, i) { return i * 100; })
	.attr("r", 10);

JSFiddle

Transition will happen between what you have assigned before and after the transion is called.

Scales

var scale = d3.scale.linear();

Examples we saw so far calculates the position of our SVG elements by using data. D3 comes with scale methods to make these mappings easy. Scales transform a dataset in a certain interval (called the domain) into a number in another interval (called the range). For ex, transforms a domain in miles to a range in pixels.

Quantitative Scale

  • Linear scales
  • Logarithmic scales
  • Power scales
var xScale = d3.scale.linear()
		.domain([0, d3.max(data)])// your data minimum and maximum
		.range([0, 420]);//the pixels to map to
d3.select(".chart")
	.selectAll("div")
		.data([3, 7, 21, 31, 35, 42])
	.enter().append("div")
		.style("width", function(d) { return xScale(d) + "px"; })
JSFiddle Scale func creates a new instance of a scale. On our new scale, we can create new properties that persists.
var xScale = d3.scale.linear()
			.domain([0, 1000])
			.range([0, 200]);
console.log(xScale(500)); //100
console.log(xScale.domain());//[0, 1000]
Domain and range is a setter and getter. If you don't pass any arguments it is returning the values.

Ordinal Scale

Ordinal Scale have a discrete domain, such as a set of names or categories.

var xScale = d3.scale.ordinal()
	.domain(["Bob", "Stuart", "Kevin", "Scarlet"])
	.rangePoints([0, 100]);
console.log(xScale("Stuart"));//33.333333333333336

xScale.range(); // [0, 33.333333333333336, 66.66666666666667, 100]

JSFiddle

You need to have the same number of arguments. If you have more in range, that is OK. RangePoints subdivides everything in between the values you give to it.

Color Categories

d3.scale.category10()

JSFiddle

Constructs a new ordinal scale with a range of ten categorical colors:

Scatterplot

var svg = d3.select('body').append('svg')
	.attr({
				'width': 250,
				'height': 250
		});

var xScale = d3.scale.linear()
	.domain([0, 5])
	.range([0, 200]);

var render = function(data, r, color){
	//Bind data
	var circles = svg.selectAll('circle').data(data);
	
	//Enter and update
	circles.enter().append('circle')
		.attr({
			'cx': function(d){return xScale(d)},
				'cy': 30,
				'r': r,
				'fill': function(){return color || 'red'} 
		});
}
var oldData = [1, 2, 3];
var newData = [1, 2, 3, 4, 5];
render(oldData, 10);
render(newData, 15, 'blue');

How many people think the circles all should be blue?

jsFiddle

var render = function(data, r, color){
		//Bind Data
		var circles = svg.selectAll('circle').data(data);
		//Enter
		circles.enter().append('circle')
		
		//Update
		circles.attr({
				'cx': function(d){return xScale(d)},
					'cy': 30,
					'r': r,
					'fill': function(){return color || 'red'} 
			})
}
I am using a function to return a default value if the color is not defined.

jsFiddle

var oldData = [1, 2, 3, 4, 5];
var newData = [1, 2, 3];
render(oldData, 10);
render(newData, 15, 'blue');

jsFiddle

var render = function(data, r, color){
		//Bind Data
		var circles = svg.selectAll('circle').data(data);

		//Enter
		circles.enter().append('circle')
		
		//Update
		circles.attr({
				'cx': function(d){return xScale(d)},
					'cy': 30,
					'r': r,
					'fill': function(){return color || 'red'} 
			})

		//Remove
		circles.exit().remove();
}

jsFiddle

Recap

  • Bind Data.
  • Enter and Append.
  • Update the attributes.
  • Exit and Remove.

Loading External Resources

  • d3.json(url[, callback])
  • d3.csv(url[, accessor][, callback])
  • d3.tsv(url[, accessor][, callback])

CSV

day,donut
Monday,91
Tuesday,23
Wednesday,7
Thursday,4
Friday,82
Saturday,39
Sunday,27

var x = "day";
var y = "donut";

var accessor = function(d) {
	d[y] = parseInt(d[y]); 
	return d;
}

d3.csv("data/data.csv", accessor, function(d) {
	console.log(d);//Array of objects [{day: "Monday", donut: 91},...]
	render(d);
});

jsFiddle

Working with Arrays

  • d3.min/d3.max
  • d3.reduce
  • d3.keys - lists the keys of an associative array.
  • d3.merge - merges multiple arrays into one array.
  • d3.nest - groups array elements hierarchically.
  • d3.quantile(numbers, p)
  • d3.deviation(array[, accessor]) - returns the standard deviation

jsFiddle

Reduce

[0, 1, 2, 3].reduce(function(a, b) {
	return a - b;
});//-6

[0, 1, 2, 3].reduceRight(function(a, b) {
	return a - b;
});//0

left-to-right

d3.nest

var donutData = [
	{day: 'Monday', donut: 34, variety: "plain"},
	{day: 'Tuesday', donut: 41, variety: "glazed"}
];

var nestedData = d3.nest()
	.key(function(d){ return d.day;})
	.entries(donutData);

[{
	key: "Monday",
	values: [
		{day: 'Monday', donut: 34, variety: "plain"}
	]},
	{
	key: "Tuesday",
	values: [
		{day: 'Tuesday', donut: 41, variety: "glazed"}
	]}
]

var nestedData = d3.nest()
	.key(function(d){ return d.day;})
	.key(function(d){ return d.variety;})
	.entries(donutData);

[{
		key: "Monday",
		values: [
			{ key: "plain", values: [{day: 'Monday', donut: 34, variety: "plain"}]}
		]},
	{
		key:"Tuesday",
		values: [
			{ key: "glazed", values: [{day: 'Tuesday', donut: 41, variety: "glazed"}]}
		]
	}
]

d3.format

d3.time.format("%Y-%m-%d"); //2015-08-26
d3.format("+,%"); //+2,400%
JSFiddle d3.time.format - creates a new local time formatter for a given specifier.

d3.time.scale - constructs a linear time scale.

var xScale = d3.time.scale()
 .domain([2009-07-13T00:02, 2009-07-13T23:48])
 .rangeRound([0, width]);
 //rangeRound does the same thing as range but rounds the values to integers or begining of dates.
JSFiddle

d3.time.intervals - a time interval in local time.

  • d3.time.hour
  • d3.time.week
  • d3.time.monday
  • d3.time.year

Good News!

THE END

BY Aysegul Yonet / aysegul@anniecannons.com

Slides : bit.ly/sfhtml5-d3