Lightningcharts JS, different charts for same patient with live feed from websocket

I am currently using Medical Dashboard of Lightningcharts js. When the same patient is opened in two different machines (say two desktops) then the charts for the channels/parameters (ECG, Pulse rate and Respiratory rate) are different for different machines. Currently the data fed to the charts is live from a WebSocket and is coming from a device present at the client location. The expectation is to have consistent and same graphs on multiple devices for a particular patient.

After performing rigorous end to end testing, it was found that the data sent from backend and received on the frontend is exactly same and is received on the same timestamp in different devices. We also checked that the array used as input for the channels is being fed with the correct data. This was observed in multiple machines/devices. The question that we are currently pondering on is why different graphs are getting drawn if the data fed to the lightning charts component are the same in multiple devices?

I think it might have to do something with the addData() function used as callback with requestAnimatedFrame() method (depends on the system performance to handles all those executions).

I am also wondering, does generating charts depend on the performance of the system as well? We verified this by checking FPS on multiple devices and found out that even with multiple devices having same FPS (around 60) shows different graphs.

We also created the single chart from scratch and used different (scheduler) logic to push data into the array (apart from logic used in Medical Dashboard example) and in this case as well the graphs generated are different in multiple devices.

Sharing pieces of code used for this component.

Main object that holds all channels with the dataset:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>const channels: iChannel[] = [
{
shortName: "ECG/EKG",
name: "Electrocardiogram",
type: "ecg",
dataSet: [],
yStart: -50,
yEnd: 160,
}, other channels...
];
</code>
<code>const channels: iChannel[] = [ { shortName: "ECG/EKG", name: "Electrocardiogram", type: "ecg", dataSet: [], yStart: -50, yEnd: 160, }, other channels... ]; </code>
const channels: iChannel[] = [ 

{ 
    shortName: "ECG/EKG", 
    name: "Electrocardiogram", 
    type: "ecg", 
    dataSet: [], 
    yStart: -50, 
    yEnd: 160, 
}, other channels... 
]; 

Add data function used to push data into array:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> const addData = () => {
const tNow = window.performance.now();
const seriesNewPoints = seriesList.map((_) => []);
if (tNow - lastDataReceivedTime.current > 20000) {
// If no data received, reset everything.
channels.forEach((channel) => {
channel.dataSet.length = 0;
});
uiList.forEach((ui) => {
ui.labelEcgHeartRate && ui.labelEcgHeartRate.setText("");
ui.labelBpmValue && ui.labelBpmValue.setText("");
ui.labelBpmValue && ui.labelBloodPIValue.setText("");
ui.labelMinMaxBPValue && ui.labelMinMaxBPValue.setText("");
ui.labelMeanBPValue && ui.labelMeanBPValue.setText("");
ui.labelRespiratoryValue && ui.labelRespiratoryValue.setText("");
});
}
while (tNow > tSamplePos) {
const x = tSamplePos;
for (let i = 0; i < seriesList.length - 1; i++) {
const channel = channels[i];
const dataSet = channel.dataSet;
const sample = dataSet[iSampleX % dataSet.length];
// @ts-ignore
seriesNewPoints[i].push({ x, y: sample });
}
tSamplePos += 1000 / SAMPLE_RATE;
iSampleX += 1;
}
seriesList.forEach((series, i) => series.add(seriesNewPoints[i]));
channelIncomingDataPointsCount += seriesNewPoints[0].length;
requestAnimationFrame(addData);
};
requestAnimationFrame(addData);
let channelIncomingDataPointsCount = 0;
let channelIncomingDataPointsLastUpdate = window.performance.now();
setInterval(() => {
const tNow = window.performance.now();
const chDataPointsPerSecond = Math.round(
(channelIncomingDataPointsCount * 1000) /
(tNow - channelIncomingDataPointsLastUpdate)
);
uiList.forEach((ui, i) => {
ui.labelSampleRate.setText(`${chDataPointsPerSecond} samples / second`);
});
channelIncomingDataPointsCount = 0;
channelIncomingDataPointsLastUpdate = tNow;
}, 2000);
</code>
<code> const addData = () => { const tNow = window.performance.now(); const seriesNewPoints = seriesList.map((_) => []); if (tNow - lastDataReceivedTime.current > 20000) { // If no data received, reset everything. channels.forEach((channel) => { channel.dataSet.length = 0; }); uiList.forEach((ui) => { ui.labelEcgHeartRate && ui.labelEcgHeartRate.setText(""); ui.labelBpmValue && ui.labelBpmValue.setText(""); ui.labelBpmValue && ui.labelBloodPIValue.setText(""); ui.labelMinMaxBPValue && ui.labelMinMaxBPValue.setText(""); ui.labelMeanBPValue && ui.labelMeanBPValue.setText(""); ui.labelRespiratoryValue && ui.labelRespiratoryValue.setText(""); }); } while (tNow > tSamplePos) { const x = tSamplePos; for (let i = 0; i < seriesList.length - 1; i++) { const channel = channels[i]; const dataSet = channel.dataSet; const sample = dataSet[iSampleX % dataSet.length]; // @ts-ignore seriesNewPoints[i].push({ x, y: sample }); } tSamplePos += 1000 / SAMPLE_RATE; iSampleX += 1; } seriesList.forEach((series, i) => series.add(seriesNewPoints[i])); channelIncomingDataPointsCount += seriesNewPoints[0].length; requestAnimationFrame(addData); }; requestAnimationFrame(addData); let channelIncomingDataPointsCount = 0; let channelIncomingDataPointsLastUpdate = window.performance.now(); setInterval(() => { const tNow = window.performance.now(); const chDataPointsPerSecond = Math.round( (channelIncomingDataPointsCount * 1000) / (tNow - channelIncomingDataPointsLastUpdate) ); uiList.forEach((ui, i) => { ui.labelSampleRate.setText(`${chDataPointsPerSecond} samples / second`); }); channelIncomingDataPointsCount = 0; channelIncomingDataPointsLastUpdate = tNow; }, 2000); </code>
    const addData = () => {
      const tNow = window.performance.now();
      const seriesNewPoints = seriesList.map((_) => []);
      if (tNow - lastDataReceivedTime.current > 20000) {
        // If no data received, reset everything.
        channels.forEach((channel) => {
          channel.dataSet.length = 0;
        });
        uiList.forEach((ui) => {
          ui.labelEcgHeartRate && ui.labelEcgHeartRate.setText("");
          ui.labelBpmValue && ui.labelBpmValue.setText("");
          ui.labelBpmValue && ui.labelBloodPIValue.setText("");
          ui.labelMinMaxBPValue && ui.labelMinMaxBPValue.setText("");
          ui.labelMeanBPValue && ui.labelMeanBPValue.setText("");
          ui.labelRespiratoryValue && ui.labelRespiratoryValue.setText("");
        });
      }
      while (tNow > tSamplePos) {
        const x = tSamplePos;
        for (let i = 0; i < seriesList.length - 1; i++) {
          const channel = channels[i];
          const dataSet = channel.dataSet;
          const sample = dataSet[iSampleX % dataSet.length];
          // @ts-ignore
          seriesNewPoints[i].push({ x, y: sample });
        }
        tSamplePos += 1000 / SAMPLE_RATE;
        iSampleX += 1;
      }
      seriesList.forEach((series, i) => series.add(seriesNewPoints[i]));
      channelIncomingDataPointsCount += seriesNewPoints[0].length;
      requestAnimationFrame(addData);
    };
    requestAnimationFrame(addData);

    let channelIncomingDataPointsCount = 0;
    let channelIncomingDataPointsLastUpdate = window.performance.now();
    setInterval(() => {
      const tNow = window.performance.now();
      const chDataPointsPerSecond = Math.round(
        (channelIncomingDataPointsCount * 1000) /
          (tNow - channelIncomingDataPointsLastUpdate)
      );

      uiList.forEach((ui, i) => {
        ui.labelSampleRate.setText(`${chDataPointsPerSecond} samples / second`);
      });
      channelIncomingDataPointsCount = 0;
      channelIncomingDataPointsLastUpdate = tNow;
    }, 2000);

Websocket connection:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>function createSocketConnection(uiList, addData) {
const socket = new WebSocket(websocketURL);
socket.onmessage = function (event) {
const message = JSON.parse(event.data);
updateChartData(message, uiList);
requestAnimationFrame(addData);
};
</code>
<code>function createSocketConnection(uiList, addData) { const socket = new WebSocket(websocketURL); socket.onmessage = function (event) { const message = JSON.parse(event.data); updateChartData(message, uiList); requestAnimationFrame(addData); }; </code>
function createSocketConnection(uiList, addData) { 
    const socket = new WebSocket(websocketURL); 
    socket.onmessage = function (event) { 
    const message = JSON.parse(event.data); 
    updateChartData(message, uiList); 
    requestAnimationFrame(addData); 
}; 

Here updateChartData() is used to push data to the array.

UpdateChartData function(): starts

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>const updateChartData = (response: any, uiList: any) => {
let ecgData = response?.ecg?.split("^");
let ecgHeartRate = response?.ecgHeartRate;
...Extracting all parameters needed
//For Absolute values
uiList.forEach((ui) => {
if (ui.labelEcgHeartRate) {
if (ecgHeartRate) {
ui.labelEcgHeartRate.setText(ecgHeartRate.toString());
}
}
</code>
<code>const updateChartData = (response: any, uiList: any) => { let ecgData = response?.ecg?.split("^"); let ecgHeartRate = response?.ecgHeartRate; ...Extracting all parameters needed //For Absolute values uiList.forEach((ui) => { if (ui.labelEcgHeartRate) { if (ecgHeartRate) { ui.labelEcgHeartRate.setText(ecgHeartRate.toString()); } } </code>
const updateChartData = (response: any, uiList: any) => { 
    let ecgData = response?.ecg?.split("^");
    let ecgHeartRate = response?.ecgHeartRate;
    ...Extracting all parameters needed 

    //For Absolute values  
    uiList.forEach((ui) => {
      if (ui.labelEcgHeartRate) {
        if (ecgHeartRate) {
          ui.labelEcgHeartRate.setText(ecgHeartRate.toString());
        }
      }

Pushing data into the array:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> const ecgIndex = channels.findIndex(
(channel) => channel.type === ChartDataType.ecg
);
if (ecgData) {
ecgData
?.filter((item: number) => item < 1000)
?.map((item: string) => {
return channels[ecgIndex].dataSet.push(item);
});
}
Similar logic used for other channels.
</code>
<code> const ecgIndex = channels.findIndex( (channel) => channel.type === ChartDataType.ecg ); if (ecgData) { ecgData ?.filter((item: number) => item < 1000) ?.map((item: string) => { return channels[ecgIndex].dataSet.push(item); }); } Similar logic used for other channels. </code>
 const ecgIndex = channels.findIndex(
      (channel) => channel.type === ChartDataType.ecg
    );
    if (ecgData) {
      ecgData
        ?.filter((item: number) => item < 1000)
        ?.map((item: string) => {
          return channels[ecgIndex].dataSet.push(item);
        });
    }
Similar logic used for other channels. 

UpdateChartData function(): ends

Screenshots stating the same: Both screenshots are taken at the same time in different devices.

Device/Machine 1:

Device/Machine 2:

I thought that since its real time data and data received will be huge and rate of receiving the data will be significantly greater, so I did a controlled testing in the testing environment with limited sets of data being sent periodically. This was tested only for one (ECG) channel, which currently has a Y–range set from –50 to 160. So, we created custom dataset where each follow-up object will have values ranging from –50 to 160 (increasing order) and next object will be ranging from 160 to –50 (decreasing order) and so on… This would form a chart that would look like this: ////////. I confirmed this by checking timestamp when we receive the data, extracting data from it and pushing the data in to an array. We verified that the input array also has the same data being pushed and in the same order what we receive.

But the charts being generated are different in this case as well. Also, the chart generated should always be in /////// form, but the charts also seem to be distorted.

Attaching screenshots stating the same:

Device/Machine 1:

Device/Machine 2:

Apart from this, there are few observations that I would like to share:

  1. If we open the charts on multiple devices at the same time, then the charts drawn on different devices are the same. But in case if we open the charts in one device and let’s say 2 mins later in another device, then the charts are different.

  2. Following up the first point, we have 2 devices which have two charts opened at the same time. If we stop sending the data and wait for the charts to clear up. And again, send the data after some time has passed. Now both devices are receiving the same set of data at the same time, but charts drawn on different devices are different.

  3. We checked on the FPS part as well, one device has 30-40 FPS and another has 60 FPS, the charts are different. Another case, when two devices have constant 60FPS, in this case as well the charts are different.

  4. When we stopped sending the data into the WebSocket, we verified that the last few objects being received are the same (verified with timestamp). But when the charts end, it does not end with the same figure/pattern on both devices. This is also valid for starting point, but we understand that it takes some time to subscribe to WebSocket, so the initial few data values might be different, so to some extent charts will be different. (Please correct us in this case if our thinking is not aligning with your expertise). Attaching screenshots stating the same.

Observation 5-Device 1:

Observation 5-Device 2:

Lightningcharts JS version used: "@arction/lcjs": "^5.0.5", in React.JS application

Need help regarding this. Please guide.

I tried checking timestamps when data received and data being pushed to the array in multiple devices. It turns out to be same.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật