Skip to content
Advertisement

Remove redundant legends on the chart using generateLabels with ChartJS v3

I have a chart as follows, I only need one pair of legends (data 1 and data 2) to show on the chart.

In ChartJS v2, I can use generateLabels like this but it doesn’t seem to work the same way in v3 that I’m using.

Is there an easy way to achieve this in v3 using generateLabels or do I have to change the DOM structure?

const trend_options = {
  responsive: true,
  maintainAspectRatio: false,
  plugins: {
    datalabels: {
      formatter: function(value, ctx) {
const otherDatasetIndex = ctx.datasetIndex % 2 === 0 ? ctx.datasetIndex + 1 : ctx.datasetIndex - 1;
        const total = ctx.chart.data.datasets[otherDatasetIndex].data[ctx.dataIndex] + value;
        return `${(value / total * 100).toFixed()}%`;
      },

      font: {
        weight: "bold"
      },
      color: "#fff",
      display: function(context) {
        let index = context.dataIndex;
        let value = context.dataset.data[index];
        return value > 0; // display labels with a value greater than 0
      }
    },
  },
  scales: {
    x: {
      stacked: true
    },
    y: {
      stacked: true,
      suggestedMax: 15,
      ticks: {
        beginAtZero: true,
        stepSize: 5,
      }
    }
  },
};


const app_data_trend = [{
    label: 'data 1',
    data: [3, 4, 5, 6, 4, 4, 4],
    backgroundColor: '#007bff',
    stack: 'Stack 0',
  },
  {
    label: 'data 2',
    data: [3, 4, 5, 4, 3, 2, 3],
    backgroundColor: '#6DB526',
    stack: 'Stack 0',
  },
  {
    label: 'data 1',
    data: [4, 4, 4, 3, 2, 5, 4],
    backgroundColor: '#007bff',
    stack: 'Stack 1',
  },
  {
    label: 'data 2',
    data: [4, 2, 5, 2, 3, 3, 4],
    backgroundColor: '#6DB526',
    stack: 'Stack 1',
  },
  {
    label: 'data 1',
    data: [2, 4, 5, 2, 4, 5, 3],
    backgroundColor: '#007bff',
    stack: 'Stack 2',
  },
  {
    label: 'data 2',
    data: [1, 1, 2, 4, 4, 3, 5],
    backgroundColor: '#6DB526',
    stack: 'Stack 2',
  },

]

const ctx8 = document.getElementById("tsa-applications-trending");
const chart8 = new Chart(ctx8, {
  plugins: [ChartDataLabels],
  type: 'bar',
  data: {
    labels: ['person1', 'person2', 'person3', 'person4'],
    datasets: app_data_trend
  },
  options: trend_options,
});
.chart-container{
position: relative; height:80vh; width:80vw
}
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.0/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
<div class="chart-container">
  <canvas id="tsa-applications-trending"></canvas>
</div>

Advertisement

Answer

options.plugins.legend.labels.generateLabels can still be used in Chart.js v3. For your case, this could look as follows:

generateLabels: chart => chart.data.labels.slice(0, 2).map((l, i) => ({
     datasetIndex: i,
     text: l,
     fillStyle: chart.data.datasets[i].backgroundColor,
     strokeStyle: chart.data.datasets[i].backgroundColor,
     hidden: chart.getDatasetMeta(i).hidden
  }))

const trend_options = {
  responsive: true,
  maintainAspectRatio: false,
  plugins: {
    datalabels: {
      formatter: function(value, ctx) {
        const otherDatasetIndex = ctx.datasetIndex % 2 === 0 ? ctx.datasetIndex + 1 : ctx.datasetIndex - 1;
        const total = ctx.chart.data.datasets[otherDatasetIndex].data[ctx.dataIndex] + value;
        return `${(value / total * 100).toFixed()}%`;
      },
      font: {
        weight: "bold"
      },
      color: "#fff",
      display: function(context) {
        let index = context.dataIndex;
        let value = context.dataset.data[index];
        return value > 0; // display labels with a value greater than 0
      }
    },
    legend: {
      labels: {
        generateLabels: chart => chart.data.labels.slice(0, 2).map((l, i) => ({
          datasetIndex: i,
          text: l,
          fillStyle: chart.data.datasets[i].backgroundColor,
          strokeStyle: chart.data.datasets[i].backgroundColor,
          hidden: chart.getDatasetMeta(i).hidden
        }))
      }
    },
  },
  scales: {
    x: {
      stacked: true
    },
    y: {
      stacked: true,
      suggestedMax: 15,
      ticks: {
        beginAtZero: true,
        stepSize: 5,
      }
    }
  },
};


const app_data_trend = [{
    label: 'data 1',
    data: [3, 4, 5, 6, 4, 4, 4],
    backgroundColor: '#007bff',
    stack: 'Stack 0',
  },
  {
    label: 'data 2',
    data: [3, 4, 5, 4, 3, 2, 3],
    backgroundColor: '#6DB526',
    stack: 'Stack 0',
  },
  {
    label: 'data 1',
    data: [4, 4, 4, 3, 2, 5, 4],
    backgroundColor: '#007bff',
    stack: 'Stack 1',
  },
  {
    label: 'data 2',
    data: [4, 2, 5, 2, 3, 3, 4],
    backgroundColor: '#6DB526',
    stack: 'Stack 1',
  },
  {
    label: 'data 1',
    data: [2, 4, 5, 2, 4, 5, 3],
    backgroundColor: '#007bff',
    stack: 'Stack 2',
  },
  {
    label: 'data 2',
    data: [1, 1, 2, 4, 4, 3, 5],
    backgroundColor: '#6DB526',
    stack: 'Stack 2',
  },

]

const ctx8 = document.getElementById("tsa-applications-trending");
const chart8 = new Chart(ctx8, {
  plugins: [ChartDataLabels],
  type: 'bar',
  data: {
    labels: ['person1', 'person2', 'person3', 'person4'],
    datasets: app_data_trend
  },
  options: trend_options,
});
.chart-container {
  position: relative;
  height: 80vh;
  width: 80vw
}
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
<div class="chart-container">
  <canvas id="tsa-applications-trending"></canvas>
</div>

The problem with the approach is, that the displayed legend elements are still tied to individual datasets only. If you want to show/hide other datasets as well, you also need to define a legend.onClick function, similar to the one from this answer.

Advertisement