javascriptchartschart.js

Change color of other slices on hover with Chart.js 4


I'm trying to change the color of other slices when hovering one of them, but is a little buggy sometimes and I'm trying to understand why.

As shown in the example, there is a flickering when hovering from one slice to another and the colors don't change right away, and also when leaving the slice the colors should reset, but it doesn't work sometimes.

const colors = ["red", "green", "blue"];
const colorsRGBA = ["rgba(255,0,0,.25)", "rgba(0,255,0,.25)", "rgba(0,0,255,.25)"];

new Chart(document.getElementById("doughnut"), {
  type: "doughnut",
  data: {
    labels: ["A", "B", "C"],
    datasets: [
      {
        data: [1, 2, 3],
        backgroundColor: ["red", "green", "blue"],
        hoverBackgroundColor: ["red", "green", "blue"]
      }
    ]
  },
  options: {
    plugins: {
      tooltip: {
        enabled: false
      },
      legend: {
        display: false
      }
    },
    onHover(event, elements, chart) {
      if (event.type === "mousemove") {
        if (elements.length) {
          chart.getDatasetMeta(0).data.forEach((data, index) => {
            if (index !== elements[0].index) {
              data.options.backgroundColor = colorsRGBA[index];
            }
          });
        } else {
          chart.getDatasetMeta(0).data.forEach((data, index) => {
            data.options.backgroundColor = colors[index];
          });
        }
      }
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<div style="height: 350px">
  <canvas id="doughnut"></canvas>
</div>


Solution

  • The problem is that your explicit setting of colors is "competing against" the default chart.js process by which the colors are set when the cursor enters and leaves elements.

    The only issue I can see, is that when the cursor is moved in one short fast shift from one element to another, the element that was left will be restored its color from the dataset (non-transparent), but that is done in an animation that will happen after your code has set the semitransparent color, so you end up with two elements with full color.

    The solution is to disable the colors animations; that can be done in the chart configuration at options:

       // .....
       options: {
          animations: {colors: false},
          plugins: {
             // .....
        
    

    You may want to also set at the canvas level a mouseOut event handler for the case the cursor is moved fast outside the canvas or the browser tab is changed through the keyboard while the cursor is on an element.

    Code snippet with these changes:

    const colors = ["red", "green", "blue"];
    const colorsRGBA = ["rgba(255,0,0,.25)", "rgba(0,255,0,.25)", "rgba(0,0,255,.25)"];
    
    const chart = new Chart(document.getElementById("doughnut"), {
       type: "doughnut",
       data: {
          labels: ["A", "B", "C"],
          datasets: [
             {
                data: [1, 2, 3],
                backgroundColor: ["red", "green", "blue"],
                hoverBackgroundColor: ["red", "green", "blue"]
             }
          ]
       },
       options: {
          plugins: {
             tooltip: {
                enabled: false
             },
             legend: {
                display: false
             }
          },
          animations: {colors: false},
          onHover(event, elements, chart) {
             if (event.type === "mousemove") {
                if (elements.length) {
                   chart.getDatasetMeta(0).data.forEach((data, index) => {
                      if (index !== elements[0].index) {
                         data.options.backgroundColor = colorsRGBA[index];
                      }
                   });
                } else {
                   chart.getDatasetMeta(0).data.forEach((data, index) => {
                      data.options.backgroundColor = colors[index];
                   });
                }
             }
          }
       }
    });
    document.getElementById("doughnut").addEventListener('mouseout', function(){
       const chart = Chart.getChart("doughnut");
       chart.getDatasetMeta(0).data.forEach((data, index) => {
          data.options.backgroundColor = colors[index];
       });
       chart.render();
    });
    <div style="height: 350px">
       <canvas id="doughnut"></canvas>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

    This fiddle contains mostly the same code except for the animations, where the opposite way is taken, setting the duration to 5 seconds so one can see the effect that I described.

    Please let me know if there are still undesired effects that I couldn't detect.