2016-06-17 7 views
8

Ich versuche, eine spezielle Art von Donut-Diagramm in D3, die verschiedene Ringe für positive und negative Werte enthalten wird. Die Werte können größer als 100% oder kleiner als -100% sein, so dass ein Bogen den verbleibenden Wert darstellt. Unten ist das Beispielbild des Diagramms: enter image description hereSpezielle Donut-Tabelle mit verschiedenen Ringen/Bögen für positive und negative Werte

Die erste positive Kategorie (Category_1 - Gray) Wert ist 80, so ist es 80% füllen den Kreis mit grau, so dass die 20% für nächste positive Kategorie. Der nächste positive Kategoriewert (Category_2 - Orange) ist 160. Also verwendet er zuerst die 20%, die von Category_1 übriggeblieben sind (Wert 140, der noch übrig ist). Dann füllt es den nächsten Kreis (aufwärts) mit 100% (40 Wert ist jetzt übrig) und für den verbleibenden Wert (40) wird ein Teilkreis nach oben erzeugt.

Jetzt haben wir Category_3 (dunkelrot) als negativ (-120%), also wenn man einen nach innen gerichteten Kreis erstellt und ihn zu 100% füllt (20 Wert jetzt links) und dann einen nach innen gerichteten Bogen erzeugt verbleibender Wert (20). Wir haben eine andere negative Kategorie (Category_4 - rot), also beginnt sie dort, wo die vorherige negative Kategorie (Category_3) endete und füllt 20% von dort.

Edit 3: Ich habe ein sehr einfaches Arc-basiertes Donut-Diagramm erstellt, und wenn der Gesamtwert 100 übersteigt, kann ich für die verbleibenden Werte äußere Ringe erstellen. Unten ist die JSFiddle Link:

http://jsfiddle.net/rishabh1990/zmuqze80/

data = [20, 240]; 

var startAngle = 0; 
var previousData = 0; 
var exceedingData; 
var cumulativeData = 0; 
var remainder = 100; 
var innerRadius = 60; 
var outerRadius = 40; 
var filledFlag; 

var arc = d3.svg.arc() 
    .innerRadius(innerRadius) 
    .outerRadius(outerRadius) 

for (var i = 0; i < data.length; i++) { 

    filledFlag = 0; 
    exceedingData = 0; 

    console.log("---------- Iteration: " + (i + 1) + "---------"); 

    if (data[i] > remainder) { 
    filledFlag = 1; 
    exceedingData = data[i] - remainder; 
    console.log("Exceeding: " + exceedingData); 
    data[i] = data[i] - exceedingData; 
    data.splice(i + 1, 0, exceedingData); 
    } 

    if(filledFlag === 1) { 
    cumulativeData = 0; 
    } else { 
    cumulativeData += data[i]; 
    } 

    console.log("Previous: " + previousData); 
    console.log("Data: " + data, "Current Data: " + data[i]); 
    var endAngle = (previousData + (data[i]/50)) * Math.PI; 
    console.log("Start " + startAngle, "End " + endAngle); 
    previousData = previousData + data[i]/50; 

    //if(i===1) endAngle = 1.4 * Math.PI; 
    //if(i===2) endAngle = 2 * Math.PI; 

    var vis = d3.select("#svg_donut"); 

    arc.startAngle(startAngle).endAngle(endAngle); 


    vis.append("path") 
    .attr("d", arc) 
    .attr("transform", "translate(200,200)") 
    .style("fill", function(d) { 
     if (i === 0) return "red"; 
     //if (i === 1) return "green"; 
     //if (i === 2) return "blue" 
     //if (i === 3) return "orange" 
     //if (i === 4) return "yellow"; 
    }); 

    if (exceedingData > 0) { 
    console.log("Increasing Radius From " + outerRadius + " To " + (outerRadius + 40)); 
    outerRadius = outerRadius + 22; 
    innerRadius = innerRadius + 22; 
    arc.innerRadius(innerRadius).outerRadius(outerRadius); 
    console.log("Outer: ", outerRadius); 
    } 

    if (remainder === 100) { 
    remainder = 100 - data[i]; 
    } else { 
    remainder = 100 - cumulativeData; 
    }; 

    if (filledFlag === 1) { 
    remainder = 100; 
    } 

    console.log("Remainder: " + remainder); 

    startAngle = endAngle; 

} 

Bitte einige Ideen für die Umsetzung teilen.

+0

Was soll es tun, wenn zwei positive Werte> 100% sind? Wie wäre es, wenn Kategorie 1 180% wäre? Wie würde es zusammen mit Kategorie 2 erscheinen? – meetamit

+0

@meetamit Es würde so aussehen: http://jsfiddle.net/dw0sx79v/3/ – Rishabh

+0

Got it. Dies ist ein interessantes Problem, würde aber eine Weile dauern, um ein voll funktionsfähiges Beispiel zu erhalten. Ich denke, ich kann helfen, bin mir aber nicht sicher, wann ich dazu komme. Vielleicht morgen oder nachher. Erwägen Sie in der Zwischenzeit, eine Prämie für diese Frage zu posten. Sie werden mehr Augäpfel auf diese Weise bekommen – meetamit

Antwort

3

Ok, das hat etwas gedauert, aber es scheint zu funktionieren. Lassen Sie uns zuerst feststellen, dass das, was Sie als Donut-Diagramm beschreiben, auch als eine Reihe von Balken gerendert werden kann - mit genau denselben Daten. Also habe ich von dort angefangen und es schließlich in ein Donut-Diagramm umgewandelt, aber ich habe die Bar-Implementierung auch dort gelassen. Die andere Sache ist, dass eine generische Lösung in der Lage sein sollte, die Segmente mit jedem Wert zu umbrechen, nicht nur mit 100, also habe ich einen Schieberegler eingefügt, mit dem Sie diesen Umbruchwert variieren können. Schließlich - und das ist einfacher in einer Balken- als Donut-Implementierung zu erklären -, anstatt die Striche immer von links nach rechts wie Text umbrechen zu lassen, kann es wünschenswert sein, im Zickzack, dh abwechselnd von links nach rechts und dann rechts zu wickeln. nach links und so weiter. Dies hat zur Folge, dass beim Aufteilen eines Betrags in zwei Segmente auf zwei separaten Zeilen der Zickzack-Ansatz diese beiden Segmente nebeneinander hält. Ich habe ein Kontrollkästchen hinzugefügt, um dieses Zickzack-Verhalten ein- und auszuschalten.

Hier ist ein working jsFiddle und another iteration davon.

Hier sind die wichtigen Bits:

Es gibt eine Funktion wrap(data, wrapLength), die eine Reihe von data Werte und eine wrapLength, an dem diese Werte wickeln nimmt. Diese Funktion ermittelt, welche Datenwerte in Untersegmente aufgeteilt werden müssen, und gibt ein neues Array von ihnen zurück, wobei das Objekt jedes Segments die Werte x1, x2 und y aufweist. x1 und x2 sind der Anfang und das Ende jeder Leiste, und y ist die Zeile der Leiste. In einem Donut-Diagramm sind diese Werte äquivalent Startwinkel (x1), Endwinkel (x2) und Radius (y) jedes Bogens.

Die Funktion wrap() weiß nicht, wie negative vs positive Werte zu berücksichtigen sind, also muss wrap() zweimal aufgerufen werden - einmal mit allen Negativen und dann alle positiven.Von dort wird eine Verarbeitung selektiv nur auf die Negative angewendet, und dann wird mehr Verarbeitung auf die Kombination der zwei Sätze angewendet. Der gesamte Satz von Transformationen, der in den letzten 2 Absätzen beschrieben wurde, wird durch folgendes Snippet erfasst. Ich schließe die Implementierung von wrap() hier nicht ein, nur der Code, der sie aufruft; auch ohne den Rendering-Code, der ziemlich einfach ist, sobald segments generiert wird.

// Turn N data points into N + x segments, as dictated by wrapLength. Do this separately 
// for positive and negative values. They'll be merged further down, after we apply 
// a specific transformation to just the negatives 
var positiveSegments = wrap(data.filter(function(d) { return d.value > 0; }), wrapLength); 
var negativeSegments = wrap(data.filter(function(d) { return d.value < 0; }), wrapLength); 

// Flip and offset-by-one the y-value of every negative segment. I.e. 0 becomes -1, 1 becomes -2 
negativeSegments.forEach(function(segment) { segment.y = -(segment.y + 1); }); 

// Flip the order of the negative segments, so that their sorted from negative-most y-value and up 
negativeSegments.reverse() 

// Combine negative and positive segments 
segments = negativeSegments.concat(positiveSegments); 

if(zigzag) { 
    segments.forEach(function(segment) { 
    if(Math.abs(segment.y) % 2 == (segment.y < 0 ? 0 : 1)) { flipSegment(segment, wrapLength); } 
    }); 
} 

// Offset the y of every segment (negative or positive) so that the minimum y is 0 
// and goes up from there 
var maxNegativeY = negativeSegments[0].y * -1; 
segments.forEach(function(segment) { segment.y += maxNegativeY; }); 
Verwandte Themen