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.