2016-08-18 2 views
10

Ich habe ein Sunburst-Diagramm in D3. Jedes "Blütenblatt" repräsentiert eine Teilmenge von Daten. Wenn ein Benutzer auf einem der ‚Blätter‘ klickt, möchte ich es, den Übergang zeigen Auffächerung nur, dass die Teilmenge (siehe Bild):D3 - Transitionsbögen in Sunburst-Diagramm

enter image description here

Ich habe Probleme mit dem Code immer richtig Übergang.

Bei einem Klick sollten alle "Petalen" (neben dem ausgewählten) verschwinden und die verbleibenden Pfade sollten entlang des Kreises animieren (mit attr Tween, arcTween und interpolieren?). Der primäre Wert, der sich ändern würde, ist die angleSize (var angleSize = (2 * Math.PI)/theData.length;).

Ich habe versucht mit this, this, this und this als Referenz ohne viel Erfolg. Was ist der beste Weg, um mit der Animation umzugehen?

Danke für Ihre Zeit!

-> Siehe Plunker Here. < -

-Code ist unten:

var colors = { 
    'Rank1' : '#3FA548', 
    'Rank2' : '#00B09E', 
    'Rank3' : '#8971B3', 
    'Rank4' : '#DFC423', 
    'Rank5' : '#E74341' 
    }; 

    var $container = $('.chart'), 
     m = 40, 
     width = $container.width() - m, 
     height = $container.height() - m, 
     r = Math.min(width, height)/2; 

    var study = null; 
    var arc = d3.svg.arc(); 

    d3.csv('text.csv', ready); 

    function ready(err, data) { 
    if (err) console.warn('Error', err); 

    var svg = d3.select('.chart') 
       .append('svg') 
       .attr({ 
        'width' : (r + m) * 2, 
        'height' : (r + m) * 2, 
        'class' : 'container' 
       }) 
       .append('g') 
       .attr('transform', 'translate(' + (width/4) + ', ' + (height/2) + ')'); 

    var slice = svg.selectAll('.slice'); 

    function updateChart(study) { 
     if (study) { 
     var theData = data.filter(function(d) { 
       return d.study_name === study; 
      }); 
     } else { 
     var theData = data; 
     } 

     slice = slice.data(theData); 

     slice.enter() 
      .append('g') 
      .attr('class', 'slice'); 

     var angleSize = (2 * Math.PI)/theData.length; 

     var startRadArr = [], 
      endRadArr = []; 

     for (var i = 0; i < data.length; i++) { 
     var startRadius = (width/20), 
      endRadius = startRadius; 

     for (var x = 0; x < 4; x++) { 
      startRadArr.push(startRadius); 

      if (x == 0) { 
      endRadius += Number(data[i].group1_score) * (width/500); 
      } else if (x == 1) { 
      endRadius += Number(data[i].group2_score) * (width/500); 
      } else if (x == 2) { 
      endRadius += Number(data[i].group3_score) * (width/500); 
      } else { 
      endRadius += Number(data[i].group4_score) * (width/500); 
      } 

      endRadArr.push(endRadius); 
      startRadius = endRadius + 0.3; 
     } 
     } 

     var startRadGroup = [], 
      endRadGroup = []; 

     for (i = 0; i < startRadArr.length; i += 4) { 
     startRadGroup.push(startRadArr.slice(i, i + 4)); 
     } 

     for (i = 0; i < endRadArr.length; i += 4) { 
     endRadGroup.push(endRadArr.slice(i, i + 4)); 
     } 

     slice.selectAll('path') 
      .remove(); 

     for (var x = 0; x < 4; x++) { 
     slice.append('path') 
      .attr({ 
       'class' : function(d, i) { 
       if (x == 0) { 
        return d.group1_class; 
       } else if (x == 1) { 
        return d.group2_class; 
       } else if (x == 2) { 
        return d.group3_class; 
       } else { 
        return d.group4_class; 
       } 
       }, 
       'company' : function(d, i) { 
       return d.brand_name; 
       }, 
       'cat' : function(d, i) { 
        if (x == 0) { 
        return 'Group1'; 
       } else if (x == 1) { 
        return 'Group2'; 
       } else if (x == 2) { 
        return 'Group3'; 
       } else { 
        return 'Group4'; 
       } 
       }, 
       'study' : function(d, i) { 
       return d.study_name; 
       }, 
       'companyid' : function(d, i) { 
       return d.brand_id; 
       }, 
       'startradius' : function(d, i) { 
       return startRadGroup[i][x]; 
       }, 
       'endradius' : function(d, i) { 
       return endRadGroup[i][x]; 
       }, 
       'startangle' : function(d, i) { 
       return angleSize * i; 
       }, 
       'endangle' : function(d, i) { 
       return angleSize * (i + 1); 
       } 
      }) 
      .on('click', selectStudy); 
     } 

     slice.exit() 
      .remove(); 

     slice.selectAll('path') 
      .attr({ 
      'd' : function(d) { 
       return arc({ 
        innerRadius : +d3.select(this)[0][0].attributes.startradius.nodeValue, 
        outerRadius : +d3.select(this)[0][0].attributes.endradius.nodeValue, 
        startAngle : +d3.select(this)[0][0].attributes.startangle.nodeValue, 
        endAngle : +d3.select(this)[0][0].attributes.endangle.nodeValue 
       }) 
       } 
      }); 
    } 

    function selectStudy(d) { 
     study = $(this).attr('study'); 
     updateChart(study); 
    } 

    updateChart(); 
    } 

EDIT
den Code aktualisiert (basierend auf this) ein ordnungsgemäß funktionierende eingeben zu schließen, zu aktualisieren und Ausgabemuster. Immer noch unsicher über den Übergang. Die meisten der Beispiele, die ich verlinkt habe, verwenden etwas Ähnliches wie d3.interpolate(this._current, a);, zwischen verschiedenen Daten tweening.

In diesem Diagramm sind this._current und a gleich, angleSize (var angleSize = (2 * Math.PI)/theData.length;), startAngle und endAngle sind die einzigen Änderungen.

+1

Es gibt ein paar Dinge, die dies komplizieren - vor allem, dass Sie alle Schichten entfernen, bevor Sie versuchen, sie zu überführen, aber Sie müssen sie mit ihrem Startpunkt neu erstellen, um den Übergang zu beginnen. Erwägen Sie auch, die Daten in eine tiefere Struktur umzuformatieren, so dass Sie nicht 4 Mal über jeden Punkt eine Schleife machen müssen. – Owen

+0

@Owen Ja. Die remove() war ein temp-Fix für etwas seltsames Verhalten (neue Daten würden über alte Daten anhängen). Kommentar zum Entfernen -> [LINK] (https://plnkr.co/edit/JwCy84YBVSxDERZOp1z0?p=preview). Was die Daten betrifft, habe ich leider keine Kontrolle über das Format. Wird nach einer JS-Lösung suchen (irgendwelche Vorschläge?). Würde es vorziehen, in Excel nicht manuell zu ändern. – BastionGamma

+0

Auch ein Grund, d3 v3 statt v4 zu verwenden? – Owen

Antwort

3

Ihr Problem ist, dass Sie nicht wirklich Daten an die Elemente binden, und daher ist der Übergang nicht möglich. Ich habe Ihren Code ein wenig gemangelt, so dass die Daten alle verschachtelten Informationen über die Anfangs- und Endwinkel enthalten, so dass sie an die Pfade innerhalb jedes Slices gebunden werden können.

in diesem Plunker Werfen Sie einen Blick: https://plnkr.co/edit/a7cxRplzy66Pc1arM2a9?p=preview

Hier ist die Liste der modifizierten Version:

var colors = { 
    Rank1: '#3FA548', 
    Rank2: '#00B09E', 
    Rank3: '#8971B3', 
    Rank4: '#DFC423', 
    Rank5: '#E74341' 
}; 

// Configuration 
var $container = $('.chart'), 
    m = 40, 
    width = $container.width() - m, 
    height = $container.height() - m, 
    r = Math.min(width, height)/2; 

var study = null; 

var arc = d3.svg.arc(); 

// Load data 
d3.csv('text.csv', ready); 

// Data loaded callback 
function ready(err, data) { 
    if (err) console.warn('Error', err); 

    var svg = d3.select('.chart') 
     .append('svg') 
     .attr({ 
      'width': (r + m) * 2, 
      'height': (r + m) * 2, 
      'class': 'container' 
     }) 
     .append('g') 
     .attr('transform', 'translate(' + (width/4) + ', ' + (height/2) + ')'); 

    var slices = svg.selectAll('.slice'); 

    function updateChart(study) { 
     var theData = data; 

     if (study) { 
      theData = data.filter(function (d) { 
       return d.study_name === study; 
      }); 
     } 

     var angleSize = (2 * Math.PI)/theData.length; 

     theData.forEach(function (item, i) { 
      var startRadius = (width/20), 
       endRadius = startRadius, 
       groupName; 

      item.paths = []; 

      for (var g = 0; g < 4; g++) { 
       item.paths[g] = {}; 

       item.paths[g].startRadius = startRadius; 

       groupName = 'group' + (g + 1) + '_score'; 

       endRadius += Number(item[groupName]) * (width/500); 

       item.paths[g].endRadius = endRadius; 

       startRadius = endRadius + 0.3; 
      } 
     }); 

     // Set the data 
     slices = slices.data(theData); 

     // Enter 
     slices.enter() 
      .append('g') 
      .attr('class', 'slice'); 

     // Exit 
     slices.exit() 
      .remove(); 

     // Update 
     slices 
      .transition() 
      .duration(750) 
      .each(function (dSlice, iSlice) { 
       var slice = d3.select(this); 

       var paths = slice.selectAll('path'); 

       // Set data 
       paths = paths.data(dSlice.paths); 

       // Exit 
       paths.exit() 
        .remove(); 

       // Enter 
       paths.enter() 
        .append('path') 
        .attr('class', 'path'); 

       // Update 
       paths 
        .transition() 
        .attr({ 
         'class': function (d, i) { 
          return dSlice['group' + (i + 1) + '_class']; 
         }, 
         'company': dSlice.brand_name, 
         'cat': function (d, i) { 
          return 'Group' + (i + 1); 
         }, 
         'study': function (d, i) { 
          return dSlice.study_name; 
         }, 
         'companyid': function (d, i) { 
          return dSlice.brand_id; 
         }, 
         'startradius': function (d, i) { 
          return d.startRadius; 
         }, 
         'endradius': function (d, i) { 
          return d.endRadius; 
         }, 
         'startangle': function (d, i) { 
          return angleSize * iSlice; 
         }, 
         'endangle': function (d, i) { 
          return angleSize * (iSlice + 1); 
         }, 
         'd': function (d) { 
          return arc({ 
           innerRadius: +d.startRadius, 
           outerRadius: +d.endRadius, 
           startAngle: +angleSize * iSlice, 
           endAngle: +angleSize * (iSlice + 1) 
          }) 
         } 
        }) 
        .duration(750); 

       paths.on('click', selectStudy); 
      }); 

     function selectStudy(d, i) { 
      study = $(this).attr('study'); 

      updateChart(study); 
     } 
    } 

    updateChart(); 
} 

Wie Sie der Schlüssel richtig sehen können, ist die Vorbereitung von Daten (lassen Sie uns das Format sagen in Ihrem Beispiel .tsv Datei ist nicht die beste Wahl, aber manchmal können wir nicht unsere Datenquellen wählen ...)

Dann danach, indem Sie den Code für die Pfade Generation innerhalb der .each Anruf auf th Die Slices können auf die Daten von den function (d, i) { ... } Callbacks zugegriffen werden und jedes Element erhält die entsprechenden Daten.

Ein weiterer Trick ist die Verwendung der Scheiben Daten (Zugriff innerhalb der .each Funktion über die dSlice und iSlice Vars) auf den Callbacks der Pfade. Auf diese Weise können die Pfade diese Daten für ihre eigenen Zwecke nutzen. In diesem Fall sind die company und study_name Eigenschaften.

Um nun den Übergang zu optimieren und genauer zu machen, können sich die Startattribute ändern. Sie können versuchen, indem Sie einige Attribute für die Pfade in der .enter() Phase einrichten.

+2

Um den Übergang stabil zu machen, sollten Sie die Daten beim erstmaligen Laden massieren und eine Schlüsselfunktion bereitstellen, damit die Funktionen enter/update/exit wissen, welche Scheiben wo hingehören. Siehe [this plunkr] (https://plnkr.co/edit/b91oOy2PYRdGclZfwzOp?p=preview) (aber beachte, dass der Übergang einfach den Pfad umgeht, nicht die Winkel). – Owen

+0

Danke! Ihr seid Lebensretter. – BastionGamma