From 8cecf2885e5e65915d902dcbfc4669d5462d8441 Mon Sep 17 00:00:00 2001 From: Sina Blattmann Date: Thu, 11 May 2023 10:49:01 +0200 Subject: [PATCH] change interpolation datacache, add string values to graphs [WIP] --- .../components/Context/LogContextProvider.tsx | 1 - .../Installations/Log/CheckboxTree.tsx | 1 - .../Installations/Log/ScalarGraph.tsx | 24 +- typescript/Frontend/src/dataCache/data.ts | 6 +- .../Frontend/src/dataCache/dataCache.ts | 281 +++++++++--------- typescript/Frontend/src/util/graph.util.tsx | 25 +- 6 files changed, 160 insertions(+), 178 deletions(-) diff --git a/typescript/Frontend/src/components/Context/LogContextProvider.tsx b/typescript/Frontend/src/components/Context/LogContextProvider.tsx index 8bbe6d2df..349ed883a 100644 --- a/typescript/Frontend/src/components/Context/LogContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/LogContextProvider.tsx @@ -21,7 +21,6 @@ const LogContextProvider = ({ children }: { children: ReactNode }) => { const [checkedToggles, setCheckedToggles] = useState( null ); - console.log("provider", toggles); return ( { ) => { event.stopPropagation(); }; - const renderTree = (data: TreeElement[]): ReactNode => { return data.map((element) => { const checked = checkedToggles?.[element.id]; diff --git a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx index ce77c6cba..93c48e15c 100644 --- a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx +++ b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx @@ -15,6 +15,7 @@ import DataCache, { FetchResult } from "../../../dataCache/dataCache"; import { LogContext } from "../../Context/LogContextProvider"; import { TreeElement, ToggleElement } from "./CheckboxTree"; import { isDefined } from "../../../dataCache/utils/maybe"; +import { timeStamp } from "console"; export const createTimes = ( range: TimeRange, @@ -102,7 +103,6 @@ const ScalarGraph = () => { setTimeSeries(timeSeries); const toggleValues = timeSeries.find((timeStamp) => timeStamp.value); if (toggles === null && toggleValues && toggleValues.value) { - console.log("toggles inside", toggles); const treeElements = Object.keys(toggleValues.value) .map((path) => { return path @@ -121,7 +121,6 @@ const ScalarGraph = () => { (children, path) => insert(children, path), [] as TreeElement[] ); - console.log("elements", treeElements); setToggles(treeElements); setCheckedToggles(flattenToggles(treeElements)); } @@ -200,6 +199,7 @@ const ScalarGraph = () => { const renderGraphs = () => { const coordinateTimeSeries = transformToGraphData(timeSeries); + console.log("coordinates", coordinateTimeSeries); const graphCoordinates: GraphCoordinates[] = Object.keys( coordinateTimeSeries ) @@ -229,7 +229,7 @@ const ScalarGraph = () => { autorange: false, range: range, type: "date", - mirror: "allticks", + showticklabels: true, }, grid: { subplots: subplots as any, @@ -237,24 +237,6 @@ const ScalarGraph = () => { ygap: 0.1, }, height: graphCoordinates.length * 300, - annotations: [ - { - text: "X1/Y1 title", - showarrow: false, - x: 0, - y: 1, - yref: "paper", - yanchor: "bottom", - }, - { - text: "X2/Y2 title", - showarrow: false, - x: 0, - y: 1, - yref: "paper", - yanchor: "bottom", - }, - ], }} config={{ modeBarButtonsToRemove: [ diff --git a/typescript/Frontend/src/dataCache/data.ts b/typescript/Frontend/src/dataCache/data.ts index 8fdb168e1..ffe3f8a44 100644 --- a/typescript/Frontend/src/dataCache/data.ts +++ b/typescript/Frontend/src/dataCache/data.ts @@ -2,12 +2,12 @@ import { Maybe } from "yup"; import {Timestamped} from "./types"; import { isDefined } from "./utils/maybe"; -export type DataRecord = Record +export type DataRecord = Record export type DataPoint = Timestamped> export type RecordSeries = Array -export type PointSeries = Array>> -export type DataSeries = Array> +export type PointSeries = Array>> +export type DataSeries = Array> export function getPoints(recordSeries: RecordSeries, series: keyof DataRecord): PointSeries { diff --git a/typescript/Frontend/src/dataCache/dataCache.ts b/typescript/Frontend/src/dataCache/dataCache.ts index 71f230a99..c123658ed 100644 --- a/typescript/Frontend/src/dataCache/dataCache.ts +++ b/typescript/Frontend/src/dataCache/dataCache.ts @@ -1,169 +1,158 @@ /* eslint-disable no-mixed-operators */ -import {TimeSpan, UnixTime} from "./time"; -import {Observable, Subject} from "rxjs"; -import {SkipList} from "./skipList/skipList"; -import {createDispatchQueue} from "./promiseQueue"; -import {SkipListNode} from "./skipList/skipListNode"; -import {RecordSeries} from "./data"; +import { TimeSpan, UnixTime } from "./time"; +import { Observable, Subject } from "rxjs"; +import { SkipList } from "./skipList/skipList"; +import { createDispatchQueue } from "./promiseQueue"; +import { SkipListNode } from "./skipList/skipListNode"; +import { RecordSeries } from "./data"; import { Maybe, isUndefined } from "./utils/maybe"; +import { isNumber, isString } from "./utils/runtimeTypeChecking"; - -export const FetchResult = -{ - notAvailable : "N/A", - tryLater : "Try Later" -} as const +export const FetchResult = { + notAvailable: "N/A", + tryLater: "Try Later", +} as const; export type FetchResult = - | T - | typeof FetchResult.notAvailable - | typeof FetchResult.tryLater + | T + | typeof FetchResult.notAvailable + | typeof FetchResult.tryLater; -function reverseBits(x : number): number -{ - // https://stackoverflow.com/a/60227327/141397 +function reverseBits(x: number): number { + // https://stackoverflow.com/a/60227327/141397 - x = (x & 0x55555555) << 1 | (x & 0xAAAAAAAA) >> 1; - x = (x & 0x33333333) << 2 | (x & 0xCCCCCCCC) >> 2; - x = (x & 0x0F0F0F0F) << 4 | (x & 0xF0F0F0F0) >> 4; - x = (x & 0x00FF00FF) << 8 | (x & 0xFF00FF00) >> 8; - x = (x & 0x0000FFFF) << 16 | (x & 0xFFFF0000) >> 16; + x = ((x & 0x55555555) << 1) | ((x & 0xaaaaaaaa) >> 1); + x = ((x & 0x33333333) << 2) | ((x & 0xcccccccc) >> 2); + x = ((x & 0x0f0f0f0f) << 4) | ((x & 0xf0f0f0f0) >> 4); + x = ((x & 0x00ff00ff) << 8) | ((x & 0xff00ff00) >> 8); + x = ((x & 0x0000ffff) << 16) | ((x & 0xffff0000) >> 16); - return x >>> 0; + return x >>> 0; } +export default class DataCache> { + private readonly cache: SkipList> = new SkipList>(); + private readonly resolution: TimeSpan; -export default class DataCache> -{ - private readonly cache: SkipList> = new SkipList>() - private readonly resolution: TimeSpan; + readonly _fetch: (t: UnixTime) => Promise>; - readonly _fetch: (t: UnixTime) => Promise>; + private readonly fetchQueue = createDispatchQueue(6); + private readonly fetching: Set = new Set(); - private readonly fetchQueue = createDispatchQueue(6) - private readonly fetching: Set = new Set() + public readonly gotData: Observable; - public readonly gotData: Observable; + constructor( + fetch: (t: UnixTime) => Promise>, + resolution: TimeSpan + ) { + this._fetch = fetch; + this.resolution = resolution; + this.gotData = new Subject(); + } - constructor(fetch: (t: UnixTime) => Promise>, resolution: TimeSpan) - { - this._fetch = fetch; - this.resolution = resolution; - this.gotData = new Subject() + public prefetch(times: Array, clear = true) { + if (clear) { + this.fetching.clear(); + this.fetchQueue.clear(); } - public prefetch(times: Array, clear = true) - { - if (clear) - { - this.fetching.clear() - this.fetchQueue.clear() + const timesWithPriority = times.map((time, index) => ({ + time, + priority: reverseBits(index), + })); + timesWithPriority.sort((x, y) => x.priority - y.priority); + + for (let i = 0; i < timesWithPriority.length; i++) { + const time = timesWithPriority[i].time.round(this.resolution); + const t = time.ticks; + + const node = this.cache.find(t); + if (node.index !== t) this.fetchData(time); + } + } + + public get(timeStamp: UnixTime, fetch = true): Maybe { + const time = timeStamp.round(this.resolution); + const t = time.ticks; + + const node = this.cache.find(t); + if (node.index === t) return node.value; + + if (fetch) this.fetchData(time); + + return this.interpolate(node, t); + } + + public getSeries(sampleTimes: UnixTime[]): RecordSeries { + this.prefetch(sampleTimes); + return sampleTimes.map((time) => ({ time, value: this.get(time, false) })); + } + + private interpolate(before: SkipListNode>, t: number): Maybe { + const dataBefore = before.value; + const after = before.next[0]; + const dataAfter = after.value; + + if (isUndefined(dataBefore) && isUndefined(dataAfter)) return undefined; + + if (isUndefined(dataBefore)) return dataAfter; + + if (isUndefined(dataAfter)) return dataBefore; + + const p = t - before.index; + const n = after.index - t; + const pn = p + n; + + let interpolated: Partial> = {}; + + //What about string nodes? like Alarms + for (const k of Object.keys(dataBefore)) { + interpolated[k] = isNumber(dataBefore[k]) + ? (dataBefore[k] * n + dataAfter[k] * p) / pn + : n < p + ? dataAfter[k] + : dataBefore[k]; + } + + return interpolated as T; + } + + private fetchData(time: UnixTime) { + const t = time.ticks; + + if (this.fetching.has(t)) + // we are already fetching t + return; + + const fetchTask = () => { + const onSuccess = (data: FetchResult) => { + if (data === FetchResult.tryLater) { + console.warn(FetchResult.tryLater); + return; } - const timesWithPriority = times.map((time, index) => ({time, priority: reverseBits(index)})) - timesWithPriority.sort((x, y) => x.priority - y.priority) + const value = data === FetchResult.notAvailable ? undefined : data; + this.cache.insert(value, t); + }; - for (let i = 0; i < timesWithPriority.length; i++) - { - const time = timesWithPriority[i].time.round(this.resolution) - const t = time.ticks; + const onFailure = (_: unknown) => { + console.error(time.ticks + " FAILED!"); // should not happen + }; - const node = this.cache.find(t); - if (node.index !== t) - this.fetchData(time); - } - } + const dispatch = () => { + this.fetching.delete(time.ticks); + (this.gotData as Subject).next(time); + }; - public get(timeStamp: UnixTime, fetch = true): Maybe - { - const time = timeStamp.round(this.resolution) - const t = time.ticks; + return this._fetch(time) + .then( + (d) => onSuccess(d), + (f) => onFailure(f) + ) + .finally(() => dispatch()); + }; - const node = this.cache.find(t); - if (node.index === t) - return node.value - - if (fetch) - this.fetchData(time); - - return this.interpolate(node, t) - } - - public getSeries(sampleTimes: UnixTime[]): RecordSeries - { - this.prefetch(sampleTimes) - return sampleTimes.map(time => ({time, value: this.get(time, false)})) - } - - private interpolate(before: SkipListNode>, t: number): Maybe - { - const dataBefore = before.value - const after = before.next[0]; - const dataAfter = after.value - - if (isUndefined(dataBefore) && isUndefined(dataAfter)) - return undefined - - if (isUndefined(dataBefore)) - return dataAfter - - if (isUndefined(dataAfter)) - return dataBefore - - const p = t - before.index - const n = after.index - t - const pn = p + n - - let interpolated: Partial> = {} - - //What about string nodes? like Alarms - for (const k of Object.keys(dataBefore)) - { - interpolated[k] = (dataBefore[k] * n + dataAfter[k] * p) / pn - } - - return interpolated as T - } - - private fetchData(time: UnixTime) - { - const t = time.ticks; - - if (this.fetching.has(t)) // we are already fetching t - return - - const fetchTask = () => - { - const onSuccess = (data: FetchResult) => - { - if (data === FetchResult.tryLater) - { - console.warn(FetchResult.tryLater) - return - } - - const value = data === FetchResult.notAvailable ? undefined : data; - this.cache.insert(value, t) - } - - const onFailure = (_: unknown) => - { - console.error(time.ticks + " FAILED!") // should not happen - } - - const dispatch = () => - { - this.fetching.delete(time.ticks); - (this.gotData as Subject).next(time); - } - - return this._fetch(time) - .then(d => onSuccess(d), f => onFailure(f)) - .finally(() => dispatch()) - }; - - this.fetching.add(t) - this.fetchQueue.dispatch(() => fetchTask()); - } - -} \ No newline at end of file + this.fetching.add(t); + this.fetchQueue.dispatch(() => fetchTask()); + } +} diff --git a/typescript/Frontend/src/util/graph.util.tsx b/typescript/Frontend/src/util/graph.util.tsx index 18e0e33c8..07a15f4a9 100644 --- a/typescript/Frontend/src/util/graph.util.tsx +++ b/typescript/Frontend/src/util/graph.util.tsx @@ -1,6 +1,4 @@ import { Datum, TypedArray } from "plotly.js"; -import { RecordSeries } from "../dataCache/data"; -import { isDefined } from "../dataCache/utils/maybe"; export const mergeDeep = (...objects: any[]) => { const isObject = (obj: GraphCoordinates) => obj && typeof obj === "object"; @@ -36,11 +34,26 @@ export interface GraphData { export const parseCsv = (text: string) => { const y = text .split(/\r?\n/) - .map((l) => l.split(";")) - .filter((fields) => !isNaN(parseFloat(fields[1]))); + .filter((split) => split.length > 0) + .map((l) => { + if (l.length === 0) { + console.log("splitting", l, l.split(";")); + } + return l.split(";"); + }); + console.log("text", y); + /* .filter((fields) => !isNaN(parseFloat(fields[1]))); + */ const x = y - .map((fields) => ({ [fields[0]]: parseFloat(fields[1]) })) - .reduce((acc, current) => ({ ...acc, ...current }), {}); + .map((fields) => { + if (typeof fields[1] === "string") { + console.log("if inside", fields, { [fields[0]]: fields[1] }); + return { [fields[0]]: fields[1] }; + } + console.log("if outside", fields, { [fields[0]]: parseFloat(fields[1]) }); + return { [fields[0]]: parseFloat(fields[1]) }; + }) + .reduce((acc, current) => ({ ...acc, ...current }), {} as any); return x; };