d3.js concentric ring chart

2019-03-22 03:57发布

I am looking to develop an arc ring chart. Where each new set of data is displayed as a concentric ring inside the other.

http://jsfiddle.net/NYEaX/101/

I've tried to take a sum of the values in the data, and use this to help develop a proportion algorithm, to ensure each arc never does more than one cycle.

this part manages the concentric ring.

getArc: function(){
            var that = this;

            var radiusArray = [100, 80];

            function getRadiusRing(i){
                return that.radius-(i*20);              
            }
            var thickness = 15;

            var arc = d3.svg.arc()
                    .innerRadius(function(d){
                        return getRadiusRing(d.index);                      
                    })
                    .outerRadius(function(d){
                        return getRadiusRing(d.index)+thickness;    
                    })
                    .startAngle(function(d, i){
                        return d.startAngle;
                    })
                    .endAngle(function(d, i){
                        return d.endAngle;
                    });     
            return arc;
        }

but I think there is a flaw in the end angle calculations. If you enable more data - the concentric rings although they form correctly have suspicious arc lengths where they wrap around the entire chart?

setData: function(data){
            var diameter = 2 * Math.PI * this.radius;           
            var localData = new Array();

            var segmentValueSum = 0;
            $.each(data[0].segments, function( ri, va) {
                segmentValueSum+= va.value;
            });

            $.each(data[0].segments, function(ri, value) {

                var segmentValue = value.value;

                var fraction = segmentValue/segmentValueSum;

                var arcBatchLength = fraction*2*Math.PI;
                var arcPartition = arcBatchLength;      

                var startAngle = 0;
                var endAngle = ((ri+1)*arcPartition);               

                data[0].segments[ri]["startAngle"] = startAngle;
                data[0].segments[ri]["endAngle"] = endAngle;
                data[0].segments[ri]["index"] = ri;
            });

            localData.push(data[0].segments);

            return localData[0];        
        }

trying to build the chart to look like the following

Concentric ring chart

I've enhanced the chart, but still have issues with the placement and updating of the legend, rings and values. Why is old data remaining? http://jsfiddle.net/NYEaX/123/

if there is more or less data - the labels don't get placed correctly

 //draw labels                      
            valueLabels = value_group.selectAll("text.value").data(reversedata)
            valueLabels.enter().append("svg:text")
            .attr("class", "value")
            .attr("transform", function(d) {
                var rings = counts;

                return "translate("+(that.radius+55)/rings+", 0)";
            })
             .attr("dx", function(d, i){
                return 19*i;            })
            .attr("dy", function(d, i){
                return -5;
            })
            .attr("text-anchor", function(d){
                return "start";
            }).text(function(d){
                return d.value;
            });

            valueLabels.transition().duration(300).attrTween("d", arcTween)
            valueLabels.exit().remove();   

3条回答
祖国的老花朵
2楼-- · 2019-03-22 04:40

enter image description here

I've made a version where the chart starts from the top - so different orientation. Be good to put this as a variable - maybe have the option to start the chart in 4 different quadrants.

http://jsfiddle.net/NYEaX/364/

I've adjusted

  • the start angle in set Data

    var startAngle = Math.PI*2;

-the label placements

valueLabels.enter().append("svg:text")
                            .attr("class", "value")
                            .attr("transform", function(d) {       
                                return "translate("+(that.getRadiusRing(ir, counts-1))+", 0)";
                            })
                            .attr("dx", function(d, i){
                                return 0;
                            })
                            .attr("dy", function(d, i){
                                return (thickness + 4)*i;
                            })
                            .attr("text-anchor", function(d){
                                return "start";
                            }).text(function(d){
                                return d.value;
                            });
查看更多
Viruses.
3楼-- · 2019-03-22 04:42

I've managed to fix all the bugs - Here is the final code as a jquery plugin, enjoy

enter image description here

http://jsfiddle.net/NYEaX/165/

$(document).ready(function() {


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

                        methods.el = this;          
                        methods.setup(preparedData, options["width"], options["height"]);
                    },
                    setup: function(data, w, h){

                        var selector = methods.el["selector"];

                        var padding = 20;


                        var chart = d3.select(selector).append("svg:svg")
                            .attr("class", "chart")
                            .attr("width", w)
                            .attr("height", h)
                        .append("svg:g")
                            .attr("class", "concentricchart")
                            .attr("transform", "translate("+((w/3)+padding)+","+h/3+")");

                        methods.radius = Math.min(w, h) / 2;

                        var label_group = chart.append("svg:g")
                            .attr("class", "label_group")
                            .attr("transform", "translate("+((w/3)-15)+","+(-h/4)+")");

                        var legend_group = chart.append("svg:g")
                            .attr("class", "legend_group")
                            .attr("transform", "translate("+((w/3)-130)+","+((-h/4)-5)+")");

                        var value_group = chart.append("svg:g")
                            .attr("class", "value_group")
                            .attr("transform", "translate(0,"+(h/4)+")");

                        var path_group = chart.append("svg:g")
                            .attr("class", "path_group")
                            .attr("transform", "translate(0,"+(h/4)+")");               


                        this.generateArcs(selector, data);      
                    },
                    update: function(data){
                        var clone = jQuery.extend(true, {}, data);

                        var preparedData = methods.setData(clone);

                        methods.el = this;
                        methods.animate(preparedData);          
                        methods.oldData = preparedData;
                    },
                    animate: function(data){
                        var that = this;

                        var selector = methods.el["selector"];

                        that.generateArcs(selector, data);
                    },  
                    setData: function(data){
                            var diameter = 2 * Math.PI * this.radius;           
                            var localData = new Array();

                            var segmentValueSum = 0;
                            $.each(data[0].segments, function( ri, va) {
                                segmentValueSum+= va.value;
                            });

                            segmentValueSum = 200;//consistent total accross different data sets

                            $.each(data[0].segments, function(ri, value) {
                                var segmentValue = value.value;

                                var fraction = segmentValue/segmentValueSum;

                                var arcBatchLength = fraction*4*Math.PI;
                                var arcPartition = arcBatchLength;      

                                var startAngle = Math.PI/2;         
                                var endAngle = startAngle + arcPartition; 

                                data[0].segments[ri]["startAngle"] = startAngle;
                                data[0].segments[ri]["endAngle"] = endAngle;
                                data[0].segments[ri]["index"] = ri;
                            });

                            localData.push(data[0].segments);

                            return localData[0];        
                    },
                    textOffset: 10,
                    generateArcs: function(selector, data){
                        var that = this;

                        var chart = d3.select(selector);

                        //append previous value to it.          
                        $.each(data, function(index, value) {
                            if(that.oldData[index] != undefined){
                                data[index]["previousEndAngle"] = that.oldData[index].endAngle;
                            }
                            else{
                                data[index]["previousEndAngle"] = 0;
                            }
                        });     

                        var thickness = $(selector).data("thickness");
                        var ir = ($(selector).data("width")/3);


                        var path_group = d3.select(selector+ ' .path_group');

                        var arcpaths = path_group.selectAll("path")
                            .data(data);

                        arcpaths.enter().append("svg:path")
                            .attr("class", function(d, i){
                                return d.machineType;
                            })  
                            .style("fill", function(d, i){
                                return d.color;
                            })
                            .transition()
                            .ease("elastic")
                            .duration(750)
                            .attrTween("d", function(d){
                                 return that.arcTween(d, thickness, ir);
                            });      

                        arcpaths.transition()
                            .ease("elastic")
                            .style("fill", function(d, i){
                                return d.color;
                            })
                            .duration(750)
                            .attrTween("d", function(d){
                                 return that.arcTween(d, thickness, ir);
                            });      

                        arcpaths.exit().transition()
                            .ease("bounce")
                            .duration(750)
                            .attrTween("d", function(d){
                                 return that.arcTween(d, thickness, ir);
                            })   
                            .remove();

                        //draw labels       
                        that.drawLabels(chart, data, ir, thickness);
                        that.buildLegend(chart, data);
                    },
                    arcTween: function(b, thickness, ir){
                        var that = methods;

                        var prev = JSON.parse(JSON.stringify(b));
                        prev.endAngle = b.previousEndAngle;
                        var i = d3.interpolate(prev, b);

                        return function(t) {
                            return that.getArc(thickness, ir)(i(t));
                        };
                    },
                    drawLabels: function(chart, data, ir, thickness){
                        $(methods.el["selector"]+' .value_group').empty();

                        var that = this;

                        var reversedata = data.reverse();
                        var counts = data.length;

                        var value_group = d3.select(methods.el["selector"]+ ' .value_group');

                        valueLabels = value_group.selectAll("text.value").data(reversedata)
                        valueLabels.enter().append("svg:text")
                            .attr("class", "value")
                            .attr("transform", function(d) {       
                                return "translate("+(that.getRadiusRing(ir, counts-1))+", 0)";
                            })
                            .attr("dx", function(d, i){
                                return 20*i;            })
                            .attr("dy", function(d, i){
                                return -5;
                            })
                            .attr("text-anchor", function(d){
                                return "start";
                            }).text(function(d){
                                return d.value;
                            });

                        valueLabels
                            .transition()
                            .duration(300)
                            .attrTween("d", function(d){
                                return that.arcTween(d, thickness, ir);
                            })

                        valueLabels
                            .exit()
                            .remove();      
                    },
                    buildLegend: function(chart, data){
                        console.log("build legend");
                        $(methods.el["selector"]+' .label_group').empty();
                        $(methods.el["selector"]+' .legend_group').empty();


                        var label_group = d3.select(methods.el["selector"]+ ' .label_group');

                        //draw labels                       
                        labels = label_group.selectAll("text.labels")
                            .data(data.reverse());            

                        labels.enter().append("svg:text")
                            .attr("class", "labels")
                            .attr("dy", function(d, i){
                                return 19*i
                            })
                            .attr("text-anchor", function(d){
                                return "start";
                            })
                            .text(function(d){
                                return d.label;
                            });

                        labels.exit().remove();

                        var legend_group = d3.select(methods.el["selector"]+ ' .legend_group');

                        legend = legend_group.selectAll("circle").data(data);

                        legend.enter().append("svg:circle")
                            .attr("cx", 100)
                            .attr("cy", function(d, i){
                                return 19*i
                            })
                            .attr("r", 7)   
                            .attr("width", 18)
                            .attr("height", 18)
                            .style("fill", function(d){
                                return d.color;
                            });

                        legend.exit().remove();
                    },
                    getRadiusRing: function(ir, i){
                        return ir-(i*20);               
                    },
                    getArc: function(thickness, ir){
                        var that = this;

                        var arc = d3.svg.arc()
                            .innerRadius(function(d){
                                return that.getRadiusRing(ir, d.index);                     
                            })
                            .outerRadius(function(d){
                                return that.getRadiusRing(ir+thickness, d.index);   
                            })
                            .startAngle(function(d, i){
                                return d.startAngle;
                            })
                            .endAngle(function(d, i){
                                return d.endAngle;
                            });
                        return arc;
                    },
                    radius: 100,
                    oldData: ""
                };

                $.fn.concentric = 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": "Turkey",
                                    "value": 25,
                                    "color": "red"
                                },
                                {
                                    "label": "United States",
                                    "value": 40,
                                    "color": "blue"                         
                                },
                                {
                                    "label": "Switzerland",
                                    "value": 60,
                                    "color": "green"                            
                                },
                                {
                                    "label": "Iceland",
                                    "value": 80,
                                    "color": "gold"
                                }                           
                            ]
                        }
                    ]
                },
                {
                    "data": [
                        {
                            "segments": [
                                {
                                    "label": "Peanut Butter",
                                    "value": 50,
                                    "color": "red"
                                },
                                {
                                    "label": "Tea",
                                    "value": 25,
                                    "color": "orange"                           
                                },
                                {
                                    "label": "Cheese",
                                    "value": 25,
                                    "color": "purple"                           
                                }                       
                            ]
                        }
                    ]
                } ,
                {
                    "data": [
                        {
                            "segments": [
                                {
                                    "label": "Jam",
                                    "value": 90,
                                    "color": "purple"
                                },
                                {
                                    "label": "Lemons",
                                    "value": 15,
                                    "color": "brown"                            
                                }                       
                            ]
                        }
                    ]
                }           
            ];

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

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

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

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

                    $("#"+selector).concentric(options);
                });


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

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

                var min = 0;
                var max = 2;

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

            }); 

});
查看更多
Luminary・发光体
4楼-- · 2019-03-22 04:45

Replace

    var endAngle = ((ri+1)*arcPartition); 

by

    var endAngle = startAngle + arcPartition; 
查看更多
登录 后发表回答