Search code examples
htmlcsschart.jsvue-chartjs

How to add an extra tick on top of the highest bar in Chart.js v2.9.4 (grace)?


I would like to know how to add an extra tick on top of the highest bar in Chartjs.
I don't like the way that the graph just stops after 25 (see image), I would like it to stop at one tick more (in this example 30). The reason that I'm using V2.9.4 is because I am using it in Nuxt with the Vuejs version of Chartjs.
I did find out that in the newest versions of Chartjs its called grace.
I couldn't really find an answer on my question with the V2.9.4 version of Chartjs.
Code isn't really relevant but still included it.

BarChart.vue (component)


    <script>
    import {Bar} from "vue-chartjs";
    
    export default {
      extends: Bar,
      props: {
        data: {
          type: String,
          default: () => {},
        },
        options: {
          type: Object,
          default: () => {},
        },
      },
      computed: {
        Chart() {
          return['data', 'options'];
        },
      },
      mounted() {
        this.renderChart(this.data, this.options);
      },
    };
    </script>


HTML:

    <div class="chart">
      <BarChart :data="barChartData" :options="barChartOptions" :height="200"/>
    </div>

Script:


    <script>
    import BarChart from "~/components/plugins/BarChart";
    
    export default {
      components: {
        BarChart,
      },
      data() {
        return {
          barChartData: {
            labels: ["Verzonden", "Ontvangen", "Geopend", "Kliks"],
            datasets: [
              {
                data: [25, 20, 20, 18],
                backgroundColor: [
                  '#7782FF',
                  '#403DD3',
                  '#FFB930',
                  '#00E437',
                ],
                barThickness : 50,
              },
            ],
          },
          barChartOptions: {
            responsive: true,
            plugins: {
              customScale: {
                grace: '100%',
              },
            },
            legend: {
              display: false,
            },
            scales: {
              xAxes: [
                {
                  gridLines: {
                    display: false,
                  },
                  ticks: {
                    fontColor: "black",
                    fontSize: 14,
                  },
                },
              ],
              yAxes: [
                {
                  ticks: {
                    beginAtZero: true,
                    min: 0,
                    stepSize: 5,
                    fontColor: '#ABACB3',
                  },
                  gridLines: {
                    display: true,
                    borderDash: [4, 4],
                    color: '#EEEDFB',
                    drawBorder: false,
                  },
                },
              ],
            },
          },
        };
      },
    };
    </script>

This is how it looks now

This is how I want it to look

Thanks in advance. :)


Solution

  • You can use a custom plugin to achieve this:

    const options = {
      type: 'bar',
      data: {
        labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
        datasets: [{
            label: '# of Votes',
            data: [12, 20, 3, 5, 2, 3],
            borderWidth: 1
          },
          {
            label: '# of Points',
            data: [7, 11, 5, 8, 3, 7],
            borderWidth: 1
          }
        ]
      },
      options: {
        plugins: {
          customScale: {
            grace: '100%', // Percentage of max value
            // grace: 40 // Flatout extra value to add
          }
        }
      },
      plugins: [{
        id: "customScale",
        beforeLayout: (chart, options, c) => {
          let max = Number.MIN_VALUE;
          let min = Number.MAX_VALUE
          let grace = options.grace || 0
    
          chart.data.datasets.forEach((dataset) => {
            max = Math.max(max, Math.max(...dataset.data));
            min = Math.min(min, Math.min(...dataset.data))
          })
    
          if (typeof grace === 'string' && grace.includes('%')) {
            grace = Number(grace.replace('%', '')) / 100
    
            chart.options.scales.yAxes[0].ticks.suggestedMax = max + (max * grace)
            chart.options.scales.yAxes[0].ticks.suggestedMin = min - (min * grace)
    
          } else if (typeof grace === 'number') {
    
            chart.options.scales.yAxes[0].ticks.suggestedMax = max + grace
            chart.options.scales.yAxes[0].ticks.suggestedMin = min - grace
    
          }
    
        }
      }]
    }
    
    const ctx = document.getElementById('chartJSContainer').getContext('2d');
    new Chart(ctx, options);
    <body>
      <canvas id="chartJSContainer" width="600" height="400"></canvas>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
    </body>

    EDIT:

    I assume the 'BarChart' is a wrapper component from an external lib in which case you need to pass the plugin to it. I guess it has to be done like so but you might need to check the documentation of the specific wrapper you are using:

    <BarChart :data="barChartData" :options="barChartOptions" :plugins="[plugin]" :height="200"/>
    
    const plugin = {
      id: "customScale",
      beforeLayout: (chart, options, c) => {
        let max = Number.MIN_VALUE;
        let min = Number.MAX_VALUE
        let grace = options.grace || 0
    
        chart.data.datasets.forEach((dataset) => {
          max = Math.max(max, Math.max(...dataset.data));
          min = Math.min(min, Math.min(...dataset.data))
        })
    
        if (typeof grace === 'string' && grace.includes('%')) {
          grace = Number(grace.replace('%', '')) / 100
    
          chart.options.scales.yAxes[0].ticks.suggestedMax = max + (max * grace)
          chart.options.scales.yAxes[0].ticks.suggestedMin = min - (min * grace)
    
        } else if (typeof grace === 'number') {
    
          chart.options.scales.yAxes[0].ticks.suggestedMax = max + grace
          chart.options.scales.yAxes[0].ticks.suggestedMin = min - grace
    
        }
    
      }
    }