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 });
});
});
}
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 });
}
});
}
}
}
}