2012-03-01 17 views
17

Sto cercando di capire come aggiornare alcuni elementi di D3.js collegando nuovi dati. Non sono sicuro che sia possibile o no, ma sembra che dovrebbe essere.Manipola elementi rilegando nuovi dati

Quindi, prima ho creato quattro cerchi SVG, e impostare il cx di offset in funzione dei dati:

<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"); 

Poi ho allegare nuovi dati e una transizione. Mi sarei aspettato di vedere i cerchi si muovono lentamente alla nuova posizione (che è quello che sto cercando di realizzare), ma non è così:

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

Sto facendo un errore di base? Forse ho la selezione sbagliata. O semplicemente non è possibile aggiornare gli elementi semplicemente manipolando i dati?

AGGIORNAMENTO: se faccio console.log(circle), allora vedo una matrice di elementi del cerchio SVG, che è quello che mi aspetterei.

risposta

23

Potentemente (ma fastidiosamente) d3.js a volte ti costringe a ripetere se stessi per semplici visualizzazioni. Se si dice che si desidera creare un elemento con attributi derivati ​​dai dati in un determinato modo, e successivamente si desidera passare tali attributi a nuovi dati, è necessario ripetere nuovamente come ricavare i valori (nel caso in cui si desideri fare qualcosa di diverso, come un diverso layout visivo). Come dice @Andrew, devi dirgli cosa fare quando transita.

È possibile aggirare il 'problema' seguendo questo schema:

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 }); 
} 

In 2.0 quando si append() nuovi articoli in enter() vengono automaticamente aggiunti alla selezione originale associazione a dati, in modo che le chiamate al attr() e ciò che in seguito non si applicherà a loro. Ciò consente di utilizzare lo stesso codice sia per l'impostazione dei valori iniziali che per l'aggiornamento dei valori.

Poiché non si desidera ricreare il wrapper SVG ogni aggiornamento, è necessario creare questo al di fuori della funzione redraw.

Se si desidera eseguire le transizioni:

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 }); 
} 

Nota che i soci di cui sopra oggetti con i dati di indice. Se si desidera eliminare un punto dati intermedio per svanire quel punto dati prima di rimuoverlo (invece di rimuovere l'ultimo elemento e trasformare ogni altro elemento in modo che assomigli a quello), è necessario specificare un valore di stringa univoco (non indicizzato) da associare. ogni prodotto 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; }); 

si può vedere un esempio di questo e giocare con lui in diretta su my D3.js playground.

Innanzitutto, vedere cosa succede quando si commenta una delle righe di dati e quindi di nuovo. Quindi, rimuovere il parametro ƒ('id') dalla chiamata a data() sulla riga 4 e riprovare a commentare e nelle righe di dati.

Edit: In alternativa, come commentato dal mbostock illustre, è possibile utilizzare selection.call() insieme ad una funzione riutilizzabile come un modo per asciugare il tuo codice:

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 }); 
} 

Come indicato sopra, .call() richiama una funzione e passa lungo la selezione come argomento, in modo da poter eseguire la stessa impostazione sulla selezione in più posizioni.

+0

Risposta straordinaria, grazie. Sto ancora cercando di capire la maggior parte di questa roba, ma questo sarà un vero aiuto! – Richard

+2

Risposta riflessiva. Puoi anche scrivere la tua funzione 'redraw' in modo compatibile con [selection.call] (https://github.com/mbostock/d3/wiki/Selections#wiki-call). Correlati: [Verso grafici riutilizzabili] (http://bost.ocks.org/mike/chart/). – mbostock

+1

@mbostock Ooh, è molto utile; Non avevo mai visto '.call()' prima. Sono solo un novizio di D3.js che aiuta dove posso. Modificherò per includere tali informazioni (e farò un passaggio completo attraverso i documenti per cercare di inserire tutte le possibilità nella mia testa). – Phrogz

2

OK, ora ce l'ho! È necessario indicare cosa per la transizione:

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

Un'ottima risorsa per aggiungere nuovi dati a una visualizzazione esistente è il tutorial ufficiale sull'aggiornamento dei dati.

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

Il takeaway principale è che si desidera definire una funzione chiave quando si chiama dati in modo che D3 può tenere traccia di quali dati è nuovo, contro i quali i dati è vecchio.

Problemi correlati