Format chart.js x labels

Viewed 564

I use chart.js with React. My question is how to show the month label (MMM) only once per month?

The chart currently has labels: [May 15, May 18, May 21, May 24, ...]
As result I want to get: [May 15, 18, 21, 24, 27, 30, Jun 2, 5, ...]

CodeSandbox

enter image description here

Line Chart:

import React from 'react'
import { Line } from 'react-chartjs-2'
import date from 'date-and-time'

const startDate = new Date(2020, 4, 15)

//===fake data===
const json = '{"responses":[{"rows":[{"values":["1"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["1"]},{"values":["6"]},{"values":["7"]},{"values":["5"]},{"values":["8"]},{"values":["9"]},{"values":["2"]},{"values":["1"]},{"values":["1"]},{"values":["1"]},{"values":["6"]},{"values":["3"]},{"values":["0"]},{"values":["20"]},{"values":["9"]},{"values":["3"]},{"values":["2"]},{"values":["1"]},{"values":["13"]},{"values":["3"]},{"values":["13"]},{"values":["13"]},{"values":["7"]},{"values":["12"]},{"values":["0"]}]}]}'
const values = JSON.parse(json).responses[0].rows.map((row, index) => {
  let date = new Date(2020, 4, 20)
  date.setDate(startDate.getDate() + index)
  return {
    y: row.values[0],
    x: date,
  }
})
//===============

const options = {
  legend: {
    display: false,
  },
  hover: {
    mode: 'index',
    intersect: false,
    animationDuration: 0,
  },
  scales: {
    yAxes: [{ position: 'right' }],
    xAxes: [{
      gridLines: { display: false },
      distribution: 'linear',
      type: 'time',
      time: {
        parser: 'MMM D',
        tooltipFormat: 'MMM D',
        unit: 'day',
        unitStepSize: 3,
        displayFormats: {
          day: 'MMM D',
        },
      },
      ticks: {
        min: startDate,
        max: date.format(date.addDays(new Date(), 1), 'MMM D'),
        autoSkip: true
      },
    }],
  },
  tooltips: {
    mode: 'x-axis',
  },
}

const data = {
  datasets: [
    {
      label: 'test',
      fill: false,
      data: values,
      backgroundColor: '#fff',
      borderWidth: 2,
      lineTension: 0,
      borderColor: 'forestgreen',
      hoverBorderWidth: 2,
      pointBorderColor: 'rgba(0, 0, 0, 0)',
      pointBackgroundColor: 'rgba(0, 0, 0, 0)',
      pointHoverBackgroundColor: '#fff',
      pointHoverBorderColor: 'forestgreen',
      showLine: true,
    }
  ],
}

const LineChart = () => <Line data={data} options={options}/>

export default LineChart
2 Answers

Solution 1 label filter: According to the filtering labels sample you can set a function to define what should be displayed:

options: {
    scales: {
        x: {
            display: true,
            ticks: {
                callback: function(dataLabel, index) {
                    // Apply logic to remove name of the month
                    return dataLabel
                }
            }
        },
        y: {
            display: true,
            beginAtZero: false
        }
    }
}

Github source of the example

Solution 2: You could prepare your labels array beforehand. Filter the occurence of all the upcoming mentions and feed this array to chart.js.

  1. Define different displayFormats for day and month.
  2. Enable ticks.major.
  3. Mark the desired ticks as major through the afterBuildTicks callback.
time: {
  ...
  displayFormats: {
    day: 'D',
    month: 'MMM D',
  },
},
ticks: {
  major: {
    enabled: true
  }
},
afterBuildTicks: (scale, ticks) => {
  ticks.forEach((t, i) => t.major = i == 0 || new Date(t.value).getMonth() != new Date(ticks[i - 1].value).getMonth());
  return ticks;
}

Please take a look at your amended code and see how it works.

const startDate = new Date(2020, 4, 15)

//===fake data===
const json = '{"responses":[{"rows":[{"values":["1"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["0"]},{"values":["1"]},{"values":["6"]},{"values":["7"]},{"values":["5"]},{"values":["8"]},{"values":["9"]},{"values":["2"]},{"values":["1"]},{"values":["1"]},{"values":["1"]},{"values":["6"]},{"values":["3"]},{"values":["0"]},{"values":["20"]},{"values":["9"]},{"values":["3"]},{"values":["2"]},{"values":["1"]},{"values":["13"]},{"values":["3"]},{"values":["13"]},{"values":["13"]},{"values":["7"]},{"values":["12"]},{"values":["0"]}]}]}'
const values = JSON.parse(json).responses[0].rows.map((row, index) => {
  let date = new Date(2020, 4, 20);
  date.setDate(startDate.getDate() + index)
  return {
    y: row.values[0],
    x: date
  }
})

//===============

const options = {
  legend: {
    display: false
  },
  hover: {
    mode: 'index',
    intersect: false,
    animationDuration: 0
  },
  scales: {
    yAxes: [{
      position: 'right'
    }],
    xAxes: [{
      gridLines: {
        display: false
      },
      distribution: 'linear',
      type: 'time',
      time: {
        tooltipFormat: 'MMM D',
        unit: 'day',
        unitStepSize: 3,
        displayFormats: {
          day: 'D',
          month: 'MMM D',
        },
      },
      ticks: {
        major: {
          enabled: true
        }
      },
      afterBuildTicks: (scale, ticks) => {
        ticks.forEach((t, i) => t.major = i == 0 || new Date(t.value).getMonth() != new Date(ticks[i - 1].value).getMonth());
        return ticks;
      }
    }]
  },
  tooltips: {
    mode: 'x-axis',
  }
};

const data = {
  datasets: [{
    label: 'test',
    fill: false,
    data: values,
    backgroundColor: '#fff',
    borderWidth: 2,
    lineTension: 0,
    borderColor: 'forestgreen',
    hoverBorderWidth: 2,
    pointBorderColor: 'rgba(0, 0, 0, 0)',
    pointBackgroundColor: 'rgba(0, 0, 0, 0)',
    pointHoverBackgroundColor: '#fff',
    pointHoverBorderColor: 'forestgreen',
    showLine: true,
  }],
};

new Chart('myChart', {
  type: 'line',
  data: data,
  options: options
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="myChart" height="90"></canvas>

Related