Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3.js tick format: show when month changes

I am using D3 (v4) to display a bar chart with dates on the x-axis.

I'm trying to get the ticks to display the day of the month, unless it's the 1st where I want it to display the month i.e. to indicate when the month changes. I don't even know where to start on this.

The current format looks like this:

enter image description here

But I want it to look more like this:

enter image description here

The current code that generates the axis is as follows:

let timeFormat = "%b %d"
xAxis = g => g
      .attr("transform", `translate(0, ${height - margin.bottom})`)
      .call(d3.axisBottom(x).tickFormat(d3.timeFormat(timeFormat)))

I basically need something along the lines of

let timeFormat = "%d == 0" ? "%b" :  "%d"

I know this is nonsense JS in this context, but it illustrates the logic I'm looking for

Here's a snippet with a simplified version of my chart:

let keys = [];

var w = 400;
var h = 200;

var margin = {
  top: 30,
  bottom: 40,
  left: 50,
  right: 20,
}

var width = w - margin.left - margin.right
var height = h - margin.top - margin.bottom


var data = [
   {
      "count":100,
      "date":"2018-11-30T00:00:00"
   },
   {

      "count":50,
      "date":"2018-12-01T00:00:00"
   },
   {

      "count":75,
      "date":"2018-12-02T00:00:00"
   },
]


var x = d3.scaleTime()
  .domain(d3.extent(data, function(d) { return new Date(d.date) }))
  .range([0, width]);

var y = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.count)])
  .range([height, 0]);

var svg = d3.select('body').append('svg')
  .attr('class', 'chart')
  .attr('width', w)
  .attr('height', h);

var chart = svg.append('g')
  .classed('graph', true)
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
 
let timeFormat = "%b %d"
  
chart.append('g')
  .classed('x axis', true)
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(x)
    .tickFormat(d3.timeFormat(timeFormat))
    .ticks(2)
  )

chart.append('g')
  .classed('y axis', true)
  .call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.0.0/d3.min.js"></script>
like image 444
Charlie Avatar asked Feb 06 '26 04:02

Charlie


1 Answers

You can get the ticks from the scale and use that to see if the previous tick was another month, and choose your format based on that:

const longFormat = d3.timeFormat('%b');
const shortFormat = d3.timeFormat('%d');
const axis = d3.axisBottom(x);
axis.tickFormat((d, i) => {
    const ticks = axis.scale().ticks();

    if (i > 0 && ticks[i - 1].getMonth() === d.getMonth()) {
        return shortFormat(d);
    }
    else {
        return longFormat(d);
    }
});

xAxis = g => g
    .attr("transform", `translate(0, ${height - margin.bottom})`)
    .call(axis)

Edit: If you're not using time scale but rather scaleBand, you should be able to use the domain to calculate the format:

const ticks = axis.scale().domain();
like image 105
JLe Avatar answered Feb 09 '26 10:02

JLe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!