Dynamic Data
1. Ordinal Scales
var xScale = d3.scale.ordinal() // used for ordinal data, typically categories with some inherent order to them
.domain(d3.range(dataset.length()) //.domain([0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
domain() sets the domain of our new ordinal scale to those values.
.rangeRoundBands([0,w],0.05); // 1) it will caculate each band's width: (w-0)/xScale.domain().length;
2) the second parameter specify a bit of spacing between each band. 0.05 means that 5% percent of the width of each band will be used for spacing in between bands.
3) use ROUND, it will smooth the width of bar to integer
2. Reference the ordinal Scale
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x",function(d,i){return xScale(i);})
.attr(“y",function(d,i){return yScale(d);})
.attr("height",function(d){return yScale(d);})
.attr("width",xScale.rangeBand());
3. Interaction via event listeners
d3.select("p").on("click",function(){ //do something on click});
scenario 1: the number of values stay the same
1. Changing the Data
step1: update dataset //dataset = new_dataset;
step2: rebind new values to the existing elements //svg.selectAll("rect").data(dataset);
2. Updating the bars
step3: update visual attributes, referencing the (now-updated) data values. In this case, we only need to update y values.
svg.selectAll("rect").data(dataset).attr("y",function(d){return yScale(d);{).attr("height",fuction(d) {return yScale(d);}).attr("fill",function (d) {return "rgb(0, 0, " + (d * 10) + ")";
3. updating labels
step4:
svg.selectAll("text").data(dataset).attr("y", function(d){return yScale(d)+10;}).attr("text",function(d) {return d;})
4. Transitions
.transition()
//Update all rects
svg.selectAll("rect")
.data(dataset)
.transition() // <-- This is new! Everything else here is unchanged.
.attr("y", function(d) {
return h - yScale(d);
})
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
without transition, D3 evaluate every attr() statement immediately, so the changes in height and fill happen right away.
when you add transition, D3 introduces the element of time.
D3 interpolates between the old values and the new values, meaning it normalizes the beginning and ending values, and calculates all their in-between states.
5. Duration()
the default time is 250ms
duration must be specified after transition(), and it is always specified in ms.
svg.selectAll("text")
.data(dataset)
.transition() // <-- This is new,
.duration(5000) // and so is this.
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
6. ease(): control the quality of motion for transition
the default easing is "cubic-in-out"
ease("linear")
ease("circle")
ease("elastic")
ease("bounce")
ease comes after .transition(), before attr.()//before changing attributes. (usually, it will comes after duration())
delay(): specifies when the transition begins
.transition()
.delay(1000) //1,000 ms or 1 second //delay happens first, followed by transition itself
.duration(2000) //2,000 ms or 2 seconds
delay is useful when apply to generate staggered delays, which means some elements transition happens before others. in order to do this, we need to give it a function, to produce different values for each element.
.transition()
.delay(function(d, i) {
return i * 100;
})
.duration(500)....// the total running time is max(i)*100+500
we need to scale our delay values dynamically to the length of the data.
.delay(function(d,i)
{ return i/data.length *1000;} //so the maximum delay time is 1s, and the maximum transition time is 1.5s
7. Randomizing the data
svg.select("p").on("click",function(){
//create new values
var numValues=dataset.length;
dataset=[ ];
for (var i=0;i<numValues;i++){
var newNumber = Math.floor(Math.random()*25);
dataset.push(newNumber);
}
//update scales
yScale .domain([0,d3.max(dataset)]);
//update bars
svg.selectAll("rect")
.dataset(dataset)
.transition()
.delay(function(d,i) {return i/dataset.length * 10000;})
.duration(500)
.attr("y",function(d) {return yScale(d);})
.attr("height",function {return yScale(d);})
......
};
8. updating axes
svg.select(".x.axis").transition().duration().call(xAxis);
// each axis is already referencing a scale(either xScale or yScale), because those scales are being updated, the axis generators can calculate what thenew tick mark should be.
1. Select the axis.
2. Initiate a transition.
3. Set the transition’s duration.
4. Call the appropriate axis generator.
9. each() Transition starts and ends
start or end of the transition;
start used only for immediate transformations, no transition. But end can used for transition
each("start or end",function(){...})
//Update all circles
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.each("start", function() { // <-- Executes at start of transition
d3.select(this)
.attr("fill", "magenta")
.attr("r", 3);
})
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.each("end", function() { // <-- Executes at end of transition
d3.select(this)
.transition()
.duration(1000) // this means the current element.
.attr("fill", "black")
.attr("r", 2);
});
Schedule multiple transitions to run one after the other: we simply chain transitions together
svg.selectAll("circle")
.data(dataset)
.transition() // <-- Transition #1
.duration(1000)
.each("start", function() {
d3.select(this)
.attr("fill", "magenta")
.attr("r", 7);
})
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.transition() // <-- Transition #2
.duration(1000)
.attr("fill", "black")
.attr("r", 2);
Clip Path: this will limit elements reference to it acting within it.
steps to define cilp path
1. Define the clippath and give it an ID
2. Put visual elements within the clippath(usually just a rect, but this could be circles or any other visual elements)
3. Add a reference to the clipPath from whatever elements you wish to be masked.
#define clipPath
svg.append("cilpPath")
.attr("id","chart-area")
.append("rect")
.attr("x",padding")
attr("y",padding")
attr("width",w-padding*3)
attr("height",h-padding*2)
#add reference to the clipPath
svg.append("g")
.attr("class","circles")
.attr("clip-path","url(#chart-area")
.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
......
Adding values
//event happens
d3.select("p").on("click",function(d){
//add a new varibale by randomness
var maxvalue=25;
var newNumber = Math.floor(Math.random() * maxvalue);
dataset.push(newNumber);
// update xSclae
xScale.domain([d3.range(dataset.length));
yScale.domain([0,d3.max(dataset)]);
//update axis
xaxis.scale(xScale);
yaxis.scale(yScale);
//update selection
var bars = d3.selectAll("rect")
.dataset(dataset); //now the update selection is stored in bars
// enter
bars.enter().append("rect")
.attr("x", w) //first set x of the new bar to be far away from the sece, then we can use a smooth transition to move it into view.
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
//update; Now we made the new rect, now all that's left is to update all rect's visual attributes
bars.transition()
.duration(500)
.attr("x",function(d,i){return xScale(i);})
.attr("y",function(d){return yScale(d);}
.attr("width",xScale.rangeBand())
.attr("height",function(d) {return yScale(d)};);
Remove value
d3.select("p").on("click",function(){
//remove the first element form the array
dataset.shift();
//update bars
bars.exit()
.transition()
.duration(500)
.attr("x",w)
.remove(); // make a smooth remove, rather than sudden remove
});
Data joins with keys
the default join is by index order, meaning the first data value is bound to the first DOM element in the selection
the second value is second element.....
key function: control data join with more specificity and ensure that the right datum is bound to the right rect element
var dataset = [ { key: 0, value: 5 },
{ key: 1, value: 10 },
{ key: 2, value: 13 },
{ key: 3, value: 19 },
{ key: 4, value: 21 },
{ key: 5, value: 25 },
{ key: 6, value: 22 },
{ key: 7, value: 18 },
{ key: 8, value: 15 },
{ key: 9, value: 13 },
{ key: 10, value: 11 },
{ key: 11, value: 12 },
{ key: 12, value: 15 },
{ key: 13, value: 20 },
{ key: 14, value: 18 },
{ key: 15, value: 17 },
{ key: 16, value: 16 },
{ key: 17, value: 18 },
{ key: 18, value: 23 },
{ key: 19, value: 25 } ];
key function: var key = function(d){return d.value;};
now we replace .data(dataset) with .data(dataset,key) //now key is the function to get key value)
Add and Remove:
<p id="add">Add a new data value</p>
<p id="remove">Remove a data value</p>
//Decide what to do next and update dataset
d3.selectAll("p").on("click",function(){
var pId= d3.select(this).attr("id);
if (pId == "add") {
var maxValue = 25;
var newNumber=Math.floor(Math.random() * maxValue;
var lastKeyValue = dataset[dataset.length-1].key;
dataset.push({key: lastKeyValue+1,
value: newNumber});
}else{ dataset=dataset.shift();
}
});
//Updata scales
xScale.domain(d3.range(dataset.length));
yScale.domain([0,d3.max(dataset, function(d){reuturn d.value;}]);
//Update axis
xAxis.scale(xScale);
yAxis.scale(yScale);
//Select
var bars = d3.selectAll("rect").data(dataset, key);
//enter..
bars.enter().append("rect")
.attr("x",w);
.attr('y",function(d){
return h-yScale(d.value);
}
.attr("width",xScale.rangeBand())
.attr("height",yScale(d.value))
.attr("fill",function(d){
return "rgb(0, 0, " + (d.value * 10) + ")";
});
//update.
bars.transition().duration(500).
.attr("x",funciton(d,i){
return xScale(i);
}
.attr("y",function(d){return h-yScale(d);}
.attr("width",xScale.rangeBand())
.attr("height",function(d){return yScale(d.value);})
});
//exit
bars.exit().transition().duration(500).attr("x",-xScale.rangeBand()).remove();