带有角度/水平标签的d3.js饼图

时间:2014-02-14 02:43:21

标签: javascript 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();

3 个答案:

答案 0 :(得分: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;
});

添加文本标签后(在这种情况下,只是值),我们得到每个边界框并计算路径的剩余两个点,就在左边的文本下方和右下方的文本下方。 / p>

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

答案 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);
                });

            }); 

});

答案 2 :(得分: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);
                });

            }); 

});