Software Dev
Software Dev

Reputation: 1102

Chart.js hover over label

I have a bar chart in Chart.js (using the latest version), and I want to make some visual change when the mouse is hovering over a category label. How would I implement either or both of the following visual changes?

  1. Make the cursor be a pointer while hovering over a label.
  2. Make the label be in a different color while it is being hovered on.

A related question is here: How to detect click on chart js 3.7.1 axis label?. However, my question is about hovering over a label, without clicking on the label.

In the example below, I want something to happen when hovering on these texts: Item A, Item B, Item C.

window.onload = function() {
  var ctx = document.getElementById('myChart').getContext('2d');
  window.myBar = new Chart(ctx, {
    type: 'bar',
    data: {
      labels: ['Item A', 'Item B', 'Item C'],
      datasets: [{
        data: [1, 2, 3],
        backgroundColor: 'lightblue'
      }]
    },
    options: {
      responsive: true,
      indexAxis: 'y',
      plugins: {
        legend: {
          display: false
        },
        tooltip: {
          enabled: false
        },
      }
    }
  });
};
.chart-container {
  position: relative;
  height: 90vh;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>

<div class="chart-container">
  <canvas id="myChart"></canvas>
</div>

Upvotes: 5

Views: 4483

Answers (3)

rycha
rycha

Reputation: 804

To change the cursor to a pointer when hovering over a category label in a Chart.js bar chart, you can add:

options: {
  plugins: {
    tooltip: {
      mode: 'index',
      intersect: false
    },
  },
  interaction: {
    mode: 'index',
    intersect: false
  },
  onHover: function(evt, elements) {
    if (elements.length) {
      document.getElementById("myChart").style.cursor = "pointer";
    } else {
      document.getElementById("myChart").style.cursor = "default";
    }
  },
  // ...
}

To change the color of a label when it is being hovered on, you can add:

options: {
  plugins: {
    tooltip: {
      mode: 'index',
      intersect: false
    },
  },
  interaction: {
    mode: 'index',
    intersect: false
  },
  onHover: function(evt, elements) {
    if (elements.length) {
      var chart = evt.chart;
      var datasetIndex = elements[0].datasetIndex;
      var index = elements[0].index;
      chart.data.labels[index] = '<span style="color: red;">' + chart.data.labels[index] + '</span>';
      chart.update();
    } else {
      var chart = evt.chart;
      chart.data.labels = ['Item A', 'Item B', 'Item C'];
      chart.update();
    }
  },
  // ...
}

Upvotes: 0

Jordy
Jordy

Reputation: 1958

To make the cursor a pointer while hovering over a label, you can try to assign a CSS cursor value to event.native.target.style.cursor when hover is triggered.

event.native.target.style.cursor = 'pointer';

To make the label a different color while it is being hovered on, you can try this

myChart.config.options.scales.y.ticks.color = hoverColors; // ['black','red','black'], ['black','black','red'], ['red','black','black']

UPDATE

Thanks to LeeLenalee for giving an almost correct answer. I've edited the code above so it fits what is required in the problem. Don't forget to change source of the library in the HTML from :

https://cdn.jsdelivr.net/npm/[email protected]

to :

https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js

Updated code:

window.onload = function() {
  const findLabel = (labels, evt) => {
      let found = false;
      let res = null;
      try {
          labels.forEach(l => {
              l.labels.forEach((label, index) => {
                  if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
                      res = {
                          label: label.label,
                          index
                      };
                      found = true;
                  }
              });
          });
      } catch (e) {}

      return [found, res];
  };

  const getLabelHitboxes = (scales) => {
      try {
          return Object.values(scales).map((s) => ({
              scaleId: s.id,
              labels: s._labelItems.map((e, i) => ({
                  x: e.translation[0] - s._labelSizes.widths[i],
                  x2: e.translation[0] + s._labelSizes.widths[i] / 2,
                  y: e.translation[1] - s._labelSizes.heights[i] / 2,
                  y2: e.translation[1] + s._labelSizes.heights[i] / 2,
                  label: e.label,
                  index: i
              }))
          }));
      } catch (e) {}
  };


  const changeCursorAndLabelColor = (event, chart, index, hoverMode) => {
      // your hover color here
      // const hoverColor = '#ff0000';
      const hoverColor = 'red';
      const hoverColors = [];

      for (let i = 0; i < myChart.data.datasets[0].data.length; i++) {
          if (hoverMode) {
              // change cursor
              event.native.target.style.cursor = 'pointer';
              if (index === i) {
                  hoverColors.push(hoverColor);
              } else {
                  hoverColors.push(defaultLabelColor);
              }
          } else {
              // change cursor
              event.native.target.style.cursor = 'default';
              hoverColors.push(defaultLabelColor);
          }
      }
      // change label to your hover color
      myChart.config.options.scales.y.ticks.color = hoverColors;

      // update chart when hover is triggered
      myChart.update();
  }

  let foundMode = false;
  const plugin = {
      id: 'customHover',
      afterEvent: (chart, event, opts) => {
          const evt = event.event;
          if (evt.type !== 'mousemove') {
              return;
          }
          const [found, labelInfo] = findLabel(getLabelHitboxes(chart.scales), evt);
          if (found && myChart.data.labels.includes(labelInfo.label)) {
              changeCursorAndLabelColor(evt, chart, labelInfo.index, true);
              foundMode = true;
          } else {
              if (foundMode) changeCursorAndLabelColor(evt, chart, null, false);
              foundMode = false;
          }

      }
  }

  Chart.register(plugin);
  var ctx = document.getElementById('myChart');
  const myChart = new Chart(ctx, {
      type: 'bar',
      data: {
          labels: ['Item A', 'Item B', 'Item C'],
          datasets: [{
              label: 'My Data',
              data: [1, 2, 3],
              backgroundColor: 'lightblue'
          }]
      },
      options: {
          responsive: true,
          indexAxis: 'y',
          plugins: {
              legend: {
                  display: false
              },
              tooltip: {
                  enabled: false
              },
          },
          onHover: (event, chart) => {
              if (foundMode) changeCursorAndLabelColor(event, chart, null, false);
              foundMode = false;
          }
      }
  });
  const defaultLabelColor = myChart.config.options.scales.y.ticks.color;
};
.chart-container {
  position: relative;
  height: 90vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script>
<div class="chart-container">
  <canvas id="myChart"></canvas>
</div>

Upvotes: 1

LeeLenalee
LeeLenalee

Reputation: 31421

You can just use the custom plugin from that question and ignore everything but mousemove events instead of ignoring everything but click events:

const findLabel = (labels, evt) => {
  let found = false;
  let res = null;

  labels.forEach(l => {
    l.labels.forEach((label, index) => {
      if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
        res = {
          label: label.label,
          index
        };
        found = true;
      }
    });
  });

  return [found, res];
};

const getLabelHitboxes = (scales) => (Object.values(scales).map((s) => ({
  scaleId: s.id,
  labels: s._labelItems.map((e, i) => ({
    x: e.translation[0] - s._labelSizes.widths[i],
    x2: e.translation[0] + s._labelSizes.widths[i] / 2,
    y: e.translation[1] - s._labelSizes.heights[i] / 2,
    y2: e.translation[1] + s._labelSizes.heights[i] / 2,
    label: e.label,
    index: i
  }))
})));

const plugin = {
  id: 'customHover',
  afterEvent: (chart, event, opts) => {
    const evt = event.event;

    if (evt.type !== 'mousemove') {
      return;
    }

    const [found, labelInfo] = findLabel(getLabelHitboxes(chart.scales), evt);

    if (found) {
      console.log(labelInfo);
    }

  }
}

Chart.register(plugin);

const options = {
  type: 'line',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        borderColor: 'pink'
      },
      {
        label: '# of Points',
        data: [7, 11, 5, 8, 3, 7],
        borderColor: 'orange'
      }
    ]
  },
  options: {}
}

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/3.7.1/chart.js"></script>
</body>

Upvotes: 0

Related Questions