user1046415
user1046415

Reputation: 789

Polylines in D3 and Legend spacing

I am using the below example and wanted to have the legend outside the Pie chart and also have the Polyline for the Text and the count and Percentage for each slice.

With the current code I have Pie inside the pie and Text and Percentage are showing when I mouse over the slice.

Appreciate the help a lot.Thanks

Can some one please help as I am unable to move forward.

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <link rel="stylesheet" href="normalize.css">
        <style>
            #chart {
                height: 360px;
                margin: 0 auto; /* NEW */
                position: relative;
                width: 360px;
            }
    
            .tooltip {
                background: #eee;
                box-shadow: 0 0 5px #999999;
                color: #333;
                display: none;
                font-size: 12px;
                left: 130px;
                padding: 10px;
                position: absolute;
                text-align: center;
                top: 95px;
                width: 80px;
                z-index: 10;
            }
    
            .legend {
                font-size: 12px;
            }
    
            rect {
                cursor: pointer; /* NEW */
                stroke-width: 2;
            }
    
                rect.disabled { /* NEW */
                    fill: transparent !important; /* NEW */
                }
            /* NEW */
            h1 { /* NEW */
                font-size: 14px; /* NEW */
                text-align: center; /* NEW */
            }
            /* NEW */
        </style>
    </head>
    <body>
       
        <div id="chart"></div>
        <script src="Scripts/d3.v3.min.js"></script>
        <script>
          (function(d3) {
            'use strict';
    
            var width = 360;
            var height = 360;
            var radius = Math.min(width, height) / 2;
            var donutWidth = 75;
            var legendRectSize = 18;
            var legendSpacing = 4;
    
            var color = d3.scale.category20(); //builtin range of colors
    
            var svg = d3.select('#chart')
              .append('svg')
              .attr('width', width)
              .attr('height', height)
              .append('g')
              .attr('transform', 'translate(' + (width / 2) +
                ',' + (height / 2) + ')');
    
            var arc = d3.svg.arc()
              .innerRadius(radius - donutWidth)
              .outerRadius(radius);
    
            var pie = d3.layout.pie()
              .value(function(d) { return d.count; })
              .sort(null);
    
            var tooltip = d3.select('#chart')
              .append('div')
              .attr('class', 'tooltip');
    
            tooltip.append('div')
              .attr('class', 'label');
    
            tooltip.append('div')
              .attr('class', 'count');
    
            tooltip.append('div')
              .attr('class', 'percent');
    
            d3.csv('weekdays.csv', function(error, dataset) {
              dataset.forEach(function(d) {
                d.count = +d.count;
                d.enabled = true;                                         // NEW
              });
    
              var path = svg.selectAll('path')
                .data(pie(dataset))
                .enter()
                .append('path')
                .attr('d', arc)
                .attr('fill', function(d, i) {
                  return color(d.data.label);
                })                                                        // UPDATED (removed semicolon)
                .each(function(d) { this._current = d; });                // NEW
    
              path.on('mouseover', function(d) {
                var total = d3.sum(dataset.map(function(d) {
                  return (d.enabled) ? d.count : 0;                       // UPDATED
                }));
                var percent = Math.round(1000 * d.data.count / total) / 10;
                tooltip.select('.label').html(d.data.label);
                tooltip.select('.count').html(d.data.count);
                tooltip.select('.percent').html(percent + '%');
                tooltip.style('display', 'block');
              });
    
              path.on('mouseout', function() {
                tooltip.style('display', 'none');
              });
    
              /* OPTIONAL
              path.on('mousemove', function(d) {
                tooltip.style('top', (d3.event.pageY + 10) + 'px')
                  .style('left', (d3.event.pageX + 10) + 'px');
              });
              */
    
              var legend = svg.selectAll('.legend')
                .data(color.domain())
                .enter()
                .append('g')
                .attr('class', 'legend')
                .attr('transform', function(d, i) {
                  var height = legendRectSize + legendSpacing;
                  var offset =  height * color.domain().length / 2;
                  var horz = -2 * legendRectSize;
                  var vert = i * height - offset;
                  return 'translate(' + horz + ',' + vert + ')';
                });
    
              legend.append('rect')
                .attr('width', legendRectSize)
                .attr('height', legendRectSize)
                .style('fill', color)
                .style('stroke', color)                                   // UPDATED (removed semicolon)
                .on('click', function(label) {                            // NEW
                  var rect = d3.select(this);                             // NEW
                  var enabled = true;                                     // NEW
                  var totalEnabled = d3.sum(dataset.map(function(d) {     // NEW
                    return (d.enabled) ? 1 : 0;                           // NEW
                  }));                                                    // NEW
    
                  if (rect.attr('class') === 'disabled') {                // NEW
                    rect.attr('class', '');                               // NEW
                  } else {                                                // NEW
                    if (totalEnabled < 2) return;                         // NEW
                    rect.attr('class', 'disabled');                       // NEW
                    enabled = false;                                      // NEW
                  }                                                       // NEW
    
                  pie.value(function(d) {                                 // NEW
                    if (d.label === label) d.enabled = enabled;           // NEW
                    return (d.enabled) ? d.count : 0;                     // NEW
                  });                                                     // NEW
    
                  path = path.data(pie(dataset));                         // NEW
    
                  path.transition()                                       // NEW
                    .duration(750)                                        // NEW
                    .attrTween('d', function(d) {                         // NEW
                      var interpolate = d3.interpolate(this._current, d); // NEW
                      this._current = interpolate(0);                     // NEW
                      return function(t) {                                // NEW
                        return arc(interpolate(t));                       // NEW
                      };                                                  // NEW
                    });                                                   // NEW
                });                                                       // NEW
    
              legend.append('text')
                .attr('x', legendRectSize + legendSpacing)
                .attr('y', legendRectSize - legendSpacing)
                .text(function(d) { return d; });
    
            });
    
          })(window.d3);
        </script>
    </body>
    </html>

Upvotes: 5

Views: 4183

Answers (2)

Cyril Cherian
Cyril Cherian

Reputation: 32327

You can place the legends where ever you wish by making the legends in a group and placing it using the translate

First Make SVG:

  var s = d3.select('#chart')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

Now make a legend group:

  var legend_group = s.append('g').attr('transform',
    'translate(' + (width / 3) + ',' + (height / 1.4) + ')');

Use translate it to a place of your choice. I have moved it to (width/3, height/1.4)

Make a group in which the pie chart will be drawn.

  var svg = s.append('g')
    .attr('transform', 'translate(' + (width / 2) +
      ',' + (radius) + ')');

Lets make a polyline for each slice: This function will make as many polylines as the dataset length.

function makePolyLines() {
  var polyline = svg.selectAll("polyline")
    .data(pie(dataset), key);

  polyline.enter()
    .append("polyline");
  //hide polyline for which value is 0, a case when legend is clicked.
  svg.selectAll("polyline").style("display", function(d) {
    if (d.value == 0) {
      return "none";
    } else {
      return "block";
    }
  });

  polyline.transition().duration(1000)
    .attrTween("points", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        var pos = outerArc.centroid(d2);
        pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
        return [arc.centroid(d2), outerArc.centroid(d2), pos];
      };
    });

  polyline.exit()
    .remove();
}

Similarly make text for labels.

function makeTexts() {
  var text = svg.selectAll(".labels")
    .data(pie(dataset), key);

  text.enter()
    .append("text")
    .attr("dy", ".35em")
    .classed("labels", true)
    .text(function(d) {
      return d.data.label + " (" + d.data.count + ")";
    });
  //hide text for which value is 0, a case when legend is clicked.
  svg.selectAll(".labels").style("display", function(d) {
    if (d.value == 0) {
      return "none";
    } else {
      return "block";
    }
  });

  text.transition().duration(1000)
    .attrTween("transform", function(d) {

      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        var pos = outerArc.centroid(d2);
        pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
        return "translate(" + pos + ")";
      };
    })
    .styleTween("text-anchor", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        return midAngle(d2) < Math.PI ? "start" : "end";
      };
    });

  text.exit()
    .remove();
}

finally call these two functions.

1) initially after the data is fetched.

2) whenever legend is clicked and the piechart is updated.

Working code here

Upvotes: 6

Gilad Artzi
Gilad Artzi

Reputation: 3084

First, you need to make the svg element wider. Currently it's var width = 360;, you can change it to var width = 700; for example.

After you gained some more space, determine the width of the legend, for the example let's use 300px. Declare a new variable: var legendWidth = 300;

Now, when the legend is being declared:

var legend = svg.selectAll('.legend')
    .data(color.domain())
    .enter()
    .append('g')
    .attr('class', 'legend')
    .attr('transform', function(d, i) {
      var height = legendRectSize + legendSpacing;
      var offset =  height * color.domain().length / 2;
      var horz = (-2 * legendRectSize);
      var vert = i * height - offset;
      return 'translate(' + (horz) + ',' + vert + ')';
    });

When calculation to horizontal translation, we need to take the legendWidth into consideration:

var horz = (-2 * legendRectSize) - legendWidth;

Note: You will need to fix the left and top CSS properties for the .tooltip element.

Another note: If you want to take this solution to the next level, you can implement it in a dynamic way instead of having the "magic number" of var legendWidth = 300.

Upvotes: 3

Related Questions