D3.js - ¿Gráfico de barras apilado de JSON?

Aug 19 2020

He estado intentando recrear este violín: https://jsfiddle.net/1c5eqrp3/3/

En el contexto de un gráfico de barras apiladas horizontales que estoy construyendo usando mis datos.

var bar1Data = [{
    position: 1,
    label: '70k',
    value: 1708026
  },
  {
    position: 2,
    label: '71K - 149K',
    value: 1915059
  },
];

//sort bars based on value
bar1Data = bar1Data.sort(function(a, b) {
  return d3.ascending(a.position, b.position);
})

var colors = ['green', 'blue'];

var bars = d3.select('#bars')
  .append('svg')
  .attr('width', 800)
  .attr('height', 200);

bars.selectAll('rect')
  .data(bar1Data)
  .enter()
  .append('rect')
  .attr('width', function(d) {
    return d;
  })
  .attr('x', function(d, i) {
    return sum(bar1Data.value, 0, i);
  })
  .attr('fill', function(d, i) {
    return colors[i];
  })
  .attr('y', 0)
  .attr('height', 100);

function sum(array, start, end) {
  var total = 0;
  for (var i = start; i < end; i++) total += array[i];
  return total;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="bar"></div>

Todo iba bien, hasta que intenté que leyera el bar1Data que creé.

Ahora, recibo los siguientes errores:

Error: <rect> attribute width: Expected length, "[object Object]".
(anonymous) @ d3.js:1211
selection_each @ d3.js:1176
selection_attr @ d3.js:1233
(anonymous) @ bars3.js:23
Promise.then (async)
(anonymous) @ bars3.js:1
bars3.js:33 Uncaught (in promise) TypeError: Cannot read property '0' of undefined
    at sum (bars3.js:33)
    at SVGRectElement.<anonymous> (bars3.js:26)
    at SVGRectElement.<anonymous> (d3.js:1209)
    at Selection.selection_each [as each] (d3.js:1176)
    at Selection.selection_attr [as attr] (d3.js:1233)
    at bars3.js:25

He estado luchando enormemente para crear un gráfico de barras horizontales apiladas usando d3.v5. He estado probando varios ejemplos usando d3.v4 pero no he podido hacer que funcionen. Este fue el ejemplo más simple y liviano que he encontrado. Pero todavía no incluye escalas (que espero incluir un poco más adelante).

¿Podría ayudarme a resolver este problema con una explicación?

Respuestas

1 RubenHelsloot Aug 19 2020 at 12:48

En .attr('width', function(d){ return d;}), está configurando el ancho de la barra para todo el objeto d. Debe comprender que .data(bar1Data)crea una entrada por valor de bar1Datay que ese valor es accesible como den todas esas funciones. Eso significa que, tan pronto como hayas pasado .data(bar1Data), ya no deberías tocar directamente ese objeto.

Ahora, a partir de sus datos, parece que entiendo que desea usar los valores de todas las entradas anteriores para calcular la suma acumulada hasta ese punto. Implementaré eso en la respuesta, pero también me gustaría señalarle que stack = d3.stack()lo hace por usted.

Tenga en cuenta también que sus valores son demasiado grandes. Debe transformarlos usando un dominio; de lo contrario, obtendrá un píxel por dólar (u otra unidad de dinero), lo que significa que las barras se dibujan 100 veces el ancho de su monitor a la derecha.

var bar1Data = [{
    position: 1,
    label: '70k',
    value: 50
  },
  {
    position: 2,
    label: '71K - 149K',
    value: 40
  },
];

//sort bars based on value
bar1Data = bar1Data.sort(function(a, b) {
  return d3.ascending(a.position, b.position);
})

var colors = ['green', 'blue'];

var bars = d3.select('#bars')
  .append('svg')
  .attr('width', 800)
  .attr('height', 200);

bars.selectAll('rect')
  .data(bar1Data)
  .enter()
  .append('rect')
  .attr('width', function(d) {
    return d.value;
  })
  .attr('x', function(d, i) {
    // Note that I use `.map` here to take only the `.value` attribute
    // from the data and create an array of numbers.
    return sum(bar1Data.map((e) => e.value), 0, i);
  })
  .attr('fill', function(d, i) {
    return colors[i];
  })
  .attr('y', 0)
  .attr('height', 100);

function sum(array, start, end) {
  var total = 0;
  for (var i = start; i < end; i++) total += array[i];
  return total;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id='bars'>
</div>