From 491e4edb3cb86a97b4d2ad6845db9bfd9e02c095 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:00:57 +0000 Subject: [PATCH 01/12] send large numeric data as typed arrays to reduce ipc serialization overhead --- gcs/electron/fla.js | 24 ++++++++++++++++++------ gcs/src/fla.jsx | 26 +++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/gcs/electron/fla.js b/gcs/electron/fla.js index 3a17e0123..2a9fc98e0 100644 --- a/gcs/electron/fla.js +++ b/gcs/electron/fla.js @@ -490,14 +490,26 @@ export async function retrieveMessages(_event, requestedMessages) { const categoryName = label.slice(0, slash) const fieldName = label.slice(slash + 1) + const series = logData[categoryName] + const len = series.length + // return typed arrays to reduce IPC serialization overhead + // Time as Float64 (microseconds or ms depending on log), values as Float32 for size efficiency + const x = new Float64Array(len) + const y = new Float32Array(len) + + for (let j = 0; j < len; j++) { + const point = series[j] + // Fall back to 0 if missing + x[j] = typeof point.TimeUS === "number" ? point.TimeUS : 0 + const vy = point[fieldName] + y[j] = typeof vy === "number" ? vy : 0 + } + datasets.push({ - label: label, + label, yAxisID: getUnit(categoryName, fieldName, formatMessages, units), - // I guess this is the expensive part. We're looping through every data point - data: logData[categoryName].map((d) => ({ - x: d.TimeUS, - y: d[fieldName], - })), + x, + y, }) } diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index e73dfec75..31ac770e0 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -126,8 +126,8 @@ export default function FLA() { "fla:get-messages", labelsToFetch, ) - if (newDatasets) { - // Dispatch to add the new data to our master cache in Redux + + if (Array.isArray(newDatasets) && newDatasets.length > 0) { dispatch(setBaseChartData([...(baseChartData || []), ...newDatasets])) } } @@ -141,12 +141,32 @@ export default function FLA() { const visibleDataWithColors = useMemo(() => { if (!baseChartData) return [] + const toChartPoints = (ds) => { + // If already in Chart.js format, return as is + if (Array.isArray(ds.data)) return ds.data + // If typed arrays present, build point objects lazily for visible series only + if (ds && ds.x instanceof Float64Array && ds.y instanceof Float32Array) { + const len = Math.min(ds.x.length, ds.y.length) + const points = new Array(len) + for (let i = 0; i < len; i++) { + points[i] = { x: ds.x[i], y: ds.y[i] } + } + return points + } + // Last resort: empty + return [] + } + return baseChartData .filter((dataset) => requestedLabels.has(dataset.label)) .map((dataset) => { const color = customColors[dataset.label] || "#000000" + const chartData = toChartPoints(dataset) return { - ...dataset, + // Preserve label and unit; ensure .data is present for Chart.js + label: dataset.label, + yAxisID: dataset.yAxisID, + data: chartData, borderColor: color, backgroundColor: hexToRgba(color, 0.5), } From a02f37cedfa364e4363b39e4fb1a23c7ce6fe2a8 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:26:40 +0000 Subject: [PATCH 02/12] removing redundant state and caching data --- gcs/electron/fla.js | 6 +++--- gcs/src/fla.jsx | 50 +++++++++++++++------------------------------ 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/gcs/electron/fla.js b/gcs/electron/fla.js index 2a9fc98e0..225342bf4 100644 --- a/gcs/electron/fla.js +++ b/gcs/electron/fla.js @@ -492,14 +492,14 @@ export async function retrieveMessages(_event, requestedMessages) { const series = logData[categoryName] const len = series.length - // return typed arrays to reduce IPC serialization overhead - // Time as Float64 (microseconds or ms depending on log), values as Float32 for size efficiency + // use typed arrays to reduce IPC serialization overhead + // Time as Float64, values as Float32 for size efficiency const x = new Float64Array(len) const y = new Float32Array(len) for (let j = 0; j < len; j++) { const point = series[j] - // Fall back to 0 if missing + // making sure all the entries are numbers x[j] = typeof point.TimeUS === "number" ? point.TimeUS : 0 const vy = point[fieldName] y[j] = typeof vy === "number" ? vy : 0 diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index 31ac770e0..d77ae1803 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -48,9 +48,6 @@ export default function FLA() { const customColors = useSelector(selectCustomColors) const baseChartData = useSelector(selectBaseChartData) - // Local states - const [chartData, setLocalChartData] = useState({ datasets: [] }) - /** * Dispatch the lightweight summary info to Redux */ @@ -82,7 +79,6 @@ export default function FLA() { function closeLogFile() { dispatch(setFile(null)) dispatch(setLogMessages(null)) - setLocalChartData({ datasets: [] }) dispatch(setMessageFilters(null)) dispatch(setCustomColors({})) dispatch(setUtcAvailable(false)) @@ -126,9 +122,18 @@ export default function FLA() { "fla:get-messages", labelsToFetch, ) - + // Unpack and Cache if (Array.isArray(newDatasets) && newDatasets.length > 0) { - dispatch(setBaseChartData([...(baseChartData || []), ...newDatasets])) + const transformed = newDatasets.map((ds) => { + if (Array.isArray(ds?.data)) return ds + const len = Math.min(ds.x.length, ds.y.length) + const points = new Array(len) + for (let i = 0; i < len; i++) { + points[i] = { x: ds.x[i], y: ds.y[i] } + } + return { label: ds.label, yAxisID: ds.yAxisID, data: points } + }) + dispatch(setBaseChartData([...(baseChartData || []), ...transformed])) } } fetchMissingData() @@ -141,50 +146,27 @@ export default function FLA() { const visibleDataWithColors = useMemo(() => { if (!baseChartData) return [] - const toChartPoints = (ds) => { - // If already in Chart.js format, return as is - if (Array.isArray(ds.data)) return ds.data - // If typed arrays present, build point objects lazily for visible series only - if (ds && ds.x instanceof Float64Array && ds.y instanceof Float32Array) { - const len = Math.min(ds.x.length, ds.y.length) - const points = new Array(len) - for (let i = 0; i < len; i++) { - points[i] = { x: ds.x[i], y: ds.y[i] } - } - return points - } - // Last resort: empty - return [] - } - return baseChartData .filter((dataset) => requestedLabels.has(dataset.label)) .map((dataset) => { const color = customColors[dataset.label] || "#000000" - const chartData = toChartPoints(dataset) return { - // Preserve label and unit; ensure .data is present for Chart.js - label: dataset.label, - yAxisID: dataset.yAxisID, - data: chartData, + ...dataset, borderColor: color, backgroundColor: hexToRgba(color, 0.5), } }) }, [baseChartData, customColors, requestedLabels]) - // Step 4: Update the chart's state. - // This is now very simple and just syncs the memoized data to the local state. - useEffect(() => { - setLocalChartData({ datasets: visibleDataWithColors }) - }, [visibleDataWithColors]) - return ( {messageFilters === null ? ( ) : ( - + )} ) From 00b1af9c17b0d9a73d9c79a91e6c8d70365e7ed1 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:21:33 +0000 Subject: [PATCH 03/12] fiuring off preload with setTimeout, works so far --- gcs/electron/fla.js | 14 +++++- gcs/electron/main.ts | 4 +- gcs/src/components/fla/constants.js | 71 +++++++++++++++++++++++++++++ gcs/src/fla.jsx | 44 ++++++++++-------- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/gcs/electron/fla.js b/gcs/electron/fla.js index 225342bf4..5b284f5a2 100644 --- a/gcs/electron/fla.js +++ b/gcs/electron/fla.js @@ -453,7 +453,7 @@ export default async function openFile(event, filePath) { } // on-demand retrieval of messages -export async function retrieveMessages(_event, requestedMessages) { +export async function getMessages(_event, requestedMessages) { // each requestedMessage should be of the form `${requestedMessageName}/${requestedFieldName}` // like ['ARM/ArmState', 'ARSP/Airspeed'] @@ -461,7 +461,7 @@ export async function retrieveMessages(_event, requestedMessages) { if (!logData) { console.error( - "retrieveMessages: logData is null or undefined. Unable to retrieve messages.", + "getMessages: logData is null or undefined. Unable to retrieve messages.", ) return { success: false, @@ -490,7 +490,17 @@ export async function retrieveMessages(_event, requestedMessages) { const categoryName = label.slice(0, slash) const fieldName = label.slice(slash + 1) + // Validate existence of requestedMessage in our log + const fmt = formatMessages[categoryName] + const hasField = fmt.fields.includes(fieldName) + const series = logData[categoryName] + + if (!hasField || !Array.isArray(series) || series.length === 0) { + // Skip unknown or unavailable labels to make preload calls resilient + continue + } + const len = series.length // use typed arrays to reduce IPC serialization overhead // Time as Float64, values as Float32 for size efficiency diff --git a/gcs/electron/main.ts b/gcs/electron/main.ts index d7b7d508f..d87bf5a00 100644 --- a/gcs/electron/main.ts +++ b/gcs/electron/main.ts @@ -19,7 +19,7 @@ import packageInfo from "../package.json" import openFile, { clearRecentFiles, getRecentFiles, - retrieveMessages, + getMessages, // @ts-expect-error - no types available } from "./fla" import registerAboutIPC, { @@ -460,7 +460,7 @@ app.whenReady().then(() => { ipcMain.handle("fla:clear-recent-logs", clearRecentFiles) // Load Messages on demand - ipcMain.handle("fla:get-messages", retrieveMessages) + ipcMain.handle("fla:get-messages", getMessages) // Save mission file ipcMain.handle( diff --git a/gcs/src/components/fla/constants.js b/gcs/src/components/fla/constants.js index 75f4220e1..8f0176961 100644 --- a/gcs/src/components/fla/constants.js +++ b/gcs/src/components/fla/constants.js @@ -49,3 +49,74 @@ export const colorInputSwatch = [ "#fab005", "#fd7e14", ] + +// Fixed list of message labels to preload for FLA +export const PRELOAD_LABELS = { + dataflash: [ + // Attitude + "ATT/DesRoll", + "ATT/Roll", + "ATT/DesPitch", + "ATT/Pitch", + "ATT/DesYaw", + "ATT/Yaw", + + // Speed + "GPS/Spd", + "ARSP/Airspeed", + + // Vibration + "VIBE/VibeX", + "VIBE/VibeY", + "VIBE/VibeZ", + + // Battery + "BAT1/Volt", + "BAT1/Curr", + + // Control tuning (copter/plane) + "CTUN/DAlt", + "CTUN/Alt", + "CTUN/BAlt", + "CTUN/DCRt", + "CTUN/CRt", + "CTUN/ThI", + "CTUN/ThO", + + // Control tuning (quadplane) + "QTUN/DAlt", + "QTUN/Alt", + "QTUN/BAlt", + "QTUN/DCRt", + "QTUN/CRt", + "QTUN/ThI", + "QTUN/ThO", + + // RC Inputs (first four) + "RCIN/C1", + "RCIN/C2", + "RCIN/C3", + "RCIN/C4", + ], + + fgcs_telemetry: [ + // Attitude + "ATTITUDE/roll", + "ATTITUDE/pitch", + + // Speed + "VFR_HUD/groundspeed", + "VFR_HUD/airspeed", + + // Vibration + "VIBRATION/vibration_x", + "VIBRATION/vibration_y", + "VIBRATION/vibration_z", + + // RC Inputs (first four) + "RC_CHANNELS/chan1_raw", + "RC_CHANNELS/chan2_raw", + "RC_CHANNELS/chan3_raw", + "RC_CHANNELS/chan4_raw", + ], +} diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index d77ae1803..91cb8d519 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -14,6 +14,7 @@ import { useDispatch, useSelector } from "react-redux" import { hexToRgba } from "./components/fla/utils" // Custom components and helpers +import { PRELOAD_LABELS } from "./components/fla/constants.js" import { logEventIds } from "./components/fla/logEventIds.js" import SelectFlightLog from "./components/fla/SelectFlightLog.jsx" @@ -73,6 +74,8 @@ export default function FLA() { ), ) dispatch(setBaseChartData([])) + // Fire off preload in the background without blocking + setTimeout(() => fetchData(PRELOAD_LABELS[summary.logType]), 0) } // Close file @@ -89,6 +92,26 @@ export default function FLA() { dispatch(setBaseChartData([])) } + async function fetchData (labelsToFetch){ + const newDatasets = await window.ipcRenderer.invoke( + "fla:get-messages", + labelsToFetch, + ) + // Unpack and Cache + if (Array.isArray(newDatasets) && newDatasets.length > 0) { + const transformed = newDatasets.map((ds) => { + if (Array.isArray(ds?.data)) return ds + const len = Math.min(ds.x.length, ds.y.length) + const points = new Array(len) + for (let i = 0; i < len; i++) { + points[i] = { x: ds.x[i], y: ds.y[i] } + } + return { label: ds.label, yAxisID: ds.yAxisID, data: points } + }) + dispatch(setBaseChartData([...(baseChartData || []), ...transformed])) + } + } + // Step 1: Memoize the calculation of which labels are currently requested. // This loop only runs when `messageFilters` changes. const requestedLabels = useMemo(() => { @@ -117,26 +140,7 @@ export default function FLA() { if (labelsToFetch.length > 0) { console.log("Cache miss. Fetching:", labelsToFetch) - const fetchMissingData = async () => { - const newDatasets = await window.ipcRenderer.invoke( - "fla:get-messages", - labelsToFetch, - ) - // Unpack and Cache - if (Array.isArray(newDatasets) && newDatasets.length > 0) { - const transformed = newDatasets.map((ds) => { - if (Array.isArray(ds?.data)) return ds - const len = Math.min(ds.x.length, ds.y.length) - const points = new Array(len) - for (let i = 0; i < len; i++) { - points[i] = { x: ds.x[i], y: ds.y[i] } - } - return { label: ds.label, yAxisID: ds.yAxisID, data: points } - }) - dispatch(setBaseChartData([...(baseChartData || []), ...transformed])) - } - } - fetchMissingData() + fetchData(labelsToFetch) } }, [requestedLabels, baseChartData, dispatch]) From 8f25ad277466fdea31ad0433f12241c9d73a4ce3 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:31:44 +0000 Subject: [PATCH 04/12] formatting --- gcs/src/fla.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index 91cb8d519..bb528b22d 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -92,7 +92,7 @@ export default function FLA() { dispatch(setBaseChartData([])) } - async function fetchData (labelsToFetch){ + async function fetchData(labelsToFetch) { const newDatasets = await window.ipcRenderer.invoke( "fla:get-messages", labelsToFetch, From 3f6f3c4c0176ff2d364cc1758a2416e1c9159515 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:36:15 +0000 Subject: [PATCH 05/12] removing unused import --- gcs/electron/fla.js | 2 +- gcs/src/fla.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gcs/electron/fla.js b/gcs/electron/fla.js index 5b284f5a2..70b7d4504 100644 --- a/gcs/electron/fla.js +++ b/gcs/electron/fla.js @@ -497,7 +497,7 @@ export async function getMessages(_event, requestedMessages) { const series = logData[categoryName] if (!hasField || !Array.isArray(series) || series.length === 0) { - // Skip unknown or unavailable labels to make preload calls resilient + // Skip unknown or unavailable labels continue } diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index bb528b22d..c2b15cec2 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -5,7 +5,7 @@ */ // Base imports -import { useEffect, useMemo, useState } from "react" +import { useEffect, useMemo } from "react" // 3rd Party Imports import { useDispatch, useSelector } from "react-redux" From b77d1d4c7e6b4ac7b6f9a55248b707de62f04ff9 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:37:07 +0000 Subject: [PATCH 06/12] formatting --- gcs/electron/fla.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gcs/electron/fla.js b/gcs/electron/fla.js index 70b7d4504..f10455a26 100644 --- a/gcs/electron/fla.js +++ b/gcs/electron/fla.js @@ -511,8 +511,7 @@ export async function getMessages(_event, requestedMessages) { const point = series[j] // making sure all the entries are numbers x[j] = typeof point.TimeUS === "number" ? point.TimeUS : 0 - const vy = point[fieldName] - y[j] = typeof vy === "number" ? vy : 0 + y[j] = typeof point[fieldName] === "number" ? point[fieldName] : 0 } datasets.push({ From 104c1034c945eceba8054da998f8687f9205b84a Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:40:08 +0000 Subject: [PATCH 07/12] clean up --- gcs/electron/fla.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcs/electron/fla.js b/gcs/electron/fla.js index f10455a26..f1c7e5829 100644 --- a/gcs/electron/fla.js +++ b/gcs/electron/fla.js @@ -492,7 +492,7 @@ export async function getMessages(_event, requestedMessages) { // Validate existence of requestedMessage in our log const fmt = formatMessages[categoryName] - const hasField = fmt.fields.includes(fieldName) + const hasField = fmt?.fields.includes(fieldName) const series = logData[categoryName] From 116adf97d64a826045dd27d956b5d65de27f30e1 Mon Sep 17 00:00:00 2001 From: "Kwashie A." <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:51:05 +0000 Subject: [PATCH 08/12] Update gcs/src/fla.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- gcs/src/fla.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index c2b15cec2..2d9300065 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -142,7 +142,7 @@ export default function FLA() { console.log("Cache miss. Fetching:", labelsToFetch) fetchData(labelsToFetch) } - }, [requestedLabels, baseChartData, dispatch]) + }, [requestedLabels, baseChartData]) // Step 3: Memoize the final chart data. // This filters the master cache and applies colors. It only re-runs if From c5b56aed497d5806dc42c0aefab41ffddc0772c8 Mon Sep 17 00:00:00 2001 From: "Kwashie A." <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:52:01 +0000 Subject: [PATCH 09/12] Update gcs/src/fla.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- gcs/src/fla.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index 2d9300065..19b70d0d7 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -75,7 +75,9 @@ export default function FLA() { ) dispatch(setBaseChartData([])) // Fire off preload in the background without blocking - setTimeout(() => fetchData(PRELOAD_LABELS[summary.logType]), 0) + if (PRELOAD_LABELS.hasOwnProperty(summary.logType)) { + setTimeout(() => fetchData(PRELOAD_LABELS[summary.logType]), 0) + } } // Close file From e95d50b537d4844e97aaa9452844d97cc0480da4 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:56:08 +0000 Subject: [PATCH 10/12] fixing lint error --- gcs/src/fla.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index 19b70d0d7..75b98b47f 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -75,8 +75,9 @@ export default function FLA() { ) dispatch(setBaseChartData([])) // Fire off preload in the background without blocking - if (PRELOAD_LABELS.hasOwnProperty(summary.logType)) { - setTimeout(() => fetchData(PRELOAD_LABELS[summary.logType]), 0) + const labelsToPreload = PRELOAD_LABELS[summary.logType] + if (labelsToPreload && labelsToPreload.length > 0) { + setTimeout(() => fetchData(labelsToPreload), 0) } } From 24f838a7e1587e4e4518cae22dd13b62f4461a41 Mon Sep 17 00:00:00 2001 From: "Kwashie A." <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 01:03:14 +0000 Subject: [PATCH 11/12] Applying copilot's suggestions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- gcs/src/fla.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index 75b98b47f..70ddac268 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -111,7 +111,11 @@ export default function FLA() { } return { label: ds.label, yAxisID: ds.yAxisID, data: points } }) - dispatch(setBaseChartData([...(baseChartData || []), ...transformed])) + // Deduplicate by label: new datasets override old ones + const existing = baseChartData || []; + const newLabels = new Set(transformed.map(ds => ds.label)); + const filteredExisting = existing.filter(ds => !newLabels.has(ds.label)); + dispatch(setBaseChartData([...filteredExisting, ...transformed])); } } From 762bc6c6e262fe72f8b038147a56dbcea260d084 Mon Sep 17 00:00:00 2001 From: Kwashie A <104215256+Kwash67@users.noreply.github.com> Date: Wed, 5 Nov 2025 01:05:14 +0000 Subject: [PATCH 12/12] formatting --- gcs/src/fla.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gcs/src/fla.jsx b/gcs/src/fla.jsx index 70ddac268..a36f2ddd1 100644 --- a/gcs/src/fla.jsx +++ b/gcs/src/fla.jsx @@ -112,10 +112,10 @@ export default function FLA() { return { label: ds.label, yAxisID: ds.yAxisID, data: points } }) // Deduplicate by label: new datasets override old ones - const existing = baseChartData || []; - const newLabels = new Set(transformed.map(ds => ds.label)); - const filteredExisting = existing.filter(ds => !newLabels.has(ds.label)); - dispatch(setBaseChartData([...filteredExisting, ...transformed])); + const existing = baseChartData || [] + const newLabels = new Set(transformed.map((ds) => ds.label)) + const filteredExisting = existing.filter((ds) => !newLabels.has(ds.label)) + dispatch(setBaseChartData([...filteredExisting, ...transformed])) } }