深入解析d3弦圖


記得上次看d3應該是1年前的事情了,當時還一邊看一邊寫了d3(v5.7)的一個學習筆記:https://www.cnblogs.com/eco-just/tag/d3/

后來轉戰three.js就沒繼續研究了(其實也是感覺api層面的東西也沒有深入研究的必要,何況后續項目也不會用到這些東西)。

期間也有同行通過博客問過弦圖的問題,出於種種原因吧,當時並沒有深入研究。

但是今天!我們就結合d3的3.5.16版本來深入解析一下d3的弦圖吧。(demo是找的簡書上這為同學的筆記:https://www.jianshu.com/p/4b44c708c2da

先上效果:

step1:根據數據初始化布局

code:

// 初始數據
    var city_name = [ "北京" , "上海" , "廣州" , "深圳" , "香港"  ];
    var population = [
              [ 1000,  3045 , 4567 , 1234 , 3714 ],
              [ 3214,  2000 , 2060 , 124  , 3234 ],
              [ 8761,  6545 , 3000 , 8045 , 647  ],
              [ 3211,  1067 , 3214 , 4000 , 1006 ],
              [ 2146,  1034 , 6745 , 4764 , 5000 ]
            ];

    // 弦布局初始化
    var chord_layout = d3.layout.chord()
                            .padding(0.03)
                            .sortSubgroups(d3.descending)
                            .matrix(population);

    // 獲取弦布局初始化后的數據
    var groups = chord_layout.groups();
    var chords = chord_layout.chords();

解析:

population數據表格化

  北京 上海 廣州 深圳 香港
北京 1000 3045 4567 1234 3714
上海 3214 2000 2060 124 3234
廣州 8761 6545 3000 8045 647
深圳 3211 1067 3214 4000 1006
香港 2146 1034 6745 4764 5000

先用d3.layout.chord()這個api傳入數據,初始化布局所需要的數據groups、chords;

groups數據5條,5個城市,根據population所占權重分配圓弧的大小,在上述數據上的反應就是startAngle和endAngle;

chords數據15條,5個城市選兩個(source,target),根據排列組合應該是5+4+3+2+1=15種(source,target可以相同);

step2:繪制畫布和計算內外圓半徑

// svg畫布
    var width = 600;
    var height = 600;
    var svg = d3.select(".d3content")
                .append("svg")
                .attr("width",width)
                .attr('height', height)
                .append("g")
                .attr('transform', 'translate(' + width/2 + "," + height/2 + ")");

    var color20 = d3.scale.category20();

    var innerRadius = width/2 * 0.7;
    var outerRadius = innerRadius * 1.1;

解析:

.d3content是畫布依賴的根元素dom,上述代碼將會在600X600的畫布上繪制接下來的弦圖;

step3:繪制外圓和文字

    var outer_arc = d3.svg.arc()
                        .innerRadius(innerRadius)
                        .outerRadius(outerRadius);
    //繪制外圓(5個城市)
    var g_outer = svg.append("g");
    g_outer.selectAll("path")
            .data(groups)
            .enter()
            .append("path")
            .style("fill",function(d) {
                return color20(d.index);
            })
            .style("stroke",function(d) {
                color20(d.index);
            })
            .attr("d",outer_arc)   // 此處調用了弧生成器
            ;
  //繪制文字 g_outer.selectAll(
"text") .data(groups) .enter() .append("text") .each(function(d,i) { // 對每個綁定的數據添加兩個變量 d.angle = (d.startAngle + d.endAngle) / 2; d.name = city_name[i]; }) .attr("dy",".35em") .attr('transform', function(d) { // 平移屬性 var result = "rotate(" + (d.angle*180/Math.PI) + ")"; result += "translate(0," + -1 * (outerRadius + 10) + ")"; if (d.angle > Math.PI * 3 / 4 && d.angle < Math.PI * 5 / 4 ) result += "rotate(180)"; return result; }) .text(function(d) { return d.name; });

效果圖:

注意上述有一句代碼:

.attr("d",outer_arc)   // 此處調用了弧生成器

對於每個path都會根據這個函數來繪制,而這個函數對於對應源碼里的d3.svg.arc,並且這里有個隱藏的東西:

.attr("d",out_arc),第二個參數執行的時候(他是一個函數),會將數據作為實參傳給他,於是到了源碼里:

d3.svg.arc = function() {
    var innerRadius = d3_svg_arcInnerRadius,
     outerRadius = d3_svg_arcOuterRadius, 
     cornerRadius = d3_zero, 
     padRadius = d3_svg_arcAuto, 
     startAngle = d3_svg_arcStartAngle, 
     endAngle = d3_svg_arcEndAngle, 
     padAngle = d3_svg_arcPadAngle;
    function arc() {
      var r0 = Math.max(0, +innerRadius.apply(this, arguments)), 
      r1 = Math.max(0, +outerRadius.apply(this, arguments)), 
      a0 = startAngle.apply(this, arguments) - halfπ, 
      a1 = endAngle.apply(this, arguments) - halfπ, 
      da = Math.abs(a1 - a0), 
      cw = a0 > a1 ? 0 : 1; if (r1 < r0) rc = r1, r1 = r0, r0 = rc; if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = [];   ..... }

這個arguments就是如下這樣的單條數據:

最終根據傳入的數據,加上一系列的邏輯處理返回了一個path節點的d屬性值,具體的判斷邏輯,我截圖一張供各位欣賞,如果你有興趣可以逐個去找他的函數:

最后,由這個屬性值繪制了一個path節點:

后面的繪制文字就不多說了,注意一點,回調函數形參里面的d是data(數據)的意思,i是index(索引)的意思。

step4:繪制內弦chord

    // 弦生成器
    var inner_chord = d3.svg.chord()
                            .radius(innerRadius);
                            console.log(inner_chord)

    // 添加g元素,接下來在這個元素里面繪制chord
    var g_inner = svg.append("g")
                    .attr("class","chord");

    g_inner.selectAll("path")
            .data(chords)
            .enter()
            .append("path")
            .attr("d",inner_chord)  // 調用弦的路徑值
            .style("fill",function(d,i) {
                return color20(d.source.index);
            })
            .style("opacity",1)
            ;

為了大家看得清晰,我把之前繪制的外圓注釋了:

解析:

同理,這里通過d3.svg.chord來繪制弦,依據的數據是:

同樣貼上源碼的主要部分:

d3.svg.chord = function() {
    var source = d3_source, 
     target = d3_target, 
     radius = d3_svg_chordRadius, 
     startAngle = d3_svg_arcStartAngle, 
     endAngle = d3_svg_arcEndAngle;
    function chord(d, i) {
      var s = subgroup(this, source, d, i), 
      t = subgroup(this, target, d, i);
      return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
    }

equals(s,t)判斷兩個端點是否相同來決定繪制的方式;

我們看到這里繪制路徑,主要用到了兩個函數arc()和curve();

   function arc(r, p, a) {
      return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
    }
    function curve(r0, p0, r1, p1) {
      return "Q 0,0 " + p1;
    }

關於svg的path繪制中各參數的含義,下面給一張圖,這里就不多說了:

所以弦主要就是由svg內置的弧繪制api來繪制的(普通的弧線/貝塞爾曲線)!

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM