D 3ソースコードの解体

13637 ワード

D 3はデータ可視化のjavascriptライブラリであり、highchartやechartsがグラフ可視化に専念するライブラリに比べて、D 3はビッグデータ処理の可視化に適しており、基礎的な可視化機能のみを提供し、柔軟で豊富なインタフェースで様々なグラフを開発することができます.
D 3コードバージョン:「3.5.17」D 3のコードスケルトンは比較的簡潔で、jqueryよりも読むのに適しており、新しい関数発見声明が千里の外にあるのを見ずに、コードの中で飛び回るのを見ることができます.
ないぶコードりゅうすいせん
  • 基本的な数学計算:最小最大、平均中値分散、偏分値......
  • 各種集合タイプ:map、set、nest......
  • 集合の操作、方法:text、html、append、insert、remove
  • d 3のdragging
  • グラフィック動作
  • ……

  • 自己実行匿名関数
    まず典型的な自己実行匿名関数,対外提供インタフェース,隠蔽実装方式,私有変数の実現などの機能である.
    !function() {
       // code here
    }()
    

    ここでは感嘆符を使いますが、カッコを使うのと同じ役割を果たしています.関数宣言を関数式に変えて、関数の自己実行呼び出しを容易にすることです.試してみてください.
    function() {
      console.log('no console')
    }()
    

    これは、JSが関数宣言と関数呼び出しの混用を禁止し、括弧、論理演算子(+、-、&、|)、カンマ、newなどが関数宣言を関数式に変更し、自己実行できるためです.これらの変換方法についてどの方が速いかを調べたことがある人もいますが、このブログを見ると、たぶんnewが一番遅いのではないでしょうか.括弧を使うよりも基本的に一番速いです.感嘆符はかえって性能が普通なので、どちらを使っても違いはありません.もちろん、記号を省くには感嘆符を使うことができます.
    対外曝露私有変数d 3
    d 3の場合、プライベート変数オブジェクトを作成し、拡張し、最終的に露出する
    var d3 = {
      version: '3.5.17'
    };
    
    // code here
    //...
    
    if (typeof define === 'function' && defind.amd) 
      this.d3 = d3, define(d3);
    else if (typeof module == 'object' && module.exports)
      module.exports = d3;
    else
      this.d3 = d3;
    

    1つ目は非同期モジュールロードモード、2つ目は同期モジュールロードまたはecma 6のimportメカニズム、3つ目はd 3をグローバル変数に設定します.匿名自己実行関数では、関数の環境がグローバルであるため、this==windowです.
    共通メソッドの作成
    d 3のメソッドは、d 3オブジェクトに属する属性です.
    d3_xhr( url, mimeType, response, callback) {
      // code 
    }
    d3.json = function(url, callback) {
      return d3_xhr(url, 'application/json', d3_json, callback);
    };
    function d3_json(request) {
      return JSON.parse(request.responseText);
    }
    

    あまりよくないのはd 3がネーミングでどれがプライベート関数なのか、どれがパブリック関数なのかを区別していないことですが、オブジェクトを作成することでインタフェースを露出するオブジェクトにとっては、区別する必要もないのではないでしょうか.
    よく使われる原生関数を抽出する
    var d3_arraySlice = [].slice, d3_array = function(list) {
     return d3_arraySlice.call(list);
    };
    var d3_document = this.document;
    

    sliceメソッドを抽出し、配列のコピーを生成するために使用します.sliceは元の配列を切断するのではなく、配列のコピーを返しますが、浅いコピーであることに注意してください.配列内のオブジェクト、配列については、単純な参照なので、元の配列内のオブジェクトや数組の変更はコピーに影響します.
    一部のコードは読み取りを実現
    d 3_をテストするためのセグメントarrayの関数ですが、どのような場合にd 3_が書き換えられますか?array関数は?【line15】
    if (d3_document) {
      var test = d3_array(d3_document.documentElement.childNodes);
      console.log(test);
      try {
        d3_array(d3_document.documentElement.childNodes)[0].nodeType;
      } catch (e) {
        console.log('catch error:', e);
        d3_array = function(list) {
          var i = list.length, array = new Array(i);
          while (i--) array[i] = list[i];
          return array;
        };
      }
    }
    

    前からd 3_がわかりますarrayは受信配列のコピーを取得し、tryでdocumentのサブノードの最初のサブ要素をテストするのに使用できます.一般的にはheaderという要素です.w 3 cを調べるとnodeTypeが1でhtml elementを表していることがわかります.ブラウザの環境かどうかをテストするような気がしますが、そうでなければ自分で書いた関数の意味に変えますか.それとも少数のブラウザと互換性を持つためですか?
    オブジェクト属性の互換性を設定しますか?【line 30】
    if (d3_document) {
      try {
        d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
      } catch (error) {
        var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
        d3_element_prototype.setAttribute = function(name, value) {
          d3_element_setAttribute.call(this, name, value + "");
        };
        d3_element_prototype.setAttributeNS = function(space, local, value) {
          d3_element_setAttributeNS.call(this, space, local, value + "");
        };
        d3_style_prototype.setProperty = function(name, value, priority) {
          d3_style_setProperty.call(this, name, value + "", priority);
        };
      }
    }
    

    ブラウザにまたがるかドキュメントにまたがるかの検出はしばらく分からないが,検討する必要がある.
    配列最小値関数【line 53】
      d3.min = function(array, f) {
        var i = -1, n = array.length, a, b;
        if (arguments.length === 1) {
          while (++i < n) if ((b = array[i]) != null && b >= b) {
            a = b;
            break;
          }
          while (++i < n) if ((b = array[i]) != null && a > b) a = b;
        } else {
          while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
            a = b;
            break;
          }
          while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
        }
        return a;
      };
    

    まず最初に比較可能な要素を取得し、テストしたところ、b>=bに対して、bが数字であれ、文字列であれ、配列であれ、オブジェクトであれ、比較可能であることが分かった.だからNaNという問題のある数字を排除するためだ.
    d 3のシャッフル方法
     d3.shuffle = function(array, i0, i1) {
       if ((m = arguments.length) < 3) {
         i1 = array.length;
         if (m < 2) i0 = 0;
       }
       var m = i1 - i0, t, i;
       while (m) {
         i = Math.random() * m-- | 0;
         t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
         console.log(i, m);
       }
       return array;
     };
    

    d 3で使用されるトランプアルゴリズムは、Fisher-Yates shuffleに関する文章を参考にすることができ、その進化の構想は簡単で優雅である.
    通常の考え方は、元の配列からランダムに1つの要素を選択するたびに、すでに選択されているかどうかを判断し、そうであれば削除して新しい配列に入れるのではなく、そうでなければ欠点を再選択する:後になるほど繰り返し選択する確率が大きくなり、新しい配列を入れる時間が長くなる.
    最適化繰り返しを防止するために、m枚目のカードをランダムに選択するたびに、mは元の長さnから徐々に減少する値の欠点である:毎回残りの配列の中のカードのコンパクトな配列を再取得し、実際の効率はn 2である.
    その場でランダムにシャッフルすることを再最適化し,配列の後部を新しいシャッフルを格納した後の場所とし,前の部分をシャッフル前の場所とし,効率をnに向上させる.
    d3.map内蔵オブジェクト【line 291】について
      function d3_class(ctor, properties) {
        for (var key in properties) {
          Object.defineProperty(ctor.prototype, key, {
            value: properties[key],
            enumerable: false
          });
        }
      }
      d3.map = function(object, f) {
        var map = new d3_Map();
        if (object instanceof d3_Map) {
          object.forEach(function(key, value) {
            map.set(key, value);
          });
        } else if (Array.isArray(object)) {
          var i = -1, n = object.length, o;
          if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o);
        } else {
          for (var key in object) map.set(key, object[key]);
        }
        return map;
      };
      function d3_Map() {
        this._ = Object.create(null);
      }
      var d3_map_proto = "__proto__", d3_map_zero = "\x00";
      d3_class(d3_Map, {
        has: d3_map_has,
        get: function(key) {
          return this._[d3_map_escape(key)];
        },
        set: function(key, value) {
          return this._[d3_map_escape(key)] = value;
        },
        remove: d3_map_remove,
        keys: d3_map_keys,
        values: function() {
          var values = [];
          for (var key in this._) values.push(this._[key]);
          return values;
        },
        entries: function() {
          var entries = [];
          for (var key in this._) entries.push({
            key: d3_map_unescape(key),
            value: this._[key]
          });
          return entries;
        },
        size: d3_map_size,
        empty: d3_map_empty,
        forEach: function(f) {
          for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
        }
      });
    

    **enumerable**については、ここでd 3_を使用します.Mapはオブジェクトのコンストラクション関数として、d 3_クラスはクラスをカプセル化し、ここでObjectを呼び出した.definePropertyでプロパティと値を設定します.ここにはenumerable:falseのプロパティがあります.このプロパティの列挙性をfalseに設定し、プロパティが一般的な遍歴にあるようにします(for...in...)などでは取得できませんが、objを通過することができます.keyは、enumerableの値にかかわらず、オブジェクト自体のすべての属性を取得する必要がある場合にObjectを使用することができることを直接取得する.getownPropertyNameメソッド.なぜこのプロパティを設定するのですか?d 3_に対してMapがオブジェクトを構築する際には、emptyという方法が導入されています.この関数の実装を見てみましょう.
      function d3_map_empty() {
        for (var key in this._) return false;
        return true;
      }
    

    見終わってから前述したenumerableをfalseに設定した属性はforループでは無視されますが、これでは追加条件を書いて内蔵属性かどうかを判断する必要がなく、素晴らしい実現方法です.
    データバインド関数data!!!【line 832】
    D 3独自のデータとグラフィック領域を結びつける方法を覚えていますか?アクセス(enter)--更新(update)--終了(exit)モード
    d3.selectAll('div')
      .data(dataSet)
      .enter()
      .append('div')
      ;
    d3.selectAll('div')
      .data(data)
      .style('width', function(d) {
         return d + 'px';
      })
     ;
    d3.selectAll('div')
      .data(newDataSet)
      .exit()
      .remove()
      ;
    

    ここでは、data、enter、exitの3つの関数について説明します.操作を行う前に、dataを呼び出してデータをバインドし、enterまたはexitを呼び出してグラフィック領域を操作する必要があります.では、内部実装原理はどうなっているのでしょうか.次のコードを見てみると、はっとします.
      d3_selectionPrototype.data = function(value, key) {
        var i = -1, n = this.length, group, node;
        if (!arguments.length) {
          value = new Array(n = (group = this[0]).length);
          while (++i < n) {
            if (node = group[i]) {
              value[i] = node.__data__;
            }
          }
          return value;
        }
        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];
            }
          }
          enterNodes.update = updateNodes;
          enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
          enter.push(enterNodes);
          update.push(updateNodes);
          exit.push(exitNodes);
        }
        var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
        if (typeof value === "function") {
          while (++i < n) {
            bind(group = this[i], value.call(group, group.parentNode.__data__, i));
          }
        } else {
          while (++i < n) {
            bind(group = this[i], value);
          }
        }
        update.enter = function() {
          return enter;
        };
        update.exit = function() {
          return exit;
        };
        return update;
      };
    

    データバインド関数dataは最終的に変数updateを返し、この変数updateは最初は空の集合であり、d 3の集合操作方法を有し、その後data関数はbind関数を呼び出して入力されたパラメータを項目ごとにバインドし、updateセットのコラボレーション自体とenter集合とexit集合を獲得し、最後にupdate上で関数enterとexitをバインドし、ユーザがdataを呼び出した後、enterとexitを再び呼び出して他の2つのセットを取得できるようにする.
    後期デバグの足跡について【1167】
    d 3もバグがある場合は、このときバグを修復してから更新する必要があります.次回修正したバグを見つけやすいように、コードに名前を付けるのが良い方法です.
    var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
    

    D 3の色空間【line 1582】
    D 3は、rgb、hslに頻繁に接触しているほか、lab、hcl、cubehelixの5つの色表現をサポートしています.これらの間にrgbに変換することができます.内部の実現方法は参考に値します.
      function d3_hsl_rgb(h, s, l) {
        var m1, m2;
        h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
        s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
        l = l < 0 ? 0 : l > 1 ? 1 : l;
        m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
        m1 = 2 * l - m2;
        function v(h) {
          if (h > 360) h -= 360; else if (h < 0) h += 360;
          if (h < 60) return m1 + (m2 - m1) * h / 60;
          if (h < 180) return m2;
          if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
          return m1;
        }
        function vv(h) {
          return Math.round(v(h) * 255);
        }
        return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
      }
    

    csv、dsv、tsvストレージ方式について
    コードを見るメリットの一つは、普段は使わないインタフェースがたくさん見られ、何をしているのかを自発的に知ることです.
    csvフォーマットは、テキストデータの処理と伝送の過程で、有名なCSVフォーマット(comma-separated values)を採用するなど、複数のフィールドをセパレータで接続する必要があることがよくあります.CSVファイルの各行はレコードであり、各行の各フィールドはカンマ','で区切られている.dsvフォーマットはカンマと二重引用符の2つの特殊な文字の存在により,文字列のsplit操作でCSVファイルを簡単に解析することはできず,CSV構文解析を行う必要がある.ライブラリの形式でカプセル化したり、既存のライブラリを直接採用したりすることができますが、さまざまなプラットフォームのライブラリの豊富さの違いは大きく、split、joinのような簡単な文字列操作に比べて複雑です.この目的のためにCSVフォーマットに基づいてDSV(double separated values)フォーマットを設計した.DSVフォーマットの主な設計目的はCSV構文を簡略化するためであり,生成と解析はreplace,join,splitの3つの基本的な文字列操作のみを必要とし,構文解析を必要としない.
    DSVの文法は非常に簡単で、以下の2点しか含まれていません.
  • は、フィールド区切り記号
  • として二重縦線'|′を通過する.
  • フィールド値の'|'を'|'に置き換えるエスケープ
  • を行う
    **tsv形式**TSVは、Tab-separated valuesの略、すなわちタブ区切り値です.
    クエリーネットワークでは、この3つのフォーマットの定義は上記のようになっていますが、d 3の実装は異なり、dsvは任意の区切り記号として定義できますが、区切り記号は長さ1の文字しか使用できません.csvは半角記号カンマを分割記号とし、tsvはスラッシュを区切り記号として使用します.
    d3.geo【line 2854】
    geoはd 3のグラフィック処理実装であり、コアコードであるべきであるが、4.0バージョンになると依存に分割され、d 3は存在しない.geo.pathですが、d 3に変更します.geoPathの方法で引用する
    まとめ
    バージョン3のd 3は9千行余りのコードで、バージョン4のd 4は依存分割を行い、すべての依存が導入されると圧縮しないと16000行を超え、全体的にスケルトンを見たいならバージョン3は比較的明確で、バージョン4は各部分の実現を深く研究するのに適しており、依存のためにはっきり分割され、互いに独立している.d 3全体のスケルトンを初歩的に理解した後,コード関数実装に深く入り込んでその奥義を研究することができる.