I am using line chart (chart.js) to display two pens of data over time. In the sample code below the chart is created and 30 seconds of historical data is immediately added to each pen. Thereafter a single point of realtime data (current time) is added to each pen via the OnTimer() function which is executed every second.
If one uses only one pen (const _PENS = 1;
) the realtime data is correctly added to the historical data (right hand side of chart) as one would expect. Try it.
If one uses two pens (const _PENS = 2;
) the realtime data is NOT added to the historical data but added to the left hand side of the chart. Try it.
How does one fix the chart configuration in the code below so that realtime data for multiple pens is always appended to the historical data?
<html>
<body>
<canvas hidden="0" id="myLineChart"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/
Answer
There's a simple error in the original code: in the data
initialization sequence the same label
is added multiple times, that is
once for each "pen". So, when there are two pens, there are 2 * _HISTORY
labels, while the datasets have only _HISTORY
values;
when the new data values are added on timer, they are paired with the
duplicated (supplemental) labels already set, and the new labels are ignored.
That can be corrected easily, either by guarding the labels.push
with
an if(i===0)
that will make the push to the labels execute just for
one dataset, and not for each one:
for (i = 0; i < _PENS; i++)
{
// --- get the time _HISTORY secs ago
dt = new Date(new Date().getTime() - (_HISTORY*1000));
for (j = 0; j < _HISTORY; j++)
{
// add one second
dt = new Date(dt.getTime() + 1000);
// add the new data point to the chart
if(i === 0)
{
MyLineChart.data.labels.push(dt);
}
MyLineChart.data.datasets[i].data.push(dt.getSeconds()+i);
}
MyLineChart.update();
}
jsFiddle demo.
or by reversing the two for
s in the initialization code:
var MyLineChart = new Chart(document.getElementById('myLineChart'), MyChartConfig);
dt = new Date(new Date().getTime() - (_HISTORY*1000));
for (j = 0; j <= _HISTORY; j++)
{
// add one second
dt = new Date(dt.getTime() + 1000);
MyLineChart.data.labels.push(dt);
for (i = 0; i < _PENS; i++)
{
MyLineChart.data.datasets[i].data.push(dt.getSeconds()+i);
}
MyLineChart.update();
}
jsFiddle demo.
There's also a chart.js specific approach that simplifies things somehow: to use the object data structure, and eliminate the labels; that will allow one to add the same "label" (x coordinate) for each dataset, as it was in the original code:
for (i = 0; i < _PENS; i++)
{
// --- get the time _HISTORY secs ago
dt = new Date(new Date().getTime() - (_HISTORY*1000));
for (j = 0; j < _HISTORY; j++)
{
// add one second
dt = new Date(dt.getTime() + 1000);
// add the new data point to the chart
MyLineChart.data.datasets[i].data.push({x: dt, y: dt.getSeconds()+i});
}
MyLineChart.update();
}
The code has to be adapted for the OnTimer
procedure, snippet demo:
var MyDataset = {
datasets: [
{
label: 'Red pen (seconds)',
borderColor: 'red',
backgroundColor: 'red',
},
{
label: 'Orange pen (seconds+1)',
borderColor: 'orange',
backgroundColor: 'orange',
},
],
};
// --- define my chart configuration
var MyChartConfig = {
type: "line",
data: MyDataset,
options: {
plugins: {
title: {
display: true,
text: 'My Line Chart'
}
},
scales: {
x: {
type: 'time',
time: {
unit: 'second',
displayFormats: {
second: 'YYYY-MM-DD, HH:mm:ss', // Format for the unit
}
},
},
y: {
position: 'left',
type: 'linear',
display: true,
title: {
display: true, // Set to true to display the title
text: 'Left axis', // The text content of the y-axis title
},
},
},
},
};
// --- set the number of pens to use
// using only 1 pen produces the desired and correct effect - realtime data is appended to the historical data on the right hand side of the chart
// using 2 pens gives a whacky result - realtime data is added to the left hand side of the chart !!!
const _PENS = 2; // pens
const _HISTORY = 10; // seconds
// --- add an OnTimer function to execute every second
// this function will add realtime data to the pens
function OnTimer()
{
// --- add a new value to each pen at the current timestamp
dt = new Date();
//MyLineChart.data.labels.push(dt);
for (i = 0; i < _PENS; i++)
{
MyLineChart.data.datasets[i].data.push({x: dt, y: dt.getSeconds()+i});
}
MyLineChart.update();
}
// --- create the chart
var MyLineChart = new Chart(document.getElementById('myLineChart'), MyChartConfig);
for (i = 0; i < _PENS; i++)
{
// --- get the time _HISTORY secs ago
dt = new Date(new Date().getTime() - (_HISTORY*1000));
for (j = 0; j < _HISTORY; j++)
{
// add one second
dt = new Date(dt.getTime() + 1000);
// add the new data point to the chart
MyLineChart.data.datasets[i].data.push({x: dt, y: dt.getSeconds()+i});
}
MyLineChart.update();
}
// --- execute the OnTimer function every second
const interval = setInterval(OnTimer, 1000);
<div style="min-height: 250px">
<canvas id="myLineChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/