fix: add gaps in plots where no data exists (#1065)

Scans the window from the most recent timestamp to however many seconds
back are being stored. Then adds blank spaces (NaN values) in between
data items that are too far apart. Lastly, if the earliest item is
blank, there are probably fewer than the expected number of items. In
that case, it adjusts the first timestamp to keep plot width mostly
consistent.
This commit is contained in:
Sylvie Crowe 2024-10-27 12:00:15 -07:00 committed by GitHub
parent bbd530c052
commit cbb825982b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -101,7 +101,8 @@ class SysinfoViewModel {
viewText: jotai.Atom<string>;
viewName: jotai.Atom<string>;
dataAtom: jotai.PrimitiveAtom<Array<DataItem>>;
addDataAtom: jotai.WritableAtom<unknown, [DataItem[]], void>;
addInitialDataAtom: jotai.WritableAtom<unknown, [DataItem[]], void>;
addContinuousDataAtom: jotai.WritableAtom<unknown, [DataItem], void>;
incrementCount: jotai.WritableAtom<unknown, [], Promise<void>>;
loadingAtom: jotai.PrimitiveAtom<boolean>;
numPoints: jotai.Atom<number>;
@ -117,18 +118,57 @@ class SysinfoViewModel {
this.viewType = viewType;
this.blockId = blockId;
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
this.addDataAtom = jotai.atom(null, (get, set, points) => {
this.addInitialDataAtom = jotai.atom(null, (get, set, points) => {
const targetLen = get(this.numPoints) + 1;
try {
const newDataRaw = [...points];
if (newDataRaw.length == 0) {
return;
}
const latestItemTs = newDataRaw[newDataRaw.length - 1]?.ts ?? 0;
const cutoffTs = latestItemTs - 1000 * targetLen;
const blankItemTemplate = { ...newDataRaw[newDataRaw.length - 1] };
for (const key in blankItemTemplate) {
blankItemTemplate[key] = NaN;
}
const newDataFiltered = newDataRaw.filter((dataItem) => dataItem.ts >= cutoffTs);
if (newDataFiltered.length == 0) {
return;
}
const newDataWithGaps: Array<DataItem> = [];
if (newDataFiltered[0].ts > cutoffTs) {
const blankItemStart = { ...blankItemTemplate, ts: cutoffTs };
const blankItemEnd = { ...blankItemTemplate, ts: newDataFiltered[0].ts - 1 };
newDataWithGaps.push(blankItemStart);
newDataWithGaps.push(blankItemEnd);
}
newDataWithGaps.push(newDataFiltered[0]);
for (let i = 1; i < newDataFiltered.length; i++) {
const prevIdxItem = newDataFiltered[i - 1];
const curIdxItem = newDataFiltered[i];
const timeDiff = curIdxItem.ts - prevIdxItem.ts;
if (timeDiff > 2000) {
const blankItemStart = { ...blankItemTemplate, ts: prevIdxItem.ts + 1, blank: 1 };
const blankItemEnd = { ...blankItemTemplate, ts: curIdxItem.ts - 1, blank: 1 };
newDataWithGaps.push(blankItemStart);
newDataWithGaps.push(blankItemEnd);
}
newDataWithGaps.push(curIdxItem);
}
set(this.dataAtom, newDataWithGaps);
} catch (e) {
console.log("Error adding data to sysinfo", e);
}
});
this.addContinuousDataAtom = jotai.atom(null, (get, set, newPoint) => {
const targetLen = get(this.numPoints) + 1;
let data = get(this.dataAtom);
try {
if (data.length > targetLen) {
data = data.slice(data.length - targetLen);
}
if (data.length < targetLen) {
const defaultData = this.getDefaultData();
data = [...defaultData.slice(defaultData.length - targetLen + data.length), ...data];
}
const newData = [...data.slice(points.length), ...points];
const latestItemTs = newPoint?.ts ?? 0;
const cutoffTs = latestItemTs - 1000 * targetLen;
data.push(newPoint);
const newData = data.filter((dataItem) => dataItem.ts >= cutoffTs);
set(this.dataAtom, newData);
} catch (e) {
console.log("Error adding data to sysinfo", e);
@ -188,7 +228,7 @@ class SysinfoViewModel {
}
return connValue;
});
this.dataAtom = jotai.atom(this.getDefaultData());
this.dataAtom = jotai.atom([]);
this.loadInitialData();
this.connStatus = jotai.atom((get) => {
const blockData = get(this.blockAtom);
@ -214,8 +254,8 @@ class SysinfoViewModel {
const newData = this.getDefaultData();
const initialDataItems: DataItem[] = initialData.map(convertWaveEventToDataItem);
// splice the initial data into the default data (replacing the newest points)
newData.splice(newData.length - initialDataItems.length, initialDataItems.length, ...initialDataItems);
globalStore.set(this.addDataAtom, newData);
//newData.splice(newData.length - initialDataItems.length, initialDataItems.length, ...initialDataItems);
globalStore.set(this.addInitialDataAtom, initialDataItems);
} catch (e) {
console.log("Error loading initial data for sysinfo", e);
} finally {
@ -301,7 +341,7 @@ function SysinfoView({ model, blockId }: SysinfoViewProps) {
const connName = jotai.useAtomValue(model.connection);
const lastConnName = React.useRef(connName);
const connStatus = jotai.useAtomValue(model.connStatus);
const addPlotData = jotai.useSetAtom(model.addDataAtom);
const addContinuousData = jotai.useSetAtom(model.addContinuousDataAtom);
const loading = jotai.useAtomValue(model.loadingAtom);
React.useEffect(() => {
@ -323,7 +363,13 @@ function SysinfoView({ model, blockId }: SysinfoViewProps) {
return;
}
const dataItem = convertWaveEventToDataItem(event);
addPlotData([dataItem]);
const prevData = globalStore.get(model.dataAtom);
const prevLastTs = prevData[prevData.length - 1]?.ts ?? 0;
if (dataItem.ts - prevLastTs > 2000) {
model.loadInitialData();
} else {
addContinuousData(dataItem);
}
},
});
console.log("subscribe to sysinfo", connName);
@ -348,6 +394,7 @@ type SingleLinePlotProps = {
defaultColor: string;
title?: boolean;
sparkline?: boolean;
targetLen: number;
};
function SingleLinePlot({
@ -358,6 +405,7 @@ function SingleLinePlot({
defaultColor,
title = false,
sparkline = false,
targetLen,
}: SingleLinePlotProps) {
const containerRef = React.useRef<HTMLInputElement>();
const domRect = useDimensionsWithExistingRef(containerRef, 300);
@ -440,12 +488,15 @@ function SingleLinePlot({
);
let maxY = resolveDomainBound(yvalMeta?.maxy, plotData[plotData.length - 1]) ?? 100;
let minY = resolveDomainBound(yvalMeta?.miny, plotData[plotData.length - 1]) ?? 0;
let maxX = plotData[plotData.length - 1].ts;
let minX = maxX - targetLen * 1000;
const plot = Plot.plot({
axis: !sparkline,
x: {
grid: true,
label: "time",
tickFormat: (d) => `${dayjs.unix(d / 1000).format("HH:mm:ss")}`,
domain: [minX, maxX],
},
y: { label: labelY, domain: [minY, maxY] },
width: plotWidth,
@ -469,6 +520,7 @@ const SysinfoViewInner = React.memo(({ model }: SysinfoViewProps) => {
const yvals = jotai.useAtomValue(model.metrics);
const plotMeta = jotai.useAtomValue(model.plotMetaAtom);
const osRef = React.useRef<OverlayScrollbarsComponentRef>();
const targetLen = jotai.useAtomValue(model.numPoints) + 1;
let title = false;
let cols2 = false;
if (yvals.length > 1) {
@ -491,6 +543,7 @@ const SysinfoViewInner = React.memo(({ model }: SysinfoViewProps) => {
blockId={model.blockId}
defaultColor={"var(--accent-color)"}
title={title}
targetLen={targetLen}
/>
);
})}