Search code examples
d3.jsdonut-chart

How to divide donut chart into fixed portion


I have tried to search but cannot find a solution.

My data look like this :

var data =  [
        {   "hour":"10",    
            "percentage":"50"
        },
        {   "hour":"11",    
            "percentage":"20"
        },
        {   "hour":"3", 
        "percentage":"90"
        },
        {   "hour":"55",    
            "percentage":"40"
        },
        {   "hour":"6", 
            "percentage":"70"
        },
        {   "hour":"8", 
            "percentage":"40"
        }
    ];

I draw donut chart according to this data. I need :

  1. To divide chart into 12 equal parts like clock.

  2. I have color range to describe percentage, but what if there is no data in my hour attribute ?

I am new to D3JS and I cannot figure out the logic. Below is my donut chart.

Thanks in advanced.

    var data =	[
        	{	"hour":"10",	
        		"percentage":"50"
        	},
        	{	"hour":"11",	
        		"percentage":"20"
        	},
        	{	"hour":"3",	
    		"percentage":"90"
        	},
        	{	"hour":"55",	
        		"percentage":"40"
        	},
        	{	"hour":"6",	
        		"percentage":"70"
        	},
        	{	"hour":"8",	
        		"percentage":"40"
        	}
        ];



    var can =     d3.select("body").append("svg").attr("height",1000).attr("width",1000);
                //var svg = d3.select(can[0]);
                var r =100;
                var p = Math.PI*2;
                var color = d3.scale.linear()
                            .domain([0,100])
                            .range(["white","red"]);
                 var group = can.append("g")
                            .attr("transform","translate(100,100)");

                var arc = d3.svg.arc()
                        .innerRadius(r - 30)
                        .outerRadius(r)
                        //.startAngle(0)
                        .endAngle(p-1);


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

                 var arcs = group.selectAll(".arc")
                            .data(pie(data))
                            .enter().append("g")
                            .attr("class", "arc")
                            .attr('fill',function(d){return     color(d.data.percentage)})
                            .on("mouseover", function(d){
                                div.style("display", "inline")
                                 .text(d.data.percentage + ", " + d.data.hour)
                                .style("left", (d3.event.pageX - 34) + "px")
                                .style("top", (d3.event.pageY - 12) + "px");
                                     })
                             .on("mouseout", mouseout);


                arcs.append("path")
                                .attr("d", arc)
                                .style("fill", function (d) {
                                return color(d.data.percentage);
                });

                var div = d3.select("body").append("div")
                                                .attr("class", "tooltip")
                                                .style("display", "none");


                 function mouseout() {
                                 div.style("display", "none");
                        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>


Solution

  • If you want them in equal parts then how come your using percentage in the pie.value() ?

    Pie.value() is how you split the pie chart up.

    So what I would do is create a dummy value to create equal parts :

    for(i=0;i<data.length;i++){
       data[i].value = 1;
    }
    

    Now you pass the value to the pie.value() instead of the percentage:

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

    And the reason your chart doesnt go the whole way round is because of this line :

    .endAngle(p-1);
    

    Math.PI is in radians thus this is what you are setting as the angle :

    p = 6.28319 radians;
    
    p-1 = 5.28319 radians;
    

    Thus

    p-1 = 302.7044894 degrees;
    

    Take the -1 away and you have a full circle.

    Here is it with the current data :

    var data = [{
      "hour": "10",
      "percentage": "50"
    }, {
      "hour": "11",
      "percentage": "20"
    }, {
      "hour": "3",
      "percentage": "90"
    }, {
      "hour": "55",
      "percentage": "40"
    }, {
      "hour": "6",
      "percentage": "70"
    }, {
      "hour": "8",
      "percentage": "40"
    }];
    
    for (i = 0; i < data.length; i++) {
      data[i].value = 1;
    }
    
    
    var can = d3.select("body").append("svg").attr("height", 1000).attr("width", 1000);
    //var svg = d3.select(can[0]);
    var r = 100;
    var p = Math.PI * 2;
    var color = d3.scale.linear()
      .domain([0, 100])
      .range(["white", "red"]);
    var group = can.append("g")
      .attr("transform", "translate(100,100)");
    
    var arc = d3.svg.arc()
      .innerRadius(r - 30)
      .outerRadius(r)
      //.startAngle(0)
      .endAngle(p );
    
    
    var pie = d3.layout.pie()
      .sort(null)
      .value(function(d) {
        return d.value
          //return d.percentage
      });
    
    var arcs = group.selectAll(".arc")
      .data(pie(data))
      .enter().append("g")
      .attr("class", "arc")
      .attr('fill', function(d) {
        //console.log(d)
        return color(d.data.percentage)
      })
      .on("mouseover", function(d) {
        div.style("display", "inline")
          .text(d.data.percentage + ", " + d.data.hour)
          .style("left", (d3.event.pageX - 34) + "px")
          .style("top", (d3.event.pageY - 12) + "px");
      })
      .on("mouseout", mouseout);
    
    
    arcs.append("path")
      .attr("d", arc)
      .style("fill", function(d) {
        return color(d.data.percentage);
      });
    
    var div = d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("display", "none");
    
    
    function mouseout() {
      div.style("display", "none");
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

    Now the problem you have where you want it in 12 equal parts. I think you need to expand on what you want here. Do you want the missing parts to be shown ? Shown in a different colour ? What do you want ?

    To solve each of these youre going to have to create the data and add it to your existing data like so :

    var newData = [];
    for (i = 1; i <= 12; i++) { //go through numbers 1-12 like a clock
      var thisData;
      var inData = false; //bool to check if data exists already
      for (j = 0; j < data.length; j++) { //go through existing data
        if (data[j].hour == i) { //check if data exists
          inData = true; //data exists
          thisData = {
            "hour": data[j].hour, //add data at [i]
            "percentage": data[j].percentage, //add data at [i]
            "value": data[j].value //add data at [i]
          }
          newData.push(thisData) //push into new array
        }
      }
      if (!inData) { //if data doesnt exist
        thisData = {
          "hour": i, //set hour to i
          "percentage": 0, 
          "value": 1
        }
        newData.push(thisData) //push into new data
      }
    }
    console.log(newData)
    

    Here is the pie chart with new data, still with value as 1 so you get equal segment. They're coloured white as they have no percentage and I've added a stroke so you can tell which one is which :)

    var data = [{
      "hour": "10",
      "percentage": "50"
    }, {
      "hour": "11",
      "percentage": "20"
    }, {
      "hour": "3",
      "percentage": "90"
    }, {
      "hour": "5",
      "percentage": "40"
    }, {
      "hour": "6",
      "percentage": "70"
    }, {
      "hour": "8",
      "percentage": "40"
    }];
    
    for (i = 0; i < data.length; i++) {
      data[i].value = 1;
    }
    
    var newData = [];
    for (i = 1; i <= 12; i++) { //go through numbers 1-12 like a clock
      var thisData;
      var inData = false; //bool to check if data exists already
      for (j = 0; j < data.length; j++) { //go through existing data
        if (data[j].hour == i) { //check if data exists
          inData = true; //data exists
          thisData = {
            "hour": data[j].hour, //add data at [i]
            "percentage": data[j].percentage, //add data at [i]
            "value": data[j].value //add data at [i]
          }
          newData.push(thisData) //push into new array
        }
      }
      if (!inData) { //if data doesnt exist
        thisData = {
          "hour": i, //set hour to i
          "percentage": 0, 
          "value": 1
        }
        newData.push(thisData) //push into new data
      }
    }
    console.log(newData)
    
    
    var can = d3.select("body").append("svg").attr("height", 1000).attr("width", 1000);
    //var svg = d3.select(can[0]);
    var r = 100;
    var p = Math.PI * 2;
    var color = d3.scale.linear()
      .domain([0, 100])
      .range(["white", "red"]);
    
    var group = can.append("g")
      .attr("transform", "translate(100,100)");
    
    var arc = d3.svg.arc()
      .innerRadius(r - 30)
      .outerRadius(r)
      //.startAngle(0)
      .endAngle(p);
    
    
    var pie = d3.layout.pie()
      .sort(null)
      .value(function(d) {
        return d.value
          //return d.percentage
      });
    
    var arcs = group.selectAll(".arc")
      .data(pie(newData))
      .enter().append("g")
      .attr("class", "arc")
      .attr('fill', function(d) {
        //console.log(d)
        return color(d.data.percentage)
      })
      .style('stroke','black')
      .on("mouseover", function(d) {
        div.style("display", "inline")
          .text(d.data.percentage + ", " + d.data.hour)
          .style("left", (d3.event.pageX - 34) + "px")
          .style("top", (d3.event.pageY - 12) + "px");
      })
      .on("mouseout", mouseout);
    
    
    arcs.append("path")
      .attr("d", arc)
      .style("fill", function(d) {
        return color(d.data.percentage);
      });
    
    var div = d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("display", "none");
    
    
    function mouseout() {
      div.style("display", "none");
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>