I have a few temperature sensors that upload per-minute readings to a mySQL database, and a simple web interface that uses Chart.js to display ranges of data.
Because I ran into performance issues displaying long time ranges (over a month or so), I have enabled data decimation using the min-max algorithm, as explained here, as well as adding a custom plugin to change the background color of the chart. Here's the relevant code, which has worked fine for a static chart:
const bgPlugin = {
id: 'customBgColor',
beforeDraw: (chart, args, options) => {
const {ctx} = chart;
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = options.color || "#f5f3ef";
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
};
var config = {
type: 'line',
data: data,
options: {
parsing: false,
animation: false,
plugins: {
decimation: {
enabled: true,
algorithm: 'min-max',
},
customBgColor: {
color: "#f5f3ef",
}
},
scales: {
x: {
type: 'time',
time : {
unit: 'day',
},
adapters: {
date: {
zone: "America/Los_Angeles",
},
},
},
},
},
plugins: [bgPlugin],
};
I recently decided to add a functionality that checks for updates to the mySQL database and adds those new readings to the chart that is being displayed, as described here. Here's the relevant code (and my apologies for poor readability):
function getFreshTemps() {
var tempSensors = '';
var starting = '?unit='+theUnit;
var readyToUpdate = false;
if(!Object.is(latestOne, null)) {
starting += '&sk=' + latestOne;
}
const theURL = 'REDACTED' + starting;
fetch(theURL)
.then((response) => {
if(!response.ok) {
throw new Error('HTTP error, status = '+response.status);
}
return response.json();
})
.then((isParsed) => {
if(isParsed.lastKey > latestOne) {
latestOne = isParsed.lastKey;
}
if(isParsed.rows !== undefined) {
isParsed.rows.forEach((theRow) => {
if(sensorIndices[theRow['sensor']] !== undefined) {
thaChart.data.datasets[sensorIndices[theRow['sensor']]].data.push({x: theRow['chartTime'], y: theRow['temp']});
readyToUpdate = true;
}
});
if(readyToUpdate) {
thaChart.update();
readyToUpdate = false;
}
}
});
}
I have ensured that theRow.chartTime is in the expected format (milliseconds since the epoch) to allow for data decimation to work. I should add that latestOne is a global variable set by the PHP script that generates the intial chart, and it represents the last-entered ID in that mySQL table, while the array sensorIndices is generated by the PHP script to indicate the dataset index for each sensor name, since different time series will have different sensors in them.
For charts that are not strictly historical (i.e., any chart ending with the current datetime), the following line is added by the PHP script that generates the page (the final code will use a longer interval, but for testing 15 seconds works well):
window.onload = setInterval(getFreshTemps, 15000);
As stated above, for short time series (a few hours up to nearly one day), the chart updates as expected. However, it fails to update for longer time series, where decimation has been applied (e.g., my default display, which is the previous 3 days).
I could not find any information in the documentation about forcing decimation to be run again after a chart updates. Is this possible? I assume that is the problem here since the update works for small ranges. I am using Chart.js 3.9.1, in case there's a fix that would only work in other versions.
EDIT:
After a great deal more tinkering with this problem, I have also discovered that the number of data points required to trigger this issue depends on the screen width. If you manually reduce the width of the window before getFreshTemps() runs, and then maximize the window again before it runs a second time, any data points added by the first update will fail to be added to the chart, but any data points added by the next update will be added, with a gap in the line where the intervening data should have been added. I assume that this is also related to data decimation (where the threshold for triggering decimation depends on the width of the <canvas> container).