2014-02-14 42 views
8

我正在製作餅圖模擬。我需要嘗試和設計相匹配,使標籤擠出水平線連接到切片刻度。這可能嗎?在這些片段上形成黑點將會是一個好處。d3.js帶傾斜/水平標籤的餅圖

Pie chart with horizontal labels

http://jsfiddle.net/BxLHd/15/

下面是刻度標記的代碼。是否會創建另一組相交的線?

     //draw tick marks 
         var label_group = d3.select('#'+pieId+' .label_group'); 
         lines = label_group.selectAll("line").data(filteredData); 
         lines.enter().append("svg:line") 
           .attr("x1", 0) 
           .attr("x2", 0) 
           .attr("y1", function(d){ 
            if(d.value > threshold){ 
             return -that.r-3; 
            }else{ 
             return -that.r; 
            } 
           }) 
           .attr("y2", function(d){ 
            if(d.value > threshold){ 
             return -that.r-8; 
            } 
            else{         
             return -that.r; 
            } 
           }) 
           .attr("stroke", "gray") 
           .attr("transform", function(d) { 
             return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")"; 
           }); 

         lines.transition() 
           .duration(this.tweenDuration) 
           .attr("transform", function(d) { 
             return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")"; 
           }); 

         lines.exit().remove(); 
+0

我已經提高了最終的代碼放到一個插件 - http://jsfiddle.net/Qh9X5/1322/ –

+0

http://jsfiddle.net/Qh9X5/1332/最新 –

回答

1

總之,我裹着這的最新代碼的jQuery插件。現在可以使用這些標籤開發多個餅圖。

最新代碼 - ** http://jsfiddle.net/Qh9X5/1336/ - 在退出時正確刪除標籤。

enter image description here

$(document).ready(function() { 


      (function($){ 
       var methods = { 
        el: "", 
        init : function(options) { 
         var clone = jQuery.extend(true, {}, options["data"]); 

         methods.el = this;   
         methods.setup(clone, options["width"], options["height"], options["r"], options["ir"]); 
        }, 
        getArc: function(radius, innerradius){ 
         var arc = d3.svg.arc() 
          .innerRadius(innerradius) 
          .outerRadius(radius); 

         return arc; 
        }, 
        setup: function(dataset, w, h, r, ir){ 

         var padding = 80; 

         this.width = w; 
         this.height = h; 
         this.radius = r 
         this.innerradius = ir; 

         this.color = d3.scale.category20(); 

         this.pie = d3.layout.pie() 
          .sort(null) 
          .value(function(d) { return d.total; }); 

         this.arc = this.getArc(this.radius, this.innerradius); 

         this.svg = d3.select(methods.el["selector"]).append("svg") 
          .attr("width", this.width + padding) 
          .attr("height", this.height + padding) 
          .append("g") 
           .attr("class", "piechart") 
           .attr("transform", "translate(" + ((this.width/2) + (padding/2)) + "," + ((this.height/2) + (padding/2)) + ")");     

         this.segments = this.svg.append("g") 
           .attr("class", "segments"); 

         this.labels = this.svg.append("g") 
           .attr("class", "labels"); 

         this.pointers = this.svg.append("g") 
           .attr("class", "pointers"); 


        }, 
        oldPieData: "", 
        pieTween: function(r, ir, d, i){ 
         var that = this; 

         var theOldDataInPie = methods.oldPieData; 
         // Interpolate the arcs in data space 

         var s0; 
         var e0; 

         if(theOldDataInPie[i]){ 
           s0 = theOldDataInPie[i].startAngle; 
           e0 = theOldDataInPie[i].endAngle; 
         } else if (!(theOldDataInPie[i]) && theOldDataInPie[i-1]) { 
           s0 = theOldDataInPie[i-1].endAngle; 
           e0 = theOldDataInPie[i-1].endAngle; 
         } else if(!(theOldDataInPie[i-1]) && theOldDataInPie.length > 0){ 
           s0 = theOldDataInPie[theOldDataInPie.length-1].endAngle; 
           e0 = theOldDataInPie[theOldDataInPie.length-1].endAngle; 
         } else { 
           s0 = 0; 
           e0 = 0; 
         } 

         var i = d3.interpolate({startAngle: s0, endAngle: e0}, {startAngle: d.startAngle, endAngle: d.endAngle}); 

         return function(t) { 
           var b = i(t); 
           return methods.getArc(r, ir)(b); 
         }; 
        }, 
        removePieTween: function(r, ir, d, i) {    
         var that = this; 
         s0 = 2 * Math.PI; 
         e0 = 2 * Math.PI; 
         var i = d3.interpolate({startAngle: d.startAngle, endAngle: d.endAngle}, {startAngle: s0, endAngle: e0}); 

         return function(t) { 
           var b = i(t); 
           return methods.getArc(r, ir)(b); 
         }; 
        }, 
        update: function(dataSet){ 
         var that = this; 

         methods.el = this; 
         var r = $(methods.el["selector"]).data("r"); 
         var ir = $(methods.el["selector"]).data("ir"); 

         methods.svg = d3.select(methods.el["selector"] + " .piechart"); 

         methods.segments = d3.select(methods.el["selector"] + " .segments"); 
         methods.labels = d3.select(methods.el["selector"] + " .labels"); 
         methods.pointers = d3.select(methods.el["selector"] + " .pointers"); 

         dataSet.forEach(function(d) { 
          d.total = +d.value; 
         });      

         this.piedata = methods.pie(dataSet); 

         //__slices 
         this.path = methods.segments.selectAll("path.pie") 
          .data(this.piedata); 

         this.path.enter().append("path") 
          .attr("class", "pie") 
          .attr("fill", function(d, i) { 
           return methods.color(i); 
          }) 
          .transition() 
           .duration(300) 
           .attrTween("d", function(d, i) { 
            return methods.pieTween(r, ir, d, i); 
           }); 

         this.path 
           .transition() 
           .duration(300) 
           .attrTween("d", function(d, i) { 
            return methods.pieTween(r, ir, d, i); 
           }); 

         this.path.exit() 
           .transition() 
           .duration(300) 
           .attrTween("d", function(d, i) { 
            return methods.removePieTween(r, ir, d, i); 
           }) 
           .remove();  
         //__slices 


         //__labels 
         var labels = methods.labels.selectAll("text") 
          .data(this.piedata); 

         labels.enter() 
          .append("text") 
          .attr("text-anchor", "middle") 


         labels 
          .attr("x", function(d) { 
           var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2; 
           d.cx = Math.cos(a) * (ir+((r-ir)/2)); 
           return d.x = Math.cos(a) * (r + 20); 
          }) 
          .attr("y", function(d) { 
           var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2; 
           d.cy = Math.sin(a) * (ir+((r-ir)/2)); 
           return d.y = Math.sin(a) * (r + 20); 
          }) 
          .text(function(d) { 
           return d.data.label; 
          }) 
          .each(function(d) { 
           var bbox = this.getBBox(); 
           d.sx = d.x - bbox.width/2 - 2; 
           d.ox = d.x + bbox.width/2 + 2; 
           d.sy = d.oy = d.y + 5; 
          }) 
          .transition() 
           .duration(300) 

         labels 
          .transition() 
          .duration(300)  

         labels.exit() 
          .transition() 
          .duration(300) 
         //__labels 


         //__pointers 
         methods.pointers.append("defs").append("marker") 
          .attr("id", "circ") 
          .attr("markerWidth", 6) 
          .attr("markerHeight", 6) 
          .attr("refX", 3) 
          .attr("refY", 3) 
          .append("circle") 
          .attr("cx", 3) 
          .attr("cy", 3) 
          .attr("r", 3); 

         var pointers = methods.pointers.selectAll("path.pointer") 
          .data(this.piedata); 

         pointers.enter() 
          .append("path") 
          .attr("class", "pointer") 
          .style("fill", "none") 
          .style("stroke", "black") 
          .attr("marker-end", "url(#circ)"); 

         pointers 
          .attr("d", function(d) { 
           if(d.cx > d.ox) { 
            return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy; 
           } else { 
            return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy; 
           } 
          }) 
          .transition() 
           .duration(300) 

         pointers 
          .transition() 
          .duration(300)  

         pointers.exit() 
          .transition() 
          .duration(300) 

         //__pointers 

         this.oldPieData = this.piedata; 

        } 
       }; 

       $.fn.piechart = function(methodOrOptions) { 
        if (methods[methodOrOptions]) { 
         return methods[ methodOrOptions ].apply(this, Array.prototype.slice.call(arguments, 1)); 
        } else if (typeof methodOrOptions === 'object' || ! methodOrOptions) { 
         // Default to "init" 
         return methods.init.apply(this, arguments); 
        } else { 
         $.error('Method ' + methodOrOptions + ' does not exist'); 
        }  
       }; 

      })(jQuery); 



      var dataCharts = [ 
       { 
        "data": [ 
         { 
          "segments": [ 
           { 
            "label": "apple", 
            "value": 53245 
           }, 
           { 
            "label": "cherry", 
            "value": 145 
           }, 
           { 
            "label": "pear", 
            "value": 2245 
           }, 
           { 
            "label": "bananana", 
            "value": 15325 
           }       
          ] 
         } 
        ] 
       }, 
       { 
        "data": [ 
         { 
          "segments": [ 
           { 
            "label": "milk", 
            "value": 532 
           }, 
           { 
            "label": "cheese", 
            "value": 145 
           }, 
           { 
            "label": "grapes", 
            "value": 22 
           } 
          ] 
         } 
        ] 
       }, 
       { 
        "data": [ 
         { 
          "segments": [ 
           { 
            "label": "pineapple", 
            "value": 1532 
           }, 
           { 
            "label": "orange", 
            "value": 1435 
           }, 
           { 
            "label": "grapes", 
            "value": 22 
           }    
          ] 
         } 
        ] 
       }, 
       { 
        "data": [ 
         { 
          "segments": [ 
           { 
            "label": "lemons", 
            "value": 133 
           }, 
           { 
            "label": "mango", 
            "value": 435 
           }, 
           { 
            "label": "melon", 
            "value": 2122 
           }    
          ] 
         } 
        ] 
       }    
      ]; 

      var clone = jQuery.extend(true, {}, dataCharts); 

       //__invoke concentric 
       $('[data-role="piechart"]').each(function(index) { 
        var selector = "piechart"+index; 

        $(this).attr("id", selector); 

        var options = { 
         data: clone[0].data, 
         width: $(this).data("width"), 
         height: $(this).data("height"), 
         r: $(this).data("r"), 
         ir: $(this).data("ir") 
        } 

        $("#"+selector).piechart(options); 
        $("#"+selector).piechart('update', clone[0].data[0].segments); 
       }); 


      $(".testers a").on("click", function(e) { 
       e.preventDefault(); 

       var clone = jQuery.extend(true, {}, dataCharts); 

       var min = 0; 
       var max = 3; 

       //__invoke pie chart 
       $('[data-role="piechart"]').each(function(index) { 
        pos = Math.floor(Math.random() * (max - min + 1)) + min; 
        $("#"+$(this).attr("id")).piechart('update', clone[pos].data[0].segments); 
       }); 

      }); 

}); 
19

下面是一個概念證明(使用與你的不同的例子作爲基礎,因爲你的代碼很多)。這是基本的方法:

  • 對於每個標籤,計算它下面的行的開始和結束。這是通過繪製標籤並獲得邊界框來完成的。
  • 這給出了指針路徑上的兩個點,第三個是相應段的中心。這是在計算標籤的位置時計算的。
  • 這三點成爲數據的一部分。現在使用之前計算的三個點爲每個數據元繪製path
  • 在點的每個路徑末尾添加一個SVG標記。

下面是一步一步做的代碼。

.attr("x", function(d) { 
    var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2; 
    d.cx = Math.cos(a) * (radius - 75); 
    return d.x = Math.cos(a) * (radius - 20); 
}) 
.attr("y", function(d) { 
    var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2; 
    d.cy = Math.sin(a) * (radius - 75); 
    return d.y = Math.sin(a) * (radius - 20); 
}) 

這是計算段外標籤的x和y位置。我們還計算指針路徑的最後一個點在該段中心的位置。也就是說,無論是在開始角度和結束角度之間以及內半徑和外半徑之間的中間。這被添加到數據中。

.text(function(d) { return d.value; }) 
.each(function(d) { 
    var bbox = this.getBBox(); 
    d.sx = d.x - bbox.width/2 - 2; 
    d.ox = d.x + bbox.width/2 + 2; 
    d.sy = d.oy = d.y + 5; 
}); 

添加文本標籤(在這種情況下,只需將值)後,我們得到每個邊界框和計算剩下的兩個點的路徑,只是文本的左側下方,只是下方正確的。

svg.selectAll("path.pointer").data(piedata).enter() 
    .append("path") 
    .attr("class", "pointer") 
    .style("fill", "none") 
    .style("stroke", "black") 
    .attr("marker-end", "url(#circ)") 
    .attr("d", function(d) { 
    if(d.cx > d.ox) { 
     return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy; 
    } else { 
     return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy; 
    } 
    }); 

現在我們實際上可以添加路徑。它們是之前計算的三個點的直接連接,最後添加了一個標記。唯一需要注意的是,根據標籤位於圖表的左側還是右側,路徑需要從標籤的左下角或右下角開始。這是if語句。

完整演示here

+0

這是偉大的拉爾斯。我的例子被封裝在一個插件中。你將如何去動畫你的例子來處理不同的數據集? –

+0

轉換'path'元素的'd'屬性應該足夠了。 –

+0

拉斯 - 很好的解釋。非常感謝你。這對我來說很有用。 +1方式:) –

1

這是插件代碼,應該允許餅圖的多個實例 - 以及能夠用一組新數據更新每個餅圖。

我打開增強代碼的方法。我覺得它仍然看起來有點笨重 - 特別是我在更新時重置選擇器的方式。任何建議來簡化這一點?

http://jsfiddle.net/Qh9X5/1318/

$(document).ready(function() { 


      (function($){ 
       var methods = { 
        el: "", 
        init : function(options) { 
         var clone = jQuery.extend(true, {}, options["data"]); 

         methods.el = this;   
         methods.setup(clone); 
        }, 
        setup: function(dataset){    

         this.width = 300; 
         this.height = 300; 
         this.radius = Math.min(this.width, this.height)/2; 

         this.color = d3.scale.category20(); 

         this.pie = d3.layout.pie() 
          .sort(null); 

         this.arc = d3.svg.arc() 
          .innerRadius(this.radius - 100) 
          .outerRadius(this.radius - 50); 

         this.svg = d3.select(methods.el["selector"]).append("svg") 
          .attr("width", this.width) 
          .attr("height", this.height) 
          .append("g") 
           .attr("class", "piechart") 
           .attr("transform", "translate(" + this.width/2 + "," + this.height/2 + ")");     

         //this.update(dataset[0].segments); 
        }, 
        oldPieData: "", 
        pieTween: function(d, i){ 
         var that = this; 

         var theOldDataInPie = methods.oldPieData; 
         // Interpolate the arcs in data space 

         var s0; 
         var e0; 

         if(theOldDataInPie[i]){ 
           s0 = theOldDataInPie[i].startAngle; 
           e0 = theOldDataInPie[i].endAngle; 
         } else if (!(theOldDataInPie[i]) && theOldDataInPie[i-1]) { 
           s0 = theOldDataInPie[i-1].endAngle; 
           e0 = theOldDataInPie[i-1].endAngle; 
         } else if(!(theOldDataInPie[i-1]) && theOldDataInPie.length > 0){ 
           s0 = theOldDataInPie[theOldDataInPie.length-1].endAngle; 
           e0 = theOldDataInPie[theOldDataInPie.length-1].endAngle; 
         } else { 
           s0 = 0; 
           e0 = 0; 
         } 

         var i = d3.interpolate({startAngle: s0, endAngle: e0}, {startAngle: d.startAngle, endAngle: d.endAngle}); 

         return function(t) { 
           var b = i(t); 
           return methods.arc(b); 
         }; 
        }, 
        removePieTween: function(d, i) {     
         var that = this; 
         s0 = 2 * Math.PI; 
         e0 = 2 * Math.PI; 
         var i = d3.interpolate({startAngle: d.startAngle, endAngle: d.endAngle}, {startAngle: s0, endAngle: e0}); 

         return function(t) { 
           var b = i(t); 
           return methods.arc(b); 
         }; 
        }, 
        update: function(dataSet){ 
         var that = this; 

         methods.el = this; 
         methods.svg = d3.select(methods.el["selector"] + " .piechart"); 

         this.piedata = methods.pie(dataSet); 

         //__slices 
         this.path = methods.svg.selectAll("path.pie") 
          .data(this.piedata); 

         this.path.enter().append("path") 
          .attr("class", "pie") 
          .attr("fill", function(d, i) { 
           return methods.color(i); 
          }) 
          .transition() 
           .duration(300) 
           .attrTween("d", methods.pieTween); 

         this.path 
           .transition() 
           .duration(300) 
           .attrTween("d", methods.pieTween); 

         this.path.exit() 
           .transition() 
           .duration(300) 
           .attrTween("d", methods.removePieTween) 
           .remove();  
         //__slices 


         //__labels 
         var labels = methods.svg.selectAll("text") 
          .data(this.piedata); 

         labels.enter() 
          .append("text") 
          .attr("text-anchor", "middle") 


         labels 
          .attr("x", function(d) { 
           var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2; 
           d.cx = Math.cos(a) * (methods.radius - 75); 
           return d.x = Math.cos(a) * (methods.radius - 20); 
          }) 
          .attr("y", function(d) { 
           var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2; 
           d.cy = Math.sin(a) * (methods.radius - 75); 
           return d.y = Math.sin(a) * (methods.radius - 20); 
          }) 
          .text(function(d) { 
           return d.value; 
          }) 
          .each(function(d) { 
           var bbox = this.getBBox(); 
           d.sx = d.x - bbox.width/2 - 2; 
           d.ox = d.x + bbox.width/2 + 2; 
           d.sy = d.oy = d.y + 5; 
          }) 
          .transition() 
           .duration(300) 

         labels 
          .transition() 
          .duration(300)  

         labels.exit() 
          .transition() 
          .duration(300) 
         //__labels 


         //__pointers 
         methods.svg.append("defs").append("marker") 
          .attr("id", "circ") 
          .attr("markerWidth", 6) 
          .attr("markerHeight", 6) 
          .attr("refX", 3) 
          .attr("refY", 3) 
          .append("circle") 
          .attr("cx", 3) 
          .attr("cy", 3) 
          .attr("r", 3); 

         var pointers = methods.svg.selectAll("path.pointer") 
          .data(this.piedata); 

         pointers.enter() 
          .append("path") 
          .attr("class", "pointer") 
          .style("fill", "none") 
          .style("stroke", "black") 
          .attr("marker-end", "url(#circ)"); 

         pointers 
          .attr("d", function(d) { 
           if(d.cx > d.ox) { 
            return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy; 
           } else { 
            return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy; 
           } 
          }) 
          .transition() 
           .duration(300) 

         pointers 
          .transition() 
          .duration(300)  

         pointers.exit() 
          .transition() 
          .duration(300) 

         //__pointers 

         this.oldPieData = this.piedata; 

        } 
       }; 

       $.fn.piechart = function(methodOrOptions) { 
        if (methods[methodOrOptions]) { 
         return methods[ methodOrOptions ].apply(this, Array.prototype.slice.call(arguments, 1)); 
        } else if (typeof methodOrOptions === 'object' || ! methodOrOptions) { 
         // Default to "init" 
         return methods.init.apply(this, arguments); 
        } else { 
         $.error('Method ' + methodOrOptions + ' does not exist'); 
        }  
       }; 

      })(jQuery); 



      var dataCharts = [ 
       { 
        "data": [ 
         { 
          "segments": [ 
           53245, 28479, 19697, 24037, 40245       
          ] 
         } 
        ] 
       }, 
       { 
        "data": [ 
         { 
          "segments": [ 
           855, 79, 97, 237, 245     
          ] 
         } 
        ] 
       }, 
       { 
        "data": [ 
         { 
          "segments": [ 
           22, 79, 97, 12, 245     
          ] 
         } 
        ] 
       }, 
       { 
        "data": [ 
         { 
          "segments": [ 
           122, 279, 197, 312, 545     
          ] 
         } 
        ] 
       }    
      ]; 

      var clone = jQuery.extend(true, {}, dataCharts); 

       //__invoke concentric 
       $('[data-role="piechart"]').each(function(index) { 
        var selector = "piechart"+index; 

        $(this).attr("id", selector); 

        var options = { 
         data: clone[0].data 
        } 

        $("#"+selector).piechart(options); 
        $("#"+selector).piechart('update', clone[0].data[0].segments); 
       }); 


      $(".testers a").on("click", function(e) { 
       e.preventDefault(); 

       var clone = jQuery.extend(true, {}, dataCharts); 

       var min = 0; 
       var max = 3; 

       //__invoke pie chart 
       $('[data-role="piechart"]').each(function(index) { 
        pos = Math.floor(Math.random() * (max - min + 1)) + min; 
        $("#"+$(this).attr("id")).piechart('update', clone[pos].data[0].segments); 
       }); 

      }); 

}); 
+0

請檢查上面的代碼,並幫助我簡化插件。我將着眼於使用數據屬性來調整餅圖。過去,我在現有餅圖中選擇了過多的問題 –

+0

下面是我所指的錯誤 - http://jsfiddle.net/Qh9X5/1319/ - 數據屬性設置了餅圖。但設置後 - 在更新階段,最後一組選項被視爲參數的聖盃,所有餅圖都使用最後設置的選項進行更新。 –

+0

我已經更新了包含標籤的演示 - 我已經添加了數據屬性來爲每個圖表配置寬度/高度 - 但我有同樣的問題,其中最後一個圖表的參數否決了其他圖表.... http: //jsfiddle.net/BxLHd/18/ - 這怎麼解決。它是我構建的jquery插件的一個主要問題 - 它有什麼問題。 –