46 Stimmen

Beschriftung außerhalb des Bogens (Kreisdiagramm) d3.js

Ich bin neu bei d3.js und versuche, damit ein Tortendiagramm zu erstellen. Ich habe nur ein Problem: Ich kann meine Beschriftungen nicht außerhalb meiner Bögen platzieren... Die Beschriftungen sind mit arc.centroid positioniert

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

Wer kann mir dabei helfen?

65voto

nrabinowitz Punkte 54138

Ich kann dieses Problem lösen - mit Trigonometrie :).

Siehe Beispiel: http://jsfiddle.net/nrabinowitz/GQDUS/

Grundsätzlich liefert der Aufruf von arc.centroid(d) ein [x,y] Array zurück. Sie können den Satz des Pythagoras verwenden, um die Hypotenuse zu berechnen, die die Länge der Linie vom Zentrum des Kreises zum Bogenzentrum ist. Anschließend können Sie die Berechnungen x/h * desiredLabelRadius und y/h * desiredLabelRadius verwenden, um das gewünschte x,y für den Anker Ihres Labels zu berechnen:

.attr("transform", function(d) {
    var c = arc.centroid(d),
        x = c[0],
        y = c[1],
        // Satz des Pythagoras für die Hypotenuse
        h = Math.sqrt(x*x + y*y);
    return "translate(" + (x/h * labelr) +  ',' +
       (y/h * labelr) +  ")"; 
})

Der einzige Nachteil hierbei ist, dass text-anchor: middle keine gute Wahl mehr ist - es wäre besser, den text-anchor basierend auf welcher Seite des Kreises wir uns befinden, zu setzen:

.attr("text-anchor", function(d) {
    // sind wir über dem Zentrum?
    return (d.endAngle + d.startAngle)/2 > Math.PI ?
        "end" : "start";
})

17voto

clayzermk1 Punkte 1008

Speziell für Tortendiagramme wird die Funktion d3.layout.pie() Daten mit den Attributen startAngle und endAngle formatieren. Der Radius kann beliebig sein (wie weit vom Zentrum entfernt Sie das Etikett platzieren möchten).

Wenn Sie diese Informationen mit einigen trigonometrischen Funktionen kombinieren, können Sie die x- und y-Koordinaten für Etiketten bestimmen.

Betrachten Sie dieses gist/block.

Was die x/y-Positionierung des Textes betrifft, steckt das Magische in dieser Zeile (für bessere Lesbarkeit formatiert):

.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 gibt uns unseren Winkel (Theta) in Radiant.
  • (radius - 12) ist der willkürliche Radius, den ich für die Position des Textes gewählt habe.
  • -1 * die y-Achse ist invertiert (siehe unten).

Die verwendeten trigonometrischen Funktionen sind: cos = Ankathete / Hypotenuse und sin = Gegenkathete / Hypotenuse. Aber es gibt ein paar Dinge, die wir berücksichtigen müssen, damit diese mit unseren Etiketten funktionieren.

  1. Der 0-Winkel ist um 12 Uhr.
  2. Der Winkel nimmt immer noch im Uhrzeigersinn zu.
  3. Die y-Achse ist invertiert gegenüber dem Standard-Koordinatensystem. Positives y ist in Richtung 6 Uhr - nach unten.
  4. Positives x ist immer noch in Richtung 3 Uhr - nach rechts.

Das bringt einiges durcheinander und hat im Grunde den Effekt, dass wir sin und cos vertauschen. Unsere trigonometrischen Funktionen werden dann: sin = Ankathete / Hypotenuse und cos = Gegenkathete / Hypotenuse.

Wenn wir Variablennamen substituieren, haben wir sin(Radiant) = x / r und cos(Radiant) = y / r. Nach etwas Algebra können wir beide Funktionen in Bezug auf x und y erhalten r * sin(Radiant) = x und r * cos(Radiant) = y. Danach stecken Sie diese einfach in das transform/translate-Attribut.

Dadurch platzieren Sie die Etiketten am richtigen Ort. Um sie ansprechend aussehen zu lassen, benötigen Sie eine Styling-Logik wie diese:

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

Dadurch werden die Etiketten von 10:30 Uhr bis 1:30 Uhr und von 4:30 Uhr bis 7:30 Uhr in der Mitte verankert (sie befinden sich über und unterhalb), die Etiketten von 1:30 Uhr bis 4:30 Uhr an der linken Seite verankert (sie befinden sich rechts) und die Etiketten von 7:30 Uhr bis 10:30 Uhr an der rechten Seite verankert (sie befinden sich links).

Die gleichen Formeln können für jeden D3-Radialgraphen verwendet werden, der einzige Unterschied liegt darin, wie Sie den Winkel bestimmen.

Ich hoffe, das hilft allen, die darauf stoßen!

16voto

Ibe Vanmeenen Punkte 928

Danke!

Ich habe einen anderen Weg gefunden, um dieses Problem zu lösen, aber deiner scheint besser zu sein :-)

Ich habe einen zweiten Bogen mit größerem Radius erstellt und ihn verwendet, um meine Beschriftungen zu positionieren.

///// Bogen Beschriftungen ///// 
// Position berechnen
var pos = d3.svg.arc().innerRadius(r + 20).outerRadius(r + 20); 

// Beschriftungen platzieren
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); }) //Farbarray Beschriftungen
       .attr("display", function(d) { return d.value >= 2 ? null : "none"; })  
       .text(function(d, i) { return d.value.toFixed(0) + "%"});

5voto

Ich weiß nicht, ob das hilft, aber ich konnte Bögen erstellen, wo ich Text platziere, sowohl auf dem Bogen als auch kurz außerhalb davon. In einem Fall, wo ich die Magnituden des Bogens innerhalb der Bögen platziere, drehe ich den Text auf dem Bogen, um den Winkel des Bogens anzupassen. Im anderen Fall, wo ich den Text außerhalb des Bogens platziere, ist er einfach horizontal. Der Code befindet sich unter: http://bl.ocks.org/2295263

Mein Bestes,

Frank

3voto

spencercooly Punkte 6208

Ja baby, es ist 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)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X