martinbshp
martinbshp

Reputation: 1173

Unable to display x axis categories and make y axis start at 0

In this D3.js version 6.7 bar chart I am trying to align the x axis to show the categories and show the y axis to start at 0. Extending the height of the svg and changing the transforms does not appear to be working. How can I make the x axis categories appear under the bars and make the y axis start at 0? Thank you.

async function barChart() {
    const dataset = await d3.csv("https://assets.codepen.io/417105/bodypart-injury-clean.csv");
    console.log(dataset);

    const width = 400;
    const height = 400;
  
    const margin = {top:20, right:30, bottom:30, left:40};

    const canvas = d3.select("#viz")
                    .append("svg")
                    .attr("width", width)
                    .attr("height", height);
    
    const wrapper = canvas.append("g").style("transform",`translate(${margin.left}px,${margin.top}px)`);
 

    const xScale = d3.scaleBand()
                    .domain(["Arm","Eye","Head","Hand","Leg","Other"])
                    .range([0,width - margin.left])
                    .padding(0.2);

    const yScale = d3.scaleLinear()
                    .domain(d3.extent(dataset, d => +d.Total))
                    .range([height,0]);

    console.log(xScale("Leg"));
    console.log(yScale(1700));

    const barRect = wrapper.append("g")
                    .selectAll('rect')
                    .data(dataset)
                    .join('rect')
                    .attr('x', d => xScale(d.BodyRegion))
                    .attr('y', d => yScale(+d.Total))
                    .attr('width', xScale.bandwidth())
                    .attr('height', d => height - yScale(+d.Total))
                    .attr('fill', 'teal');

    const yAxis = d3.axisLeft().scale(yScale);
    wrapper.append("g").call(yAxis);
  
    const xAxis = d3.axisBottom().scale(xScale);
    wrapper.append("g").attr('transform', `translate(0,${height-margin.bottom})`).call(xAxis);
}

barChart();

Upvotes: 1

Views: 342

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38201

The Y scale

The scale's domain sets the extent of the scale in your data's units, the scale's range sets the scale's extent in scaled units (pixels here). The first value in the domain is mapped to the first value in the range.

Your domain is set to:

.domain(d3.extent(dataset, d => +d.Total)) 

d3.extent returns the minimum and maximum matching values, as your minimum value is not zero, your scale's domain does not start at 0. If you want to set the scale's domain's lower bounds to zero, you need to set that, like so:

.domain([0,d3.max(dataset,d=> +d.Total)])

.domain/.range take arrays, these arrays for a linear scale must have the same number of elements

But you also don't want your scale's range to be [height,0] because of margins:

 .range([height-margin.bottom,margin.top])

You want the data to be scaled from between within the two margins, height-margin.bottom is the furthest down the page you want to plot data, and margin.top is the furthest to the top of the SVG you want to plot data.

Now your bars are a bit off, that's because you aren't accounting for the margin in the height attribute:

.attr('height', d => height - yScale(+d.Total))

you need:

.attr('height', d => height - margin.bottom - yScale(+d.Total))

Note, a common approach to avoid having to constantly reference the margin is to apply the margin to a parent g and have width height reflect the size of the plot area within that g (not the entire SVG).

The X Axis

Now that the y scale is configured, let's look at the x axis. All you need to do here is boost the bottom margin: the text is appended (you can inspect the page to see it is there, just not visible). Try margin.bottom = 50.

Upvotes: 3

Related Questions