2013-09-16 14 views
20

Sto costruendo un grafico a barre D3 con una scala temporale sull'asse x. La gamma dell'asse x può variare.D3.js: calcola la larghezza delle barre nella scala temporale con l'intervallo di variazione?

Come è possibile specificare la larghezza corretta per le barre sul grafico a barre? Ho visto persone usare rangeBands per scale ordinali, ma non sono sicuro di come farlo con una scala temporale.

Ecco il mio codice:

var x = d3.time.scale().range([0, width]); 
var xAxis = d3.svg.axis().scale(x).orient("bottom"); 
[...] 

// After new data has been fetched, update the domain of x... 
x.domain(d3.extent(data, function(d) { return d.date; })); 
d3.select(".x.axis").call(xAxis); 

// ... and draw bars for chart 
var bars = svg.selectAll(".air_used") 
    .data(data, function(d) { return d.date; }); 

bars.attr("y", function(d) { return y(d.air_used); }) 
.attr("height", function(d) { return height - y(d.air_used); }) 
.attr("x", function(d) { return x(d.date); }) 
.attr("width", 20) // ?? 

Cosa devo mettere su l'ultima riga, al posto di 20?

UPDATE: JSFiddle qui: http://jsfiddle.net/aWJtJ/2/

risposta

28

Non c'è alcuna funzione per ottenere la larghezza, ma si può calcolare abbastanza facilmente:

.attr("width", width/data.length); 

si potrebbe desiderare di sottrarre una piccola quantità da che se don Voglio che le barre tocchino. Dovresti anche modificare la posizione x di conseguenza, ad es.

.attr("x", function(d) { return x(d.date) - (width/data.length)/2; }) 
.attr("width", width/data.length); 

per ottenere lo zecche per allineare correttamente, avrete anche bisogno di regolare l'intervallo della scala di asse x perché il primo segno di spunta sarà posto al primo valore:

var x = d3.time.scale().range([width/data.length/2, width-width/data.length/2]); 

completa jsfiddle here.

+1

Grazie! Il primo suggerimento è quasi arrivato, ma la funzione 'x' sembra inserire qualche padding tra le barre: http://jsfiddle.net/aWJtJ/1/ Se provo ad usare la seconda funzione, di nuovo, è quasi corretta ma è ancora inserire padding tra le barre: http://jsfiddle.net/aWJtJ/2/ - Non capisco perché la funzione 'x' possa dire al mio codice dove dovrebbe essere la posizione' x' delle barre, ma non può dire dove dovrebbe essere la larghezza, dato che ovviamente ha un'idea precisa di quale dovrebbe essere la larghezza. – Richard

+0

Ok, hai bisogno di un po 'di più - in particolare, devi regolare l'intervallo perché il primo tick sarà al primo valore (vedi la risposta modificata). Per quanto riguarda il motivo per cui D3 non ti dice la larghezza stessa, non ho idea: potresti aprire una richiesta di funzionalità. –

+0

Perché non posso revocare questa risposta due volte? – AperioOculus

2

Poiché le scale di tempo sono continue, ci possono essere molte idee su cosa sia una larghezza di barra "corretta". Se i punti dati sono molto granulari e distribuiti in modo non uniforme, è possibile utilizzare una barra sottile di larghezza fissa per ridurre al minimo le sovrapposizioni. Se si conoscono i valori dei dati previsti in anticipo e la loro granularità è uniforme, è possibile fare qualcosa di simile a ciò che @LarsKotthoff distribuirà in modo uniforme.

Una cosa da considerare è se effettivamente si desidera una scala temporale. Le barre sono generalmente utilizzate per rappresentare i valori categoriali, non i punti su una scala continua. Forse una scala ordinale con il dominio derivato da un intervallo di date è in realtà ciò che vuoi. In tal caso, puoi utilizzare i rangeBands come per il tuo post originale.

Non dire che è sbagliato fare quello che stai facendo. Solo spunti di riflessione.

+0

Grazie! Ho bisogno di usare una scala temporale perché voglio usare i formati temporali per i tick: https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear_tickFormat – Richard

+2

Se la formattazione è l'unica ragione e hai un numero noto di valori discreti è possibile formattare le date come stringhe prima di passarle nella scala ordinale. Naturalmente, è necessario fare attenzione a formattare tutti i dati di data in stringhe nello stesso modo prima di ridimensionarli, probabilmente con una funzione getter condivisa o qualcosa del genere. O convertire i dati in stringhe nella fase iniziale. Dipende davvero se hai bisogno della semantica di un tipo di dati temporali o meno. –

+0

BTW, puoi utilizzare l'analizzatore del tempo e il formattatore di D3 indipendentemente da qualsiasi scala: https://github.com/mbostock/d3/wiki/Time-Formatting –

3

Anche se sono d'accordo con Scott sul fatto che un grafico a barre debba essere utilizzato con dati ordinali o categoriali sull'asse x, suppongo che la domanda riguardi più la convenienza di disegnare un asse del tempo. Come d3.time.scale fa un ottimo lavoro con l'aiuto di d3.time.format.multi dell'asse del tempo di disegno di varie durate (ore, giorni, mesi, ecc.) E le sue zecche, potrebbe essere una buona idea combinare d3.time.scale per un asse e d3.scale.ordinal per una banda calcolo della larghezza.

Lo snippet di seguito è ispirato a the discussion in D3 Google Group sull'argomento. L'unità per la scala ordinale è un giorno.

function prepare(data) 
 
{ 
 
    var dateParse = d3.time.format('%Y-%m-%d'); 
 
    data.forEach(function(v) 
 
    { 
 
    v.date = dateParse.parse(v.date); 
 
    }); 
 

 
    var dateValueMap = data.reduce(function(r, v) 
 
    { 
 
    r[v.date.toISOString()] = v.value; 
 
    return r; 
 
    }, {}); 
 

 
    var dateExtent = d3.extent(data.map(function(v) 
 
    { 
 
    return v.date; 
 
    })); 
 

 
    // make data have each date within the extent 
 
    var fullDayRange = d3.time.day.range(
 
    dateExtent[0], 
 
    d3.time.day.offset(dateExtent[1], 1) 
 
); 
 
    fullDayRange.forEach(function(date) 
 
    { 
 
    if(!(date.toISOString() in dateValueMap)) 
 
    { 
 
     data.push({ 
 
     'date' : date, 
 
     'value' : 0 
 
     }); 
 
    } 
 
    }); 
 

 
    data = data.sort(function(a, b) 
 
    { 
 
    return a.date - b.date; 
 
    }); 
 

 
    return data; 
 
} 
 

 
function draw(data) 
 
{ 
 
    var margin = { 
 
    'top' : 10, 
 
    'right' : 20, 
 
    'bottom' : 20, 
 
    'left' : 60 
 
    }; 
 
    var size = { 
 
    'width' : 600 - margin.left - margin.right, 
 
    'height' : 180 - margin.top - margin.bottom 
 
    }; 
 

 
    var svg = d3.select('#chart').append('svg') 
 
    .attr('width', '100%') 
 
    .attr('height', '100%') 
 
    .append('g') 
 
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 
 

 
    var dates = data.map(function(v) 
 
    { 
 
    return v.date; 
 
    }); 
 
    var x = d3.time.scale() 
 
    .range([0, size.width]) 
 
    .domain(d3.extent(dates)); 
 

 
    var y = d3.scale.linear() 
 
    .range([size.height, 0]) 
 
    .domain([0, d3.max(data.map(function(v) 
 
    { 
 
     return v.value; 
 
    }))]); 
 

 
    var xAxis = d3.svg.axis() 
 
    .scale(x) 
 
    .orient('bottom'); 
 

 
    var yAxis = d3.svg.axis() 
 
    .scale(y) 
 
    .orient('left'); 
 

 
    var barWidth = d3.scale.ordinal() 
 
    .domain(dates) 
 
    .rangeRoundBands(x.range(), 0.1) 
 
    .rangeBand(); 
 

 
    svg.append('g') 
 
    .attr('class', 'x axis') 
 
    .attr('transform', 'translate(' + barWidth/2 + ',' + size.height + ')') 
 
    .call(xAxis); 
 

 
    svg.append('g') 
 
    .attr('class', 'y axis') 
 
    .call(yAxis) 
 
    .append('text') 
 
    .attr('transform', 'rotate(-90)') 
 
    .attr('y', 6) 
 
    .attr('dy', '.71em') 
 
    .style('text-anchor', 'end') 
 
    .text('Amount'); 
 

 
    svg.selectAll('.bar') 
 
    .data(data) 
 
    .enter() 
 
    .append('rect') 
 
    .attr('class', 'bar') 
 
    .attr('x', function(d) 
 
    { 
 
     return x(d.date); 
 
    }) 
 
    .attr('width', barWidth) 
 
    .attr('y', function(d) 
 
    { 
 
     return y(d.value); 
 
    }) 
 
    .attr('height', function(d) 
 
    { 
 
     return size.height - y(d.value); 
 
    }); 
 
} 
 

 
function getData() 
 
{ 
 
    return [ 
 
    {'date': '2014-01-31', 'value': 5261.38}, 
 
    {'date': '2014-02-02', 'value': 7460.23}, 
 
    {'date': '2014-02-03', 'value': 8553.39}, 
 
    {'date': '2014-02-04', 'value': 3897.18}, 
 
    {'date': '2014-02-05', 'value': 2822.22}, 
 
    {'date': '2014-02-06', 'value': 6762.49}, 
 
    {'date': '2014-02-07', 'value': 8624.56}, 
 
    {'date': '2014-02-08', 'value': 7870.35}, 
 
    {'date': '2014-02-09', 'value': 7991.43}, 
 
    {'date': '2014-02-10', 'value': 9947.14}, 
 
    {'date': '2014-02-11', 'value': 6539.75}, 
 
    {'date': '2014-02-12', 'value': 2487.3}, 
 
    {'date': '2014-02-15', 'value': 3517.38}, 
 
    {'date': '2014-02-16', 'value': 1919.08}, 
 
    {'date': '2014-02-19', 'value': 1764.8}, 
 
    {'date': '2014-02-20', 'value': 5607.57}, 
 
    {'date': '2014-02-21', 'value': 7148.87}, 
 
    {'date': '2014-02-22', 'value': 5496.45}, 
 
    {'date': '2014-02-23', 'value': 296.89}, 
 
    {'date': '2014-02-24', 'value': 1578.59}, 
 
    {'date': '2014-02-26', 'value': 1763.16}, 
 
    {'date': '2014-02-27', 'value': 8622.26}, 
 
    {'date': '2014-02-28', 'value': 7298.99}, 
 
    {'date': '2014-03-01', 'value': 3014.06}, 
 
    {'date': '2014-03-05', 'value': 6971.12}, 
 
    {'date': '2014-03-06', 'value': 2949.03}, 
 
    {'date': '2014-03-07', 'value': 8512.96}, 
 
    {'date': '2014-03-09', 'value': 7734.72}, 
 
    {'date': '2014-03-10', 'value': 6703.21}, 
 
    {'date': '2014-03-11', 'value': 9798.07}, 
 
    {'date': '2014-03-12', 'value': 6541.8}, 
 
    {'date': '2014-03-13', 'value': 915.44}, 
 
    {'date': '2014-03-14', 'value': 9570.82}, 
 
    {'date': '2014-03-16', 'value': 6459.17}, 
 
    {'date': '2014-03-17', 'value': 9389.62}, 
 
    {'date': '2014-03-18', 'value': 6216.9}, 
 
    {'date': '2014-03-19', 'value': 4433.5}, 
 
    {'date': '2014-03-20', 'value': 9017.23}, 
 
    {'date': '2014-03-23', 'value': 2828.45}, 
 
    {'date': '2014-03-24', 'value': 63.29}, 
 
    {'date': '2014-03-25', 'value': 3855.02}, 
 
    {'date': '2014-03-26', 'value': 4203.06}, 
 
    {'date': '2014-03-27', 'value': 3132.32} 
 
    ]; 
 
} 
 

 
draw(prepare(getData()));
#chart { 
 
    width : 600px; 
 
    height : 180px; 
 
} 
 
.bar { 
 
    fill : steelblue; 
 
} 
 

 
.axis { 
 
    font : 10px sans-serif; 
 
} 
 

 
.axis path, 
 
.axis line { 
 
    fill   : none; 
 
    stroke   : #000; 
 
    shape-rendering : crispEdges; 
 
} 
 

 
.x.axis path { 
 
    display : none; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
 
<div id='chart'></div>

2

ho funzionato in questo problema quando cambia livelli di ingrandimento su un grafico a barre con un asse x basata serie temporali e trovarono due soluzioni..

1) Creare una scala ordinale compagno per aiutare a calcolare le larghezze (un dolore)

2) L'asse x serie temporale è il tuo amico - usarlo per calcolare la larghezza.

Se si desidera che la barra sia sempre di un "mese", indipendentemente dal livello di zoom, è possibile fare qualcosa di simile. Im assuming d.date è disponibile nei dati.

svg.selectAll(".air_used").attr("width", function(d.date) { 
    var next = d3.time.month.offset(d.date, 1); 
    return (x(next)- x(d)); 
    }); 

Questo funziona bene perché funziona per ogni scala di zoom, e basta chiamare questa funzione alla fine del vostro su "zoom" handler cioè .on ("zoom", ingrandita); L'asse x di solito è stato regolato a questo punto.

Problemi correlati