mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
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:
parent
bbd530c052
commit
cbb825982b
@ -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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
Loading…
Reference in New Issue
Block a user