update, transition
- 當資料會隨著時間變化,就需要動態更新這些資料,視覺處理以 transition 動畫展現。
d3.select("p")
.on("click", function() {
//New values for dataset 更新資料
dataset = [ 11, 12, 15, 20, 18, 17, 16, 18, 23, 25,
5, 10, 13, 19, 21, 25, 22, 18, 15, 13 ];
//Update all rects 更新矩形的大小
svg.selectAll("rect")
.data(dataset)
.transition() // <-- 加上這一個 method 就會有更新過程的動畫
.duration(5000) // 設定動畫更新時間 5s
.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) + ")";
});
//Update all labels 更新 label
svg.selectAll("text")
.data(dataset)
.transition() // <-- 加上這一個 method 就會有更新過程的動畫
.duration(5000) // 設定動畫更新時間 5s
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
});
- 延遲時間
dealy() 設定固定的時間,延遲幾毫秒後開始進行動畫,也可以用匿名函數動態設定延遲時間
ease() 設定動畫改變的加速模型,有 linear, circle, elastic, bounce...
// v3 的寫法
.ease("linear")
//v4
.ease(d3.easeLinear)
.ease(d3.easeCircle)
.ease(d3.easeBounce)
在更新矩形或是 label 時,加上 ease 就可以了
//Update all rects
svg.selectAll("rect")
.data(dataset)
.transition()
.duration(2000)
.ease(d3.easeBounce)
.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) + ")";
});
.delay(1000) // 固定延遲時間
.delay(function(d, i) {
return i * 100; // 後面的動畫開始時間比前一個晚 100ms
})
.duration(500) // 總時間縮短,避免動畫時間過長
// 這種方式,可以確保 dataset 不管有多少個,動畫時間都是合理的長度
.delay(function(d, i) {
return i / dataset.length * 1000; // 先將 i/data.length 做 normalized,然後再放大 1000 倍
})
.duration(500)
- 套用亂數產生的 dataset
<p>Click on this text to update the chart with new data values as many times as you like!</p>
<script type="text/javascript">
//Width and height
var w = 600;
var h = 250;
var barPadding = 1;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, w], 0.05);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, h]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth()-barPadding)
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
//On click, update with new data
d3.select("p")
.on("click", function() {
//New values for dataset
var numValues = dataset.length;
var maxValue = 100; //Highest possible new value
dataset = [];
for (var i = 0; i < numValues; i++) {
var newNumber = Math.floor(Math.random() * maxValue); //New random integer (0-100)
dataset.push(newNumber);
}
// 因為 dataset 的資料範圍改變了, 要重新計算 yScale 的 domain
//Recalibrate the scale domain, given the new max value in dataset
yScale.domain([0, d3.max(dataset)]);
//Update all rects
svg.selectAll("rect")
.data(dataset)
.transition()
.delay(function(d, i) {
return i / dataset.length * 1000;
})
.duration(500)
.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) + ")";
});
//Update all labels
svg.selectAll("text")
.data(dataset)
.transition()
.delay(function(d, i) {
return i / dataset.length * 1000;
})
.duration(500)
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
});
</script>
- 換成 二維 dataset
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
</style>
<p>Click on this text to update the chart with new data values as many times as you like!</p>
<script type="text/javascript">
//Width and height
var w = 500;
var h = 300;
var padding = 30;
//Dynamic, random dataset
var dataset = [];
var numDataPoints = 50;
var maxRange = Math.random() * 1000;
for (var i = 0; i < numDataPoints; i++) {
var newNumber1 = Math.floor(Math.random() * maxRange);
var newNumber2 = Math.floor(Math.random() * maxRange);
dataset.push([newNumber1, newNumber2]);
}
//Create scale functions
var xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
//Define X axis
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
//Define Y axis
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 2);
//Create X axis,加上 css class
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
//On click, update with new data
d3.select("p")
.on("click", function() {
//New values for dataset
var numValues = dataset.length;
var maxRange = Math.random() * 1000;
dataset = [];
for (var i = 0; i < numValues; i++) {
var newNumber1 = Math.floor(Math.random() * maxRange);
var newNumber2 = Math.floor(Math.random() * maxRange);
dataset.push([newNumber1, newNumber2]);
}
//Update scale domains
xScale.domain([0, d3.max(dataset, function(d) { return d[0]; })]);
yScale.domain([0, d3.max(dataset, function(d) { return d[1]; })]);
//Update all circles
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
});
//Update X axis
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
//Update Y axis
svg.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
});
</script>
- transition 開始與結束的 callback function
// v3
.each("start", function() {
d3.select(this)
.attr("fill", "magenta")
.attr("r", 3);
})
// v4
.on("start", function() {
d3.select(this)
.attr("fill", "magenta")
.attr("r", 3);
})
//Update all circles
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
// 開始時執行
.on("start", function() {
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]);
})
// 結束後執行
.on("end", function() {
d3.select(this)
.attr("fill", "black")
.attr("r", 2);
});
在 start 時,不能再加上其他的 transition,因為 D3 限制任何元素同一時間,只能有一個 transition,新的 transition 會覆蓋舊的,這跟 jQuery 的設計不同,jQuery 會把動畫效果排隊依序執行。
// 錯誤的用法
.each("start", function() {
d3.select(this)
.transition()
.duration(250)
.attr("fill", "magenta")
.attr("r", 3);
})
// end 可以加上另一個 transition
.on("end", function() {
d3.select(this)
.transition()
.duration(1000)
.attr("fill", "black")
.attr("r", 2);
});
也可以在 svg 中 transition + on + transition + on 的方式,進行連續的動畫。
//Update all circles
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.on("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()
.duration(1000)
.attr("fill", "black")
.attr("r", 2);
- clipPath
剛剛的轉場動畫中,因為將散點圖的圓形放大,因此在接近軸線的地方,圓形會超過軸線,超過中間的圖形區塊。
可以利用 D3 的 clipPath 製造一塊遮罩板片,遮住超過該區塊的圖形。
//Define clipping path 產生 clip path,id 為 chart-area,矩形區塊
svg.append("clipPath")
.attr("id", "chart-area")
.append("rect")
.attr("x", padding)
.attr("y", padding)
.attr("width", w - padding * 3)
.attr("height", h - padding * 2);
//Create circles 產生的散點放在 chart-area 這個 clipPath 裡面
svg.append("g")
.attr("id", "circles")
.attr("clip-path", "url(#chart-area)")
.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 2);
- 新增資料到舊的 dataset
先調整 dataset 將,新資料放進去,接下來產生新的 rect 及 text,並將初始位置訂在畫面的右邊,一開始就看不到新的 rect, text。
然後再將全部的 rect, text 以 transition 移動到新的位置。
<p id="add">Add a new data value</p>
<p id="remove">Remove a data value</p>
<script type="text/javascript">
//Width and height
var w = 600;
var h = 250;
var barPadding = 1;
// dataset 改為 key, value pair
var dataset = [ { key: 0, value: 5 }, //dataset is now an array of objects.
{ key: 1, value: 10 }, //Each object has a 'key' and a 'value'.
{ 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 } ];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, w], 0.05);
// d3.max 改為 使用 dataset 的 value
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d.value; })])
.range([0, h]);
//定義用來 bind data 的 key function
var key = function(d) {
return d.key;
};
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset, key)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.value);
})
.attr("width", xScale.bandwidth()-barPadding)
.attr("height", function(d) {
return yScale(d.value);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d.value * 10) + ")";
});
//Create labels
svg.selectAll("text")
.data(dataset, key)
.enter()
.append("text")
.text(function(d) {
return d.value;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d.value) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
//On click, update with new data
d3.selectAll("p")
.on("click", function() {
// 判斷點擊了 add / remove
var paragraphID = d3.select(this).attr("id");
//Decide what to do next
if (paragraphID == "add") {
//Add a data value
var maxValue = 25;
var newNumber = Math.floor(Math.random() * maxValue+1);
// 新的 key 以 dataset 最後一個元素的 key +1 來設定
var lastKeyValue = dataset[dataset.length - 1].key;
console.log(lastKeyValue);
dataset.push({
key: lastKeyValue + 1,
value: newNumber
});
} else {
//Remove a value, 移除 dataset 最前面那個元素
dataset.shift();
}
//Update scale domains
xScale.domain(d3.range(dataset.length));
yScale.domain([0, d3.max(dataset, function(d) { return d.value; })]);
//Select…
var bars = svg.selectAll("rect")
.data(dataset, key);
// exit 會回傳被移除的元素
bars.exit().remove();
// bars.exit()
// .transition()
// .duration(500)
// .attr("x", -xScale.bandwidth())
// .remove();
//Enter…
var newbars = bars.enter()
.append("rect")
.attr("x", w)
.attr("y", function(d) {
return h - yScale(d.value);
})
.attr("width", xScale.bandwidth()-barPadding)
.attr("height", function(d) {
return yScale(d.value);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d.value * 10) + ")";
});
//Update…
// bars.transition()
svg.selectAll("rect").transition()
.duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.value);
})
.attr("width", xScale.bandwidth()-barPadding)
.attr("height", function(d) {
return yScale(d.value);
});
//Update all labels
//Select…
var labels = svg.selectAll("text")
.data(dataset, key);
labels.exit().remove();
//Enter…
labels.enter()
.append("text")
.text(function(d) {
return d.value;
})
.attr("text-anchor", "middle")
.attr("x", w)
.attr("y", function(d) {
return h - yScale(d.value) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
//Update…
// labels.transition()
svg.selectAll("text").transition()
.duration(500)
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
});
});
</script>
互動式圖表
- event listener
以 on method 綁定 evnet listener,在 callback function 中調整畫面。
// 以 on 綁定 click 事件
.on("click", function(d) {
console.log(d);
})
css mouse hover
// 加上 css,讓滑鼠 hover 時,改變顏色
<style type="text/css">
rect:hover {
fill: orange;
}
</style>
mouseover, mouseout
// 以 on 綁定 mouseover 事件
.on("mouseover", function() {
// this 就是目前這個操作的元素
d3.select(this)
.attr("fill", "orange");
})
// 以 on 綁定 mouseout 事件
.on("mouseout", function(d) {
d3.select(this)
.attr("fill", "rgb(0, 0, " + (d * 10) + ")");
})
在 mouseout 以 transition 方式改回原本的顏色,讓畫面更流暢
// 以 on 綁定 mouseover 事件
.on("mouseover", function() {
d3.select(this)
.attr("fill", "orange");
})
// 以 on 綁定 mouseout 事件, 以 transition 方式改回原本的顏色,讓畫面更流暢
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(250)
.attr("fill", "rgb(0, 0, " + (d * 10) + ")");
})
完整的範例
<script type="text/javascript">
//Width and height
var w = 600;
var h = 250;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, w], 0.05);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, h]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
// 以 on 綁定 mouseover 事件
.on("mouseover", function() {
d3.select(this)
.attr("fill", "orange");
})
// 以 on 綁定 mouseout 事件, 以 transition 方式改回原本的顏色,讓畫面更流暢
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(250)
.attr("fill", "rgb(0, 0, " + (d * 10) + ")");
});
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
// 加上這個 css style,讓滑鼠移動到 label 時,不會變成鍵盤輸入的游標
.style("pointer-events", "none")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
</script>
- sorting
可以在 on action listener 中,呼叫 sortBars function,可針對矩形及 label 進行排序。
但上面最後一個例子中,綁定了 mouseover 及 mouseout 進行 hover 顏色變化的處理,如果在 sorting 時,移動了滑鼠,因為 D3 預設會覆蓋動畫,這會造成 mouseover 及 mouseout 的元素,會停留在滑鼠指到的地方的問題。
要解決這個問題,必須將 hover 的顏色處理,回歸讓 css 來調整。
//Define sort function
var sortBars = function() {
svg.selectAll("rect")
.sort(function(a, b) {
return d3.ascending(a, b);
})
.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
});
svg.selectAll("text")
.sort(function(a, b) {
return d3.ascending(a, b);
})
.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
});
};
以下為實例,排序會在順序及倒序兩個一直變換。
<style type="text/css">
rect:hover {
fill: orange;
}
</style>
<script type="text/javascript">
//Width and height
var w = 600;
var h = 250;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0, w], 0.05);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, h]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
.on("click", function() {
sortBars();
});
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
//Define sort order flag
var sortOrder = false;
//Define sort function
var sortBars = function() {
//Flip value of sortOrder
sortOrder = !sortOrder;
svg.selectAll("rect")
.sort(function(a, b) {
if (sortOrder) {
return d3.ascending(a, b);
} else {
return d3.descending(a, b);
}
})
.transition()
// 加上 delay 會減慢變化的過程
//.delay(function(d, i) {
// return i * 50;
//})
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
});
svg.selectAll("text")
.sort(function(a, b) {
if (sortOrder) {
return d3.ascending(a, b);
} else {
return d3.descending(a, b);
}
})
.transition()
// 加上 delay 會減慢變化的過程
//.delay(function(d, i) {
// return i * 50;
//})
.duration(1000)
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
});
};
</script>
- tooltip
瀏覽器標準 tooltip: 在產生矩形時以 title 及 text 產生 tooltip
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
.on("click", function() {
sortBars();
})
// 在矩形中以 title 產生 tooltip
.append("title")
.text(function(d) {
return "This value is " + d;
});
svg tooltip: mouseover 中,動態產生 svg 的 text 區塊,然後在 mouseout 移除
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) + 14;
//Create the tooltip label
svg.append("text")
.attr("id", "tooltip")
.attr("x", xPosition)
.attr("y", yPosition)
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(d);
})
.on("mouseout", function() {
//Remove the tooltip
d3.select("#tooltip").remove();
})
.on("click", function() {
sortBars();
});
div tooltip: 跟 svg 一樣,mouseover 中,動態產生 div 區塊,然後在 mouseout 隱藏
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + h / 2;
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#value")
.text(d);
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
})
.on("click", function() {
sortBars();
});
References
D3: Data-Driven Documents - Michael Bostock, Vadim Ogievetsky and Jeffrey Heer
《D3 API 詳解》隨書源碼 後面的 Refereces 有很多 D3.js 的網頁資源
用 D3.js v4 看 Pokemon 屬性表 D3.js v3 到 v4 的 migration 差異
沒有留言:
張貼留言