D3源碼解讀系列之Selections

在web開發中咱們會花大量的時間用於dom的操做上,通常狀況下咱們會選擇第三方庫如jQuery來替代原生的方法,由於原生的方法在操做上會使代碼大量重複冗餘而不易操做。d3一樣提供了一套本身的方法來很方便的對dom進行操做,像修改樣式、註冊事件等等均可以經過它來完成。html

d3的selection主要用於直接對DOM進行操做,如設置屬性、修改樣式等等,同時它能夠和data join(數據鏈接)這一強大的功能結合起來對元素和元素上綁定的數據進行操做。 selection中的方法計算後返回當前selection,這樣能夠進行方法的鏈式調用。因爲經過這種方式調用方法會使得每行的代碼很長,所以按照約定:若該方法返回的是當前的selection,則使用四個空格進行縮進;若方法返回的是新的selection,則使用兩個空格進行縮進。node

選擇元素

元素的選擇經過兩種方法selectselectAll來實現,前者只返回第一個匹配的元素,然後者返回全部匹配元素。 因爲以後全部的操做都是在selection上進行,而該對象則是經過Selection構造函數獲得的,源碼以下:web

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的子元素,源碼以下:bash

/*
 * 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方法將其轉化爲函數,源碼以下:app

function selector(selector) {
    return selector == null ? none$2 : function() {
        //只返回第一個選中元素
        return this.querySelector(selector);
    };
}
複製代碼

能夠看出,其內部調用的是js的原生方法querySelector。 上述代碼對select參數進行處理後,使得其轉化爲函數,在後來的循環中調用時,經過select.call進行調用,傳入的參數依次爲結點的__data__屬性值,結點在該結點組中的索引,該結點組。 有如下幾點值得注意:dom

  • 若結點中包含__data__屬性則會對匹配的子元素也設置該屬性。
  • 經過select方法獲得的新的selection的_parents值並不會改變。

selection.selectAll(selector)

var p = d3.selectAll('div')
          .selectAll('p');
複製代碼

該方法對selection中的每一個元素進行查找,選擇其中匹配selector的子元素,返回的selection中的元素根據其父結點進行對應的分組,源碼以下:svg

/*
 * 對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並返回,源碼以下:ui

//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的操做過程當中即可以直接使用綁定好的數據。主要要理解updateenterexit,可參考文章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中的每一個元素設置綁定數據,該方法並不會影響到enterexit的值。

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._;
    }
};
複製代碼
相關文章
相關標籤/搜索