Transition: D3 Introduction to interactive data visualization for the web

时间:2021-03-09 03:57:04

 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();