2012-03-01 13 views
17

Estoy tratando de encontrar la forma de actualizar algunos elementos D3.js simplemente vinculando nuevos datos. No estoy seguro de si esto es posible o no, pero parece que debería ser así.Manipular elementos vinculando datos nuevos

Así que primero he creado cuatro círculos SVG, y establecer el cx compensado en función de los datos:

<body><div id="container"></div></body> 

var svg = d3.select("div.container").append("svg") 
    .attr("class", "chart") 
    .attr("width", 1000) 
    .attr("height", 500); 

    // Create initial data and display 
    var data = [0, 10, 20, 30]; 
    var circle = svg.selectAll("circle") 
     .data(data) 
     .enter() 
     .append('circle') 
     .attr("cx", function(d) { 
     return d*10; 
     }) 
     .attr("cy", 100) 
     .attr("r", 10) 
     .style("fill", "steelblue"); 

siguiente que adjuntar nuevos datos y una transición. Yo esperaría a ver los círculos se mueven lentamente a través de la nueva posición (eso es lo que estoy tratando de lograr), pero no es así:

var data1 = [40, 50, 60, 70]; 
    circle.data(data1).transition().duration(2500); 

¿Estoy haciendo un error básico? Tal vez tengo la selección incorrecta. ¿O simplemente no es posible actualizar elementos únicamente mediante la manipulación de datos?

ACTUALIZACIÓN: si hago console.log(circle) entonces veo una serie de elementos del círculo SVG, que es lo que esperaría.

Respuesta

23

poderosamente (pero molesto) D3.js veces te obliga a repetir a sí mismo para visualizaciones simples. Si le dice que desea crear un elemento con atributos derivados de los datos de una determinada manera, y luego desea transferir esos atributos a nuevos datos, debe contarle nuevamente cómo derivar los valores (en caso de que desee) hacer algo diferente, como un diseño visual diferente). Como dice @Andrew, debes decirle qué hacer cuando transiciones.

Puede solucionar este 'problema' siguiendo este patrón:

var foo = d3.select('foo'); 
function redraw(someArray){ 
    var items = foo.selectAll('bar').data(someArray); 
    items.enter().append('bar'); 
    items.exit().remove(); 
    items 
    .attr('foo',function(d){ return d }); 
} 

En 2.0 cuando se append() nuevos artículos en enter() que se agregan automáticamente a la selección original enlazado a datos, por lo que las llamadas a attr() y lo que luego se aplicará a ellos. Esto le permite usar el mismo código para establecer valores iniciales y actualizar valores.

Dado que no desea volver a crear el contenedor SVG en cada actualización, debe crearlo fuera de la función redraw.

Si desea realizar transiciones:

function redraw(someArray){ 
    var items = foo.selectAll('bar').data(someArray); 
    items.enter().append('bar') 
    .attr('opacity',0) 
    .attr('foo',initialPreAnimationValue); 
    items.exit().transition().duration(500) 
    .attr('opacity',0) 
    .remove(); 
    items.transition.duration(500) 
    .attr('opacity',1) 
    .attr('foo',function(d){ return d }); 
} 

Tenga en cuenta que los socios anteriores objetos con los datos de índice. Si desea eliminar un punto de datos intermedios para atenuar ese punto de datos antes de eliminarlo (en lugar de eliminar el último elemento y transformar cada otro elemento para que se parezca a ese), debe especificar un valor de cadena único (sin índice) para asociar cada artículo con:

var data = [ 
    {id:1, c:'red', x:150}, 
    {id:3, c:'#3cf', x:127}, 
    {id:2, c:'green', x:240}, 
    {id:4, c:'red', x:340} 
]; 
myItems.data(data,function(d){ return d.id; }); 

Usted puede ver un ejemplo de esto y jugar con él en directo en my D3.js playground.

Primero, vea qué sucede cuando comenta una de las líneas de datos y luego vuelva a colocarla. A continuación, elimine el parámetro ƒ('id') de la llamada al data() en la línea 4 y vuelva a intentar comentar y en las líneas de datos.

Editar: Como alternativa, como se ha comentado por el mbostock ilustre, puede utilizar selection.call() junto con una función reutilizable como una manera de secar su código:

var foo = d3.select('foo'); 
function redraw(someArray){ 
    var items = foo.selectAll('bar').data(someArray); 
    items.enter().append('bar').call(setEmAll); 
    items.exit().remove(); 
    items.call(setEmAll); 
} 

function setEmAll(myItems){ 
    myItems 
    .attr('foo',function(d){ return d*2 }) 
    .attr('bar',function(d){ return Math.sqrt(d)+17 }); 
} 

Como puede observarse, .call() invoca una función y pasa a lo largo de la selección como argumento, para que pueda realizar la misma configuración en su selección en múltiples ubicaciones.

+0

Awesome answer, thanks. Todavía estoy tratando de entender la mayoría de estas cosas, ¡pero será una gran ayuda! – Richard

+2

Respuesta reflexiva. También podría escribir su función 'redraw' de una manera que sea compatible con [selection.call] (https://github.com/mbostock/d3/wiki/Selections#wiki-call). Relacionado: [Towards Reusable Charts] (http://bost.ocks.org/mike/chart/). – mbostock

+1

@mbostock Ooh, eso es muy útil; No había visto '.call()' antes. Solo soy un principiante de D3.js que ayuda donde puedo. Editaré para incluir esa información (y dar un pase completo a través de los documentos para tratar de incluir todas las posibilidades en mi cabeza). – Phrogz

2

OK, ahora lo tengo! Usted necesita decirle qué transición:

circle.data(data1).transition().duration(2500).attr("cx", function(d) { 
     return d*10; 
    }); 
2

Un recurso muy bueno para agregar nuevos datos a una visualización existente es el tutorial oficial sobre la actualización de datos.

http://bl.ocks.org/mbostock/3808221

La conclusión principal es que se quiere definir una función clave cuando se llama a los datos de tal manera que d3 puede realizar un seguimiento de qué datos son nuevos, frente a los cuales los datos es viejo.

Cuestiones relacionadas