1

I would like to know if someone has an example of d3 stacked chart with canvas because the only example I found on internet are with SVG.

Thanks for your help, i dont know why it so hard to find example with d3 canvas

Try to have a stacked chart with canvas on d3 lib

Aris381
  • 11
  • 1

1 Answers1

0

Your secondary question

i dont know why it so hard to find example with d3 canvas

has an easy answer; namely, SVG offers a lot of advantages over Canvas. Canvas is really only the preferred choice for raster images or when there are many thousands of objects to manipulate; this is rarely the case for a bar chart.

Having said that, it's not particularly hard to draw a bar chart using the standard HTML Canvas Context and, of course, you can use D3 to help. Here's one approach:

d3.csv(
  "https://raw.githubusercontent.com/sandialabs/slycat-data/master/cars.csv",
  d3.autoType
).then(function(cars) {

  // Massage the data into a form suitable for a stack.
  let data = [];
  d3.group(
    d3.sort(
      cars,
      (o) => o.Year,
      (o) => o.Cylinders
    ),
    (o) => o.Year,
    (o) => o.Cylinders
  ).forEach(function (cylinder_group, Year) {
    let accumulation = 0;
    cylinder_group.forEach(function (a, Cylinders) {
      data.push({
        Year,
        Cylinders,
        height: a.length,
        accumulation
      });
      accumulation = accumulation + a.length;
    });
  });
  
  // Define a few parameters
  let w = 800;
  let h = 500;
  let pad = 20;
  
  // Setup the scales
  let x_scale = d3
    .scaleBand()
    .domain(data.map((o) => o.Year))
    .range([pad, w - pad])
    .padding(0.2);
  let max_count = d3.max(
    Array.from(d3.group(data, (o) => o.Year).values())
      .map((a) => a.slice(-1)[0])
      .map((o) => o.height + o.accumulation)
  );
  let y_scale = d3
    .scaleLinear()
    .domain([0, max_count])
    .range([h - pad, pad]);
  let color = d3.scaleOrdinal()
    .domain([3, 4, 5, 6, 8])
    .range(d3.schemeCategory10);
  
  // Set up the canvas
  let canvas = d3
    .select('#viz')
    .append("canvas")
    .style("margin", `${pad}px`)
    .attr("width", w)
    .attr("height", h)
    .style("border", "solid 1px black");
  let ctx = canvas.node().getContext("2d");

  // Draw the rectangles
  data.forEach(function (o) {
    ctx.fillStyle = color(o.Cylinders);
    ctx.fillRect(
      x_scale(o.Year),
      y_scale(o.accumulation),
      x_scale(71) - x_scale(70) - 15,
      y_scale(o.height) - y_scale(0) + 3
    );
  });

  // Draw the axes
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(pad, y_scale(0));
  ctx.lineTo(w - pad, y_scale(0));
  ctx.stroke();
  ctx.fillStyle = "black";
  ctx.lineWidth = 0.5;
  d3.range(70, 83).forEach(function (x) {
    ctx.beginPath();
    ctx.moveTo(x_scale(x) + 26, y_scale(0));
    ctx.lineTo(x_scale(x) + 26, y_scale(0) + 8);
    ctx.stroke();
    ctx.fillText(x, x_scale(x) + 20, y_scale(0) + 15);
  });
  ctx.lineWidth = 1;
  d3.range(0, 45, 5).forEach(function (y) {
    ctx.beginPath();
    ctx.moveTo(pad, y_scale(y));
    ctx.lineTo(pad + 5, y_scale(y));
    ctx.stroke();
    ctx.fillText(y, pad - 15, y_scale(y) + 3);
  });
 });
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id='viz'></div>

The data is the classic cars dataset. The bar chart illustrates number of cars per year from 1970 to 1982 with the bars stacked by number of cylinders. Thus, I guess we can see how the number of cylinders generally decreased over that time frame, with 4 cylinders becoming predominant - at least, for this sample.

Note that, if you were to draw the bar chart with SVG, then much of the code would be unchanged. You might be interested in this Observable notebook that illustrates both approaches.

Mark McClure
  • 4,862
  • 21
  • 34