Why d3 is not generating X axis label at x=min and x=max

I am working with a test data set to create a viz.

The full code is below and a notebook

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>


<body>
    <link rel="stylesheet" href="style.css"></link>
    <div id="container" class="svg-container"></div>
    <svg>   
 
   
</svg>
    <!--d3 script-->
    <script type="text/javascript">
	async function draw() {
    const data = await d3.json("https://raw.githubusercontent.com/smpa01/d3data/main/test.json");
	
    //namespace
    const svgns = 'http://www.w3.org/2000/svg'
    const svg = d3.select('svg')

    //define dimension
    const width = 1280;
    const height = 720;
    svg
    //.attr('xmlns', svgns)
        .attr('viewBox', `0 0 ${width} ${height}`)  


    svg.append('rect')
        .attr('class', 'vBoxRect')
        .attr('width', `${width}`)
        .attr('height', `${height}`)
        .attr('fill', 'none')
        .attr('stroke', 'red')
   
    const padding = {
        top: 70,
        bottom: 50,
        left: 70,
        right: 50
    }
    const boundHeight = height - padding.top - padding.bottom;
    const boundWidth = width - padding.right - padding.left;

    //create BOUND rect -- to be removed later
    svg.append('rect')
        .attr('class', 'boundRect')
        .attr('x', `${padding.left}`)
        .attr('y', `${padding.top}`)
        .attr('width', `${boundWidth}`)
        .attr('height', `${boundHeight}`)
        .attr('fill', 'none')
        .attr('stroke', 'green')


    //create bound element
    const bound = svg.append('g')
        .attr('class', 'bound')
        .style('transform', `translate(${padding.left}px,${padding.top}px)`)



    //scale converts a domain (data) to range (pixel)
    const scaleX = d3.scaleLinear()
        .range([0, boundWidth])
        .domain(d3.extent(data, d => d.Year))

    bound.append('g')
        .attr('class', 'xAxis')
        .append('g')
        .attr('class', 'xAxisBottom')
        .call(d3.axisBottom(scaleX).ticks().tickFormat(d3.format("d")))
        .style('transform', `translateY(${boundHeight}px)`)
}
draw();
	</script>
</body>

</html>

The dataset min=1997 and max=2021. But d3 is generating this

I am not sure how to fix this. But I want d3 to return the first and last tick labels.
Also, is it possible to fix this issue dynamically rather than manually cause I desire the code to work flawlessly with the production data (dynamically controlled by the end-user).

Thank you in advance

As you can see the first date is 1997 but the scale adds ticks only on even dates. You could add more ticks (says, .ticks(24)), or try to add .nice() to the definition of the scale.

1 Like

@Fil many thanks for this.

With your suggestion, I got away with this,

const arr = data.map(d=>d.Year)
const tickCount = arr.filter((a,i)=>arr.indexOf(a)==i).length-1

    bound.append('g')
        .attr('class', 'xAxis')
        .append('g')
        .attr('class', 'xAxisBottom')
        .call(d3.axisBottom(scaleX).ticks(tickCount).tickFormat(d3.format("d")))
        .style('transform', `translateY(${boundHeight}px)`)

However, I was wondering, if you can please explain, why d3 was only generating ticks for even dates (year) only. Is this something that can be controlled at the source (meaning while generating the axis, with any additional arguments). The reason, is d3 creates auto-generated nice evenly spaced out ticks and labels by itself which I want to achieve if I can also ask d3 somehow to geenrate at least 1st and last tick label and everything evenly spaced out in-between. I don’t need to see all the ticks or labels.

With a continuous scale we can’t generate “all the ticks”, so we need a strategy to find “good looking” values. This strategy usually gets the multiples of 10 (with the appropriate order of magnitude to cover the domain), then multiples of 5, then multiples of 2, to reach a number that is approximately the number you pass as .ticks().

You can specify the ticks yourself, by passing an array of values: ticks([1997, 1998, …, 2021]) should work—then it is up to you to compute what you need exactly.

1 Like