首先說明,d3支持所有的JS事件——甚至其他代碼的自定義事件。這里有一個列表,The MDN Event Reference, 包含了幾乎所有瀏覽器創建的事件類型。大家有需要可以去查看。
D3的事件綁定的語法,與jquery等其他類庫用起來區別不大,都是object.on( event, listener )的形式。但是在具體實踐中,我們經常會遇到給同一個對象綁定多個事件監聽器的問題。這里就原生js、jquery和d3分別進行討論。
一.原生JS的事件綁定
在探討這個問題之前,我們首先需要看一下原生js事件綁定機制的實現方法。原生js典型方法就是使用onclick屬性來進行事件綁定。但當同一個對象使用.onclick的寫法觸發多個方法的時候,后一個方法會把前一個方法覆蓋掉,也就是說,在對象的onclick事件發生時,只會執行最后綁定的方法。所以下面這個例子,可以想到最后的結果只能彈出一個alert對話框。
1
2
3
4
5
6
7
8
9
10
11
12
|
<span style="font-size:18px;">window.onload = function(){
var btn = document.getElementById("yuanEvent");
btn.onclick = function(){
alert("第一個事件");
}
btn.onclick = function(){
alert("第二個事件");
}
btn.onclick = function(){
alert("第三個事件");
}
}</span>
|
在原生JS中,對詞問題的解決方法是使用事件監聽器對象來處理事件響應。下面這段代碼展示了事件監聽器的用法,這下可以彈出兩個對話框了。
1
2
3
4
5
6
7
8
9
10
11
12
|
var eventOne = function(){
alert("第一個監聽事件");
}
function eventTwo(){
alert("第二個監聽事件");
}
window.onload = function(){
var btn = document.getElementById("yuanEvent");
//addEventListener:綁定函數
btn.addEventListener("click",eventOne);
btn.addEventListener("click",eventTwo);
}
|
當然,能綁定就能解除綁定,方法如下:
1
2
|
btn.addEventListener("click",eventTwo);
btn.removeEventListener("click",eventOne);
|
————————————————————————————————–
二.d3的事件綁定機制
同樣的,在使用d3進行事件綁定的時候,也會遇到類似的問題。如果直接進行綁定,那么后果是一樣的,后面的方法會覆蓋掉前面的方法。例如在下面的例子中,只有第二個函數Listenerbt會執行。
1
2
3
|
d3.select("#timebasis")
.on("change", listenersp)
.on("change", listenerbt);
|
我們想啟用監聽器。但是d3 API里面並沒有這個東東。難道要我用原生JS寫?雖然不是不可以,但事實上是,d3js對此有更好的解決方式,那就是使用命名空間,對事件響應進行區分!
see: https://github.com/mbostock/d3/wiki/Selections#wiki-on
If an event listener was already registered for the same type on the selected element, the existing listener is removed before the new listener is added. To register multiple listeners for the same event type, the type may be followed by an optional namespace, such as “click.foo” and “click.bar”. To remove a listener, pass null as the listener.
下面這個例子,就是使用了命名空間來處理多個事件綁定到同一對象這一問題。運行結果是,兩個函數listenersp,listenerbt都會被執行。
1
2
3
|
d3.select("#timebasis")
.on("change.sp", listenersp)
.on("change.bt", listenerbt);
|
能綁定當然也能解除綁定,方法如下:
1
2
3
|
d3.select("#timebasis")
.on("change.sp", null)
.on("change.bt", null);
|
事實上,在d3兩個自建的交互方法drag和zoom中,都使用了復數的事件綁定機制。例如在d3的ZOOM方法中,實際綁定了7個事件:mousedown, mousemove, dbclick, touchstart, wheel, mousewheel, MOzMousePixelScroll. 為了避免這些事件與用戶自己的事件產生覆蓋沖突,他們都使用了zoom這一命名空間。這個例子值得我們學習。
see: https://github.com/mbostock/d3/wiki/Zoom-Behavior
有時候zoom的功能太豐富了也很頭痛,會跟你的需求產生沖突。這個時候就需要解除綁定一些事件。解綁只需要置空就行:
1
2
3
4
5
6
7
|
svg.on("mousedown.zoom", null);
svg.on("mousemove.zoom", null);
svg.on("dblclick.zoom", null);
svg.on("touchstart.zoom", null);
svg.on("wheel.zoom", null);
svg.on("mousewheel.zoom", null);
svg.on("MozMousePixelScroll.zoom", null);
|
特別提一點:jquery中也是使用命名空間對不同綁定事件進行區分的。但是它是隱式地使用命名空間,用戶不需要特別聲明。例如下面的例程,兩個console都會出現。
1
2
|
$(".logo").mouseenter(function(){console.log("paomian")});
$(".logo").mouseenter(function(){console.log("niuroumian")});
|
這雖然很方便,但許多從jquery入門的程序員若不了解這一點,會導致在其他類庫中會出現錯誤。
————————————————————————————————–
三.d3的自定義事件
大多數時候,我們只要利用這些已有事件就可以了。但是正如別的類庫會自定義事件用以處理一些定制問題一樣,d3也可以創建自定義事件。這里就要用到d3.dispatch對象。
see: https://github.com/mbostock/d3/wiki/Internals#rebind
使用起來也很簡單,首先聲明一個命名空間,例如:
1
|
var dispatch = d3.dispatch('nodeClick', 'evaluationDialog', 'infoboxDblclick');
|
然后就可以分別定義具體的處理函數和調用方式:
1
|
dispatch.on("nodeClick", listener);
|
別的事件也可以觸發自定義事件:
1
|
.on('click.dispatch', dispatch.nodeClick);
|
當然,這個dispatch的具體用法,情況要比之前討論的內容復雜得多。具體我們還是要看例程才能理解其作用。
一個例子是:http://bl.ocks.org/mbostock/5872848,這是一個自定義事件的典型用法,通過自定義事件和命名空間將又一個交互造成的多個響應事件統一起來,從而得到一致的處理邏輯。
第二個例子是:https://github.com/kristw/angularjs-requirejs-d3-seed。它使用自定義事件來定義d3 vis模塊與外層angularjs框架之間的交互,取得了很好的效果。
PS:最后這里必須要吐槽一下d3的API文檔,雖然介紹很詳細,但是缺少代碼示例,這點就不如jquery API和angularjs API。本來很簡單的東西讓人看不懂,還得我去stackoverflow上查。代碼這個東西很多時候不是光用語言能描述清楚的。