http://taurusj.slides.com/taurusj/deck-1-5-9
Slides:
github:
git clone https://github.com/taurusj-k/strangeLoop
2004
2017
Front End Developer
Flash Developer
consulting
The vinzer project
Chart Library
render things for you
for IE8 & below (use sizzle for compatibility)
The refugee project
There are many websites built based on D3
*I use bower to install all packages
<script src="https://d3js.org/d3.v4.min.js"></script>
npm install d3
<script src="https://d3js.org/d3.v4.min.js">
<script src="https://d3js.org/d3-time.v1.min.js"></script>
<script>
var day = d3.timeDay(new Date);
</script>
import { extent } from "d3-array";
Initial visualization decision based on data
State | Population |
---|---|
Vermont | 625741 |
Rhode Island | 1052567 |
New Hampshire | 1316470 |
Massachusetts | 6547629 |
Maine | 1328361 |
Connecticut | 3574097 |
define SVG viewport
define axis
Bind data
SVG rectangles
Scale data
0
6.500.000
5.500.000
4.500.000
3.500.000
2.500.000
1.500.000
500.000
set domain
names of the states
set range
width of the chart
choose data representation
bar chart
Find data
Data
Selections
Data Join
SVG
Scales
Axis
Responsive
Complete chart
Final result is here
Selections
Data Join
SVG
Scale
Axis
Responsive
<svg width="300" height="300">
<rect x="25" y="50" width="50" height="200" />
<rect x="75" y="50" width="50" height="200" />
<rect x="125" y="50" width="50" height="200" />
</svg>
Data can be represented in many ways
as an array
use length of myData to create rectangles
var myData = [25, 75, 125];
or as SVG rectangles that use myData for x
renders like this
Human readable data interchange
JS object
JSON object
var myData = {x: 5, "y!": 5};
var myData = {"x": 5, "y": 5};
vs.
both of them contain a “key: value” pair
key in JSON must be enclosed in double quotes.
in JS object key needs "" only when you use a special character
Why is that so important to keep data in json object?
D3 can read all these formats, as long and you can construct an array
var myData = [{
"name": " Massachusetts",
"population": 6547629
}];
myData.name;
in console:
// Massachusetts
3
1316470
New Hampshire
6547629
Massachusetts
19378102
New York
2
1
myData = [{
"name": " Massachusetts",
"population": 6547629
},
{
"name": "New Hampshire",
"population": 1316470
},
{
"name": "New York",
"population": 19378102
}];
Data Join
SVG
Scale
Axis
Responsive
Data
select();
Selects elements and attaches data
selects only the 1st matching element in the DOM
d3.select("p");
d3.selectAll("p");
selects All matching elements in traversal order
d3.select(".chart").selectAll("p");
Selections are nested.
Very similar to JQuery, D3 uses selector chaining
<div class="chart">
<p>Chart will be here</p>
<p>Soon...</p>
</div>
d3.selectAll("p");//[ [<p>], [<p>] ]
//console
zi {_groups: Array[1], _parents: Array[1]}
group
selection
p
check "How Selections Work"
d3.selectAll("p");
selection returns a group containing the selected element
Selection can use regular JS methods
.size();
.length();
d3.selectAll("circle").attr("r", 25);
element
class/id
attribute
d3.select(".row:first-child");
d3.select("input[type=checkbox]")
.checked(true);
var tooltip = d3.select(".tooltip");
tooltip.on("mouseover", function(d,i) { alert("mouseover"); });
D3 can capture user events
d3.select("body").attr("class", "container").style("color", "red");
d3.attr();
d3.style();
d3.property();
d3.classed();
d3.html();
d3.text();
set or modify attributes, styles, properties, HTML or text content
Chart will be here
soon...
Chart will be here
soon...
before
after
.call();
calls a function on the selection
function showAlert() {
alert("hi");
}
var callSelection = d3.select("body").call(showAlert);
Elements can be added or removed from a selection
.append();
a new element as a last child of the selection
d3.select("body")
.append("div")
.text("added a new div");
.insert();
similar to append, but can be inserted before specified element.
d3.insert("p", "div")
.text("I am before div");
.remove();
once the selection is removed you can't put it back
svg.remove("circle");
SVG
Scale
Axis
Responsive
Data
Selections
<div>25</div>
<div>75</div>
<div>125</div>
25 75 125
myData
var myData = [25,75,125];
can represent 1 datum of the array with a div
var myData = [1,25,75,45,150];
bigger array == more divs
<div>1</div>
<div>25</div>
<div>75</div>
<div>45</div>
<div>150</div>
var myData = [1,25];
smaller array == less divs
<div>1</div>
<div>25</div>
var myData = [1,25,75,45,150];
var myData = [1,25];
Entered 1, 25, 75
var myData = [1,25,75];
Updated 1, 25, 75 and Entered 45, 150
Updated 1, 25 and Removed 75, 45, 150
<div>1</div>
<div>2</div>
<div>3</div>
join specifies array of data with current selection . In d3 d stands for datum and i for the index
.data();
Binds 1 datum with 1 element in the selection
1 2 3
myData
var myData = [1,2,3];
d3.selectAll('div').data(myData);
returns a new update selection, means all data points were successfully bound to the elements
selection.data();
1. Select an element that you want to bind to
var dataJoin = d3.selectAll('div');
var myData = [1,2,3]; dataJoin.data(myData);
2. Attach data to a selected element
var myData = [1,2,3]; d3.selectAll('div').data(myData);
no changes in the DOM, why?
If you run this code in the console:
Data stored in __data__ property.
Elements for which the data previously existed
.data();
Update
Create a new element
.enter();
Enter
Remove an element
.exit();
Exit
Different types of selections based on a state
Once new selection was returned we can call .enter() or .exit()
var bar = d3.selectAll(".bar").data(data);
bar.enter().append("div").attr("class", "bar");
exit selection is determined by the previous selection. Extremely useful for animation. When animation is done -> remove an element.
bar.exit().remove();
d3.selectAll('div') .data(data) .enter() .append("div");
check Mike Bostock's General Update Pattern
select the element
bind the data
create a placeholder
return a new selection containing the element
var data = [10,20,30];
1. Define the data
d3.selectAll(".bar") .data(data) .enter() .append("div") .text(function(d, i){ return d; });
2. Select the element
3. Bind data to the element
4. Create a DOM element
5. Output the data on the screen
var data = [
{name: 'Vermont', value: 625741},
{name: 'Rhode Island', value: 1052567},
{name: 'New Hampshire', value: 1316470},
{name: 'Massachusetts', value: 6547629},
{name: 'Maine', value: 1328361},
{name: 'Connecticut', value: 3574097}
];
d3.select("body")
.data(data)
.enter()
.append("div")
.text(function(d){
return d.name;
});
Scale
Axis
Responsive
Data
Selections
Data Join
<svg xmlns="http://www.w3.org/2000/svg"
width="500" height="40" viewBox="0 0 500 40">
<text x="0" y="35" font-family="Verdana" font-size="35">
Hello, out there
</text>
</svg>
SVG stands for scalable vector graphic, defined in XML file.
Hello, out there
<svg width="300" height="300">
<rect x="50" y="50" width="150" height="150"/>
</svg>
<svg width="300" height="300">
<path d="M0,0 150,0 150,50 0,50" style="fill:red;" />
</svg>
Path is a shape to build all shapes. "d" attribute has "path mini language
renders as
renders as
<svg width="300" height="300">
<rect x="50" width="20" y="150" height="200"></rect>
<rect x="100" width="20" y="150" height="200"></rect>
<rect x="150" width="20" y="150" height="200"></rect>
</svg>
Basic STATIC rectangle in HTML
Renders as
what is you need to update an attribute for each rect?
<g> element used for grouping elements and transforming them as a unit
<svg width="300" height="300">
<rect x="50" width="20" y="150" height="200" fill="red"></rect>
<rect x="100" width="20" y="150" height="200" fill="red"></rect>
</svg>
all children will get the style
<svg width="300" height="300">
<g fill="red">
<rect x="50" width="20" y="150" height="200"></rect>
<rect x="100" width="20" y="150" height="200"></rect>
</g>
</svg>
We can wrap rectangles into <g> element, apply styles to it
D3 can generate SVG basic shapes on the fly
var svg = d3.select("body")
.append("svg")
.attr("width", "300")
.attr("height", "300");
define SVG container
svg.append("rect")
.attr("x", 150)
.attr("width", 50)
.attr("y", 150)
.attr("height", 200)
.attr("fill", "pink");
attach rectangle via D3
Renders as
var svg = d3.select("body")
.append("svg")
.attr("width", "300")
.attr("height", "300");
svg.append("g").attr("fill", "red")
.append("circle")
.attr("cx", "50")
.attr("cy", "50")
.attr("r", "25");
Use D3 to create elements and apply styles on the fly
Renders as
D3 and SVG coordinate space
var svg = d3.select("body")
.append("svg")
.attr("width", "300")
.attr("height", "300");
var circle = svg.append("circle")
.attr("cy", 5)
.attr("cx", 5)
.attr("r", 25);
SVG generated via D3 starts drawing from the top left corner
Basic shape is visible Only within SVG dimensions
y
x
Circle starts drawing x:5 y:5 of SVG viewport
0
100
100
50
0
100
100
50
svg.append("g")
.attr("transform", "translate(50,50)")
.append("circle")
.attr("cx", "50")
.attr("cy", "50")
.attr("r", "25");
translate moves svg by 50 pixels
apply "transform", "translate(50,50)"
to move a circle 50px down and 50 px to the right
define a new coordinate system by applying transform property
50
50
0
600
500
SVG viewport
0
600
500
new drawing area
SVG + margin
0
apply margin conversion to SVG viewport to avoid cutting elements
var margin = {top: 20, right: 20, bottom: 30, left: 60},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Define margin, width and height
apply attribute transform to <g> element to give a space to render svg elements
data = [{"x": 5, "y": 5},{"x": 50, "y": 50},{"x": 100, "y": 100}];
var svg = d3.select("body")
.append("svg")
.attr("width", "300")
.attr("height", "300");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("rx", 5)
.attr("ry", 5)
.attr("x", function(d) { return d.x; })
.attr("width", 20)
.attr("y", function(d) { return d.y; })
.attr("height", 50);
D3 can generate SVG based on data
Axis
Responsive
Data
Selections
Data Join
SVG
think of maps 100 km: 1 point
maps a dimension of abstract data to a visual representation
DOMAIN is what we are mapping From :100km
RANGE is what we are mapping TO: 1 point
var data = [625741, 1052567, 1316470];
range
domain
0
5
10
domain:[625741, 1052567, 1316470];
range:[0,5,10];
how to find min and max of the data points?
return array of min and max
.extent();
.max();
returns max number
var data = [1, 2, 3, 5, 20, 57, 1];
d3.extent(data); //[1, 57]
var data = [1, 2, 3, 5, 20, 57, 1];
d3.max(data); //[57]
Continuous (Linear, Power, Log, Identity, Time)
Ordinal (Band, Point, Category)
map a continuous, quantitative input domain to a continuous output range.
discrete input domains, such as names or categories.
Other (Sequential Quantize Quantile Threshold)
var data = [
{name: 'Rhode Island', value: 1052567},
{name: 'Maine', value: 1328361}
];
for continuous numerical input
1052567 1328361
range
domain
0
500
height of SVG
var y = d3.scaleLinear()
.range([500, 0]);
for continuous numerical domain
with the unit domain [0, 1], the unit range [0, 1],
d3.scaleLinear();
can be any number
.domain();
output range is continuous and numeric
.range();
Range: use fixed numbers or define the range based on screen size
var data = [
{name: 'Rhode Island', value: 1052567},
{name: 'Maine', value: 1328361}
];
var y = d3.scaleLinear()
.range([500, 0]);
y.domain([0, d3.max(data, function(d)
{ return d.value; })]);
//console
y(600000);
285.7142857142857
Domain: from 0 to the maximum value of data.value
it's an ordinal scale that takes discrete input such as names, numeric output
Rhode Island
Maine
var data = [
{name: 'Rhode Island', value: 1052567},
{name: 'Maine', value: 1328361}
];
1
2
output
constructs a scale with an empty domain and range
d3.scaleBand();
takes discrete values such as names of the states.
.domain();
output range is continuous and numeric
.range();
d3.scaleBand()
.domain(["Rhode Island", "Maine"])
.range([0, 100]);
it's an ordinal scale that takes discrete input such as names, numeric output
output separated into equal "bands" with some padding in between
for blank space between bands
.padding(0.02);
Rhode Island
Maine
d3.scaleBand()
.domain(["Rhode Island", "Maine"])
.range([0, 100]);
Band
Band
how to set up domain and range from the data?
var data = [
{name: 'Rhode Island', value: 1052567},
{name: 'Maine', value: 1328361}
];
var x = d3.scaleBand()
.range([0, width])
.padding(0.02);
x.domain(data.map(function(d) { return d.name; }));
Range: use fixed numbers or define the range based on screen size
Domain: names of the state
Constructs a new ordinal scale with a range categorical colors
.schemeCategory10
.schemeCategory20
.schemeCategory20b
.schemeCategory20c
1. Category scales use d3-scale-chromatic.module
2. Use set of colors provided by Color Brewer
D3 ordinal scale with a range of categorical colors
var color = d3.scaleOrdinal(d3.schemeCategory10);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("fill", function(d,i) { return color(i % data.length); });
Responsive
Data
Selections
Data Join
SVG
Scale
Earlier I claimed that D3 is not good for rendering things for you...
Well, D3 can provide axis for you!
1. Set up a container for axis (SVG viewport)
2. Define a scale with a range & domain.
3. Append axis to svg
var margin = {top: 20, right: 20, bottom: 30, left: 60},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
1. Set up a container for axis (SVG viewport)
var x = d3.scaleBand().range([0, width]).padding(0.02);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
2. Use scale to transform the data for axis
svg.append("g")
//add classes for styling
.attr("class", "x_axis")
//move xAxis: x: 0 y:from the top down to the height
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
//add classes for styling
.attr("class", "y_axis")
//we don't want to shift up or down
.call(yAxis);
3. Call axis when you create SVG
Axis component has some useful methods:
.axisTop();
D3 reserves the right to override the number of each tick if the number won't come out clean after scaling
.axisBottom();
.axisLeft();
.axisRight();
.axis.ticks();
Data
Selection
Data Join
SVG
Scales
Axis
most visualizations are built for large screens
the refugee project or mayoral race campaign
Option one: Make SVG responsive
preserveAspectRatio="xMinYMin meet" viewBox="0 0 800 960"
Option two: SVG re-draw depends on the screen size
//get dimensions based on window size
updateDimensions(window.innerWidth);
function updateDimensions(winWidth) {
width = winWidth - margin.left - margin.right;
height = .5 * width; //aspect ratio is 0.5
}
Render the chart on resize
1. Create a render function that re-calculates height and width based on screen size
function render() {
/* Define a new width and height */
...
}
window.addEventListener('resize', Chart.render);
2. Add an event listener to re-render the chart when event is fired
DON'T use fixed height
Use aspect ratio instead of px
height = .4 * width; //aspect ratio is 0.4
use breakpoints to modify visualization
var breakPoint = 768;
// in render function
if(width < breakPoint) {
...
} else {
...
}
Final result is here
Load data from the server using Python
Try D3 layouts
Hierarchy
Force
Map
Mike Bostock's website
Mike Bostock's blocks
Building Responsive Data Visualizations with D3.js
DashingD3js Screencasts
D3.js tutorials
Iuliia Amodeo Konivetska
taurusj.k2@gmail.com
@taurusJ_K