Transition lors de l'ajout de nouvelles données au flux XML d3

J'utilise d3 pour dessiner un graphique de flux très similaire à l'exemple officiel http://bl.ocks.org/mbostock/4060954 :

Entrez la description de l'image ici

La seule différence est la façon dont je l'ai mis à jour avec de nouvelles données. Je ne veux pas seulement une transition verticale (valeur y), mais je veux aussi ajouter de nouveaux points de données sur la droite. Le graphique entier devrait être comprimé dans le sens horizontal.

Ce n'était pas un problème pour obtenir le résultat souhaité, le seul problème est que la transition entre les deux états ne semble pas être attendue.

Vous pouvez trouver un exemple minimal de l'étrange effet de transition sur JSfiddle: http://jsfiddle.net/jaYJ9/4/

Appuyez sur le bouton de mise à jour pour voir l'effet

test_data0 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}] test_data1 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}] $('#update').click(function(){ streamed_history(test_data1) }); var width = 300, height = 200, colors = {'0': '#6ff500', '1': '#ffad0a', '-1': '#f90035'}, feedbacks = [-1, 0, 1], stack = d3.layout.stack(); var svg = d3.select("#timeline").append("svg") .attr("width", width) .attr("height", height); var y = d3.scale.linear() .domain([0, 1]) .range([height, 0]); streamed_history(test_data0) function streamed_history(data) { data_array = feedbacks.map(function (f) { return data.map(function(element, i) { return {x: i, y: element[f]}; }) }), layers = stack(data_array) layers = feedbacks.map(function (f, i) { return {layer: layers[i], feedback: f, color: colors[f]} }) var x = d3.scale.linear() .domain([0, data.length - 1]) .range([0, width]); var area = d3.svg.area().interpolate("basis") .x(function(d) { return x(dx); }) .y0(function(d) { return y(d.y0); }) .y1(function(d) { return y(d.y0 + dy); }); //enter svg.selectAll("path") .data(layers) .enter().append("path") .attr("d", function (d) {return area(d.layer);}) .style("fill", function(d) { return d.color; }); //update d3.selectAll("path") .data(layers) .transition() .duration(2000) .attr("d", function (d) {return area(d.layer);}); } 

Ce problème tourne autour du fait que, pour les animations SVG, vous ne pouvez que pousser des points sur la fin d'un chemin.

Tout d'abord, le correctif (cela ne fonctionnera que si les graphiques sont toujours verticalement denses et ont une commande cohérente pour quel graphique est le plus élevé):

 ... var area = d3.svg.area().interpolate("basis") ... .y0(function(d) { return y(null); }) // The null here is super important! ... ... // Add this function function fixPath (path) { var Lidx = path.indexOf('L'); var Cidx = path.slice(Lidx).indexOf('C'); var PCidx = path.slice(0,Lidx).lastIndexOf('C'); var lp = path.substr(PCidx, Lidx-PCidx); var ss = path.substr(Lidx, Cidx); return (path.slice(0,Lidx) + lp + ss + path.slice(Lidx)); } ... svg.selectAll("path") .data(layers.reverse()) // Gotta reverse the order! .attr("d", function (d) { return fixPath(area(d.layer)); }) // Have to double up the bottom right corner to avoid artifacts ... ... d3.selectAll("path") .data(layers) .attr("d", function (d) { return fixPath(area(d.layer)); }) // When updating too! ... 

Exemple de travail ici: http://jsfiddle.net/f5JSR/2/

Maintenant, l'explication …

Chacune des bandes de couleurs de votre graphique est un chemin fermé, et d3.js les construit de sorte qu'aucune de ces bandes de couleurs ne se chevauche les unes avec les autres. Le problème est que cela signifie que chacun de ces chemins commence dans le coin inférieur gauche et se déplace tout autour. Lorsque vous ajoutez un nouveau point sur ces chemins, vous l'ajoutez à la fin, et vous appuyez sur le reste du chemin dans le sens inverse des aiguilles d'une montre (créant cet effet d'animation étrange).

J'ai d'abord essayé de résoudre ceci en utilisant l'excitation de SVG et la fill-rule: evenodd propriété de fill-rule: evenodd , mais il semble que pour utiliser l'écrêtage, vous devez créer des chemins composés et ajouter de nouveaux points les pousse à la fin de ce chemin composé (vous ne pouvez pas, Par exemple, pousser un point sur le premier chemin du chemin composé), de sorte que le problème persiste.

Au lieu de cela, cette solution supprime l'habileté de d3.js et fait que toutes vos bandes de couleurs se développent au bas du graphique (c'est ce que fait la ligne y(null); ). Il ordonne ensuite les chemins, de sorte que les plus élevés sont dessins en premier. Cette approche se décompose si la hauteur d'un graphique est inférieure à la hauteur d'un autre graphe.

Enfin, lorsque les points sont poussés autour du coin inférieur droit, il peut y avoir des artefacts étranges. Pour réparer cela, je double le nombre de points en bas à droite en utilisant la fonction fixPath .

Quoi qu'il en soit, cette solution fonctionne pour l'exemple de cas que vous aviez. J'espère que ça aide.