国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

D3 源代碼解析(二)

tainzhi / 2093人閱讀

摘要:第一節(jié)點(diǎn)位于第二節(jié)點(diǎn)內(nèi)。例如,返回意味著在在內(nèi)部,并且在之前。這個(gè)函數(shù)返回一個(gè)函數(shù),返回的函數(shù)綁定了當(dāng)前對(duì)象并執(zhí)行。

這是繼上一篇D3源碼解構(gòu)文章后的對(duì)D3的研究筆記,筆者的能力有限,如有哪里理解錯(cuò)誤,歡迎指正。

對(duì)集合的操作 關(guān)于d3.attr

一個(gè)可以處理很多情況的函數(shù),當(dāng)只傳入一個(gè)參數(shù)時(shí),如果是string,則返回該屬性值,如果是對(duì)象,則遍歷設(shè)置對(duì)象的鍵值對(duì)屬性值,如果參數(shù)大于等于2,則是普通的設(shè)置樣式:

var node = d3.select("body")

node.attr("class")
> 返回該屬性值

node.attr("class", "haha")
> 設(shè)置該屬性值

node.attr({"class": "haha", "x": "10"})
> 設(shè)置該屬性值

那么怎么做到一個(gè)函數(shù)處理多種情況,很明顯是根據(jù)參數(shù)的數(shù)量來(lái)區(qū)別對(duì)待:

  d3_selectionPrototype.attr = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node();
        name = d3.ns.qualify(name);
        return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
      }
      for (value in name) this.each(d3_selection_attr(value, name[value]));
      return this;
    }
    return this.each(d3_selection_attr(name, value));
  };

關(guān)于getAttributeNS我們可以不用理會(huì),對(duì)于web端,d3在設(shè)置和獲取屬性的時(shí)候用的都是getAttribute和setAttribute。
對(duì)于d3_selection_attr函數(shù),它返回一個(gè)通用函數(shù),該函數(shù)會(huì)對(duì)當(dāng)前對(duì)象設(shè)置對(duì)應(yīng)的屬性值:
大概的思想:

function d3_selection_attr(name, value) {
  return function() {
    this.setAttribute(name, value);
  }
}
selection.classed

具體用法可以看文檔介紹,大概的意思是如果有鍵值對(duì)或者對(duì)象傳入,則根據(jù)value值來(lái)添加或刪除name類,否則則檢測(cè)是否含有該類, 如果selection有多個(gè),只檢測(cè)第一個(gè)并返回該值

var line = d3.selectAll("line");
line.classed("a b c d", true)
>對(duì)所有節(jié)點(diǎn)設(shè)置class
line classed({"a": true, "b": false})
>分別添加和刪除類

和attr一樣,通過(guò)對(duì)參數(shù)長(zhǎng)度和類型的區(qū)分,執(zhí)行不同的方法

  d3_selectionPrototype.classed = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1;
        if (value = node.classList) {
          while (++i < n) if (!value.contains(name[i])) return false;
        } else {
          value = node.getAttribute("class");
          while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
        }
        return true;
      }
      for (value in name) this.each(d3_selection_classed(value, name[value]));
      return this;
    }
    return this.each(d3_selection_classed(name, value));
  };

這里考慮到傳入的字符串可能含有多個(gè)類名,d3_selection_classes函數(shù)用來(lái)分割:

return (name + "").trim().split(/^|s+/)

這里涉及到一個(gè)小細(xì)節(jié),先用trim過(guò)濾掉字符串兩邊的空白字符,然后用正則表達(dá)式去分割類名,正則表達(dá)式中的s匹配任何空白字符,包括空格、制表符、換頁(yè)符等等。等價(jià)于 [?fnrtv],而且還有一個(gè)^,它在這里應(yīng)該是匹配第一個(gè)的意思,測(cè)試了一下,發(fā)現(xiàn)如果不加這個(gè)匹配的話,對(duì)于空白字符串不會(huì)返回長(zhǎng)度為0的數(shù)組,而是會(huì)返回含有一個(gè)空字符串長(zhǎng)度為一的數(shù)組,所以這應(yīng)該是為了防止出現(xiàn)這種情況而做的匹配,不過(guò)原理還是不懂。對(duì)于正則的組合,暫時(shí)不理解加^就能防止該問(wèn)題的原因。

關(guān)于匹配是否存在該類,為了防止匹配的時(shí)候發(fā)生類名為’asdf",測(cè)試的類名為"a",由于包含關(guān)系而被匹配成功,所以不能簡(jiǎn)單的使用indexOf的方法,而是要使用正則表達(dá)式去做匹配,由于類名要么在最開始,要么在中間兩邊有空格,要么在末尾,所以使用

new RegExp("(?:^|s+)" + d3.requote(name) + "(?:s+|$)", "g")

去做正則匹配

這里用到了(?:pattern)的方法,意思是匹配?pattern?但不獲取匹配結(jié)果,也就是說(shuō)這是一個(gè)非獲取匹配,不進(jìn)行存儲(chǔ)供以后使用。這在使用 "或" 字符 (|) 來(lái)組合一個(gè)模式的各個(gè)部分是很有用。例如, "industr(?:y|ies) 就是一個(gè)比 "industry|industries" 更簡(jiǎn)略的表達(dá)式。

d3_selectionPrototype.style

和attr結(jié)構(gòu)類似的函數(shù),特別在于如果傳入的值是函數(shù),則會(huì)分別對(duì)每個(gè)元素調(diào)用一次函數(shù),并傳入元素和元素的位置、優(yōu)先級(jí)等

  d3_selectionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
        return this;
      }
      if (n < 2) {
        var node = this.node();
        return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
      }
      priority = "";
    }
    return this.each(d3_selection_style(name, value, priority));
  };

關(guān)于樣式的設(shè)置,d3用的是style.getProperty(name)和style.setProperty(name, x, priority)
樣式的獲取,用的是和jquery的實(shí)現(xiàn)方法,具體可以看看鑫大大的文章,

一般我們用的是window.getComputedStyle(elem, "偽類")還有IE自?shī)首詷?lè)的currentStyle, 具體的細(xì)節(jié)就不說(shuō)了。
兩者的不同在于getPropertyValue只能獲取設(shè)置在style中的屬性,而window.getComputedStyle則會(huì)得到元素最終顯示在頁(yè)面上的綜合樣式,就算沒(méi)有顯示聲明也可以拿到,這點(diǎn)是最重要的區(qū)別。

selectionPrototype.property、 selectionPrototype.text

property 給元素設(shè)置額外的屬性,例如:
node.property("bar", "hahahaha")
node.property("bar") // hahahaha

text 設(shè)置元素的文本,是通過(guò)element.textContent來(lái)設(shè)置文本的,之前我們?cè)O(shè)置文本和html都是通過(guò)innerText和innerHTML去設(shè)置,那么這和textContent有什么區(qū)別嗎?

實(shí)驗(yàn)
筆者測(cè)試了下在Chrome和firefox下的情況,發(fā)現(xiàn)最新版本的瀏覽器其實(shí)都是支持兩者的,不過(guò)innerText并不是w3c標(biāo)準(zhǔn),所以以前firefox并不支持innerText。

兩者的區(qū)別

轉(zhuǎn)義上,textContent對(duì)傳入的文本如果帶有n等換行符,不會(huì)忽略,而innText會(huì)忽略并轉(zhuǎn)義為空格

textContent會(huì)獲取所有子節(jié)點(diǎn)的文本,而innerText不會(huì)理會(huì)隱藏節(jié)點(diǎn)的文本。

selectionProperty.html

這個(gè)沒(méi)什么好講的,封裝了innerHTML的方法

d3_selectionPrototype.append

比較特別的是實(shí)現(xiàn)的代碼:

  d3_selectionPrototype.append = function(name) {
    name = d3_selection_creator(name);
    return this.select(function() {
      return this.appendChild(name.apply(this, arguments));
    });
  };

函數(shù)中返回一個(gè)函數(shù)的執(zhí)行結(jié)果,該執(zhí)行函數(shù)中又返回一個(gè)函數(shù)的執(zhí)行結(jié)果,層層嵌套卻又非常聰明的做法,我們從最里面的一層看,首先對(duì)當(dāng)前的節(jié)點(diǎn)添加子元素,然后返回該子節(jié)點(diǎn)元素,最后再通過(guò)select方法獲取該子元素。

d3_selectionPrototype_creator(name) {
  function create() {
    return document.createElement(name);
  }
  return typeof name == "function" ? name : create;
}

這是簡(jiǎn)易版本的creator,d3還要考慮到在xml中的情況,xml創(chuàng)建子節(jié)點(diǎn)調(diào)用的是document.createElementNS,d3是通過(guò)namespaceURI來(lái)判斷頁(yè)面類型的吧,不過(guò)在MDN上查詢發(fā)現(xiàn)這個(gè)屬性已經(jīng)被列為廢詞,隨時(shí)可能被廢除的,查詢了版本4,發(fā)現(xiàn)還是沿用了這個(gè)屬性,這個(gè)比較危險(xiǎn)吧。

d3_selectionPrototype.insert && d3_selectionPrototype.remove

insertBefore
同append類似,不過(guò)是封裝了insertBefore的方法,注意需要用元素節(jié)點(diǎn)才能調(diào)用該方法,正確的調(diào)用方法是:
existNodeParents.insertBefore(newNode, existNodeToBeInsertBefore)
remove
很簡(jiǎn)單的實(shí)現(xiàn):

  function d3_selectionRemove() {
    var parent = this.parentNode;
    if (parent) parent.removeChild(this);
  }
Data 關(guān)于d3_selectionPrototype.data函數(shù)

這個(gè)函數(shù)是D3經(jīng)常使用到也是比較關(guān)鍵的函數(shù),用它來(lái)進(jìn)行數(shù)據(jù)的綁定、更新,具體解析可以參考上一篇文章D3源代碼解構(gòu)
這里涉及到一個(gè)特殊的屬性data,如果不傳入?yún)?shù),data會(huì)返回所有算中集合元素的屬性值(property),但是為什么是通過(guò)node.__data__拿到的,通過(guò)搜索,終于找到了綁定該值得函數(shù)(一開始還以為是DOM的隱藏變量- -)

  d3_selectionPrototype.datum = function(value) {
    return arguments.length ? this.property("__data__", value) : this.property("__data__");
  };

如果傳入?yún)?shù),它會(huì)創(chuàng)建三個(gè)特殊的私有變量,分別是

enter = d3_selection_enter([])

update = d3_selection([])

exit = d3_selection([])
我們可以知道update和exit都是一個(gè)繼承了d3_selectionPrototype原型對(duì)象的數(shù)組,所以它擁有我們上面提到的selectionPrototype所有的方法,而enter比較特殊,它多帶帶使用一套原型方法,實(shí)現(xiàn)方法如下:

  function d3_selection_enter(selection) {
    d3_subclass(selection, d3_selection_enterPrototype);
    return selection;
  }
  var d3_selection_enterPrototype = [];
  d3.selection.enter = d3_selection_enter;
  d3.selection.enter.prototype = d3_selection_enterPrototype;
  d3_selection_enterPrototype.append = d3_selectionPrototype.append;
  d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
  d3_selection_enterPrototype.node = d3_selectionPrototype.node;
  d3_selection_enterPrototype.call = d3_selectionPrototype.call;
  d3_selection_enterPrototype.size = d3_selectionPrototype.size;
  d3_selection_enterPrototype.select = function(selector) {
    var subgroups = [], subgroup, subnode, upgroup, group, node;
    for (var j = -1, m = this.length; ++j < m; ) {
      upgroup = (group = this[j]).update;
      subgroups.push(subgroup = []);
      subgroup.parentNode = group.parentNode;
      for (var i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
          subnode.__data__ = node.__data__;
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_selection(subgroups);
  };
  d3_selection_enterPrototype.insert = function(name, before) {
    if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
    return d3_selectionPrototype.insert.call(this, name, before);
  };

然后調(diào)用bind函數(shù)對(duì)傳入的data和key(可選)進(jìn)行數(shù)據(jù)綁定,我們知道d3會(huì)根據(jù)傳入的數(shù)據(jù)和已有的元素進(jìn)行一一對(duì)應(yīng),一開始以為是基于什么算法去對(duì)應(yīng),看代碼實(shí)現(xiàn)就發(fā)現(xiàn)如果我們不傳入key參數(shù),其實(shí)就是簡(jiǎn)單的索引對(duì)應(yīng):

function bind(group, groupData) {
      var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
      if (key) {
        var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
        for (i = -1; ++i < n; ) {
          if (node = group[i]) {
            if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
              exitNodes[i] = node;
            } else {
              nodeByKeyValue.set(keyValue, node);
            }
            keyValues[i] = keyValue;
          }
        }
        for (i = -1; ++i < m; ) {
          if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          } else if (node !== true) {
            updateNodes[i] = node;
            node.__data__ = nodeData;
          }
          nodeByKeyValue.set(keyValue, true);
        }
        for (i = -1; ++i < n; ) {
          if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
            exitNodes[i] = group[i];
          }
        }
      } else {
        for (i = -1; ++i < n0; ) {
          node = group[i];
          nodeData = groupData[i];
          if (node) {
            node.__data__ = nodeData;
            updateNodes[i] = node;
          } else {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          }
        }
        for (;i < m; ++i) {
          enterNodes[i] = d3_selection_dataNode(groupData[i]);
        }
        for (;i < n; ++i) {
          exitNodes[i] = group[i];
        }
      }

而當(dāng)我們傳入了key后,這個(gè)時(shí)候就不一樣了,D3會(huì)根據(jù)我們傳入的這個(gè)函數(shù)去將元素和數(shù)據(jù)做綁定和更新、退出,這個(gè)key函數(shù)會(huì)在三次循環(huán)中分別被調(diào)用,一次是檢查是否有已經(jīng)綁定了數(shù)據(jù)的元素,并初始化一個(gè)映射集合,第二次進(jìn)行數(shù)據(jù)綁定元素,確定update和enter集合,第三次確定exit集合。
建議先看看官方文檔,了解具體的用法在看代碼會(huì)清晰很多。通俗的說(shuō),假設(shè)我們傳入的數(shù)據(jù)有主鍵即唯一區(qū)分每個(gè)數(shù)據(jù)的屬性,那么,我們便可以告訴data說(shuō)用這個(gè)屬性來(lái)區(qū)分,也就是:

selection.data(mydata, function(d, i) {
  return d.主鍵名稱
}

關(guān)于d3_map集合可以參考d3_map解析

Animation & Interaction (動(dòng)畫和交互) [d3_selectionPrototype.datum]()

這是上面講到的一個(gè)函數(shù)datum,可惜在data中其實(shí)沒(méi)有用到,我遍歷了整個(gè)代碼只有一處地方調(diào)用了這個(gè)函數(shù),它和data類似用來(lái)獲取或者設(shè)置元素的值,它是基于property上進(jìn)行一層封裝,但是和data不同的是它沒(méi)有所謂的enter、exit集合返回,那么它有什么用呢?我們可以看看這篇文章

d3_selectionPrototype.filter

可以傳入函數(shù)或者選擇器字符串進(jìn)行集合的過(guò)濾

d3的事件監(jiān)聽機(jī)制

看d3關(guān)于事件監(jiān)聽的實(shí)現(xiàn),看到了關(guān)于JS事件的一個(gè)屬性relatedTarget,關(guān)于JS的event對(duì)象之前接觸的不多,突然看到關(guān)于這個(gè)屬性,上網(wǎng)查找資料,才發(fā)現(xiàn)了這么冷門的屬性:

relatedTarget 事件屬性返回與事件的目標(biāo)節(jié)點(diǎn)相關(guān)的節(jié)點(diǎn)。
對(duì)于 mouseover 事件來(lái)說(shuō),該屬性是鼠標(biāo)指針移到目標(biāo)節(jié)點(diǎn)上時(shí)所離開的那個(gè)節(jié)點(diǎn)。
對(duì)于 mouseout 事件來(lái)說(shuō),該屬性是離開目標(biāo)時(shí),鼠標(biāo)指針進(jìn)入的節(jié)點(diǎn)。
對(duì)于其他類型的事件來(lái)說(shuō),這個(gè)屬性沒(méi)有用。

怎么樣,夠冷門吧,只對(duì)兩種事件生效

還有一個(gè)方法叫做compareDocumentPosition,比較兩個(gè)節(jié)點(diǎn),并返回描述它們?cè)谖臋n中位置的整數(shù)
1:沒(méi)有關(guān)系,兩個(gè)節(jié)點(diǎn)不屬于同一個(gè)文檔。
2:第一節(jié)點(diǎn)(P1)位于第二個(gè)節(jié)點(diǎn)后(P2)。
4:第一節(jié)點(diǎn)(P1)定位在第二節(jié)點(diǎn)(P2)前。
8:第一節(jié)點(diǎn)(P1)位于第二節(jié)點(diǎn)內(nèi)(P2)。
16:第二節(jié)點(diǎn)(P2)位于第一節(jié)點(diǎn)內(nèi)(P1)。
32:沒(méi)有關(guān)系,或是兩個(gè)節(jié)點(diǎn)是同一元素的兩個(gè)屬性。
注釋:返回值可以是值的組合。例如,返回 20 意味著在 p2 在 p1 內(nèi)部(16),并且 p1 在 p2 之前(4)。

知道了這兩個(gè)屬性,d3的一個(gè)函數(shù)就看懂了:

  function d3_selection_onFilter(listener, argumentz) {
    var l = d3_selection_onListener(listener, argumentz);
    return function(e) {
      var target = this, related = e.relatedTarget;
      if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
        l.call(target, e);
      }
    };
  }

獲取事件對(duì)應(yīng)的對(duì)象和相關(guān)的對(duì)象,如果不存在相關(guān)的對(duì)象或者相關(guān)的對(duì)象不等于當(dāng)前對(duì)象且相關(guān)對(duì)象不在當(dāng)前對(duì)象之內(nèi),則執(zhí)行監(jiān)聽函數(shù)。

  function d3_selection_onListener(listener, argumentz) {
    return function(e) {
      var o = d3.event;
      d3.event = e;
      argumentz[0] = this.__data__;
      try {
        listener.apply(this, argumentz);
      } finally {
        d3.event = o;
      }
    };
  }

這個(gè)函數(shù)返回一個(gè)函數(shù),返回的函數(shù)綁定了當(dāng)前對(duì)象并執(zhí)行。

  var d3_selection_onFilters = d3.map({
    mouseenter: "mouseover",
    mouseleave: "mouseout"
  });
  if (d3_document) {  
    d3_selection_onFilters.forEach(function(k) {
      if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
    });
  }

D3還做了一個(gè)事件 映射,將mouseenter映射為mouseover,mouseleave映射為mouseout,然后判斷環(huán)境中是否有這兩個(gè)事件,如果有的話就取消這個(gè)映射。

以上三段代碼都是為了處理執(zhí)行環(huán)境中沒(méi)有mouseenter和mousemove情況下如何利用mouseover和mouseleave去實(shí)現(xiàn)相同效果的問(wèn)題。然后通過(guò)下面這個(gè)函數(shù)來(lái)判斷:

  function d3_selection_on(type, listener, capture) {
    var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
    if (i > 0) type = type.slice(0, i);
    var filter = d3_selection_onFilters.get(type);
    if (filter) type = filter, wrap = d3_selection_onFilter;
    function onRemove() {
      var l = this[name];
      if (l) {
        this.removeEventListener(type, l, l.$);
        delete this[name];
      }
    }
    function onAdd() {
      var l = wrap(listener, d3_array(arguments));
      onRemove.call(this);
      this.addEventListener(type, this[name] = l, l.$ = capture);
      l._ = listener;
    }
    function removeAll() {
      var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
      for (var name in this) {
        if (match = name.match(re)) {
          var l = this[name];
          this.removeEventListener(match[1], l, l.$);
          delete this[name];
        }
      }
    }
    console.log("d3_selection_on:", i, listener, i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll);
    return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
  }

現(xiàn)在再來(lái)看這個(gè)函數(shù)就可以看懂了,首先它判斷傳入的事件類型是否含有".",因?yàn)镈3在實(shí)現(xiàn)事件綁定時(shí),會(huì)清除同種事件類型之前綁定的監(jiān)聽函數(shù),所以對(duì)于同一類型的事件,如果要綁定多個(gè)監(jiān)聽函數(shù),那么就需要使用click.foo*click.bar*這種方式去進(jìn)行區(qū)分,防止舊的事件被覆蓋掉,查看onAdd函數(shù)就可以知道每次添加事件監(jiān)聽的時(shí)候,就會(huì)調(diào)用onRemove去清除該事件監(jiān)聽。

關(guān)于capture,默認(rèn)是false,表示在冒泡階段響應(yīng)事件,如果設(shè)置為true,則是在捕獲階段響應(yīng)事件,可以參考這篇文章,這是歷史遺留原因,好像當(dāng)初的瀏覽器響應(yīng)事件的設(shè)置不是冒泡階段,而是捕獲階段,后來(lái)為了兼容而給了這個(gè)參數(shù)。

好了,懂得了D3事件綁定的原理,那么實(shí)現(xiàn)這個(gè)函數(shù)就很容易,一樣的根據(jù)參數(shù)的數(shù)量和類型做不同的處理就好了:

  d3_selectionPrototype.on = function(type, listener, capture) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof type !== "string") {
        if (n < 2) listener = false;
        for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
        return this;
      }
      if (n < 2) return (n = this.node()["__on" + type]) && n._;
      capture = false;
    }
    return this.each(d3_selection_on(type, listener, capture));
  };
[d3.mouse]()

MDN上關(guān)于svg的一些屬性
一篇關(guān)于svg的講解
關(guān)于svg坐標(biāo)轉(zhuǎn)換為屏幕坐標(biāo).aspx)
關(guān)于使用矩陣轉(zhuǎn)換的實(shí)現(xiàn)
我們要知道一些新的屬性:

ownerSVGElement】,用來(lái)獲取這個(gè)元素最近的svg祖先,沒(méi)有的話就返回元素本身。

svg.createSVGPoint】這個(gè)函數(shù)不在MDN中,看下MF的介紹.aspx),大概意思是初始化一個(gè)不在document文檔內(nèi)的坐標(biāo)點(diǎn)

getScreenCTM

當(dāng)我們獲取網(wǎng)頁(yè)上鼠標(biāo)的坐標(biāo)點(diǎn)的時(shí)候,可以很簡(jiǎn)單地調(diào)用e.clientXY,或者e.pageXY,但是svg有自己的一套坐標(biāo)系,它可以自身旋轉(zhuǎn)、平移,所以我們想知道按鈕點(diǎn)擊的位置相對(duì)于svg元素的位置時(shí),需要考慮這些因素,從而使得獲取鼠標(biāo)在svg的位置時(shí)變得沒(méi)那么容易,再加上各種瀏覽器的坑……
這個(gè)時(shí)候就是線性代數(shù)就用上了(感謝線代老師!),忘的差不多的可以參考上面的幾篇文章,svg自身已經(jīng)提供了對(duì)應(yīng)的矩陣運(yùn)算,節(jié)省了我們的一些實(shí)現(xiàn)的代碼。
再看看D3的代碼,就知道原作者也是被坑過(guò)的:

  function d3_mousePoint(container, e) {
    if (e.changedTouches) e = e.changedTouches[0];
    var svg = container.ownerSVGElement || container;
    if (svg.createSVGPoint) {
      var point = svg.createSVGPoint();
      if (d3_mouse_bug44083 < 0) {
        var window = d3_window(container);
        if (window.scrollX || window.scrollY) {
          svg = d3.select("body").append("svg").style({
            position: "absolute",
            top: 0,
            left: 0,
            margin: 0,
            padding: 0,
            border: "none"
          }, "important");
          var ctm = svg[0][0].getScreenCTM();
          d3_mouse_bug44083 = !(ctm.f || ctm.e);
          svg.remove();
        }
      }
      if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, 
      point.y = e.clientY;
      point = point.matrixTransform(container.getScreenCTM().inverse());
      return [ point.x, point.y ];
    }
    var rect = container.getBoundingClientRect();
    return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
  }

clientX是獲取相對(duì)于瀏覽器屏幕的坐標(biāo),減去元素相對(duì)于屏幕的左邊距,為了兼容IE等坑爹的默認(rèn)開始位置為(2,2),減去container的clienLeft,最終得到svg的鼠標(biāo)位置,但真的是為了獲取相對(duì)的位置么,需要再看看。

Behavior [d3的touch、drag、touches]()

看不太懂這幾個(gè)的實(shí)現(xiàn),和自己沒(méi)有怎么使用到這幾個(gè)函數(shù)有關(guān)吧

[d3.zoom]()

zoom函數(shù)的實(shí)現(xiàn),大概知道它通過(guò)綁定mouseWheel事件去記錄了放縮的值、中心、放縮位置等。也是涉及到event的綁定,表示hin暈。

D3的顏色空間

具體可以參考前一篇文章

d3.xhr

D3對(duì)于ajax的實(shí)現(xiàn),沒(méi)有兼容IE6及6以下的xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
只考慮了window.XMLHttpRequest,因?yàn)槔习姹镜腎E壓根就無(wú)法正常使用各種圖形和動(dòng)畫。

D3的timer的實(shí)現(xiàn)有點(diǎn)厲害

當(dāng)我們要用D3實(shí)現(xiàn)一個(gè)永久循環(huán)的動(dòng)畫的時(shí)候,就可以使用timer函數(shù),向這個(gè)函數(shù)傳入一個(gè)函數(shù),timer函數(shù)會(huì)在每個(gè)動(dòng)畫針中調(diào)用傳入的函數(shù)直至該函數(shù)返回‘true’,所以只要我們始終不返回true就好了。
如果是這么簡(jiǎn)單當(dāng)然就好實(shí)現(xiàn)了,但是如果有多個(gè)timer怎么去控制呢?這個(gè)問(wèn)題導(dǎo)致了實(shí)現(xiàn)的方法復(fù)雜了很多,直接上代碼:

  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
    setTimeout(callback, 17);
  };
  d3.timer = function() {
    d3_timer.apply(this, arguments);
  };
  function d3_timer(callback, delay, then) {
    var n = arguments.length;
    if (n < 2) delay = 0;
    if (n < 3) then = Date.now();
    var time = then + delay, timer = {
      c: callback,
      t: time,
      n: null
    };
    if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
    d3_timer_queueTail = timer;
    if (!d3_timer_interval) {
      d3_timer_timeout = clearTimeout(d3_timer_timeout);
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
    return timer;
  }
  
  function d3_timer_step() {
    var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
    if (delay > 24) {
      if (isFinite(delay)) {
        clearTimeout(d3_timer_timeout);
        d3_timer_timeout = setTimeout(d3_timer_step, delay);
      }
      d3_timer_interval = 0;
    } else {
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
  }
  // 立即執(zhí)行時(shí)間隊(duì)列,然后清洗掉已經(jīng)結(jié)束的事件。
  d3.timer.flush = function() {
    d3_timer_mark();
    d3_timer_sweep();
  };
  // 遍歷時(shí)間隊(duì)列,如果回調(diào)函數(shù)返回真,則將該事件的回調(diào)賦值為空,然后繼續(xù)檢查下一個(gè),最后返回當(dāng)前時(shí)間。
  function d3_timer_mark() {
    var now = Date.now(), timer = d3_timer_queueHead;
    while (timer) {
      if (now >= timer.t && timer.c(now - timer.t)) timer.c = null;
      timer = timer.n;
    }
    return now;
  }
  // 時(shí)間事件隊(duì)列的清洗,循環(huán)遍歷隊(duì)列中的時(shí)間對(duì)象,如果回調(diào)函數(shù)為空,去掉,否則檢測(cè)下一個(gè),最后返回最近要執(zhí)行的事件時(shí)間點(diǎn)。
  function d3_timer_sweep() {
    var t0, t1 = d3_timer_queueHead, time = Infinity;
    while (t1) {
      if (t1.c) {
        if (t1.t < time) time = t1.t;
        t1 = (t0 = t1).n;
      } else {
        t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
      }
    }
    d3_timer_queueTail = t0;
    return time;
  }

D3使用隊(duì)列的方法實(shí)現(xiàn),每次有新的timer進(jìn)來(lái),判斷隊(duì)列是否為空,如果為空,就將Head和隊(duì)尾指向它,否則,將隊(duì)尾和隊(duì)尾的下一個(gè)指向它

    if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
    d3_timer_queueTail = timer;

感謝C和C++,告訴我指針實(shí)現(xiàn)鏈表的概念!

然后開始執(zhí)行回調(diào)函數(shù)。

    if (!d3_timer_interval) {
      d3_timer_timeout = clearTimeout(d3_timer_timeout);
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }

timer_frame的實(shí)現(xiàn)是兼容了老版本的瀏覽器沒(méi)有 requestAnimationFrame 而退而使用setTimeout去實(shí)現(xiàn),如果不太清楚這個(gè)api的同學(xué)可以看看鑫旭的這篇文章或者上MDN查。
然后每個(gè)幀都會(huì)調(diào)用d3_timer_step這個(gè)函數(shù),它調(diào)用了d3_timer_mark和d3_timer_sweep函數(shù),循環(huán)遍歷了一遍時(shí)間隊(duì)列,然后獲取最近的待執(zhí)行的時(shí)間點(diǎn),得到了delay時(shí)間差,當(dāng)時(shí)間差大于24并且不為Infinity的時(shí)候,便重新設(shè)置時(shí)間器,讓其在delay ms后執(zhí)行,減少性能的消耗,若為Infinity,表示沒(méi)有時(shí)間事件等待調(diào)用,停止了遞歸,否則,delay小于24ms,遞歸調(diào)用d3_timer_frame。

那么為什么為24ms呢?我們知道瀏覽器的最佳動(dòng)畫幀是60fbps,算起來(lái)每一幀的間隔為1000/60 = 16.7ms,所以如果使用setTimeout實(shí)現(xiàn)動(dòng)畫針的話,d3選擇的時(shí)間間隔是17ms,因?yàn)樘〉脑挄?huì)出現(xiàn)掉幀的情況,那么這個(gè)和24有什么關(guān)系呢?為什么要設(shè)定為24呢?我也不清楚...在github上面提交了issues,不知道會(huì)不會(huì)有人解答,好緊張。
關(guān)于timer的一些擴(kuò)展:
timer實(shí)現(xiàn)永久動(dòng)畫
作者的實(shí)現(xiàn)

早上提交的issue下午原作者就給了回復(fù),不過(guò)作者的解釋就尷尬了,大概的意思就是由于setTimeout的不穩(wěn)定和不準(zhǔn)確,存在一定的延遲,所以在設(shè)定這個(gè)值的時(shí)候也是拍腦袋設(shè)置的,值剛好在16.7到33.4之間,并回復(fù)說(shuō)左右偏移都不會(huì)有什么影響就對(duì)了。

[d3關(guān)于number 的方法:formatPrefix 和 round]()

提供了將number轉(zhuǎn)化為特定格式的字符串方法,基于正則表達(dá)做匹配,然后對(duì)應(yīng)地做轉(zhuǎn)化。這部分的實(shí)現(xiàn)比較瑣碎,就沒(méi)去仔細(xì)研究了,有興趣的可以看看。

[d3.time]()

同樣的,將d3.time初始化為一個(gè)空對(duì)象,并且將window.Date對(duì)象設(shè)置為私有變量:d3_date = Date
萬(wàn)物皆為我所用!
首先我們要了解Date的UTC函數(shù),UTC() 方法可根據(jù)世界時(shí)返回 1970 年 1 月 1 日 到指定日期的毫秒數(shù)。
然后來(lái)看這個(gè)函數(shù):

  function d3_date_utc() {
    this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
  }

這個(gè)函數(shù)是一個(gè)構(gòu)造函數(shù),當(dāng)我們new d3_date_utc(xxx)的時(shí)候,它會(huì)創(chuàng)建一個(gè)日期對(duì)象,并根據(jù)我們傳入的參數(shù)數(shù)量去創(chuàng)建,如果我們傳入的參數(shù)多余1個(gè),那么很顯然我們傳入的是年月日這些參數(shù),那么便調(diào)用 Date.UTC.apply去返回時(shí)間戳,如果參數(shù)只有一個(gè)的話,那就直接返回咯,那么參數(shù)為0會(huì)怎么樣?
我們可以實(shí)踐下,相當(dāng)于new Date(undefined),返回的結(jié)果是 Invalid Date的Date對(duì)象。
為什么能肯定是Date對(duì)象呢,我們使用instanceof Date去測(cè)試,發(fā)現(xiàn)結(jié)果為true,那么當(dāng)我們打印出來(lái)為什么為Invalid Date呢,很明顯,它調(diào)用了 toString方法或者valueOf()方法,經(jīng)過(guò)測(cè)試是toString方法,valueOf方法返回的是NaN。
好了,擴(kuò)展就到這里,繼續(xù)看下去,
有了構(gòu)造函數(shù),那么怎么可以沒(méi)有原型對(duì)象呢,來(lái)了:

d3_date_utc.prototype = {
  getDate: function() {
    return this._.getUTCDate();
  ,
  getDate: function() {
    return this._.getUTCDay();
  },
  ...
}

可以看到,D3封裝了原始Date對(duì)象的一些方法,例如getDay和GetHours等,它不適用原生的Date.getDay
等,而是使用getUTCDay去拿,那么這兩者有什么不一樣嗎?
當(dāng)你new一個(gè)Date對(duì)象的時(shí)候,返回的是本地的時(shí)間,注意,是你所在時(shí)區(qū)的時(shí)間哦,所以假設(shè)你現(xiàn)在的時(shí)間是
Tue Jul 19 2016 14:44:19 GMT+0800 (中國(guó)標(biāo)準(zhǔn)時(shí)間)
那么當(dāng)你使用getHours的時(shí)候,返回的時(shí)間是14,但是,當(dāng)你使用getUTCHours的時(shí)候,它返回的是全球的時(shí)間,什么叫全球?請(qǐng)參考MDN上關(guān)于這個(gè)函數(shù)的解釋:

The?**getUTCHours()

**?method returns the hours in the specified date according to universal time.

它的意思是會(huì)參考0時(shí)區(qū)的時(shí)間來(lái)給你時(shí)間,由于我們所處的地方(中國(guó))是在8時(shí)區(qū),所以在0時(shí)區(qū)比我們這里早8個(gè)小時(shí),所以他們那邊現(xiàn)在還是早晨8點(diǎn)正在洗臉?biāo)⒀莱栽绮汀?/p>

所以這個(gè)對(duì)象封裝了Date對(duì)象的UTC方法,變成一個(gè)全球流的時(shí)間器,然后它的方法不再需要添加UTC這個(gè)名字就可以調(diào)用了,其實(shí)我們也可以做到。

接下來(lái)是幾個(gè)函數(shù)的聲明和定義:

function d3_time_interval(local, step, number) {
  fucntion round(date) {}
  function ceil(date) {}
  function offset(date, k) {}
  function range(t0, t1, dt) {}
  function range_utc(t0, t1, dt) {}
    local.floor = local;
    local.round = round;
    local.ceil = ceil;
    local.offset = offset;
    local.range = range;
    var utc = local.utc = d3_time_interval_utc(local);
    utc.floor = utc;
    utc.round = d3_time_interval_utc(round);
    utc.ceil = d3_time_interval_utc(ceil);
    utc.offset = d3_time_interval_utc(offset);
    utc.range = range_utc;
    return local;
}

暫時(shí)不看這個(gè)函數(shù)里面的函數(shù)是做什么的,首先d3_time_interval這個(gè)函數(shù)接受三個(gè)參數(shù),然后對(duì)傳入的local參數(shù),我們給了它五個(gè)方法,分別是我們定義的五個(gè)方法,然后又給local定義個(gè)utc的屬性,這個(gè)屬性還額外擁有五個(gè)方法,最后返回了這個(gè)local對(duì)象,可以看出來(lái)這個(gè)函數(shù)是一個(gè)包裝器,對(duì)傳入的local對(duì)象進(jìn)行包裝,讓它擁有固定的方法,接下來(lái)看下一個(gè)函數(shù):

  function d3_time_interval_utc(method) {
    return function(date, k) {
      try {
        d3_date = d3_date_utc;
        var utc = new d3_date_utc();
        utc._ = date;
        return method(utc, k)._;
      } finally {
        d3_date = Date;
      }
    };
  }

一個(gè)返回函數(shù)的函數(shù),這是在類庫(kù)里面經(jīng)常見(jiàn)到的用法,我經(jīng)常被它給迷醉,能用的好能創(chuàng)造出很奇妙的作用??创a我們?nèi)匀徊恢谰唧w是做什么的,不急,繼續(xù)往下看

d3_time.year = d3_time_interval(function(date) {
    date = d3_time.day(date);
    date.setMonth(0, 1);
    return date;
  }, function(date, offset) {
    date.setFullYear(date.getFullYear() + offset);
  }, function(date) {
    return date.getFullYear();
  });

我們知道d3_time就是d3.time對(duì)象,是一個(gè)空對(duì)象目前,這里開始給它添加屬性了,并且調(diào)用了上面的d3_time_interval函數(shù),向它傳入了三個(gè)函數(shù),d3沒(méi)有注釋就是慘,完全不知道傳入的參數(shù)類型,這點(diǎn)以后寫代碼需要注意

    function round(date) {
      // d0是是初始化的date的本地日期,時(shí)間為默認(rèn)的凌晨或者時(shí)區(qū)時(shí)間,d1是本地時(shí)間加了一個(gè)單位,而date則相對(duì)于這兩個(gè)時(shí)間取最近的,這就是時(shí)間的round方法。
      var d0 = local(date), d1 = offset(d0, 1);
      return date - d0 < d1 - date ? d0 : d1;
    }
    // 對(duì)傳入的時(shí)間進(jìn)行加一個(gè)單位
    function ceil(date) {
      step(date = local(new d3_date(date - 1)), 1);
      return date;
    }
    // 對(duì)傳入的時(shí)間做加減法
    function offset(date, k) {
      step(date = new d3_date(+date), k);
      return date;
    }

后面的一部分主要有針對(duì)傳入的參數(shù)對(duì)時(shí)間進(jìn)行不同的格式化等等

d3.geo

d3的圖形化算法的實(shí)現(xiàn),這一部分涉及到了幾何、數(shù)據(jù)結(jié)構(gòu)等方面的知識(shí),大概三千多行的代碼量,基本是各種符號(hào)和公式,沒(méi)有注釋的話看起來(lái)和天書沒(méi)有區(qū)別,需要多帶帶花時(shí)間來(lái)慢慢看了。

[d3.interpolate]()

接下來(lái)的是d3關(guān)于不同類型的插值的實(shí)現(xiàn)
首先是顏色:d3.interpolateRgb

  d3.interpolateRgb = d3_interpolateRgb;
  function d3_interpolateRgb(a, b) {
    a = d3.rgb(a);
    b = d3.rgb(b);
    var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
    return function(t) {
      return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
    };
  }

顏色的插值實(shí)現(xiàn)其實(shí)沒(méi)有什么技巧,就是分別取rgb三個(gè)值做插值,然后再將三種顏色合并為一種顏色,以后可以自己實(shí)現(xiàn)一個(gè)顏色插值器了。

除了顏色,還有對(duì)對(duì)象的插值實(shí)現(xiàn):

  d3.interpolateObject = d3_interpolateObject;
  function d3_interpolateObject(a, b) {
    var i = {}, c = {}, k;
    for (k in a) {
      if (k in b) {
        i[k] = d3_interpolate(a[k], b[k]);
      } else {
        c[k] = a[k];
      }
    }
    for (k in b) {
      if (!(k in a)) {
        c[k] = b[k];
      }
    }
    return function(t) {
      for (k in i) c[k] = i[k](t);
      return c;
    };
  }

遍歷兩個(gè)對(duì)象,用i存儲(chǔ)兩個(gè)對(duì)象都有的屬性的值的插值,用c來(lái)存儲(chǔ)兩個(gè)對(duì)象各自獨(dú)有的屬性值,最后合并i到c中,完事。

D3還實(shí)現(xiàn)了字符串的插值,不過(guò)不是對(duì)字符的插值,而是檢測(cè)字符串的數(shù)字做插值,對(duì)傳入的參數(shù)a和b,每次檢測(cè)到a中的數(shù)字,便到b中找對(duì)應(yīng)的數(shù)字然后做插值,如果a的數(shù)字找不到對(duì)應(yīng),就會(huì)被拋棄,a中的其他字符串都會(huì)被拋棄,只保留b中的字符串。

/[-+]?(?:d+.?d*|.?d+)(?:[eE][-+]?d+)?/g

匹配數(shù)字的正則表達(dá)式

除了d3本身提供的這些插值器外,我們也可以自定義插值器

  d3.interpolate = d3_interpolate;
  function d3_interpolate(a, b) {
    var i = d3.interpolators.length, f;
    while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
    return f;
  }
  d3.interpolators = [ function(a, b) {
    var t = typeof b;
    return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb(|hsl()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
  } ];

d3會(huì)自己循環(huán)遍歷插值器隊(duì)列,直到有插值器返回了對(duì)應(yīng)的對(duì)象。

[d3.ease]()

d3.ease實(shí)現(xiàn)了多種動(dòng)畫函數(shù),開發(fā)者可以根據(jù)自身的需要調(diào)用不同的動(dòng)畫效果,具體的示例可以參考這篇文章

d3.transform

d3只涉及到平面上的轉(zhuǎn)化,tranform包含四個(gè)屬性:rotate、translate、scale、skew(斜交),transform也是一個(gè)變化,所以也可以作為插值器,關(guān)于csstransform的文檔

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/90847.html

相關(guān)文章

  • d3-force 力導(dǎo)圖 源碼解讀與原理分析【 : 四叉樹(一)】

    摘要:我們?cè)谏衔脑创a解析發(fā)現(xiàn)版的節(jié)點(diǎn)碰撞采用四叉樹進(jìn)行了優(yōu)化。那么版本的力導(dǎo)圖具體和版的有何不同點(diǎn)呢,四叉樹又如何優(yōu)化碰撞校驗(yàn)的呢原文鏈接被重命名為。性能的提高歸功于的新的四叉樹。 我們?cè)谏衔脑创a解析發(fā)現(xiàn)v4版的節(jié)點(diǎn)碰撞采用四叉樹進(jìn)行了優(yōu)化。那么V4版本的力導(dǎo)圖具體和v3版的有何不同點(diǎn)呢,四叉樹又如何優(yōu)化碰撞校驗(yàn)的呢? v3-force VS v4-force https://github...

    pepperwang 評(píng)論0 收藏0
  • D3js之入門

    摘要:子選集直接通過(guò)返回,和子選集分別通過(guò)和返回。截止上面也并不是非得用不可,就是一些插入操作,原生也是可以實(shí)現(xiàn)的。 相對(duì)于echart, highchart等其他圖表庫(kù)算是一個(gè)比較底層的可視化工具,簡(jiǎn)單來(lái)講他不提供任何一種現(xiàn)成的圖表,所有的圖表都是我們?cè)谒膸?kù)里挑選合適的方法構(gòu)建而成。 基于上面的理解,d3無(wú)疑會(huì)復(fù)雜很多但是也強(qiáng)大自由的多,另外因?yàn)閐3基于svg所以修改圖表的樣式和結(jié)構(gòu)也會(huì)...

    guqiu 評(píng)論0 收藏0
  • D3 代碼解構(gòu)

    摘要:查詢網(wǎng)上關(guān)于這三種格式的定義是如上所示,不過(guò)的實(shí)現(xiàn)不太一樣,是可以定義為任何一種分隔符,但是分隔符只能為長(zhǎng)度為的字符,是以半角符逗號(hào)作為分割符,則是以斜杠作為分隔符。 D3是一個(gè)數(shù)據(jù)可視化的javascript庫(kù),相對(duì)于highchart和echarts專注圖表可視化的庫(kù),D3更適合做大數(shù)據(jù)處理的可視化,它只提供基礎(chǔ)的可視化功能,靈活而豐富的接口讓我們能開發(fā)出各式各樣的圖表。 D3代碼...

    AbnerMing 評(píng)論0 收藏0
  • 交互式數(shù)據(jù)可視化-D3.js()選擇集和數(shù)據(jù)

    摘要:相關(guān)的函數(shù)有兩個(gè)和的工作過(guò)程的方法很簡(jiǎn)單,使用的也比較少。的工作過(guò)程能將數(shù)據(jù)各項(xiàng)分別綁定到選擇的元素集上。當(dāng)數(shù)組長(zhǎng)度與元素?cái)?shù)量不一致時(shí),同樣能夠處理。多出的元素在最后。 選擇集 select和selectAll類似jquery: d3.select(body) d3.select(.body) d3.select(#body) d3.selectAll(...

    leanote 評(píng)論0 收藏0
  • 交互式數(shù)據(jù)可視化-D3.js()選擇集和數(shù)據(jù)

    摘要:相關(guān)的函數(shù)有兩個(gè)和的工作過(guò)程的方法很簡(jiǎn)單,使用的也比較少。的工作過(guò)程能將數(shù)據(jù)各項(xiàng)分別綁定到選擇的元素集上。當(dāng)數(shù)組長(zhǎng)度與元素?cái)?shù)量不一致時(shí),同樣能夠處理。多出的元素在最后。 選擇集 select和selectAll類似jquery: d3.select(body) d3.select(.body) d3.select(#body) d3.selectAll(...

    褰辯話 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<