2
votes

I want to develop "drill down" "pie chart" using "D3.JS". I found the below sample, and perfect to me to use.

D3 Js sample drill down pie chart

The above sample is perfect fit for me.

Additionally from the sample drill down pie chart, I want to place TEXT LABEL in each section of pie chart divided.

I followed many samples,

  1. Sample 1 - pie chart with section text label

  2. Sample 2 - JSFiddle - pie chart with section text label

Based on the above samples for placing text label in pie chart sections, I followed the below code, I tried adding sample "var dataSet", "var arcs = svg.selectAll("g.slice")" code. But, when I execute the program, it doesn't display any text label in centre of each section in pie chart.

Could someone guide me to fix this please?

    <!doctype html>
<html>
<head>
  <head>
    <meta charset="utf-8">
    <title>Drill down pie chart test</title>
    <script type="text/javascript" src="http://d3js.org/d3.v2.min.js?2.9.6"></script>
    <style type="text/css">
      body {
        text-align: center;
        padding: 50px;
        font-family: "Helvetica Neue",Arial,Sans-serif;
        font-weight: 200;
        color: #333;
      }
      .header {        
        font-size: 20px;
      }
      .sector {
        cursor: pointer;
      }
      .slice text {
            font-size: 16pt;
            font-family: Arial;
        } 

    </style>
  </head>
  <body>
    <script type="text/javascript">


    // Globals
    var width = 500,
        height = 400,
        margin = 50,
        radius = Math.min(width - margin, height - margin) / 2,  
        // Pie layout will use the "val" property of each data object entry
        pieChart = d3.layout.pie().sort(null).value(function(d){return d.val;}), 
        arc = d3.svg.arc().outerRadius(radius),
        MAX_SECTORS = 15, // Less than 20 please
        colors = d3.scale.category20();

    var dataSet = [
                   {"legendLabel":"One", "magnitude":20}, 
                   {"legendLabel":"Two", "magnitude":40}, 
                   {"legendLabel":"Three", "magnitude":50}, 
                   {"legendLabel":"Four", "magnitude":16}, 
                   {"legendLabel":"Five", "magnitude":50}, 
                   {"legendLabel":"Six", "magnitude":8}, 
                   {"legendLabel":"Seven", "magnitude":30}];

        //mydata = {"Medical", "Agriculture", "Security"}; 

        var st = {};
        st.data = [{"label":"less than a week","value":169,"pos":0},{"label":"1 week - 30 days","value":1,"pos":1},{"label":"30 - 90 days","value":22,"pos":2},{"label":"90 - 180 days","value":35,"pos":3},{"label":"180 days - 1 year","value":47,"pos":4},{"label":"more than 1 year","value":783,"pos":5}] ;


    // Synthetic data generation ------------------------------------------------
    var data = [];
    var numSectors = 8; //Math.ceil(Math.random()*MAX_SECTORS);
    for(i = -1; i++ < numSectors; ) {
      var children = [];
      var numChildSectors = Math.ceil(Math.random()*MAX_SECTORS);
      var color = colors(i);
      for( j=-1; j++ < numChildSectors; ){

        // Add children categories with shades of the parent color
        children.push(
          { cat: "cat"+((i+1)*100+j), 
            val: Math.random(), 
            color: d3.rgb(color).darker(1/(j+1))
          });  
      }
      data.push({ 
        cat: "cat"+i, 
        val: Math.random(), 
        color: color, 
        children: children});
    }
    // --------------------------------------------------------------------------


    // SVG elements init
    var svg = d3.select("body").append("svg").data([dataSet]).attr("width", width).attr("height", height),
        defs = svg.append("svg:defs"),
        // .data(pieChart)

        // Declare a main gradient with the dimensions for all gradient entries to refer
        mainGrad = defs.append("svg:radialGradient")
          .attr("gradientUnits", "userSpaceOnUse")  
          .attr("cx", 0).attr("cy", 0).attr("r", radius).attr("fx", 0).attr("fy", 0)
          .attr("id", "master"),
        // The pie sectors container
        arcGroup = svg.append("svg:g")
          .attr("class", "arcGroup")
          .attr("filter", "url(#shadow)")
          .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"),
        // Header text
        header = svg.append("text").text("Biotechnology")
                  .attr("transform", "translate(10, 20)").attr("class", "header");

                //svg.append("text").attr("text-anchor", "middle").text("$" + "sample"),
                //svg.append("text").text("sample").attr("text-anchor", "middle")

        /*svg.append("text")
                .attr("transform", "translate(" + arcGroup.centroid(d) + ")")
                .attr("dy", ".35em")
                .attr("text-anchor", "middle")
                .text("sample");
                */

        // Declare shadow filter
        var shadow = defs.append("filter").attr("id", "shadow")
                      .attr("filterUnits", "userSpaceOnUse")
                      .attr("x", -1*(width / 2)).attr("y", -1*(height / 2))
                      .attr("width", width).attr("height", height);
        shadow.append("feGaussianBlur")
          .attr("in", "SourceAlpha")
          .attr("stdDeviation", "4")
          .attr("result", "blur");
        shadow.append("feOffset")
          .attr("in", "blur")
          .attr("dx", "4").attr("dy", "4")
          .attr("result", "offsetBlur");
        shadow.append("feBlend")
          .attr("in", "SourceGraphic")
          .attr("in2", "offsetBlur")
          .attr("mode", "normal");

       /* var arcs = svg.selectAll("g.slice")

       arcs.append("text")
    .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) { return d.value; });
        */



    // Redraw the graph given a certain level of data
    function updateGraph(cat){
      var currData = data;

      // Simple header text
      if(cat != undefined){
        currData = findChildenByCat(cat);
        d3.select(".header").text("Biotechnology → "+cat);
      } else {
        d3.select(".header").text("Biotechnology");
      }


      // Create a gradient for each entry (each entry identified by its unique category)
      var gradients = defs.selectAll(".gradient").data(currData, function(d){return d.cat;});      
      gradients.enter().append("svg:radialGradient")
        .attr("id", function(d, i) { return "gradient" + d.cat; })
        .attr("class", "gradient")
        .attr("xlink:href", "#master");    

      gradients.append("svg:stop").attr("offset", "0%").attr("stop-color", getColor );
      gradients.append("svg:stop").attr("offset", "90%").attr("stop-color", getColor );
      gradients.append("svg:stop").attr("offset", "100%").attr("stop-color", getDarkerColor );      


      /*var arcs = defs.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class","slice");
      arcs.append("svg:text").attr("transform", function(d){
        d.innerRadius = 0;
        d.outerRadius = r;
        return "translate(" + arc.centroid(d) + ")";}).attr("text-anchor", "middle").text( function(d, i) {
        return (data[i].value / tot ) * 100 > 10 ? ((data[i].value / tot ) * 100).toFixed(1) + "%" : "";
        }
    ).attr("fill","#fff")
      .classed("slice-label",true);
      */

      // Create a sector for each entry in the enter selection
      var paths = arcGroup.selectAll("path")
                    .data(pieChart(currData), function(d) {return d.data.cat;} );            
      paths.enter().append("svg:path").attr("class", "sector");

      // Each sector will refer to its gradient fill
      paths.attr("fill", function(d, i) { return "url(#gradient"+d.data.cat+")"; })
        .transition().duration(1000).attrTween("d", tweenIn).each("end", function(){
          this._listenToEvents = true;
        });

      // Mouse interaction handling
      paths.on("click", function(d){ 
                if(this._listenToEvents){
                  // Reset inmediatelly
                  d3.select(this).attr("transform", "translate(0,0)")
                  // Change level on click if no transition has started                
                  paths.each(function(){
                     this._listenToEvents = false;
                  });
                  updateGraph(d.data.children? d.data.cat : undefined); 
                }
              })
            .on("mouseover", function(d){ 
                 // Mouseover effect if no transition has started                
                if(this._listenToEvents){
                  // Calculate angle bisector
                  var ang = d.startAngle + (d.endAngle - d.startAngle)/2; 
                  // Transformate to SVG space
                  ang = (ang - (Math.PI / 2) ) * -1;

                  // Calculate a 10% radius displacement
                  var x = Math.cos(ang) * radius * 0.1;
                  var y = Math.sin(ang) * radius * -0.1;

                  d3.select(this).transition()
                    .duration(250).attr("transform", "translate("+x+","+y+")"); 
                }
              })
            .on("mouseout", function(d){
              // Mouseout effect if no transition has started                
              if(this._listenToEvents){
                d3.select(this).transition()
                  .duration(150).attr("transform", "translate(0,0)"); 
              }
            });


      // Collapse sectors for the exit selection
      paths.exit().transition()
        .duration(1000)
        .attrTween("d", tweenOut).remove();

   // NEWLY ADDED START

      // Select all <g> elements with class slice (there aren't any yet)
         var arcs = svg.selectAll("g.slice")
           // Associate the generated pie data (an array of arcs, each having startAngle,
           // endAngle and value properties) 
           .data(pie)
           // This will create <g> elements for every "extra" data element that should be associated
           // with a selection. The result is creating a <g> for every object in the data array
           .enter()
           // Create a group to hold each slice (we will have a <path> and a <text>
           // element associated with each slice)
           .append("svg:g")
           .attr("class", "slice");    //allow us to style things in the slices (like text)

         arcs.append("svg:path")
           //set the color for each slice to be chosen from the color function defined above
           .attr("fill", function(d, i) { return color(i); } )
           //this creates the actual SVG path using the associated data (pie) with the arc drawing function
           .attr("d", arc);

         // Add a legendLabel to each arc slice...
         arcs.append("svg:text")
           .attr("transform", function(d) { //set the label's origin to the center of the arc
             //we have to make sure to set these before calling arc.centroid
             d.outerRadius = outerRadius + 50; // Set Outer Coordinate
             d.innerRadius = outerRadius + 45; // Set Inner Coordinate
             return "translate(" + arc.centroid(d) + ")";
           })
           .attr("text-anchor", "middle") //center the text on it's origin
           .style("fill", "Purple")
           .style("font", "bold 12px Arial")
           <!-- .text(function(d, i) { return dataSet[i].legendLabel; }); //get the label from our original dat -->
           .text(function(d, i) { return "Test"; }); //get the label from our original dat

        // Add a magnitude value to the larger arcs, translated to the arc centroid and rotated.
           arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
             .attr("dy", ".35em")
             .attr("text-anchor", "middle")
             //.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
             .attr("transform", function(d) { //set the label's origin to the center of the arc
               //we have to make sure to set these before calling arc.centroid
               d.outerRadius = outerRadius; // Set Outer Coordinate
               d.innerRadius = outerRadius/2; // Set Inner Coordinate
               return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
             })
             .style("fill", "White")
             .style("font", "bold 12px Arial")
             .text(function(d) { return d.data.magnitude; });

         function angle(d) {
             var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
             return a > 90 ? a - 180 : a;
           }
           // END
    }


    // "Fold" pie sectors by tweening its current start/end angles
    // into 2*PI
    function tweenOut(data) {
      data.startAngle = data.endAngle = (2 * Math.PI);      
      var interpolation = d3.interpolate(this._current, data);
      this._current = interpolation(0);
      return function(t) {
          return arc(interpolation(t));
      };
    }


    // "Unfold" pie sectors by tweening its start/end angles
    // from 0 into their final calculated values
    function tweenIn(data) {
      var interpolation = d3.interpolate({startAngle: 0, endAngle: 0}, data);
      this._current = interpolation(0);
      return function(t) {
          return arc(interpolation(t));
      };
    }


    // Helper function to extract color from data object
    function getColor(data, index){
      return data.color;
    }


    // Helper function to extract a darker version of the color
    function getDarkerColor(data, index){
      return d3.rgb(getColor(data, index)).darker();
    }


    function findChildenByCat(cat){      
      for(i=-1; i++ < data.length - 1; ){
        if(data[i].cat == cat){
          return data[i].children;
        }
      }
      return data;
    }
    //.text(function(d, i) { return categorydata[i].label; });
    // Start by updating graph at root level
    updateGraph();

    </script>
    <!--   <p>Drill down pie chart test by Marc Baiges CamprubĂ­ <a href="mailto:[email protected]">([email protected])</a> in D3.js -->
  </body>
</html>
1

1 Answers

1
votes

Instead of doing this:

var arcs = svg.selectAll("g.slice")

do this

var arcs = arcGroup.selectAll("g.slice")

reason so that the text label and the path of the pie all are in same group.

Give proper inner and outer radius for placing the labels in the center (so that the centroid is calculated on basis of the new inner outer radius of the arc)

arcs.append("svg:text")
           .attr("transform", function(d) { //set the label's origin to the center of the arc
             //we have to make sure to set these before calling arc.centroid
             d.outerRadius = radius - 20; // Set Outer Coordinate
             d.innerRadius = radius - 100; // Set Inner Coordinate
             return "translate(" + arc.centroid(d) + ")";
           })

Next give proper data in the text:

      .text(function(d, i) { return "Test"; }); //get the label from our original data

do this

       .text(function(d, i) { return d.data.cat; }); //get the label from our original data

working code here