javascript d3.js transition chord-diagram

javascript - Cambio y transición del conjunto de datos en diagrama de acordes con D3.



d3.js transition (1)

Estoy trabajando en un diagrama de acordes usando D3.

Estoy tratando de hacerlo para que cuando un usuario haga clic en un enlace, el conjunto de datos cambie a otro conjunto de datos predefinido. He consultado http://exposedata.com/tutorial/chord/latest.html y http://fleetinbeing.net/d3e/chord.html , y he intentado usar algunos elementos para que funcione .

Aquí está el JavaScript para crear el diagrama "por defecto":

var dataset = "data/all_trips.json"; var width = 650, height = 600, outerRadius = Math.min(width, height) / 2 - 25, innerRadius = outerRadius - 18; var formatPercent = d3.format("%"); var arc = d3.svg.arc() .innerRadius(innerRadius) .outerRadius(outerRadius); var layout = d3.layout.chord() .padding(.03) .sortSubgroups(d3.descending) .sortChords(d3.ascending); var path = d3.svg.chord() .radius(innerRadius); var svg = d3.select("#chart_placeholder").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("id", "circle") .attr("transform", "translate(" + width / 1.5 + "," + height / 1.75 + ")"); svg.append("circle") .attr("r", outerRadius); d3.csv("data/neighborhoods.csv", function(neighborhoods) { d3.json(dataset, function(matrix) { // Compute chord layout. layout.matrix(matrix); // Add a group per neighborhood. var group = svg.selectAll(".group") .data(layout.groups) .enter().append("g") .attr("class", "group") .on("mouseover", mouseover); // Add a mouseover title. group.append("title").text(function(d, i) { return numberWithCommas(d.value) + " trips started in " + neighborhoods[i].name; }); // Add the group arc. var groupPath = group.append("path") .attr("id", function(d, i) { return "group" + i; }) .attr("d", arc) .style("fill", function(d, i) { return neighborhoods[i].color; }); var rootGroup = d3.layout.chord().groups()[0]; // Text label radiating outward from the group. var groupText = group.append("text"); group.append("svg:text") .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; }) .attr("xlink:href", function(d, i) { return "#group" + i; }) .attr("dy", ".35em") .attr("color", "#fff") .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; }) .attr("transform", function(d) { return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" + " translate(" + (innerRadius + 26) + ")" + (d.angle > Math.PI ? "rotate(180)" : ""); }) .text(function(d, i) { return neighborhoods[i].name; }); // Add the chords. var chord = svg.selectAll(".chord") .data(layout.chords) .enter().append("path") .attr("class", "chord") .style("fill", function(d) { return neighborhoods[d.source.index].color; }) .attr("d", path); // Add mouseover for each chord. chord.append("title").text(function(d) { if (!(neighborhoods[d.target.index].name === neighborhoods[d.source.index].name)) { return numberWithCommas(d.source.value) + " trips from " + neighborhoods[d.source.index].name + " to " + neighborhoods[d.target.index].name + "/n" + numberWithCommas(d.target.value) + " trips from " + neighborhoods[d.target.index].name + " to " + neighborhoods[d.source.index].name; } else { return numberWithCommas(d.source.value) + " trips started and ended in " + neighborhoods[d.source.index].name; } }); function mouseover(d, i) { chord.classed("fade", function(p) { return p.source.index != i && p.target.index != i; }); var selectedOrigin = d.value; var selectedOriginName = neighborhoods[i].name; } }); });

Y esto es lo que estoy tratando de hacer para que vuelva a renderizar el gráfico con los nuevos datos (hay un elemento de imagen con el id "femenino".

d3.select("#female").on("click", function () { var new_data = "data/women_trips.json"; reRender(new_data); }); function reRender(data) { var layout = d3.layout.chord() .padding(.03) .sortSubgroups(d3.descending) .matrix(data); // Update arcs svg.selectAll(".group") .data(layout.groups) .transition() .duration(1500) .attrTween("d", arcTween(last_chord)); // Update chords svg.select(".chord") .selectAll("path") .data(layout.chords) .transition() .duration(1500) .attrTween("d", chordTween(last_chord)) }; var arc = d3.svg.arc() .startAngle(function(d) { return d.startAngle }) .endAngle(function(d) { return d.endAngle }) .innerRadius(r0) .outerRadius(r1); var chordl = d3.svg.chord().radius(r0); function arcTween(layout) { return function(d,i) { var i = d3.interpolate(layout.groups()[i], d); return function(t) { return arc(i(t)); } } } function chordTween(layout) { return function(d,i) { var i = d3.interpolate(layout.chords()[i], d); return function(t) { return chordl(i(t)); } } }


Creando un diagrama de acordes

Hay una serie de capas para crear un diagrama de acordes con d3, que corresponde a la cuidadosa separación de la manipulación de datos de la visualización de datos . Si no solo va a crear un diagrama de acordes, sino que también debe actualizarlo sin problemas, deberá comprender claramente qué hace cada parte del programa y cómo interactúan.

Primero, el aspecto de la manipulación de datos. La herramienta de diseño de acordes d3 toma sus datos sobre las interacciones entre diferentes grupos y crea un conjunto de objetos de datos que contienen los datos originales, pero también se les asignan mediciones de ángulos. De esta manera, es similar a la herramienta de diseño circular , pero hay algunas diferencias importantes relacionadas con la mayor complejidad del diseño de acordes.

Al igual que las otras herramientas de diseño de d3, crea un objeto de diseño de acordes llamando a una función ( d3.layout.chord() ), y luego llama a métodos adicionales en el objeto de diseño para cambiar la configuración predeterminada. Sin embargo, a diferencia de la herramienta de diseño circular y la mayoría de los otros diseños, el objeto de diseño de acorde no es una función que toma sus datos como entrada y genera la matriz calculada de objetos de datos con los atributos de diseño (ángulos) establecidos.

En cambio, sus datos son otra configuración para el diseño, que define con el método .matrix() , y que se almacena dentro del objeto de diseño. Los datos deben almacenarse dentro del objeto porque hay dos arreglos diferentes de objetos de datos con atributos de diseño, uno para los acordes (conexiones entre diferentes grupos) y uno para los grupos mismos. El hecho de que el objeto de diseño almacene los datos es importante cuando se trata de actualizaciones, ya que debe tener cuidado de no sobrescribir los datos antiguos con nuevos si aún necesita los datos antiguos para las transiciones.

var chordLayout = d3.layout.chord() //create layout object .sortChords( d3.ascending ) //set a property .padding( 0.01 ); //property-setting methods can be chained chordLayout.matrix( data ); //set the data matrix

Se accede a los objetos de datos de grupo llamando a .groups() en el diseño de acordes después de que se haya establecido la matriz de datos. Cada grupo es equivalente a una fila en su matriz de datos (es decir, cada subarreglo en una matriz de matrices). A los objetos de datos de grupo se les han asignado valores de ángulo inicial y final que representan una sección del círculo. Esto se parece a un gráfico circular, con la diferencia de que los valores para cada grupo (y para el círculo en su totalidad) se calculan sumando los valores de toda la fila (subarray). Los objetos de datos de grupo también tienen propiedades que representan su índice en la matriz original (importante porque podrían estar ordenados en un orden diferente) y su valor total.

Se accede a los objetos de datos de acordes llamando a .chords() en el diseño de acordes después de que se haya establecido la matriz de datos. Cada acorde representa dos valores en la matriz de datos, equivalentes a las dos relaciones posibles entre dos grupos. Por ejemplo, en el ejemplo de @ latortue09, las relaciones son viajes en bicicleta entre vecindarios, por lo que el acorde que representa los viajes entre el vecindario A y el vecindario B representa el número de viajes de A a B, así como el número de B a A. Si el vecindario A está en la fila a de su matriz de datos y Neighborhood B está en la fila b , entonces estos valores deben estar en los data[a][b] y los data[b][a] , respectivamente. (Por supuesto, a veces las relaciones que está dibujando no tienen este tipo de dirección, en cuyo caso su matriz de datos debería ser simétrica , lo que significa que esos dos valores deberían ser iguales).

Cada objeto de datos de acordes tiene dos propiedades, source y target , cada uno de los cuales es su propio objeto de datos. Tanto el objeto de datos de origen como el de destino tienen la misma estructura con información sobre la relación unidireccional de un grupo a otro, incluidos los índices originales de los grupos y el valor de esa relación, y los ángulos de inicio y final que representan una sección de uno. Segmento grupal del círculo.

El nombre de origen / destino es un poco confuso, ya que, como mencioné anteriormente, el objeto de acorde representa ambas direcciones de la relación entre dos grupos. La dirección que tiene el valor más grande determina qué grupo se llama source y cuál se llama target . Entonces, si hay 200 viajes desde el Barrio A al Barrio B, pero 500 viajes desde B a A, entonces la source de ese objeto de acorde representará una sección del segmento del círculo del Barrio B, y el target representará parte del segmento del Barrio A del circulo. Para la relación entre un grupo y sí mismo (en este ejemplo, los viajes que comienzan y terminan en el mismo vecindario), los objetos de origen y destino son los mismos.

Un último aspecto importante de la matriz de objetos de datos de acordes es que solo contiene objetos donde existen relaciones entre dos grupos. Si no hay viajes entre Neighborhood A y Neighborhood B en ninguna dirección, entonces no habrá ningún objeto de datos de acordes para esos grupos. Esto se vuelve importante cuando se actualiza de un conjunto de datos a otro.

En segundo lugar, el aspecto de visualización de datos. La herramienta Diseño de acordes crea matrices de objetos de datos, convirtiendo la información de la matriz de datos en ángulos de un círculo. Pero no dibuja nada. Para crear la representación SVG estándar de un diagrama de acordes, utilice las selecciones de d3 para crear elementos unidos a una matriz de objetos de datos de diseño. Debido a que hay dos matrices diferentes de objetos de datos de diseño en el diagrama de acordes, uno para los acordes y otro para los grupos, hay dos selecciones d3 diferentes.

En el caso más simple, ambas selecciones contendrían elementos <path> (y los dos tipos de rutas se distinguirían por clase). Los <path> que se unen a la matriz de datos para los grupos de diagramas de acordes se convierten en arcos alrededor del círculo, mientras que los <path> que se unen a los datos de los propios acordes se convierten en bandas a lo largo del círculo.

La forma de una <path> está determinada por su atributo "d" (datos de ruta o direcciones) . D3 tiene una variedad de generadores de datos de ruta , que son funciones que toman un objeto de datos y crean una cadena que se puede usar para el atributo "d" una ruta. Cada generador de ruta se crea llamando a un método d3, y cada uno puede modificarse llamando a sus propios métodos.

Los grupos en un diagrama de acordes estándar se dibujan utilizando el generador de datos de ruta d3.svg.arc() . Este generador de arco es el mismo utilizado por los gráficos de tarta y rosquilla. Después de todo, si elimina los acordes de un diagrama de acordes, esencialmente solo tiene un diagrama de donut compuesto por arcos de grupo. El generador de arco predeterminado espera pasar objetos de datos con las propiedades startAngle y endAngle ; los objetos de datos de grupo creados por el diseño de acordes funcionan con este valor predeterminado. El generador de arco también necesita conocer el radio interno y externo del arco. Estos se pueden especificar como funciones de los datos o como constantes; Para el diagrama de acordes serán constantes, las mismas para cada arco.

var arcFunction = d3.svg.arc() //create the arc path generator //with default angle accessors .innerRadius( radius ) .outerRadius( radius + bandWidth); //set constant radius values var groupPaths = d3.selectAll("path.group") .data( chordLayout.groups() ); //join the selection to the appropriate data object array //from the chord layout groupPaths.enter().append("path") //create paths if this isn''t an update .attr("class", "group"); //set the class /* also set any other attributes that are independent of the data */ groupPaths.attr("fill", groupColourFunction ) //set attributes that are functions of the data .attr("d", arcFunction ); //create the shape //d3 will pass the data object for each path to the arcFunction //which will create the string for the path "d" attribute

Los acordes en un diagrama de acordes tienen una forma única para este tipo de diagrama. Sus formas se definen utilizando el generador de datos de ruta d3.svg.chord() . El generador de acordes predeterminado espera datos del formulario creado por el objeto de diseño de acordes, lo único que debe especificarse es el radio del círculo (que generalmente será el mismo que el radio interno de los grupos de arco).

var chordFunction = d3.svg.chord() //create the chord path generator //with default accessors .radius( radius ); //set constant radius var chordPaths = d3.selectAll("path.chord") .data( chordLayout.chords() ); //join the selection to the appropriate data object array //from the chord layout chordPaths.enter().append("path") //create paths if this isn''t an update .attr("class", "chord"); //set the class /* also set any other attributes that are independent of the data */ chordPaths.attr("fill", chordColourFunction ) //set attributes that are functions of the data .attr("d", chordFunction ); //create the shape //d3 will pass the data object for each path to the chordFunction //which will create the string for the path "d" attribute

Ese es el caso simple, con elementos <path> solamente. Si también desea tener etiquetas de texto asociadas con sus grupos o acordes, sus datos se unirán a los elementos <g> , y los elementos <path> y los elementos <text> para las etiquetas (y cualquier otro elemento, como la marca de verificación). marque las líneas en el ejemplo del color del cabello) son hijos de los que heredan su objeto de datos. Cuando actualice el gráfico, deberá actualizar todos los subcomponentes que se ven afectados por los datos.

Actualizando un diagrama de acordes

Con toda esa información en mente, ¿cómo debería enfocarse en crear un diagrama de acordes que pueda actualizarse con nuevos datos?

Primero, para minimizar la cantidad total de código, generalmente recomiendo que su método de actualización sea el doble como método de inicialización . Sí, todavía necesitará algunos pasos de inicialización para cosas que nunca cambian en la actualización, pero para dibujar realmente las formas que se basan en los datos, solo debería necesitar una función, independientemente de si se trata de una actualización o una nueva visualización.

Para este ejemplo, los pasos de inicialización incluirán la creación del elemento <svg> y el elemento centrado <g> , así como la lectura en la matriz de información sobre los diferentes vecindarios. Luego, el método de inicialización llamará al método de actualización con una matriz de datos predeterminada. Los botones que cambian a una matriz de datos diferente llamarán al mismo método.

/*** Initialize the visualization ***/ var g = d3.select("#chart_placeholder").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("id", "circle") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); //the entire graphic will be drawn within this <g> element, //so all coordinates will be relative to the center of the circle g.append("circle") .attr("r", outerRadius); d3.csv("data/neighborhoods.csv", function(error, neighborhoodData) { if (error) {alert("Error reading file: ", error.statusText); return; } neighborhoods = neighborhoodData; //store in variable accessible by other functions updateChords(dataset); //call the update method with the default dataset url } ); //end of d3.csv function /* example of an update trigger */ d3.select("#MenOnlyButton").on("click", function() { updateChords( "/data/men_trips.json" ); disableButton(this); });

Acabo de pasar un url de datos a la función de actualización, lo que significa que la primera línea de esa función será una llamada a la función de análisis de datos. La matriz de datos resultante se utiliza como la matriz para un nuevo objeto de diseño de datos. Necesitamos un nuevo objeto de diseño para mantener una copia del diseño anterior para las funciones de transición. (Si no iba a realizar la transición de los cambios, podría simplemente llamar al método de matrix en el mismo diseño para crear el nuevo). Para minimizar la duplicación de código, uso una función para crear el nuevo objeto de diseño y configurar todos sus elementos. opciones:

/* Create OR update a chord layout from a data matrix */ function updateChords( datasetURL ) { d3.json(datasetURL, function(error, matrix) { if (error) {alert("Error reading file: ", error.statusText); return; } /* Compute chord layout. */ layout = getDefaultLayout(); //create a new layout object layout.matrix(matrix); /* main part of update method goes here */ }); //end of d3.json }

Luego, pase a la parte principal de la función de dibujo de actualizar o crear: tendrá que desglosar todas las cadenas de métodos en cuatro partes para unir, ingresar, salir y actualizar datos. De esta manera, puede manejar la creación de nuevos elementos durante una actualización (por ejemplo, nuevos acordes para grupos que no tenían una relación en el conjunto de datos anterior) con el mismo código que utiliza para manejar la creación original de la visualización.

Primero, la cadena de unión de datos. Una para los grupos y otra para los acordes.
Para mantener la constancia de los objetos a través de las transiciones, y para reducir la cantidad de propiedades gráficas que debe configurar en la actualización, querrá establecer una función clave dentro de su combinación de datos. De forma predeterminada, d3 hace coincidir los datos con los elementos dentro de una selección basándose únicamente en su orden en la página / matriz. Debido a que el .chords() de .chords() nuestro diseño de acordes no incluye los acordes donde no hay una relación en este conjunto de datos, el orden de los acordes puede ser inconsistente entre las rondas de actualización. La matriz .groups() también se puede reorganizar en órdenes que no coinciden con la matriz de datos original, por lo que también agregamos una función clave para que sea seguro. En ambos casos, las funciones clave se basan en las propiedades .index que el diseño de acordes almacenó en los objetos de datos.

/* Create/update "group" elements */ var groupG = g.selectAll("g.group") .data(layout.groups(), function (d) { return d.index; //use a key function in case the //groups are sorted differently between updates }); /* Create/update the chord paths */ var chordPaths = g.selectAll("path.chord") .data(layout.chords(), chordKey ); //specify a key function to match chords //between updates /* Elsewhere, chordKey is defined as: */ function chordKey(data) { return (data.source.index < data.target.index) ? data.source.index + "-" + data.target.index: data.target.index + "-" + data.source.index; //create a key that will represent the relationship //between these two groups *regardless* //of which group is called ''source'' and which ''target'' }

Tenga en cuenta que los acordes son elementos de <path> , pero los grupos son elementos de <g> , que contendrán tanto una <path> como un <text> .

Las variables creadas en este paso son selecciones de unión de datos; contendrán todos los elementos existentes (si los hay) que coincidieron con el selector y un valor de datos, y contendrán punteros nulos para los valores de datos que no coincidieron con un elemento existente. También tienen los .enter() y .exit() para acceder a esas cadenas.

En segundo lugar, la cadena de entrada. Para todos los objetos de datos que no coincidieron con un elemento (que son todos si esta es la primera vez que se dibuja la visualización), necesitamos crear el elemento y sus componentes secundarios. En este momento, también desea establecer cualquier atributo que sea constante para todos los elementos (independientemente de los datos), o que se base en los valores de datos que utiliza en la función clave y, por lo tanto, no cambiará en la actualización.

var newGroups = groupG.enter().append("g") .attr("class", "group"); //the enter selection is stored in a variable so we can //enter the <path>, <text>, and <title> elements as well //Create the title tooltip for the new groups newGroups.append("title"); //create the arc paths and set the constant attributes //(those based on the group index, not on the value) newGroups.append("path") .attr("id", function (d) { return "group" + d.index; //using d.index and not i to maintain consistency //even if groups are sorted }) .style("fill", function (d) { return neighborhoods[d.index].color; }); //create the group labels newGroups.append("svg:text") .attr("dy", ".35em") .attr("color", "#fff") .text(function (d) { return neighborhoods[d.index].name; }); //create the new chord paths var newChords = chordPaths.enter() .append("path") .attr("class", "chord"); // Add title tooltip for each new chord. newChords.append("title");

Tenga en cuenta que los colores de relleno para los arcos de grupo se configuran al ingresar, pero no los colores de relleno para los acordes. Esto se debe a que el color del acorde cambiará dependiendo de qué grupo (de los dos se conecta el acorde) se llama "fuente" y cuál es "objetivo", es decir, dependiendo de qué dirección de la relación es más fuerte (tiene más disparos).

En tercer lugar, la cadena de actualización. Cuando .enter() un elemento a una selección .enter() , ese nuevo elemento reemplaza al marcador de posición nulo en la selección de unión de datos original. Después de eso, si manipula la selección original, la configuración se aplica a los elementos nuevos y de actualización. Entonces aquí es donde se configuran las propiedades que dependen de los datos.

//Update the (tooltip) title text based on the data groupG.select("title") .text(function(d, i) { return numberWithCommas(d.value) + " trips started in " + neighborhoods[i].name; }); //update the paths to match the layout groupG.select("path") .transition() .duration(1500) .attr("opacity", 0.5) //optional, just to observe the transition .attrTween("d", arcTween( last_layout ) ) .transition().duration(10).attr("opacity", 1) //reset opacity ; //position group labels to match layout groupG.select("text") .transition() .duration(1500) .attr("transform", function(d) { d.angle = (d.startAngle + d.endAngle) / 2; //store the midpoint angle in the data object return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" + " translate(" + (innerRadius + 26) + ")" + (d.angle > Math.PI ? " rotate(180)" : " rotate(0)"); //include the rotate zero so that transforms can be interpolated }) .attr("text-anchor", function (d) { return d.angle > Math.PI ? "end" : "begin"; }); // Update all chord title texts chordPaths.select("title") .text(function(d) { if (neighborhoods[d.target.index].name !== neighborhoods[d.source.index].name) { return [numberWithCommas(d.source.value), " trips from ", neighborhoods[d.source.index].name, " to ", neighborhoods[d.target.index].name, "/n", numberWithCommas(d.target.value), " trips from ", neighborhoods[d.target.index].name, " to ", neighborhoods[d.source.index].name ].join(""); //joining an array of many strings is faster than //repeated calls to the ''+'' operator, //and makes for neater code! } else { //source and target are the same return numberWithCommas(d.source.value) + " trips started and ended in " + neighborhoods[d.source.index].name; } }); //update the path shape chordPaths.transition() .duration(1500) .attr("opacity", 0.5) //optional, just to observe the transition .style("fill", function (d) { return neighborhoods[d.source.index].color; }) .attrTween("d", chordTween(last_layout)) .transition().duration(10).attr("opacity", 1) //reset opacity ; //add the mouseover/fade out behaviour to the groups //this is reset on every update, so it will use the latest //chordPaths selection groupG.on("mouseover", function(d) { chordPaths.classed("fade", function (p) { //returns true if *neither* the source or target of the chord //matches the group that has been moused-over return ((p.source.index != d.index) && (p.target.index != d.index)); }); }); //the "unfade" is handled with CSS :hover class on g#circle //you could also do it using a mouseout event on the g#circle

Los cambios se realizan utilizando transiciones d3 para crear un cambio suave de un diagrama a otro. Para los cambios en las formas de la ruta, se usan funciones personalizadas para hacer la transición mientras se mantiene la forma general. Más sobre los de abajo.

En cuarto lugar, la cadena de salida (). Si algún elemento del diagrama anterior ya no tiene una coincidencia en los nuevos datos, por ejemplo, si no existe un acorde porque no hay relaciones entre esos dos grupos (por ejemplo, no hay viajes entre esos dos barrios) en estos datos set - entonces tienes que eliminar ese elemento de la visualización. Puede eliminarlos inmediatamente, de modo que desaparezcan para dejar espacio para la transición de datos, o puede usar una transición para eliminarlos. (Al llamar a .remove() en una selección de transición se eliminará el elemento cuando se complete esa transición).

Podría crear una transición personalizada para hacer que las formas se reduzcan a nada, pero solo uso un desvanecimiento a cero opacidad:

//handle exiting groups, if any, and all their sub-components: groupG.exit() .transition() .duration(1500) .attr("opacity", 0) .remove(); //remove after transitions are complete //handle exiting paths: chordPaths.exit().transition() .duration(1500) .attr("opacity", 0) .remove();

Acerca de las funciones de interpolación personalizada:

Si acaba de usar una interpolación predeterminada para cambiar de una forma de ruta a otra, los resultados pueden parecer un poco extraños . Intente cambiar de "Sólo hombres" a "Sólo mujeres" y verá que los acordes se desconectan del borde del círculo. Si las posiciones del arco hubieran cambiado de manera más significativa, los vería cruzar el círculo para alcanzar su nueva posición en lugar de deslizarse alrededor del anillo.

Esto se debe a que la transición predeterminada de una forma de ruta a otra simplemente hace coincidir los puntos de la ruta y realiza la transición de cada punto en una línea recta de una a otra. Funciona para cualquier tipo de forma sin ningún código adicional, pero no necesariamente mantiene esa forma durante la transición.

La función de interpolación personalizada le permite definir cómo debe configurarse la ruta en cada paso de la transición. He escrito comentarios sobre las funciones de interpolación here y here , así que no voy a repetirlo. Pero la breve descripción es que la función de interpolación que pasa a .attrTween(attribute, tween) tiene que ser una función que recibe una llamada por elemento, y debe devolver una función que se llamará en cada "tick" de la transición a devuelve el valor del atributo en ese punto de la transición.

Para obtener transiciones suaves de las formas de ruta, usamos las dos funciones del generador de datos de ruta (el generador de arco y el generador de acordes) para crear los datos de ruta en cada paso de la transición. De esa manera, los arcos siempre se verán como arcos y los acordes siempre se verán como acordes. La parte que está en transición es los valores de ángulo de inicio y final. Teniendo en cuenta dos objetos de datos diferentes que describen el mismo tipo de forma, pero con valores de ángulo diferentes, puede usar d3.interpolateObject(a,b) para crear una función que le dará un objeto en cada etapa de la transición con la transición apropiada propiedades de ángulo Por lo tanto, si tiene el objeto de datos del diseño anterior y el objeto de datos coincidentes del diseño nuevo, puede cambiar sin problemas los arcos o acordes de una posición a la otra.

Sin embargo, ¿qué debe hacer si no tiene un objeto de datos antiguo? Ya sea porque este acorde no tuvo una coincidencia en el diseño anterior, o porque esta es la primera vez que se dibuja la visualización y no hay un diseño antiguo. Si pasa un objeto vacío como primer parámetro a d3.interpolateObject , el objeto de transición siempre será exactamente el valor final. En combinación con otras transiciones, como la opacidad, esto podría ser aceptable. Sin embargo, decidí hacer la transición de manera que comience con una forma de ancho cero, es decir, una forma donde los ángulos de inicio coincidan con los ángulos finales, y luego se expanda a la forma final:

function chordTween(oldLayout) { //this function will be called once per update cycle //Create a key:value version of the old layout''s chords array //so we can easily find the matching chord //(which may not have a matching index) var oldChords = {}; if (oldLayout) { oldLayout.chords().forEach( function(chordData) { oldChords[ chordKey(chordData) ] = chordData; }); } return function (d, i) { //this function will be called for each active chord var tween; var old = oldChords[ chordKey(d) ]; if (old) { //old is not undefined, i.e. //there is a matching old chord value //check whether source and target have been switched: if (d.source.index != old.source.index ){ //swap source and target to match the new data old = { source: old.target, target: old.source }; } tween = d3.interpolate(old, d); } else { //create a zero-width chord object var emptyChord = { source: { startAngle: d.source.startAngle, endAngle: d.source.startAngle}, target: { startAngle: d.target.startAngle, endAngle: d.target.startAngle} }; tween = d3.interpolate( emptyChord, d ); } return function (t) { //this function calculates the intermediary shapes return path(tween(t)); }; }; }

(Compruebe el violín para el código de arco interpolación, que es un poco más simple)

Versión en vivo en conjunto: http://jsfiddle.net/KjrGF/12/