Search code examples
chart.jsoverlap

ChartJS - handling of overlapping points in line chart


In one of my line charts I am running into instances where certain points overlap (i.e have exact same x,y coordinate). This is expected behavior, but I am interested in finding a way to potentially offset/jitter any points with overlaps so that none are hidden. Has anyone ran into a similar scenario of overlapping points on a line chart? Note: I cannot use scatter type because my x-axis is category.

Here is a fiddle demonstrating the overlaps (with a minified sample of my dataset): https://jsfiddle.net/quanda412/407dcoL9/19/

After digging through the docs, my first instinct was to tap into one of the ChartJS Plugin Hooks and in that function iterate over my datasets to search for any overlapping instances and then alter the x or y positioning. I've seen this being called a "Jitter" in other places, and it seems some charting libraries support jitter functionality out of the box.

Anyways my datasets are quite large and when I iterate over it, in for example the beforeDatasetUpdate hook, the chart not only takes a massive performance hit but the points do not adjust as expected.

Code sample of my attempt at Jittering in beforeDatasetDraw hook:

beforeDatasetDraw: chart => {
    const { datasets } = chart.config.data;
    const coordinateMap = []; // holds array of unique coord objects
    datasets.forEach(d => {
        let elements = d._meta[5].data;
        elements.forEach((el, i) => {
            let { x, y } = el._model;
            const overlap = coordinateMap.find(coord => coord.x === x && coord.y === y);
            if (overlap) { // Overlap detected!
                // Update coordinate map
                x += 1;
                y += 1;
                // Jitter the x,y positioning - not working!
                d._meta[5].data[i]._model.x = x;
                d._meta[5].data[i]._model.y = y;
            }
            coordinateMap.push({ x, y });
       });
   });
}

Here you can see overlap, a grey point within grey point: overlap


Solution

  • I was able to implement an effective x-axis jitter with this custom beforeDatasetDraw plugin:

     beforeDatasetDraw(chart, args) {
      if (chart.animating || chart.$deferred.loaded) {
        const { index: dataIndex, meta } = args;
        const points = meta.data.map(el => ({ x: el._model.x, y: el._model.y }));
        const { length: dsLength } = chart.data.datasets;
        const adjustedMap = []; // keeps track of adjustments to prevent double offsets
    
        for (let datasetIndex = 0; datasetIndex < dsLength; datasetIndex += 1) {
          if (dataIndex !== datasetIndex) {
            const datasetMeta = chart.getDatasetMeta(datasetIndex);
            datasetMeta.data.forEach(el => {
              const overlap = points.find(point => point.x === el._model.x && point.y === el._model.y);
              if (overlap) {
                const adjusted = adjustedMap.find(item => item.datasetIndex === datasetIndex && item.dataIndex === dataIndex);
                if (!adjusted && datasetIndex % 2) { el._model.x += 7; } else { el._model.x -= 7; }
                  adjustedMap.push({ datasetIndex, dataIndex });
              }
            });
           }
         }
       }
     }
    

    This produces a jitter effect as seen here: enter image description here