2012-10-25 12 views
17

ho una struttura di dati di questo tipo (supporre che la struttura dei dati non è negoziabile):Combinando Capogruppo e dati nidificate con d3.js

data = { 
    segments : [ 
     {x : 20, size : 10, colors : ['#ff0000','#00ff00']}, 
     {x : 40, size : 20, colors : ['#0000ff','#000000']} 
    ]}; 

Utilizzando la libreria javascript d3.js, mi piacerebbe disegnare quattro rettangoli, uno per ciascun colore in entrambi gli array colors. Le informazioni da ciascuna voce nell'array segments vengono utilizzate per disegnare i rettangoli corrispondenti a ciascun colore nella sua matrice color. Ad esempio, I rettangoli rossi e verdi avrà una larghezza e un'altezza di 10. Il codice HTML risultante dovrebbe essere simile a questo:

<div id="container"> 
    <svg width="200" height="200"> 
     <g> 
      <rect x="20" y="20" width="10" height="10" fill="#ff0000"></rect> 
      <rect x="30" y="30" width="10" height="10" fill="#00ff00"></rect> 
     </g> 
     <g> 
      <rect x="40" y="40" width="20" height="20" fill="#0000ff"></rect> 
      <rect x="60" y="60" width="20" height="20" fill="#000000"></rect> 
     </g> 
    </svg> 
</div> 

mi è venuta in mente un codice che realizza questo, ma ho trovato la parte sull'utilizzo i dati di due diversi livelli di nidificazione in data sono confusi e ritengo che potrebbe esserci un modo più idiomatico per ottenere lo stesso risultato con d3.js. Ecco il codice (esempio completo in http://jsbin.com/welcome/39650/edit):

function pos(d,i) { return d.x + (i * d.size); } // rect position 
function size(d,i) { return d.size; }   // rect size 
function f(d,i) { return d.color; }    // rect color 

// add the top-level svg element and size it 
vis = d3 
    .select('#container') 
    .append('svg') 
    .attr('width',200) 
    .attr('height',200); 

// add the nested svg elements 
var nested = vis 
    .selectAll('g') 
    .data(data.segments) 
    .enter() 
    .append('g'); 

// Add a rectangle for each color 
nested 
    .selectAll('rect') 
    .data(function(d) { 
     // **** ATTENTION **** 
     // Is there a more idiomatic, d3-ish way to approach this? 
     var expanded = []; 
     for(var i = 0; i < d.colors.length; i++) { 
      expanded.push({ 
       color : d.colors[i], 
       x  : d.x 
       size : d.size }); 
     } 
     return expanded; 
    }) 
    .enter() 
    .append('rect') 
    .attr('x',pos) 
    .attr('y',pos) 
    .attr('width',size) 
    .attr('height',size) 
    .attr('fill',f); 

C'è un modo migliore e/o più idiomatico per accedere ai dati provenienti da due diversi livelli di nidificazione in una struttura dati utilizzando d3.js?

Modifica

Ecco la soluzione mi è venuta, grazie alla meetamit's answer per l'idea di chiusura, e l'utilizzo di più idiomatiche d3.js indentazione grazie a nautat's answer:

$(function() { 
    var 
    vis = null, 
    width = 200, 
    height = 200, 
    data = { 
     segments : [ 
      {x : 20, y : 0, size : 10, colors : ['#ff0000','#00ff00']}, 
      {x : 40, y : 0, size : 20, colors : ['#0000ff','#000000']} 
     ] 
    }; 

    // set the color 
    function f(d,i) {return d;} 

    // set the position 
    function pos(segment) { 
     return function(d,i) { 
     return segment.x + (i * segment.size); 
     }; 
    } 

    // set the size 
    function size(segment) { 
     return function() { 
     return segment.size; 
     }; 
    } 

    // add the top-level svg element and size it 
    vis = d3.select('#container').append('svg') 
     .attr('width',width) 
     .attr('height',height); 

    // add the nested svg elements 
    var nested = vis 
     .selectAll('g') 
      .data(data.segments) 
     .enter().append('g'); 

    // Add a rectangle for each color. Size of rectangles is determined 
    // by the "parent" data object. 
    nested 
    .each(function(segment, i) { 
     var 
      ps = pos(segment), 
      sz = size(segment); 

     var colors = d3.select(this) 
     .selectAll('rect') 
      .data(segment.colors) 
     .enter().append('rect') 
      .attr('x', ps) 
      .attr('y',ps) 
      .attr('width', sz) 
      .attr('height',sz) 
      .attr('fill', f); 
    }); 

}); 

Ecco l'esempio di lavoro completo : http://jsbin.com/welcome/42885/edit

+0

perché hai bisogno di tag svg annidati nel tuo codice html? – btel

+0

Vorrei che tutti i rettangoli corrispondenti a un 'segmento' venissero raggruppati insieme. Se ho capito bene, posso spostare tutti i rettangoli dal primo 'segmento' cambiando le proprietà' x' e 'y' dell'elemento genitore' svg'. Se c'è un modo migliore, mi piacerebbe saperlo. –

+0

Come su un elemento di gruppo con l'attributo transform: ' ...': http://www.w3.org/TR/SVG/coords.html#TransformAttribute. Se ho capito bene dovresti avere un solo svg per ogni disegno. – btel

risposta

27

È possibile utilizzare le chiusure

var nested = vis 
    .selectAll('g') 
    .data(data.segments); 


nested.enter() 
    .append('g') 
    .each(function(segment, i) { 
    var colors = d3.select(this) 
     .selectAll('rect') 
     .data(segment.colors); 

    colors.enter() 
     .append('rect') 
     .attr('x', function(color, j) { return pos(segment, j); }) 
     // OR: .attr('x', function(color, j) { return segment.x + (j * segment.size); }) 
     .attr('width', function(color, j) { return size(segment); }) 
     .attr('fill', String); 
    }); 
+1

L'utilizzo delle chiusure mi fa sentire il meglio. Ho dovuto modificare un po 'il tuo esempio per far funzionare le cose, ma l'idea generale è proprio quello che stavo cercando. Vedi la modifica alla mia domanda per l'esempio operativo completo. –

1

Vorrei provare ad appiattire il colors prima di iniziare effettivamente a creare gli elementi. Se si verificano modifiche ai dati, aggiornare quindi questa struttura di dati appiattita e ridisegna. I dati appiattiti devono essere memorizzati da qualche parte per rendere possibili le transizioni d3 reali.

Ecco un esempio più lungo che ha funzionato per me. Puoi vederlo in azione here.

Ecco il codice:

var data = { 
    segments : [ 
     {x : 20, size : 10, colors : ['#ff0000','#00ff00']}, 
     {x : 40, size : 20, colors : ['#0000ff','#000000']} 
    ] 
}; 

function pos(d,i) { return d.x + (i * d.size); } // rect position 
function size(d,i) { return d.size; }   // rect size 
function f(d,i) { return d.color; }    // rect color 

function flatten(data) { 
    // converts the .colors to a ._colors list 
    data.segments.forEach(function(s,i) { 
     var list = s._colors = s._colors || []; 
     s.colors.forEach(function(c,j) { 
      var obj = list[j] = list[j] || {} 
      obj.color = c 
      obj.x = s.x 
      obj.size = s.size 
     }); 
    }); 
} 

function changeRect(chain) { 
    return chain 
    .transition() 
    .attr('x',pos) 
    .attr('y',pos) 
    .attr('width',size) 
    .attr('height',size) 
    .attr('fill',f) 
    .style('fill-opacity', 0.5) 
} 

vis = d3 
.select('#container') 
.append('svg') 
.attr('width',200) 
.attr('height',200); 

// add the top-level svg element and size it 
function update(){ 

    flatten(data); 

    // add the nested svg elements 
    var all = vis.selectAll('g') 
    .data(data.segments) 

    all.enter().append('g'); 
    all.exit().remove(); 

    // Add a rectangle for each color 
    var rect = all.selectAll('rect') 
    .data(function (d) { return d._colors; }, function(d){return d.color;}) 

    changeRect(rect.enter().append('rect')) 
    changeRect(rect) 

    rect.exit().remove() 
} 

function changeLater(time) { 
    setTimeout(function(){ 
     var ds = data.segments 
     ds[0].x = 10 + Math.random() * 100; 
     ds[0].size = 10 + Math.random() * 100; 
     ds[1].x = 10 + Math.random() * 100; 
     ds[1].size = 10 + Math.random() * 100; 
     if(time == 500) ds[0].colors.push("orange") 
     if(time == 1000) ds[1].colors.push("purple") 
     if(time == 1500) ds[1].colors.push("yellow") 
     update() 
    }, time) 
} 

update() 
changeLater(500) 
changeLater(1000) 
changeLater(1500) 

importante è la funzione flatten che fa la conversione dei dati e memorizza/riutilizza il risultato come _colors proprietà nell'elemento dati padre. Un'altra linea importante è;

.data(function (d) { return d._colors; }, function(d){return d.color;}) 

che specifica dove ottenere i dati (primo parametro) e quali l'ID univoco per ciascun elemento di dati è (secondo parametro). Ciò aiuta a identificare i colori esistenti per le transizioni, ecc.

3

Si potrebbe fare qualcosa di simile a quanto segue di ristrutturare i dati:

newdata = data.segments.map(function(s) { 
    return s.colors.map(function(d) { 
    var o = this; // clone 'this' in some manner, for example: 
    o = ["x", "size"].reduce(function(obj, k) { return(obj[k] = o[k], obj); }, {}); 
    return (o.color = d, o); 
    }, s); 
}); 

Questo trasformerà i dati di input in:

// newdata: 
    [ 
     [ 
     {"size":10,"x":20,"color":"#ff0000"}, 
     {"size":10,"x":20,"color":"#00ff00"}], 
     [ 
     {"size":20,"x":40,"color":"#0000ff"}, 
     {"size":20,"x":40,"color":"#000000"} 
     ] 
    ] 

che quindi può essere utilizzato nel modello di selezione dei dati nidificati standard:

var nested = vis.selectAll('g') 
    .data(newdata) 
    .enter().append('g'); 

nested.selectAll('rect') 
    .data(function(d) { return d; }) 
    .enter().append('rect') 
    .attr('x',pos) 
    .attr('y',pos) 
    .attr('width',size) 
    .attr('height',size) 
    .attr('fill',f); 

BTW, se desideri essere più d3-idiomatico, cambierei leggermente lo stile di indentazione per i metodi concatenati. Mike ha proposto di usare la metà dell'indentazione ogni volta che la selezione cambia. Questo aiuta a rendere molto chiaro la selezione su cui stai lavorando. Ad esempio nell'ultimo codice; la variabile nested si riferisce alla selezione enter(). Vedi il capitolo 'selezioni' in: http://bost.ocks.org/mike/d3/workshop/

+0

Grazie per il suggerimento sullo stile di indentazione. Ho notato che le persone che scrivevano il codice d3.js stavano rientrando in quel modo, ma non capivano la motivazione. Il collegamento è stato molto utile! –

+0

Direi che questo è in realtà il modo di fare D3 (secondo Mike Bostock). Vedi qui http://bost.ocks.org/mike/nest/#data –