D3.js: calculer la largeur des barres dans l'échelle de temps avec une plage de variation?

Je construis un graphique à barres D3 avec une échelle de temps sur l'axe des x. La portée de l'axe des x peut varier.

Comment puis-je spécifier la largeur correcte pour les barres sur le graphique à barres? J'ai vu des gens utiliser des rangeBands de rangeBands pour les échelles ordinales, mais je ne sais pas trop comment faire cela avec une échelle de temps.

Voici mon code:

 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) // ?? 

Que dois-je mettre sur la dernière ligne, au lieu de 20?

MISE À JOUR: JSFiddle ici: http://jsfiddle.net/aWJtJ/2/

Il n'y a aucune fonction pour obtenir la largeur, mais vous pouvez le calculer assez facilement:

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

Vous voudrez peut-être soustraire un petit montant de celui-ci si vous ne voulez pas que les barres soient touchées. Vous devriez également ajuster la position x conséquence, par exemple

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

Pour que les tiques s'alignent correctement, vous devrez également ajuster la portée de l'échelle des axes x car la première coche sera placée à la première valeur:

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

Complete jsfiddle ici .

Même si je suis d'accord avec Scott qu'un graphique à barres est censé être utilisé avec des données ordinales ou catégorielles sur les axes x, je suppose que la question est plus sur la commodité de dessiner un axe temporel. Comme d3.time.scale fait un très bon travail avec l'aide de d3.time.format.multi de l'axe du temps de dessin de différentes durées (heures, jours, mois, etc.) et ses tiques, il pourrait être judicieux de combiner d3.time.scale pour un axe, et d3.scale.ordinal pour un calcul de largeur de bande.

L'extrait ci-dessous s'inspire de la discussion dans D3 Google Group sur le sujet. L'unité pour l'échelle ordinaire est un jour.

 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> 

Étant donné que les échelles de temps sont continues, il peut y avoir beaucoup d'idées de ce qu'est une largeur de barre "correcte". Si vos points de données sont très granulaires et inégalement répartis, vous voudrez peut-être utiliser une barre mince de largeur fixe pour minimiser les chevauchements. Si vous connaissez quelque chose de vos valeurs de données attendues à l'avance et qu'ils sont une granularité uniforme, vous pouvez faire quelque chose comme quoi @LarsKotthoff pour les répartir uniformément.

Une chose à considérer est de savoir si vous voulez vraiment une échelle de temps du tout. Les barres sont généralement utilisées pour représenter des valeurs catégorielles, et non des points sur une échelle continue. Peut-être qu'une échelle ordinaire avec le domaine dérivé d'une plage de dates est-elle vraiment utile que vous voulez. Dans ce cas, vous pouvez utiliser les tables de mesure selon votre publication d'origine.

Ne dis pas qu'il est faux de faire ce que vous faites. Juste pour la réflexion.

J'ai rencontré ce problème lors de la modification des niveaux de zoom sur un diagramme à barres avec un axe x basé sur la série temporelle et j'ai trouvé deux solutions.

1) Créez une échelle ordinaire pour calculer les largeurs. (Une douleur)

2) L'axe x de la série temporelle est votre ami, utilisez-le pour calculer la largeur.

Si vous voulez que votre barre soit toujours "un mois" large, indépendamment du niveau de zoom, vous pouvez faire quelque chose comme ça. Je suppose que d.date est disponible dans les données.

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

Cela fonctionne bien, car il fonctionne pour chaque échelle de zoom, et il suffit d'appeler cette fonction à la fin de votre gestionnaire "zoom", c'est-à-dire .on ("zoom", zoom); L'axe x aura généralement été ajusté à ce stade.