D3原始碼解讀系列之Chord

arlendp2012發表於2019-11-01

d3的chord部分用於將關係或網路流繪製成一種圓形佈局。

D3原始碼解讀系列之Chord

這部分內容分為兩個方面,一方面是構造一個弦佈局,另一方面是構造一個產生帶狀圖形的生成器。

chord

chord的原始碼如下:

function chord() {
    var padAngle = 0,
        sortGroups = null,
        sortSubgroups = null,
        sortChords = null;

    function chord(matrix) {
      var n = matrix.length,
          //matrix中每組數的總和
          groupSums = [],
          groupIndex = range(n),
          subgroupIndex = [],
          chords = [],
          groups = chords.groups = new Array(n),
          subgroups = new Array(n * n),
          k,
          x,
          x0,
          dx,
          i,
          j;

      // 計算每組數的和以及所有數值的總和
      k = 0, i = -1; while (++i < n) {
        x = 0, j = -1; while (++j < n) {
          x += matrix[i][j];
        }
        groupSums.push(x);
        subgroupIndex.push(range(n));
        k += x;
      }

      // sortGroups函式根據每組資料和的大小對groupIndex進行排序
      if (sortGroups) groupIndex.sort(function(a, b) {
        return sortGroups(groupSums[a], groupSums[b]);
      });

      // sortSubgroups函式根據每個資料大小在該組內進行索引的排序
      if (sortSubgroups) subgroupIndex.forEach(function(d, i) {
        d.sort(function(a, b) {
          return sortSubgroups(matrix[i][a], matrix[i][b]);
        });
      });

      // 計算除去padAngle之後的單位弧度(每單位數值對應的弧度)
      k = max$1(0, tau$3 - padAngle * n) / k;
      dx = k ? padAngle : tau$3 / n;

      // 計算每個資料對應的startAngle和endAngle
      x = 0, i = -1; while (++i < n) {
        x0 = x, j = -1; while (++j < n) {
          var di = groupIndex[i],
              dj = subgroupIndex[di][j],
              v = matrix[di][dj],
              // startAngle
              a0 = x,
              // 計算endAngle
              a1 = x += v * k;
          //記錄matrix中每個資料在弦圖中的資訊
          subgroups[dj * n + di] = {
            index: di,
            subindex: dj,
            startAngle: a0,
            endAngle: a1,
            value: v
          };
        }
        //記錄matrix中每組資料在弦圖中的資訊
        groups[di] = {
          index: di,
          startAngle: x0,
          endAngle: x,
          value: groupSums[di]
        };
        //考慮弦圖中每組之間的間距
        x += dx;
      }

      // 產生source和target
      i = -1; while (++i < n) {
        j = i - 1; while (++j < n) {
          var source = subgroups[j * n + i],
              target = subgroups[i * n + j];
          if (source.value || target.value) {
            //將value大的設定為source,小的設定為target
            chords.push(source.value < target.value
                ? {source: target, target: source}
                : {source: source, target: target});
          }
        }
      }

      return sortChords ? chords.sort(sortChords) : chords;
    }
    // 設定相鄰組之間的間距,以弧度形式表示
    chord.padAngle = function(_) {
      return arguments.length ? (padAngle = max$1(0, _), chord) : padAngle;
    };
    // 對groupIndex進行排序
    chord.sortGroups = function(_) {
      return arguments.length ? (sortGroups = _, chord) : sortGroups;
    };
    // 對subgroupIndex進行排序
    chord.sortSubgroups = function(_) {
      return arguments.length ? (sortSubgroups = _, chord) : sortSubgroups;
    };
    //對chords陣列進行排序,影響的是chord的層疊順序,兩根弦重疊,重疊部分後面的會覆蓋掉前面的
    chord.sortChords = function(_) {
      return arguments.length ? (_ == null ? sortChords = null : (sortChords = compareValue(_))._ = _, chord) : sortChords && sortChords._;
    };

    return chord;
}
複製程式碼

chord函式最終會得到一個包含多組sourcetarget物件的陣列以及groups陣列,通過將該結果傳遞給d3.arc來繪製弦圖外層的圓弧,而其內部的帶狀圖則通過d3.ribbon來實現。

ribbon

用於繪製弦圖中間部分表示各塊之間聯絡的帶狀區域。

  function ribbon() {
    var source = defaultSource,
        target = defaultTarget,
        radius = defaultRadius$1,
        startAngle = defaultStartAngle,
        endAngle = defaultEndAngle,
        context = null;

    function ribbon() {
      var buffer,
          argv = slice$5.call(arguments),
          //source物件
          s = source.apply(this, argv),
          //target物件
          t = target.apply(this, argv),
          //帶狀圖形中弧線的半徑
          sr = +radius.apply(this, (argv[0] = s, argv)),

          sa0 = startAngle.apply(this, argv) - halfPi$2,
          sa1 = endAngle.apply(this, argv) - halfPi$2,
          sx0 = sr * cos(sa0),
          sy0 = sr * sin(sa0),
          tr = +radius.apply(this, (argv[0] = t, argv)),
          ta0 = startAngle.apply(this, argv) - halfPi$2,
          ta1 = endAngle.apply(this, argv) - halfPi$2;
      //構造path物件,用於儲存路徑
      if (!context) context = buffer = path();
      //移動到startAngle對應的起始點
      context.moveTo(sx0, sy0);
      //向endAngle位置畫弧線
      context.arc(0, 0, sr, sa0, sa1);
      //判斷source和target是否是同個位置
      if (sa0 !== ta0 || sa1 !== ta1) { // TODO sr !== tr?
        // 從source的endAngle位置繪製貝塞爾曲線至target的startAngle處
        context.quadraticCurveTo(0, 0, tr * cos(ta0), tr * sin(ta0));
        //target的startAngle繪製圓弧至endAngle位置
        context.arc(0, 0, tr, ta0, ta1);
      }
      //以(0, 0)為控制點繪製貝塞爾曲線至startAngle位置
      context.quadraticCurveTo(0, 0, sx0, sy0);
      context.closePath();

      if (buffer) return context = null, buffer + "" || null;
    }

    ribbon.radius = function(_) {
      return arguments.length ? (radius = typeof _ === "function" ? _ : constant$11(+_), ribbon) : radius;
    };

    ribbon.startAngle = function(_) {
      return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$11(+_), ribbon) : startAngle;
    };

    ribbon.endAngle = function(_) {
      return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$11(+_), ribbon) : endAngle;
    };

    ribbon.source = function(_) {
      return arguments.length ? (source = _, ribbon) : source;
    };

    ribbon.target = function(_) {
      return arguments.length ? (target = _, ribbon) : target;
    };
    //設定當前路徑上下文
    ribbon.context = function(_) {
      return arguments.length ? ((context = _ == null ? null : _), ribbon) : context;
    };

    return ribbon;
}
複製程式碼

相關文章