在web開發中我們會花大量的時間用於dom的操作上,一般情況下我們會選擇第三方庫如jQuery來替代原生的方法,因為原生的方法在操作上會使程式碼大量重複冗餘而不易操作。d3同樣提供了一套自己的方法來很方便的對dom進行操作,像修改樣式、註冊事件等等都可以通過它來完成。
d3的selection主要用於直接對DOM進行操作,如設定屬性、修改樣式等等,同時它可以和data join(資料連線)這一強大的功能結合起來對元素和元素上繫結的資料進行操作。 selection中的方法計算後返回當前selection,這樣可以進行方法的鏈式呼叫。由於通過這種方式呼叫方法會使得每行的程式碼很長,因此按照約定:若該方法返回的是當前的selection,則使用四個空格進行縮排;若方法返回的是新的selection,則使用兩個空格進行縮排。
選擇元素
元素的選擇通過兩種方法select
和selectAll
來實現,前者只返回第一個匹配的元素,而後者返回所有匹配元素。
由於之後所有的操作都是在selection上進行,而該物件則是通過Selection建構函式得到的,原始碼如下:
function Selection(groups, parents) {
this._groups = groups;
this._parents = parents;
}
複製程式碼
可以看出,selection物件包含兩個基本屬性_groups
和_parents
,前者用於儲存結點組,而後者則儲存結點的父節點資訊。
selection.select(selector)
var p = d3.selectAll('div')
.select('p');
複製程式碼
該方法對selection中的每個元素進行查詢,選擇其中第一個匹配selector的子元素,原始碼如下:
/*
* Selection的select方法
* 通過select方法選擇時,若不存在元素,則會在陣列中將該位置留出(賦值為null)用於之後插入時使用。
*/
function selection_select(select) {
if (typeof select !== "function") select = selector(select);
//當select是函式時,直接呼叫該函式,並依次對該函式傳入data資訊、當前的索引和當前的結點group,同時將函式的this設定為當前dom物件
for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
//當node中有data資訊時,node的子元素也新增該data資訊
if ("__data__" in node) subnode.__data__ = node.__data__;
subgroup[i] = subnode;
}
}
}
//將父級的_parents屬性作為子元素的_parents屬性
return new Selection(subgroups, this._parents);
}
複製程式碼
若selector為選擇器字串時,則會先呼叫selector方法將其轉化為函式,原始碼如下:
function selector(selector) {
return selector == null ? none$2 : function() {
//只返回第一個選中元素
return this.querySelector(selector);
};
}
複製程式碼
可以看出,其內部呼叫的是js的原生方法querySelector
。
上述程式碼對select引數進行處理後,使得其轉化為函式,在後來的迴圈中呼叫時,通過select.call
進行呼叫,傳入的引數依次為結點的__data__屬性值,結點在該結點組中的索引,該結點組。
有以下幾點值得注意:
- 若結點中包含
__data__
屬性則會對匹配的子元素也設定該屬性。 - 通過select方法得到的新的selection的
_parents
值並不會改變。
selection.selectAll(selector)
var p = d3.selectAll('div')
.selectAll('p');
複製程式碼
該方法對selection中的每個元素進行查詢,選擇其中匹配selector的子元素,返回的selection中的元素根據其父結點進行對應的分組,原始碼如下:
/*
* 對selection進行selectAll計算會改變selection結構,parents也會改變
*/
function selection_selectAll(select) {
if (typeof select !== "function") select = selectorAll(select);
for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
if (node = group[i]) {
subgroups.push(select.call(node, node.__data__, i, group));
parents.push(node);
}
}
}
return new Selection(subgroups, parents);
}
複製程式碼
上述程式碼可以看出,對node呼叫select方法後,查詢到的結果存入subgroups中,同時將node作為父結點存入parents陣列中,使得結點與父結點一一對應,最終返回新的selection。
selection.filter(filter)
var red = d3.selectAll('p')
.filter('.red')
複製程式碼
將使得filter為true的元素構造成新的selection並返回,原始碼如下:
//filter方法對當前selection進行過濾,保留滿足條件的元素
function selection_filter(match) {
if (typeof match !== "function") match = matcher$1(match);
for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
subgroup.push(node);
}
}
}
return new Selection(subgroups, this._parents);
}
複製程式碼
若match
不為函式,則通過matcher$1
函式對其進行處理,其原始碼如下:
var matcher = function(selector) {
return function() {
//Element.matches(s),如果元素能通過s選擇器選擇到則返回true;否則返回false
return this.matches(selector);
};
};
複製程式碼
可看出matcher
函式內部是呼叫原生的Element.matches
方法實現。
selection.merge(other_selection)
var circle = svg.selectAll("circle").data(data) // UPDATE
.style("fill", "blue");
circle.exit().remove(); // EXIT
circle.enter().append("circle") // ENTER
.style("fill", "green")
.merge(circle) // ENTER + UPDATE
.style("stroke", "black");
複製程式碼
該方法將兩個selection進行合併成一個新的selection並返回,原始碼如下:
function selection_merge(selection) {
//新的selection的_.groups長度和groups0相同,合併時只在m範圍內計算
for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
//groups0陣列大小不變,只有在group0[i]不存在,group1[i]存在時才選擇group1[i]
if (node = group0[i] || group1[i]) {
merge[i] = node;
}
}
}
// 若m1 < m0,則將groups0剩餘的複製過來
for (; j < m0; ++j) {
merges[j] = groups0[j];
}
return new Selection(merges, this._parents);
}
複製程式碼
該方法實際上相當於對this selection中的空元素進行填充。
修改元素
在選擇元素之後可以使用selection的方法來修改元素,如樣式、屬性等。
selection.attr(name[, value])
var p = d3.selectAll('p')
.attr('class', 'red');
複製程式碼
對selection中的元素以指定的name和value設定屬性,並返回當前selection,原始碼如下:
function selection_attr(name, value) {
var fullname = namespace(name);
if (arguments.length < 2) {
//得到selection中第一個存在的元素
var node = this.node();
return fullname.local
? node.getAttributeNS(fullname.space, fullname.local)
: node.getAttribute(fullname);
}
return this.each((value == null
? (fullname.local ? attrRemoveNS : attrRemove) : (typeof value === "function"
? (fullname.local ? attrFunctionNS : attrFunction)
: (fullname.local ? attrConstantNS : attrConstant)))(fullname, value));
}
複製程式碼
若只有name引數時,則返回第一個存在的元素的name屬性值,呼叫的是原生的Element.getAttribute
方法。
當有兩個引數時,呼叫selection.each
方法對selection中的每個元素進行操作,其原始碼如下:
function selection_each(callback) {
for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
if (node = group[i]) callback.call(node, node.__data__, i, group);
}
}
return this;
}
複製程式碼
若value值為函式,則將name和value傳入attrFunction函式中進行處理。
function attrFunction(name, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) this.removeAttribute(name);
else this.setAttribute(name, v);
};
}
複製程式碼
在selection.each
方法中,將引數傳入value函式中,根據value返回結果選擇設定和刪除屬性操作。
selection.classed(names[, value])
var p = d3.selectAll('p')
.classed('red warn', true);
複製程式碼
對selection中的元素設定類名,原始碼如下:
// 當value為真值時,在所有元素類名中新增name;否則刪除name
function selection_classed(name, value) {
var names = classArray(name + "");
// 當只有name引數時,判斷該selection物件的_groups裡第一個存在的結點是否包含所有的name的類名,如果是則返回true;否則,返回false
if (arguments.length < 2) {
var list = classList(this.node()), i = -1, n = names.length;
while (++i < n) if (!list.contains(names[i])) return false;
return true;
}
return this.each((typeof value === "function"
? classedFunction : value
? classedTrue
: classedFalse)(names, value));
}
複製程式碼
其中classArray
方法是將類名字串拆分成陣列:
// 將類名拆分成陣列,如'button button-warn' => ['button', 'button-warn']
function classArray(string) {
return string.trim().split(/^|\s+/);
}
複製程式碼
selection.style(name[, value[, priority]])
var p = d3.selectAll('p')
.style('color', 'red');
複製程式碼
該方法對selection中的元素設定樣式,原始碼如下:
// 設定selection的樣式,注意樣式的單位問題
function selection_style(name, value, priority) {
var node;
return arguments.length > 1
? this.each((value == null
? styleRemove : typeof value === "function"
? styleFunction
: styleConstant)(name, value, priority == null ? "" : priority))
: window(node = this.node())
.getComputedStyle(node, null)
.getPropertyValue(name);
}
複製程式碼
從上述程式碼可以看出,獲取樣式是通過window.getComputedStyle(element).getPropertyValue(name)
來得到(該方法得到的值是隻讀的),而刪除樣式則是通過element.style.removeProperty(name)
來實現,設定屬性通過element.style.setProperty(name, value)
來實現。
selection.property(name[, value])
var checkbox = d3.selectAll('input[type=checkbox]')
.property('checked', 'checked');
複製程式碼
該方法設定一些特殊的屬性。
function selection_property(name, value) {
return arguments.length > 1
? this.each((value == null
? propertyRemove : typeof value === "function"
? propertyFunction
: propertyConstant)(name, value))
: this.node()[name];
}
function propertyRemove(name) {
return function() {
delete this[name];
};
}
function propertyConstant(name, value) {
return function() {
this[name] = value;
};
}
function propertyFunction(name, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) delete this[name];
else this[name] = v;
};
}
複製程式碼
內部通過直接修改元素的屬性來實現。
selection.text([value])
該方法對selection中的所有元素設定文字內容,同時會替換掉元素中的子元素,原始碼如下:
function textRemove() {
this.textContent = "";
}
function textConstant(value) {
return function() {
this.textContent = value;
};
}
function textFunction(value) {
return function() {
var v = value.apply(this, arguments);
this.textContent = v == null ? "" : v;
};
}
// 設定元素的textContent屬性,該屬性返回的是元素內的純文字內容,不包含結點標籤(但包含標籤內的文字)
function selection_text(value) {
return arguments.length
? this.each(value == null
? textRemove : (typeof value === "function"
? textFunction
: textConstant)(value))
: this.node().textContent;
}
複製程式碼
上述程式碼是通過element.textContent
方法來獲取和修改文字內容。
selection.html([value])
對selection中的所有元素設定innerHTML。方法同上述selection.text
類似,只是通過element.innerHTML
來修改元素內的所有內容。
selection.append(type)
對selection中的元素新增新的元素。
/*
* selection的append方法
* 該方法返回新的Selection物件,
*/
function selection_append(name) {
var create = typeof name === "function" ? name : creator(name);
return this.select(function() {
//arguments是傳入當前匿名函式的引數
return this.appendChild(create.apply(this, arguments));
});
}
function creatorInherit(name) {
return function() {
//ownerDocument返回當前document物件
var document = this.ownerDocument,
uri = this.namespaceURI;
return uri === xhtml && document.documentElement.namespaceURI === xhtml
? document.createElement(name)//建立dom物件
: document.createElementNS(uri, name);
};
}
複製程式碼
該方法中呼叫到了selection.select
方法,由於element.appendChild
方法返回的是該子結點,因此返回的新的selection包含的是所有新增的子結點。
selection.insert(type, before)
對selection中的元素插入新的元素,同上述selection.append
方法類似,只是內部使用element.insertBefore
方法實現。
selection.sort(compare)
根據compare
函式對selection中的元素進行排序,排好序後按照排序結果對dom進行排序,返回排序後新建立的selection物件。
function selection_sort(compare) {
if (!compare) compare = ascending$2;
function compareNode(a, b) {
// 比較結點的data大小
return a && b ? compare(a.__data__, b.__data__) : !a - !b;
}
// copy一份selection中的_groups包含的結點
for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
if (node = group[i]) {
sortgroup[i] = node;
}
}
// 呼叫array的sort方法
sortgroup.sort(compareNode);
}
return new Selection(sortgroups, this._parents).order();
}
// 遞增
function ascending$2(a, b) {
return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}
複製程式碼
若沒有compare引數,則預設以遞增的方式排序。同時排序是首先比較元素中的__data__
屬性值的大小,對selection排好序後呼叫order方法。
selection.sort(compare)
按照selection中每組內元素的順序對dom進行排序
// 對dom結點進行排序
function selection_order() {
for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {
for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
if (node = group[i]) {
// 將node移至next的前面,並將node賦值給next
if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
next = node;
}
}
}
return this;
}
複製程式碼
連線資料
連線資料是將資料繫結到selection物件上,實際上是將資料儲存到__data__
屬性中,這樣之後對selection的操作過程中便可以直接使用繫結好的資料。主要要理解update
、enter
和exit
,可參考文章Thinking With Joins。
selection.data([data[, key]])
該方法將指定的data陣列繫結到選中的元素上,返回的selection包含成功繫結資料的元素,也叫做updata selection
,原始碼如下:
function selection_data(value, key) {
//當value為假值時,將selection所有元素的__data__屬性以陣列形式返回
if (!value) {
data = new Array(this.size()), j = -1;
this.each(function(d) { data[++j] = d; });
return data;
}
var bind = key ? bindKey : bindIndex,
parents = this._parents,
groups = this._groups;
if (typeof value !== "function") value = constant$4(value);
for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
var parent = parents[j],
group = groups[j],
groupLength = group.length,
data = value.call(parent, parent && parent.__data__, j, parents),
dataLength = data.length,
enterGroup = enter[j] = new Array(dataLength),
updateGroup = update[j] = new Array(dataLength),
exitGroup = exit[j] = new Array(groupLength);
bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
// 對enter結點設定_next屬性,儲存其索引之後的第一個update結點
for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
if (previous = enterGroup[i0]) {
if (i0 >= i1) i1 = i0 + 1;
while (!(next = updateGroup[i1]) && ++i1 < dataLength);
previous._next = next || null;
}
}
}
// 將enter和exit存入update selection的屬性中
update = new Selection(update, parents);
update._enter = enter;
update._exit = exit;
return update;
}
複製程式碼
通過上述程式碼可以看到,對每組group繫結的是相同的data資料。
當沒有key引數時,繫結資料使用的是bindIndex
方法,按照索引一次繫結。
function bindIndex(parent, group, enter, update, exit, data) {
var i = 0,
node,
groupLength = group.length,
dataLength = data.length;
/*
* 將data資料繫結到node,並將該node存入update陣列中
* 將剩餘的data資料存入enter陣列中
*/
for (; i < dataLength; ++i) {
if (node = group[i]) {
node.__data__ = data[i];
update[i] = node;
} else {
enter[i] = new EnterNode(parent, data[i]);
}
}
// 將剩餘的node存入exit陣列中
for (; i < groupLength; ++i) {
if (node = group[i]) {
exit[i] = node;
}
}
}
複製程式碼
若含有key引數,則呼叫bindKey
方法來繫結資料。
function bindKey(parent, group, enter, update, exit, data, key) {
var i,
node,
nodeByKeyValue = {},
groupLength = group.length,
dataLength = data.length,
keyValues = new Array(groupLength),
keyValue;
// 對group中每個結點計算keyValue,如果之後的結點含有與前面結點相同的keyValue則將該結點存入exit陣列中
for (i = 0; i < groupLength; ++i) {
if (node = group[i]) {
keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, group);
if (keyValue in nodeByKeyValue) {
exit[i] = node;
} else {
nodeByKeyValue[keyValue] = node;
}
}
}
// 對每一個data計算keyValue,如果該keyValue已存在nodeByKeyValue陣列中,則將其對應的node存入update陣列且繫結data資料;否則將data存入enter中
for (i = 0; i < dataLength; ++i) {
keyValue = keyPrefix + key.call(parent, data[i], i, data);
if (node = nodeByKeyValue[keyValue]) {
update[i] = node;
node.__data__ = data[i];
nodeByKeyValue[keyValue] = null;
} else {
enter[i] = new EnterNode(parent, data[i]);
}
}
// 將剩餘的沒有繫結資料的結點存入exit陣列
for (i = 0; i < groupLength; ++i) {
if ((node = group[i]) && (nodeByKeyValue[keyValues[i]] === node)) {
exit[i] = node;
}
}
}
複製程式碼
selection.enter()
返回selections中的enter selection,即selection._enter
的結果。
function sparse(update) {
return new Array(update.length);
}
// selection的enter方法
function selection_enter() {
return new Selection(this._enter || this._groups.map(sparse), this._parents);
}
複製程式碼
若selection沒有_enter
屬性,即沒有進行過data
操作,則建立空的陣列。
selection.exit()
返回selection中的exit selection,即selection._exit
的結果。
function selection_exit() {
return new Selection(this._exit || this._groups.map(sparse), this._parents);
}
複製程式碼
selection.datum([value])
對selection中的每個元素設定繫結資料,該方法並不會影響到enter
和exit
的值。
function selection_datum(value) {
return arguments.length
? this.property("__data__", value)
: this.node().__data__;
}
複製程式碼
可見其呼叫selection.property
方法來設定__data__
屬性。
經常會使用該方法來進行HTML5 data屬性的訪問,如
selection.datum(function() {return this.dataset});
複製程式碼
element.dataset
是原生方法,返回的是元素繫結的所有data屬性。
處理事件
selection.on(typenames[, listener[, capture]])
該方法用於對selection中的元素新增或者移除事件。
function selection_on(typename, value, capture) {
var typenames = parseTypenames$1(typename + ""), i, n = typenames.length, t;
// 如果只有typename引數,根據type和name值來找到selection中第一個存在的元素的__on屬性中對應的value值。
if (arguments.length < 2) {
var on = this.node().__on;
if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
for (i = 0, o = on[j]; i < n; ++i) {
if ((t = typenames[i]).type === o.type && t.name === o.name) {
return o.value;
}
}
}
return;
}
// 如果value為真值,則新增事件;否則移除事件。
on = value ? onAdd : onRemove;
if (capture == null) capture = false;
for (i = 0; i < n; ++i) this.each(on(typenames[i], value, capture));
return this;
}
複製程式碼
首先是對typename引數進行處理,使用的是parseTypenames
函式。
// 對typenames根據空格來劃分成陣列,並根據分離後的字串中的'.'來將該字串分割為type和name部分,如:'click.foo click.bar' => [{type: 'click', name: 'foo'}, {type: 'click', name: 'bar'}]
function parseTypenames$1(typenames) {
return typenames.trim().split(/^|\s+/).map(function(t) {
var name = "", i = t.indexOf(".");
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
return {type: t, name: name};
});
}
複製程式碼
根據value是否為真值來選擇新增或者移除事件。
// 新增事件函式
function onAdd(typename, value, capture) {
var wrap = filterEvents.hasOwnProperty(typename.type) ? filterContextListener : contextListener;
return function(d, i, group) {
var on = this.__on, o, listener = wrap(value, i, group);
if (on) for (var j = 0, m = on.length; j < m; ++j) {
// 如果新事件的type和name和之前已繫結的事件相同,則移除之前的事件並繫結新的事件
if ((o = on[j]).type === typename.type && o.name === typename.name) {
this.removeEventListener(o.type, o.listener, o.capture);
this.addEventListener(o.type, o.listener = listener, o.capture = capture);
o.value = value;
return;
}
}
// 新增事件,並將事件資訊存入selection.__on屬性中
this.addEventListener(typename.type, listener, capture);
o = {type: typename.type, name: typename.name, value: value, listener: listener, capture: capture};
if (!on) this.__on = [o];
else on.push(o);
};
}
複製程式碼
// 移除事件函式
function onRemove(typename) {
return function() {
var on = this.__on;
if (!on) return;
for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
//對結點移除事件
this.removeEventListener(o.type, o.listener, o.capture);
} else {
//修改結點的__on屬性值
on[++i] = o;
}
}
if (++i) on.length = i;
//on為空則刪除__on屬性
else delete this.__on;
};
}
複製程式碼
selection.dispatch(type[, parameters])
對selection中的元素分派指定型別的自定義事件,其中parameters可能包含以下內容:
- bubbles:設定為true表示事件可以冒泡
- cancelable:設定為true表示事件可以被取消
- detail:繫結到事件上的自定義資料 原始碼如下:
//分派事件
function selection_dispatch(type, params) {
return this.each((typeof params === "function"
? dispatchFunction
: dispatchConstant)(type, params));
}
function dispatchConstant(type, params) {
return function() {
return dispatchEvent(this, type, params);
};
}
function dispatchFunction(type, params) {
return function() {
return dispatchEvent(this, type, params.apply(this, arguments));
};
}
複製程式碼
dispatchEvent
函式如下:
//建立自定義事件並分派給指定元素
function dispatchEvent(node, type, params) {
var window$$ = window(node),
event = window$$.CustomEvent;
if (event) {
event = new event(type, params);
} else {
//該方法已被廢棄
event = window$$.document.createEvent("Event");
if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;
else event.initEvent(type, false, false);
}
node.dispatchEvent(event);
}
複製程式碼
d3.event
儲存當前事件,在呼叫事件監聽器的時候設定,處理函式執行完畢後重置,可以獲取其中包含的事件資訊如:event.pageX
。
d3.customEvent(event, listener[, that[, arguments]])
該方法呼叫指定的監聽器。
//呼叫指定的監聽器
function customEvent(event1, listener, that, args) {
//記錄當前事件
var event0 = exports.event;
event1.sourceEvent = exports.event;
exports.event = event1;
try {
return listener.apply(that, args);
} finally {
//監聽器執行完後恢復事件
exports.event = event0;
}
}
複製程式碼
d3.mouse(container)
返回當前當前事件相對於指定容器的x和y座標。
function mouse(node) {
var event = sourceEvent();
//如果是觸控事件,返回Touch物件
if (event.changedTouches) event = event.changedTouches[0];
return point$5(node, event);
}
複製程式碼
point$5
方法用於計算座標。
function point$5(node, event) {
//若node是svg元素,則獲取svg容器
var svg = node.ownerSVGElement || node;
if (svg.createSVGPoint) {
//建立SVGPoint物件
var point = svg.createSVGPoint();
//將事件相對客戶端的x和y座標賦值給point物件
point.x = event.clientX, point.y = event.clientY;
//進行座標轉換
point = point.matrixTransform(node.getScreenCTM().inverse());
return [point.x, point.y];
}
var rect = node.getBoundingClientRect();
//返回event事件相對於容器的座標
return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop];
}
複製程式碼
控制流
用於selection的一些高階操作。
selection.each(function)
為每一個選中的元素呼叫指定的函式。
//selection的each方法
function selection_each(callback) {
for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
//通過call方式呼叫回撥函式
if (node = group[i]) callback.call(node, node.__data__, i, group);
}
}
return this;
}
複製程式碼
selection.call(function[, arguments…])
將selection和其他引數傳入指定函式中執行,並返回當前selection,這和直接鏈式呼叫function(selection)
相同。
function selection_call() {
var callback = arguments[0];
arguments[0] = this;
callback.apply(null, arguments);
return this;
}
複製程式碼
區域性變數
d3的區域性變數可以定義與data獨立開來的區域性狀態,它的作用域是dom元素。 其建構函式和原型方法如下:
function Local() {
this._ = "@" + (++nextId).toString(36);
}
Local.prototype = local.prototype = {
constructor: Local,
get: function(node) {
var id = this._;
while (!(id in node)) if (!(node = node.parentNode)) return;
return node[id];
},
set: function(node, value) {
return node[this._] = value;
},
remove: function(node) {
return this._ in node && delete node[this._];
},
toString: function() {
return this._;
}
};
複製程式碼