2015-12-16 33 views
10

Sto costruendo un grafico a barre raggruppato annidando un file .csv. Il grafico sarà anche visualizzabile come grafico a linee, quindi voglio una struttura di nesting adatta all'oggetto line. Il mio .csv originale si presenta così:d3 accesso ai dati annidati nel grafico a barre raggruppato

Month,Actual,Forecast,Budget 
Jul-14,200000,-,74073.86651 
Aug-14,198426.57,-,155530.2499 
Sep-14,290681.62,-,220881.4631 
Oct-14,362974.9,-,314506.6437 
Nov-14,397662.09,-,382407.67 
Dec-14,512434.27,-,442192.1932 
Jan-15,511470.25,511470.25,495847.6137 
Feb-15,-,536472.5467,520849.9105 
Mar-15,-,612579.9047,596957.2684 
Apr-15,-,680936.5086,465313.8723 
May-15,-,755526.7173,739904.081 
Jun-15,-,811512.772,895890.1357 

e il mio nidificazione è come questo:

d3.csv("data/net.csv", function(error, data) { 
    if (error) throw error; 

      var headers = d3.keys(data[0]).filter(function(head) { 
      return head != "Month"; 
      }); 

        data.forEach(function(d) { 
        d.month = parseDate(d.Month); 
      }); 
      var categories = headers.map(function(name) { 

       return { 
       name: name, // "name": the csv headers except month 
       values: data.map(function(d) { 
        return { 
        date: d.month, 
        rate: +(d[name]), 
        }; 
       }), 
       }; 

      }); 

Il codice per costruire la mia tabella è:

var bars = svg.selectAll(".barGroup") 
     .data(data) // Select nested data and append to new svg group elements 
     .enter() 
     .append("g") 
     .attr("class", "barGroup") 
     .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; }); 

    bars.selectAll("rect") 
     .data(categories) 
     .enter() 
     .append("rect") 
     .attr("width", barWidth) 
     .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand()/2;}}) 
     .attr("y", function (d) { return yScale(d.rate); }) 
     .attr("height", function (d) { return h - yScale(d.rate); }) 
     .attr("class", function (d) { return lineClass(d.name); }); 

Gli elementi g sono fini e le singole barre vengono mappate su di esse, con il valore x e la classe applicata correttamente.

Il mio problema consiste nell'accedere ai dati per "rate" per il valore di altezza e y delle barre. Nella forma sopra riportata dà un NaN. Ho anche provato ad utilizzare i dati di categoria per aggiungere elementi g e poi aggiungendo i rettangoli con:

.data(function(d) { return d.values }) 

Questo mi permette di accedere ai dati dei tassi, ma le mappe tutti i 36 bar a ciascuno dei rangeBands.

Funziona bene anche in una struttura dati più piatta, ma non riesco a usarlo quando è annidato a due livelli, nonostante si guardino attraverso moltissimi esempi e domande SO.

Come accedere ai dati della velocità?

In risposta alla richiesta di Cirillo, ecco il codice completo:

var margin = {top: 20, right: 18, bottom: 80, left: 50}, 
     w = parseInt(d3.select("#bill").style("width")) - margin.left - margin.right, 
     h = parseInt(d3.select("#bill").style("height")) - margin.top - margin.bottom; 

    var customTimeFormat = d3.time.format.multi([ 
     [".%L", function(d) { return d.getMilliseconds(); }], 
     [":%S", function(d) { return d.getSeconds(); }], 
     ["%I:%M", function(d) { return d.getMinutes(); }], 
     ["%I %p", function(d) { return d.getHours(); }], 
     ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }], 
     ["%b %d", function(d) { return d.getDate() != 1; }], 
     ["%b", function(d) { return d.getMonth(); }], 
     ["%Y", function() { return true; }] 
    ]); 


    var parseDate = d3.time.format("%b-%y").parse; 

    var displayDate = d3.time.format("%b %Y"); 

    var xScale = d3.scale.ordinal() 
     .rangeRoundBands([0, w], .1); 

    var xScale1 = d3.scale.linear() 
      .domain([0, 2]); 

    var yScale = d3.scale.linear() 
     .range([h, 0]) 
     .nice(); 

    var xAxis = d3.svg.axis() 
     .scale(xScale) 
     .tickFormat(customTimeFormat) 
     .orient("bottom"); 

    var yAxis = d3.svg.axis() 
     .scale(yScale) 
     .orient("left") 
     .innerTickSize(-w) 
     .outerTickSize(0); 

    var svg = d3.select("#svgCont") 
     .attr("width", w + margin.left + margin.right) 
     .attr("height", h + margin.top + margin.bottom) 
     .append("g") 
     .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 

    var thous = d3.format(",.0f") 

    var lineClass = d3.scale.ordinal().range(["actual", "forecast", "budget"]); 

    var tip = d3.tip() 
     .attr('class', 'd3-tip') 
     .offset([-10, 0]) 
     .html(function(d) { 
     return "<p id='date'>" + displayDate(d.date) + "</p><p id='value'>$" + thous(d.rate); 
     }) 

    d3.csv("data/net.csv", function(error, data) { 
     if (error) throw error; 

       var headers = d3.keys(data[0]).filter(function(head) { 
       return head != "Month"; 
      }); 

        data.forEach(function(d) { 
         d.month = parseDate(d.Month); 
      }); 
       var categories = headers.map(function(name) { 

       return { 
        name: name, 
        values: data.map(function(d) { 
        return { 
         date: d.month, 
         rate: +(d[name]), 
         }; 
        }), 
       }; 

       }); 

    var min = d3.min(categories, function(d) { 
         return d3.min(d.values, function(d) { 
          return d.rate; 
         }); 
        }); 



    var max = d3.max(categories, function(d) { 
         return d3.max(d.values, function(d) { 
          return d.rate; 
         }); 
        }); 

    var minY = min < 0 ? min * 1.2 : min * 0.8; 

        xScale.domain(data.map(function(d) { return d.month; })); 
        yScale.domain([minY, (max * 1.1)]); 

    var barWidth = headers.length > 2 ? xScale.rangeBand()/2 : xScale.rangeBand() ; 

    svg.call(tip); 

    svg.append("g") 
     .attr("class", "x axis") 
     .attr("transform", "translate(0," + h + ")") 
     .call(xAxis); 

    svg.append("g") 
      .attr("class", "y axis") 
      .call(yAxis); 

    var bars = svg.selectAll(".barGroup") 
      .data(data) 
      .enter() 
      .append("g") 
      .attr("class", "barGroup") 
      .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; }); 

    bars.selectAll("rect") 
      .data(categories) 
      .enter() 
      .append("rect") 
      .attr("width", barWidth) 
      .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand()/2;}}) 
      .attr("y", function (d) { return yScale(d.rate); }) 
      .attr("height", function (d) { return h - yScale(d.rate); }) 
      .attr("class", function (d) { return lineClass(d.name) + " bar"; }); 


    var legend = svg.selectAll(".legend") 
      .data(headers) 
      .enter() 
      .append("g") 
      .attr("class", "legend"); 

    legend.append("line") 
      .attr("class", function(d) { return lineClass(d); }) 
      .attr("x1", 0) 
      .attr("x2", 40) 
      .attr("y1", function(d, i) { return (h + 30) + (i *14); }) 
      .attr("y2", function(d, i) { return (h + 30) + (i *14); }); 

    legend.append("text") 
     .attr("x", 50) 
     .attr("y", function(d, i) { return (h + 32) + (i *14); }) 
     .text(function(d) { return d; }); 

    svg.selectAll(".bar") 
     .on('mouseover', tip.show) 
     .on('mouseout', tip.hide); 

    }); 

Aggiornamento 18 Feb '16.

Sembra che non abbia spiegato cosa stavo cercando di fare abbastanza bene. Le versioni di riga e di barra del grafico verranno visualizzate separatamente, ovvero gli utenti potranno visualizzarne uno in base all'input di un elemento selezionato. Si noti inoltre che non ho il controllo su come i dati arrivano inizialmente.

ho a version of exactly how it should work here.

Questa questione è stata sollevata quando stavo ancora lavorando attraverso di essa, ma non ho mai risolto il problema - ho usato una soluzione di fare due nidi separati di dati.

+0

puoi pubblicare il tuo codice completo ... penso che ci sia un problema con yscale. – Cyril

+0

Ciao Cyril, Pubblicherò il codice completo in un attimo, ma sono sicuro che non si tratta di yScale. Ho controllato inserendo un numero al posto di d.rate e ho anche funzionato quando i dati nidificati sono in un formato più piatto. La scala è sotto. var yScale = d3.scale.linear(). Range ([h, 0]) .nice(); – tgerard

+0

@ tardard Per confermare: vuoi un grafico a barre raggruppato con date come asse x e i valori come asse y? Se è così, non è chiaro come pensi di usare l'oggetto categorie. Il codice fornito fornisce l'oggetto categorie per d.rate. Ma l'oggetto categorie ha la forma: {nome: "Attuale", valori: [{data: "", tasso: 0}]}. Quindi non avrà la proprietà d.rate su di esso. Puoi confermare che cosa stai cercando di costruire e posso consigliarti su come risolverlo? –

risposta

1

Il problema, I belive, è che si sono vincolanti le categorie array per la selezione barre, in questo modo:

bars.selectAll("rect").data(categories) 

Per quanto posso vedere (whithout una demo in esecuzione) categorie è un array con solo quattro valori (uno per ogni categoria).

È necessario eseguire un passaggio "più profondo" nella struttura dati nidificata.

Per disegnare una serie di barre per ogni categoria si avrebbe bisogno di iterare categorie e associare i valori matrice che contiene i valori effettivi alla selezione.

Qualcosa di simile:

categories.each(function (category) { 
    var klass = category.name; 
    bars.selectAll("rect ." + klass) 
     .data(category.values) 
     .enter() 
     .append("rect") 
     .attr("class", klass) 
     .attr("width", barWidth) 
     .attr("x", function (d, i) { /* omitted */}) 
     .attr("y", function (d) { return yScale(d.rate); }) 
     .attr("height", function (d) { return h - yScale(d.rate); }); 
    }); 

---- Edit

Invece del codice di cui sopra, pensare a disegnare le sbarre, proprio come si fa con le linee. Come questo:

var bars = svg.selectAll(".barGroup") 
    .data(categories) 
    .enter() 
    .append("g") 
    .attr("class", function (d) { return lineClass(d.name) + "Bar barGroup"; }) 
    .attr("transform", function (d, i) { 
     var x = i > 1 ? xScale.rangeBand()/2 : 0; 
     return "translate(" + x + ",0)"; 
    }) 
    .selectAll('rect') 
    .data(function (d) { return d.values; }) 
    .enter() 
    .append("rect") 
    .attr("class", "bar") 
    .attr("width", barWidth) 
    .attr("x", function (d, i) { return xScale(d.date); }) 
    .attr("y", function (d, i) { return yScale(d.rate); }) 
    .attr("height", function (d) { return h - yScale(d.rate); }); 
+0

Sì, Doktorn, è esattamente ciò che voglio fare: accedere ad un gradino più in profondità nella struttura. Questa soluzione è probabilmente corretta. Sfortunatamente, questo progetto è un po 'vecchio e ho trovato una soluzione per questo, quindi devo fare una ricostruzione di dove ero a dicembre per testarlo. Non sono in grado di farlo ora, ma rimarrò bloccato domani (ora australiana). – tgerard

+0

@Dotkom Ho cambiato 'categories.each' in' categories.forEach' per aggirare un errore, ma in caso contrario l'ho eseguito così com'è. Aggiunge tutti e 36 i rettangoli in ciascuna delle bande, ad esempio, c'è una barra per ciascuno dei 36 punti dati nella posizione di luglio, altri 36 nel mese di agosto e così via. C'è [un esempio qui:] (http://lowercasen.com/dev/tests/stackoverflowTest.html) – tgerard

4

Link jsfiddle: https://jsfiddle.net/sladav/rLh4qwyf/1/

Penso che la radice del problema è che si desidera utilizzare due variabili che non esistono in modo esplicito nel set di dati originale: (1) Categoria e (2) Tariffa.

I dati sono formattati in un formato ampio in cui ogni categoria ottiene la propria variabile e il valore per la tariffa esiste all'incrocio del mese e una delle categorie specificate. Penso che il modo in cui stai nidificando alla fine sia o almeno dovrebbe riguardarlo, ma non mi è chiaro se o dove qualcosa si perde nella traduzione. Concettualmente, penso che abbia più senso iniziare con un'organizzazione che corrisponde a ciò che stai cercando di realizzare. Ho riformattato i dati originali e mi avvicinai di nuovo - a livello concettuale la nidificazione sembra diretto e semplice ...

nuove colonne:

  • Mese: Tempo variabile; mappato sull'asse X
  • Categoria: valori categoriali [Actual, Forecast, Budget]; usato per raggruppare/colorare
  • Tasso: valore numerico; mappato all'asse Y

Riorganizzata CSV (sceso NULL):

Month,Category,Rate 
Jul-14,Actual,200000 
Aug-14,Actual,198426.57 
Sep-14,Actual,290681.62 
Oct-14,Actual,362974.9 
Nov-14,Actual,397662.09 
Dec-14,Actual,512434.27 
Jan-15,Actual,511470.25 
Jan-15,Forecast,511470.25 
Feb-15,Forecast,536472.5467 
Mar-15,Forecast,612579.9047 
Apr-15,Forecast,680936.5086 
May-15,Forecast,755526.7173 
Jun-15,Forecast,811512.772 
Jul-14,Budget,74073.86651 
Aug-14,Budget,155530.2499 
Sep-14,Budget,220881.4631 
Oct-14,Budget,314506.6437 
Nov-14,Budget,382407.67 
Dec-14,Budget,442192.1932 
Jan-15,Budget,495847.6137 
Feb-15,Budget,520849.9105 
Mar-15,Budget,596957.2684 
Apr-15,Budget,465313.8723 
May-15,Budget,739904.081 
Jun-15,Budget,895890.1357 

Con i dati appena formattata, si avvia utilizzando d3.nest per raggruppare i dati in modo esplicito con la variabile CATEGORIA. Ora i tuoi dati esistono su due livelli. Il primo livello ha tre gruppi (uno per ogni categoria). Il secondo livello contiene i dati RATE per ogni linea/gruppo di barre. Devi nidificare anche le selezioni di dati: il primo livello viene utilizzato per tracciare le linee, il secondo per le barre.

Nesting i tuoi dati:

var nestedData = d3.nest() 
     .key(function(d) { return d.Category;}) 
     .entries(data) 

Creare gruppi svg per il vostro raggruppati, i dati di 1 ° livello:

d3.select(".plot-space").selectAll(".g-category") 
    .data(nestedData) 
    .enter().append("g") 
    .attr("class", "g-category") 

utilizzare questi dati per aggiungere le linee/percorsi:

d3.selectAll(".g-category").append("path") 
    .attr("class", "line") 
    .attr("d", function(d){ return lineFunction(d.values);}) 
    .style("stroke", function(d) {return color(d.key);}) 

Infine, "passaggio in" 2 ° livello per aggiungere barre/rect:

Questo è un approccio semplice (almeno per me), in quanto si prende una categoria alla volta, utilizzando i dati raggruppati per disegnare una linea, quindi i singoli punti di dati per disegnare le barre.

EDIT LAZY:

Per ottenere lato categoria bar a fianco

Crea ordinale categoria mappatura scala [1, nCategories]. Utilizzare questo per compensare dinamicamente bar con qualcosa di simile

translate(newScale(category)*barWidth) 

Per mostrare sia barre o righe (non entrambe)

Creare una funzione che seleziona bar/linee e transizioni/alterna la loro visibilità/opacità. Esegui quando l'input a discesa cambia e con l'input a discesa come input per la funzione.

+0

Ciao e grazie per avermi aiutato. Ci sono alcuni motivi per cui ho preso l'approccio che ho. Uno è che la versione del grafico a barre è un grafico a barre raggruppato, quindi devo avere il nesting in categorie. Un altro è che non ho il controllo sulla forma originale dei dati - proviene dal client. Ho pubblicato un aggiornamento alla domanda che include un collegamento a esattamente come dovrebbe apparire e funzionare.Tuttavia, questa domanda è ancora rilevante in quanto ho utilizzato due nidi di dati separati per ottenere il risultato, il che mi sembra inefficiente. – tgerard

+0

@tardi, la tua versione funzionante è fantastica! Ho aggiunto una modifica alla mia risposta solo per spiegare come è possibile espandere quello che ho per ottenere quelle "caratteristiche" extra che stavi cercando. Per quanto riguarda l'"inefficienza" del tuo approccio, penso che potrebbe essere necessario con l'org dei tuoi dati originali. Potresti iniziare riformattando con javascript e poi seguirmi con quello che ho, ma a quel punto probabilmente ha più senso attenersi a ciò che hai (a meno che non ti trovi a dover combattere ripetutamente contro quell'approccio inefficiente). –

+0

Sì, seguirò semplicemente il mio approccio originale. Quando ho postato questa domanda per la prima volta ho pensato che rispondere a qualcuno sarebbe stato più facile per qualcuno con una migliore capacità di codifica della mia, ma ora ho raggiunto la conclusione che in realtà è piuttosto difficile e non dovrei avere paura di avere due nidi. Grazie ancora per avergli dato un crack. – tgerard