Search code examples
reactjschart.jsreact-chartjs

ChartJS plot not showing as it should be when toggling a grouped legend element


I have a ChartJS that plots multiple line chart together with confidence interval areas. The problem is that the first grouped line chart with CI is buggy so that when I toggle it, other groups have their areas hidden as well. This is pretty difficult to explain in words so I recorded it and put it up here on youtube.

Code I have so far is as such:

let predictData = [21,22,23,24,25,26,27,28];
let lowerCI80 = [20,21,22,23,24,25,26,27];
let upperCI80 = [22,23,24,25,26,27,28,29];
let lowerCI90 = [18,19,20,21,22,23,24,25];
let upperCI90 = [23,24,25,26,27,28,29,30];

const data = {
    labels: [1,2,3,4,5,6,7,8],
    datasets: [
      {
        label: "Lower_80%CI",
        type: "line",
        backgroundColor: "rgb(75, 192, 255, 0.3)",
        borderColor: "transparent",
        pointRadius: 0,
        fill: 0,
        tension: 0,
        data: low80Data,
        yAxisID: "y",
        xAxisID: "x",
      },
      {
        label: "80% CI",
        type: "line",
        backgroundColor: "rgb(75, 192, 255)",
        borderColor: "rgb(75, 192, 255)",
        hoverBorderColor: "rgb(75, 192, 255)",
        pointRadius: 0,
        fill: false,
        tension: 0,
        data: predictData,
        yAxisID: "y",
        xAxisID: "x",
      },
      {
        label: "Upper_80%CI",
        type: "line",
        backgroundColor: "rgb(75, 192, 255, 0.3)",
        borderColor: "transparent",
        pointRadius: 0,
        fill: 0,
        tension: 0,
        data: up80Data,
        yAxisID: "y",
        xAxisID: "x",
      },
      {
        label: "Lower_90%CI",
        type: "line",
        backgroundColor: "rgb(255, 75, 75, 0.3)",
        borderColor: "transparent",
        pointRadius: 0,
        fill: 0,
        tension: 0,
        data: low90Data,
        yAxisID: "y",
        xAxisID: "x",
      },
      {
        label: "90% CI",
        type: "line",
        backgroundColor: "rgb(255, 75, 75)",
        borderColor: "rgb(255, 75, 75)",
        hoverBorderColor: "rgb(255, 75, 75)",
        pointRadius: 0,
        fill: false,
        tension: 0,
        data: predictData,
        yAxisID: "y",
        xAxisID: "x",
      },
      {
        label: "Upper_90%CI",
        type: "line",
        backgroundColor: "rgb(255, 75, 75, 0.3)",
        borderColor: "transparent",
        pointRadius: 0,
        fill: 0,
        tension: 0,
        data: up90Data,
        yAxisID: "y",
        xAxisID: "x",
      },
    ],
  };
const options = {
    plugins: {
      title: {
        display: false,
      },
      legend: {
        display: true,
        position: "top",
        labels: {
          filter: function (item, chart) {
            return !item.text.includes("_");
          },
        },
        onClick: function (e, legendItem) {
          // need to hide index -1 and index +1
          var index = legendItem.datasetIndex;
          var ci = this.chart;
          var alreadyHidden =
            ci.getDatasetMeta(index).hidden === null
              ? false
              : ci.getDatasetMeta(index).hidden;
          var meta_lo = ci.getDatasetMeta(index - 1);
          var meta = ci.getDatasetMeta(index);
          var meta_hi = ci.getDatasetMeta(index + 1);
          if (!alreadyHidden) {
            meta_lo.hidden = true;
            meta.hidden = true;
            meta_hi.hidden = true;
          } else {
            meta_lo.hidden = null;
            meta.hidden = null;
            meta_hi.hidden = null;
          }

          ci.update();
        },
      },
      tooltip: {
        mode: "index",
        intersect: false,
      },
    },
    hover: {
      mode: "nearest",
      intersect: false,
    },
    scales: {
      xAxes: [
        {
          ticks: {
            stepSize: 7, // This is not working as well(?)
            fontColor: "white",
          },
        },
      ],
    },
  };
return <Line data={data} options={options} />

Solution

  • Reason it didnt work because you tried to fill to a specific dataset by specifying that dataset index. This didnt work because that dataset was hidden so no fill anymore. So you either need to specify the correct datasets or use relative fills for dataset. More info can be found here.

    Reason your stepsize didnt work is because your scale config was in V2 and in V3 the way scales are condfigured have changed. Also by default the x axis is a category axis which doesnt have the stepSize property.

    For all changes between V2 and V3 you can read the migration guide

    let predictData = [21, 22, 23, 24, 25, 26, 27, 28];
    let low80Data = [20, 21, 22, 23, 24, 25, 26, 27];
    let up80Data = [22, 23, 24, 25, 26, 27, 28, 29];
    let low90Data = [18, 19, 20, 21, 22, 23, 24, 25];
    let up90Data = [23, 24, 25, 26, 27, 28, 29, 30];
    
    const data = {
      labels: [1, 2, 3, 4, 5, 6, 7, 8],
      datasets: [{
          label: "Lower_80%CI",
          backgroundColor: "rgb(75, 192, 255, 0.3)",
          borderColor: "transparent",
          pointRadius: 0,
          data: low80Data,
        },
        {
          label: "80% CI",
          backgroundColor: "rgb(75, 192, 255)",
          borderColor: "rgb(75, 192, 255)",
          hoverBorderColor: "rgb(75, 192, 255)",
          pointRadius: 0,
          data: predictData,
        },
        {
          label: "Upper_80%CI",
          backgroundColor: "rgb(75, 192, 255, 0.3)",
          borderColor: "transparent",
          pointRadius: 0,
          fill: '-2',
          data: up80Data,
        },
        {
          label: "Lower_90%CI",
          backgroundColor: "rgb(255, 75, 75, 0.3)",
          borderColor: "transparent",
          pointRadius: 0,
          fill: '+2',
          data: low90Data,
        },
        {
          label: "90% CI",
          backgroundColor: "rgb(255, 75, 75)",
          borderColor: "rgb(255, 75, 75)",
          hoverBorderColor: "rgb(255, 75, 75)",
          pointRadius: 0,
          data: predictData,
        },
        {
          label: "Upper_90%CI",
          backgroundColor: "rgb(255, 75, 75, 0.3)",
          borderColor: "transparent",
          pointRadius: 0,
          data: up90Data,
        },
      ],
    };
    
    const options = {
      plugins: {
        title: {
          display: false,
        },
        legend: {
          display: true,
          position: "top",
          labels: {
            filter: function(item, chart) {
              return !item.text.includes("_");
            },
          },
          onClick: function(e, legendItem) {
            // need to hide index -1 and index +1
            var index = legendItem.datasetIndex;
            var ci = this.chart;
            var alreadyHidden =
              ci.getDatasetMeta(index).hidden === null ?
              false :
              ci.getDatasetMeta(index).hidden;
            var meta_lo = ci.getDatasetMeta(index - 1);
            var meta = ci.getDatasetMeta(index);
            var meta_hi = ci.getDatasetMeta(index + 1);
            if (!alreadyHidden) {
              meta_lo.hidden = true;
              meta.hidden = true;
              meta_hi.hidden = true;
            } else {
              meta_lo.hidden = null;
              meta.hidden = null;
              meta_hi.hidden = null;
            }
    
            ci.update();
          },
        },
        tooltip: {
          mode: "index",
          intersect: false,
        },
      },
      hover: {
        mode: "nearest",
        intersect: false,
      },
      scales: {
        x: {
          type: 'linear',
          ticks: {
            stepSize: 7, // This is not working as well(?)
            color: "white",
          },
        },
    
      },
    };
    
    new Chart('chartJSContainer', {
      type: 'line',
      data: data,
      options: options
    })
    <body>
      <canvas id="chartJSContainer" width="600" height="400"></canvas>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.2/chart.js"></script>
    </body>