web-dev-qa-db-fra.com

Étiqueter l'arc extérieur (graphique circulaire) d3.js

Je suis nouveau sur d3.js et j'essaie de faire un graphique à secteurs avec. J'ai un seul problème: je ne peux pas sortir mes étiquettes en dehors de mes arcs ... Les étiquettes sont positionnées avec arc.centroid

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

Qui peut m'aider avec ça?

44
Ibe Vanmeenen

Je peux résoudre ce problème - avec la trigonométrie :).

Voir violon: http://jsfiddle.net/nrabinowitz/GQDUS/

Fondamentalement, l'appel de arc.centroid(d) renvoie un tableau [x,y]. Vous pouvez utiliser le théorème de Pythagore pour calculer l'hypoténuse, qui est la longueur de la ligne allant du centre de la tarte au centre de gravité de l'arc. Ensuite, vous pouvez utiliser les calculs x/h * desiredLabelRadius Et y/h * desiredLabelRadius Pour calculer le x,y Souhaité pour votre ancre d'étiquette:

.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) +  ")"; 
})

Le seul inconvénient ici est que text-anchor: middle N'est plus un excellent choix - vous feriez mieux de définir le text-anchor En fonction de quel côté du gâteau nous sommes:

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

Spécifiquement pour les graphiques circulaires, la fonction d3.layout.pie() formatera les données avec les attributs startAngle et endAngle. Le rayon peut être celui que vous désirez (à quelle distance du centre vous souhaitez placer l'étiquette).

La combinaison de ces informations avec un couple fonctions trigonométriques vous permet de déterminer les coordonnées x et y pour les étiquettes.

Considérez ceci Gist / block .

Concernant le positionnement x/y du texte, la magie est dans cette ligne (formatée pour la lisibilité):

.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 Nous donne notre angle (thêta) en radians.
  • (radius - 12) Est le rayon arbitraire que j'ai choisi pour la position du texte.
  • -1 * L'axe y est inversé (voir ci-dessous).

Les fonctions trig utilisées sont: cos = adjacent / hypotenuse Et sin = opposite / hypotenuse. Mais il y a quelques choses que nous devons considérer pour que cela fonctionne avec nos étiquettes.

  1. L'angle 0 est à 12 heures.
  2. L'angle augmente toujours dans le sens horaire.
  3. L'axe y est inversé par rapport au système de coordonnées cartésiennes standard. Le positif est dans le sens de 6 heures - vers le bas.
  4. Le x positif est toujours dans le sens de 3 heures - à droite.

Cela gâche un peu les choses et a essentiellement pour effet d'échanger sin et cos. Nos fonctions trigonométriques deviennent alors: sin = adjacent / hypotenuse Et cos = opposite / hypotenuse.

En substituant les noms de variables, nous avons sin(radians) = x / r et cos(radians) = y / r. Après quelques manipulations algébriques, nous pouvons obtenir les deux fonctions en termes de x et y respectivement r * sin(radians) = x et r * cos(radians) = y. De là, branchez-les simplement dans l'attribut transform/translate.

Cela mettra les étiquettes au bon endroit, pour les rendre fantaisistes, vous avez besoin d'une logique de style comme celle-ci:

.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";
    }
  })

Cela rendra les étiquettes de 10 h 30 à 1 h 30 et de 4 h 30 à 7 h 30 ancrées au milieu (elles sont au-dessus et en dessous), les étiquettes de 1 : 30 h à 4 h 30 ancre à gauche (ils sont à droite), et les étiquettes de 7 h 30 à 10 h 30 ancre à droite (ils sont à droite) la gauche).

Les mêmes formules peuvent être utilisées pour n'importe quel graphique radial D3, la seule différence est la façon dont vous déterminez l'angle.

J'espère que cela aide quiconque à tomber dessus!

16
clayzermk1

Merci!

J'ai trouvé une façon différente de résoudre ce problème, mais la vôtre semble meilleure :-)

J'ai créé un deuxième arc avec un rayon plus grand et l'ai utilisé pour positionner mes étiquettes.

///// 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) + "%"});
15
Ibe Vanmeenen

Je ne sais pas si cela aide, mais j'ai pu créer des arcs où je place du texte, à la fois sur l'arc et juste à l'extérieur. Dans un cas, lorsque je place des amplitudes de l'arc dans les arcs, je fais pivoter le texte sur l'arc pour qu'il corresponde à l'angle de l'arc. Dans l'autre, où je place le texte en dehors de l'arc, il est simplement horizontal. Le code se trouve à: http://bl.ocks.org/229526

Mon meilleur,

Franc

5

oui bébé, c'est 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)
3
spencercooly

Le CoffeeScript suivant a fonctionné pour moi pour obtenir des étiquettes toujours à l'intérieur des tranches de tarte, mais vers le bord extérieur:

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) + ')'
1
Sarah Vessels

J'ai obtenu le même résultat en dessinant le pourcentage d'étiquettes en dehors du graphique à secteurs, voici le 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 + '%';
      });
1
FarazShuja

C'était la réponse à faible coût dont j'étais satisfait. Il pousse toutes les étiquettes horizontalement (c'est là que j'avais l'espace supplémentaire):

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; });
1
cheepychappy