I am working on this project with D3js and I have come across a problem now. When I have more data, the bars of my barchart will append correctly in the same line as the name but when I fetch less data from my database, the bars will "loose control" and append higher than their name and causing a bad view of my chart.
Here's a picture of what I'll have if I load more data do it.
And here's my second picture of the chart if I load less data.
I don't really understand what I am missing here but I believe is something with the height of the Y-axis and the bars y-position. Can you please help me sort this out?
Here is my code:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width margin.left margin.right)
.attr("height", height margin.top margin.bottom)
.append("g")
.attr("transform",
"translate(" margin.left "," margin.top ")");
// Parse the Data
var data2 = d3.json("/Events/BarChart/4").then(function (data) {
console.log(data);
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," height ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.range([0, height])
.domain(data.map(function (d) { return d.name; }))
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name) 10; })
.attr("width", function (d) { return x(d.value); })
.attr("height", 20)
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
})
CodePudding user response:
The issue arises because you manually assign each rectangle a height of 20 pixels, but you give the scale a range of 0 - 240 (the value of height
). The scale will divide the range into equal segments (bands), one for each value in its domain. When you have only two values in the domain they will have bands of 120 px each (reduced if there is padding). Nowhere does the scale "know" you have assigned a height of just 20 px for each bar; afterall, you told it to spread values evenly over a range of 0 - 240. These conflicting instructions are why your bars aren't aligned with your axis.
When using d3 scales you will find it much easier if you use the scale for both axis and drawing the data itself (rects/circles/etc): this way they will always be aligned.
The d3 band scale offers a convenient method: scale.bandwidth()
, this returns the length/width/height of a band in the scale: at its simplest (without padding) it is the size of the range divided by how many distinct values are in the domain. We can use this value to set bar height:
I also noticed that you add 10 pixels to the y value of each bar: this was probably to manually align the bars better with multiple data entries. Generally this will cause problems (unless manually correcting for them): scale(value) and scale.bandwidth() for y/x and height/width respectively produces bars centered on axis ticks. If you want padding (space between the bars), it is simplest to set that using the scale: scale.padding(number)
where number
is a value between 0 and 1 representing the portion of each segment that is empty:
But what if you don't want 120 px wide segments? You want your bars to be always 20-ish pixels, regardless of how many bars you have. Well we can modify the range of the scale to reflect the length of the domain:
I also updated the transform for the x axis, you could go further an adjust svg height to be better sized as well