no_parachute44
no_parachute44

Reputation: 385

D3 Labels in pie chart being cut off

I'm having an issue with a D3 pie chart where labels are cutoff when they appear. Here's a pic:

pie chart as-is

I'm new to D3, and am not sure exactly where a fix should be. The logic for making the pie chart is 400 lines, so I made a pastebin: https://pastebin.com/32CxpeDM

Maybe the issue is in this function?

function showDetails(layer, data) {
    active.inner = !active.inner
    active.outer = false
    let lines = guideContainer.selectAll('polyline.guide-line-inner')
        .data(pie(data.inner))
    let text = guideContainer.selectAll('.inner-text')
        .data(pie(data.inner))

    if (active.inner) {
        // LINES
        lines.enter()
            .append('polyline')
            .attr('points', calcPoints)
            .attr('class', 'guide-line-inner')
            .style('opacity', 0)
            .transition().duration(300)
            .style('opacity', d => { return d.data.value <= 0 ? 0 : 1 })

        lines.attr('points', calcPoints).attr('class', 'guide-line-inner')

        // TEXT
        text.enter()
            .append('text')
            .html(labelText)
            .attr('class', 'inner-text label label-circle')
            .attr('transform', labelTransform)
            .attr('dy', '1.1em')
            .attr('x', d => { return (midAngle(d) < Math.PI ? 15 : -15) })
            .style('text-anchor', d => { return (midAngle(d)) < Math.PI ? 'start' : 'end' })
            .style('opacity', 0)
            .call(wrap, 300)
            .on('click', d => { vm.$emit('details', d) })
            .transition().duration(300)
            .style('opacity', d => { return d.data.value <= 0 ? 0 : 1 })
    } else {
        lines.transition().duration(300)
            .style('opacity', 0)
            .remove()
        text.transition().duration(300)
            .style('opacity', 0)
            .remove()
    }
    guideContainer.selectAll('polyline.guide-line-outer').transition().duration(300)
        .style('opacity', 0)
        .remove()
    guideContainer.selectAll('.outer-text').transition().duration(300)
        .style('opacity', 0)
        .remove()
}

Like I said, I don't know D3 so I'm not sure what my options are for fixing this. Make the chart smaller, fix some problem with its div container, send it to the front, or edit the above code. Ideally this would be a clean fix and not a hack, which is what I've been trying.

What the easiest and cleanest fix for this problem? Thanks!

Upvotes: 3

Views: 2004

Answers (2)

Rachel Gallen
Rachel Gallen

Reputation: 28583

One approach that would conserve space would be to use d3 tooltips instead of fixed-position labels as in this fiddle (created by another jsfiddle user, I just added the margin)

Javascript:

//Width/height
var w = 300;
var h = 300;

var dataset = [5, 10, 20, 45, 6, 25];

var outerRadius = w / 2;
var innerRadius = w / 3;
var arc = d3.svg.arc()
  .innerRadius(innerRadius)
  .outerRadius(outerRadius);

var pie = d3.layout.pie();

var color = d3.scale.category10();

//Create SVG element
var svg = d3.select("#vis")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

//Set up groups
var arcs = svg.selectAll("g.arc")
  .data(pie(dataset))
  .enter()
  .append("g")
  .attr("class", "arc")
  .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");

//Draw arc paths
arcs.append("path")
  .attr("fill", function(d, i) {
    return color(i);
  })
  .attr("d", arc);

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

//
var tip = d3.tip()
  .attr("class", "d3-tip")
  .html(function(d, i) {
    return d.value
  })

svg.call(tip)

arcs
  .on("mouseover", tip.show)
  .on("mouseout", tip.hide)

This uses a d3 tip library by Justin Palmer, that can be found on Github.

Also, you could consider making the circle smaller?

Hope this helps

Upvotes: 0

nbwoodward
nbwoodward

Reputation: 3166

It looks like your labels are getting transformed past the edge of your SVG boundary.

I would try to translate the label elements farther left in this function:

function labelTransform (d) {
  var pos = guideArc.centroid(d)

  pos[0] = (radius + 100) * (midAngle(d) < Math.PI ? 1 : -1)
  return 'translate(' + pos + ')'
}

You'll have to chase down where the label line is being generated and shorten that as well.

You might also try braking the label text into more lines in this function:

  function labelText (d) {
    if ((radius + 100) * (midAngle(d) < Math.PI ? 1 : 0)) {
      return '<tspan>' + d.data.display_name + ' </tspan><tspan x="15">' + d3.format('($,.0f')(d.data.value) + '</tspan>'
    } else {
      return '<tspan>' + d.data.display_name + ' </tspan><tspan x="-15" text-anchor="end">' + d3.format('($,.0f')(d.data.value) + '</tspan>'
    }
  }

Upvotes: 2

Related Questions