Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is data parsed in this 3D piechart?

I'm trying to grasp how the functions in Donut3D.js -> http://plnkr.co/edit/g5kgAPCHMlFWKjljUc3j?p=preview handle the inserted data:

Above all, where is it set that the data's startAngle is set at 0 degrees? I want to change it to 45º, then to 135º, 225º and 315º (look at the image above).

enter image description here

I've located this function:

        Donut3D.draw = function(id, data, x /*center x*/, y/*center y*/, 
                rx/*radius x*/, ry/*radius y*/, h/*height*/, ir/*inner radius*/){

            var _data = d3.layout.pie().sort(null).value(function(d) {return d.value;})(data);

            var slices = d3.select("#"+id).append("g").attr("transform", "translate(" + x + "," + y + ")")
                .attr("class", "slices");              

            slices.selectAll(".innerSlice").data(_data).enter().append("path").attr("class", "innerSlice")
                .style("fill", function(d) { 
                    return d3.hsl(d.data.color).darker(0.7); })
                .attr("d",function(d){ 
                    return pieInner(d, rx+0.5,ry+0.5, h, ir);})
                .each(function(d){this._current=d;});

            slices.selectAll(".topSlice").data(_data).enter().append("path").attr("class", "topSlice")
                .style("fill", function(d) { 
                    return d.data.color; })
                .style("stroke", function(d) { 
                    return d.data.color; })
                .attr("d",function(d){ 
                    return pieTop(d, rx, ry, ir);})
                .each(function(d){this._current=d;});

            slices.selectAll(".outerSlice").data(_data).enter().append("path").attr("class", "outerSlice")
                .style("fill", function(d) { 
                    return d3.hsl(d.data.color).darker(0.7); })
                .attr("d",function(d){ 
                    return pieOuter(d, rx-.5,ry-.5, h);})
                .each(function(d){this._current=d;});

            slices.selectAll(".percent").data(_data).enter().append("text").attr("class", "percent")
                .attr("x",function(d){ 
                    return 0.6*rx*Math.cos(0.5*(d.startAngle+d.endAngle));})
                .attr("y",function(d){ 
                    return 0.6*ry*Math.sin(0.5*(d.startAngle+d.endAngle));})
                .text(getPercent).each(function(d){this._current=d;});              
        }

and tried to insert an arc such as :

var arc = d3.svg.arc().outerRadius(r)
        .startAngle(function(d) { return d.startAngle + Math.PI/2; })
        .endAngle(function(d) { return d.endAngle + Math.PI/2; });

but it doesn't produce the desired effects.

EDIT 1

The first answer helped in rotating the inner pie, by changing:

var _data = d3.layout.pie().sort(null).value(function(d) {
              return d.value;
            })(data);

to

var _data = d3.layout.pie()
            .startAngle(45*Math.PI/180)
            .endAngle(405*Math.PI/180).sort(null).value(function(d) {
              return d.value;
            })(data);

the problem is that now the outer pie gets broken -> http://plnkr.co/edit/g5kgAPCHMlFWKjljUc3j?p=preview

I guess the solution has something to do with the function function pieOuter(d, rx, ry, h ) and the two startAngle and endAngle variables, but they work in apparently unpredictable ways.

Thank you

I know that Pie Charts are bad, especially if in 3D; but this work is part of my thesis where my job is actually demonstrate how PieCharts are Bad! I want to rotate this PieChart in order to show how if the 3D pie Slice is positioned at the top the data shows as less important, or more important if positioned at the bottom. So a 'Evil Journalist' could alter the visual perception of data by simply inclinating and rotating the PieChart!

like image 728
Cris Avatar asked Feb 08 '16 13:02

Cris


2 Answers

Here's a corrected function which allows rotation.

First, modify function signature to include rotate variable:

Donut3D.draw = function(id, data, x /*center x*/ , y /*center y*/ ,
        rx /*radius x*/ , ry /*radius y*/ , h /*height*/ , ir /*inner radius*/, rotate /* start angle for first slice IN DEGREES */ ) {

In the draw function, modify angles. Instead of screwing with pie angles, I'd do it to the data directly:

_data.forEach(function(d,i){
  d.startAngle += rotate * Math.PI/180; //<-- convert to radians
  d.endAngle += rotate * Math.PI/180;
});

Then you need to correct the pieOuter function to fix the drawing artifacts:

function pieOuter(d, rx, ry, h) {

    var startAngle = d.startAngle,
        endAngle = d.endAngle;

    var sx = rx * Math.cos(startAngle),
      sy = ry * Math.sin(startAngle),
      ex = rx * Math.cos(endAngle),
      ey = ry * Math.sin(endAngle);

    // both the start and end y values are above
    // the middle of the pie, don't bother drawing anything
    if (ey < 0 && sy < 0)
      return "M0,0";

    // the end is above the pie, fix the points
    if (ey < 0){
      ey = 0;
      ex = -rx;
    }
    // the beginning is above the pie, fix the points.
    if (sy < 0){
      sy = 0;
      sx = rx;
    }

    var ret = [];
    ret.push("M", sx, h + sy, "A", rx, ry, "0 0 1", ex, h + ey, "L", ex, ey, "A", rx, ry, "0 0 0", sx, sy, "z");
    return ret.join(" ");

}

Here's the full code

like image 139
Mark Avatar answered Oct 10 '22 13:10

Mark


Changing the default start angle

Donut3D users d3's pie layout function here, which has a default startAngle of 0.

If you want to change the start angle, you should modify donut3d.js.

In the first place, you should certainly avoid to use 3d pie/donut charts, if you care about usability and readability of your visualizations - explained here.

Fixing bottom corner layout

The endAngle you are using is not correct, causing the "light blue" slice to overlap the "blue" one. Should be 405 (i.e. 45 + 360) instead of 415.

var _data = d3.layout.pie()
  .startAngle(45*Math.PI/180)
  .endAngle(405*Math.PI/180)

Then, the "pieOuter" angles calculation should be updated to behave correctly. The arc which doesn't work is the one where endAngle > 2 * PI, and the angle computation should be updated for it.

This does the trick (don't ask me why):

// fix right-side outer shape
if (d.endAngle > 2 * Math.PI) {
  startAngle = Math.PI / 120
  endAngle = Math.PI/4
}

demo: http://plnkr.co/edit/wmPnS9XVyQcrNu4WLa0D?p=preview

like image 2
Mehdi Avatar answered Oct 10 '22 13:10

Mehdi