Multi-colored legend stacked bar chart Chartjs

Viewed 157

I've been looking for a way to draw this graph using Chartjs but haven't been successful. Would this be possible in Chartjs?

Chart I want to draw

Chart the I attempted

Here's my setup:

const stacked_bar_data = {
    labels: ['Axi-cel', 'Tisa-cel'],
    datasets: [
    {
        label: 'Bridging therapy 2',
        data: [25, 25],
        backgroundColor: ['rgb(224, 178, 40)', 'rgb(78, 235, 255)']
    },
    {
        label: 'Bridging therapy 1',
        data: [25, 25],
        backgroundColor: ['rgb(204, 158, 20)', 'rgb(58, 215, 240)']
    }
    ]
}

const stacked_bar_config = {
    type: 'bar',
    data: stacked_bar_data,
    options: {
        responsive: true,
        scales: {
            x: {
                stacked: true,
                grid: {
                    display: false
                }
            },

            y: {
                stacked: true,
                grid: {
                    display: false
                },
                title: {
                    display: true,
                    text: 'Use, %'
                }
            }
        }
    }
}

var stacked_bar = new Chart(document.getElementById('stacked-bar').getContext('2d'), stacked_bar_config)

I know how to set up for the stacked bar chart, just don't know how to set up for the legend to be like in the image. Any help is appreciated. Thanks in advance!

1 Answers

It is possible with a custom html legend, implementation is not perfect but I think its enough for you to finetune it so it will work execlty as you want

Example:

const getOrCreateLegendList = (chart, id) => {
  const legendContainer = document.getElementById(id);
  let listContainer = legendContainer.querySelector('ul');

  if (!listContainer) {
    listContainer = document.createElement('ul');
    listContainer.style.display = 'flex';
    listContainer.style.flexDirection = 'row';
    listContainer.style.margin = 0;
    listContainer.style.padding = 0;

    legendContainer.appendChild(listContainer);
  }

  return listContainer;
};

const htmlLegendPlugin = {
  id: 'htmlLegend',
  afterUpdate(chart, args, options) {
    const ul = getOrCreateLegendList(chart, options.containerID);

    // Remove old legend items
    while (ul.firstChild) {
      ul.firstChild.remove();
    }

    // Reuse the built-in legendItems generator
    const items = chart.options.plugins.legend.labels.generateLabels(chart);

    items.forEach((item, i) => {
      const li = document.createElement('li');
      li.style.alignItems = 'center';
      li.style.cursor = 'pointer';
      li.style.display = 'flex';
      li.style.flexDirection = 'row';
      li.style.marginLeft = '10px';

      li.onclick = () => {
        const {
          type
        } = chart.config;
        if (type === 'pie' || type === 'doughnut') {
          // Pie and doughnut charts only have a single dataset and visibility is per item
          chart.toggleDataVisibility(item.index);
        } else {
          chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
        }
        chart.update();
      };

      const datasets = chart.data.datasets;

      // Color box
      const boxSpan = document.createElement('span');
      boxSpan.style.background = datasets[item.datasetIndex].backgroundColor[i];
      boxSpan.style.borderColor = item.strokeStyle;
      boxSpan.style.borderWidth = item.lineWidth + 'px';
      boxSpan.style.display = 'inline-block';
      boxSpan.style.height = '20px';
      boxSpan.style.marginRight = '10px';
      boxSpan.style.width = '20px';

      const boxSpan2 = document.createElement('span');
      boxSpan2.style.background = datasets[item.datasetIndex].backgroundColor[i + 1] || datasets[item.datasetIndex].backgroundColor[i - 1];
      boxSpan2.style.borderColor = item.strokeStyle;
      boxSpan2.style.borderWidth = item.lineWidth + 'px';
      boxSpan2.style.borderColor = item.strokeStyle;
      boxSpan2.style.borderWidth = item.lineWidth + 'px';
      boxSpan2.style.display = 'inline-block';
      boxSpan2.style.height = '20px';
      boxSpan2.style.marginRight = '10px';
      boxSpan2.style.width = '20px';

      // Text
      const textContainer = document.createElement('p');
      textContainer.style.color = item.fontColor;
      textContainer.style.margin = 0;
      textContainer.style.padding = 0;
      textContainer.style.textDecoration = item.hidden ? 'line-through' : '';

      const text = document.createTextNode(item.text);
      textContainer.appendChild(text);

      li.appendChild(boxSpan);
      li.appendChild(boxSpan2);
      li.appendChild(textContainer);
      ul.appendChild(li);
    });
  }
};

var options = {
  type: 'bar',
  data: {
    labels: ["Red", "Blue"],
    datasets: [{
        label: '# of Votes',
        data: [12, 19],
        borderWidth: 1,
        backgroundColor: ['rgb(224, 178, 40)', 'rgb(78, 235, 255)']
      },
      {
        label: '# of Points',
        data: [7, 11],
        borderWidth: 1,
        backgroundColor: ['rgb(204, 158, 20)', 'rgb(58, 215, 240)']
      }
    ]
  },
  options: {
    responsive: true,
    scales: {
      x: {
        stacked: true,
        grid: {
          display: false
        }
      },

      y: {
        stacked: true,
        grid: {
          display: false
        },
        title: {
          display: true,
          text: 'Use, %'
        }
      }
    },
    plugins: {
      htmlLegend: {
        // ID of the container to put the legend in
        containerID: 'chartjs-legend',
      },
      legend: {
        display: false,
      }
    }
  },
  plugins: [htmlLegendPlugin],
}

var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <div id="chartjs-legend">

  </div>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.3.2/chart.js"></script>
</body>

Related