Etiqueta arco exterior (gráfico circular) d3.js

I'm new to d3.js and I"m trying to make a Pie-chart with it. I have only one problem: I can't get my labels outside my arcs... The labels are positioned with arc.centroid

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

¿Quién me puede ayudar con esto?

preguntado el 08 de noviembre de 11 a las 16:11

This function calculated the center point of the pie slice for a pie graph. I add a function to get the center point of the arc. Below is image based on my new function . refer to link: github.com/mbostock/d3/issues/1124 -

So if you want to go with a very nice looking legend instead of random text hanging around. I found a pretty good solution to labels. stackoverflow.com/questions/20675617/… -

8 Respuestas

I can solve that problem - with trigonometry :).

Ver violín: http://jsfiddle.net/nrabinowitz/GQDUS/

Básicamente, llamando arc.centroid(d) devuelve un [x,y] array. You can use the Pythagorean Theorem to calculate the hypotenuse, which is the length of the line from the center of the pie to the arc centroid. Then you can use the calculations x/h * desiredLabelRadius y y/h * desiredLabelRadius to calculate the desired x,y for your label anchor:

.attr("transform", function(d) {
    var c = arc.centroid(d),
        x = c[0],
        y = c[1],
        // pythagorean theorem for hypotenuse
        h = Math.sqrt(x*x + y*y);
    return "translate(" + (x/h * labelr) +  ',' +
       (y/h * labelr) +  ")"; 
})

The only downside here is that text-anchor: middle isn't a great choice anymore - you'd be better off setting the text-anchor based on which side of the pie we're on:

.attr("text-anchor", function(d) {
    // are we past the center?
    return (d.endAngle + d.startAngle)/2 > Math.PI ?
        "end" : "start";
})

respondido 20 nov., 11:03

I tried your technique, but can't get it working almost correctly, the text doesn't fit the pie area plnkr.co/edit/VYH5QbO99ZbMkvc2D4Fe?p=preview - soñador

Looks like it works fine - you might want to set the display style of your label conditionally on d.endAngle - d.startAngle > someValue. - nrabinowitz

Specifically for pie charts, the d3.layout.pie() function will format data with a startAngle y endAngle attributes. The radius can be whatever you desire (how far out from the center you would like to place the label).

Combining these pieces of information with a couple funciones trigonométricas lets you determine the x and y coordinates for labels.

Considera esto esencia/bloquear.

Regarding the x/y positioning of the text, the magic is in this line (formatted for readability):

.attr("transform", function(d) {
  return "translate(" + 
    ( (radius - 12) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
    ", " +
    ( -1 * (radius - 12) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
  ")";
 })
  • ((d.endAngle - d.startAngle) / 2) + d.startAngle gives us our angle (theta) in radians.
  • (radius - 12) is the arbitrary radius I chose for the position of the text.
  • -1 * the y axis is inverted (see below).

The trig functions used are: cos = adjacent / hypotenuse y sin = opposite / hypotenuse. But there are a couple things we need to consider to make these work with our labels.

  1. 0 angle is at 12 o'clock.
  2. The angle still increases in a clockwise direction.
  3. The y axis is inverted from the standard cartesian coordinate system. Positive y is in the direction of 6 o'clock - down.
  4. Positive x is still in the direction of 3 o'clock - right.

That messes things up quite a bit and basically has the effect of swapping sin y cos. Our trig functions then become: sin = adjacent / hypotenuse y cos = opposite / hypotenuse.

Substituting variable names we have sin(radians) = x / r y cos(radians) = y / r. After some algebraic manipulation we can get both functions in terms of x and y respectively r * sin(radians) = x y r * cos(radians) = y. From there, just plug those into the transform/translate attribute.

That'll put the labels in the right location, to make them look fancy, you need some styling logic like this:

.style("text-anchor", function(d) {
    var rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle;
    if ( (rads > 7 * Math.PI / 4 && rads < Math.PI / 4) || (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4) ) {
      return "middle";
    } else if (rads >= Math.PI / 4 && rads <= 3 * Math.PI / 4) {
      return "start";
    } else if (rads >= 5 * Math.PI / 4 && rads <= 7 * Math.PI / 4) {
      return "end";
    } else {
      return "middle";
    }
  })

This will make the labels from 10:30 o'clock to 1:30 o'clock and from 4:30 o'clock to 7:30 o'clock anchor in the middle (they are above and below), the labels from 1:30 o'clock to 4:30 o'clock anchor on the left (they are to the right), and the labels from 7:30 o'clock to 10:30 o'clock anchor on the right (they are to the left).

The same formulas can be used for any D3 radial graph, the only difference is how you determine the angle.

I hope this helps anyone stumbling across it!

Respondido el 18 de diciembre de 12 a las 00:12

¡Gracias!

I found a different way to solve this problem, but yours seems better :-)

I created a second arc with a bigger radius and used it to position my labels.

///// Arc Labels ///// 
// Calculate position 
var pos = d3.svg.arc().innerRadius(r + 20).outerRadius(r + 20); 

// Place Labels 
arcs.append("svg:text") 
       .attr("transform", function(d) { return "translate(" + 
    pos.centroid(d) + ")"; }) 
       .attr("dy", 5) 
       .attr("text-anchor", "middle") 
       .attr("fill", function(d, i) { return colorL(i); }) //Colorarray Labels
       .attr("display", function(d) { return d.value >= 2 ? null : "none"; })  
       .text(function(d, i) { return d.value.toFixed(0) + "%"});

respondido 25 nov., 11:18

Points for legibility right here! Is there a way to clone an existing arc, so that I could say my_arc.clone.outerRadius(r + 20) en lugar del completo d3.svg.arc()... ? - Peter Ehrlich

@PeterEhrlich you could always write a function that returns the radius you want. Write once use whenever you want. - Chris Pfohl

I don't know if this helps but I was able to create arcs where I place text, both, on the arc and just outside it. In one case, where I place magnitudes of the arc within the arcs, I rotate the text on the arc to match the angle of the arc. In the other, where I place the text outside of the arc, it is simply horizontal. The code is located at: http://bl.ocks.org/2295263

Mi mejor,

Frank

Respondido 19 Abr '12, 07:04

yes baby, it's SOHCAHTOA

function coordinates_on_circle(hyp, angle){
  var radian= angle * Math.PI / 180 //trig uses radians
  return {
    x: Math.cos(radian) * hyp, //adj = cos(r) * hyp
    y: Math.sin(radian) * hyp //opp = sin(r) * hyp
  }
}
var radius=100
var angle=45
coordinates_on_circle(radius, angle)

contestado el 30 de mayo de 15 a las 23:05

I achieved the same by drawing percentage as labels outside the pie chart graph, here is the code http://bl.ocks.org/farazshuja/e2cb52828c080ba85da5458e2304a61f

g.append("text")
        .attr("transform", function(d) {
        var _d = arc.centroid(d);
        _d[0] *= 2.2;   //multiply by a constant factor
        _d[1] *= 2.2;   //multiply by a constant factor
        return "translate(" + _d + ")";
      })
      .attr("dy", ".50em")
      .style("text-anchor", "middle")
      .text(function(d) {
        if(d.data.percentage < 8) {
          return '';
        }
        return d.data.percentage + '%';
      });

Respondido 22 Jul 16, 09:07

The following CoffeeScript worked for me to get labels still inside the pie slices, but toward the outer edge:

attr 'transform', (d) ->
  radius = width / 2 # radius of whole pie chart
  d.innerRadius = radius * 0.5
  d.outerRadius = radius * 1.5
  'translate(' + arc.centroid(d) + ')'

Respondido el 30 de enero de 13 a las 02:01

This was the low-cost answer I was happy with. It pushes all the labels out horizontally (that's where I had the extra space):

g.append("text")
  .attr("transform", function(d) { 
      var pos = arc.centroid(d); 
      return "translate(" + (pos[0] + (.5 - (pos[0] < 0)) * radius) + "," + (pos[1]*2) + ")"; 
  })
  .attr("dy", ".35em")
  .style("text-anchor", function(d) { 
      return arc.centroid(d)[0] > 0 ? "start" : "end";
   })
  .text(function(d) { return d.label; });

Respondido el 18 de Septiembre de 13 a las 08:09

This might be a slightly simpler way to push the text in or out from the centroid. First initialise centroid multiplier cm to a value, >1 to push out, <1 to squeeze in toward the center. Try cm=1.1 to start. The first term multiplies horizontally and the second vertically. .attr("transform", function(d) { return "translate(" + arc.centroid(d)[0]*cm + ", " + arc.centroid(d)[1]*cm + ")"; }) (sorry originally I posted pos/neg instead of >1/<1.) - punstress

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.