change interpolation datacache, add string values to graphs [WIP]
This commit is contained in:
parent
06bc9b6b2e
commit
8cecf2885e
|
@ -21,7 +21,6 @@ const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
|||
const [checkedToggles, setCheckedToggles] = useState<ToggleElement | null>(
|
||||
null
|
||||
);
|
||||
console.log("provider", toggles);
|
||||
return (
|
||||
<LogContext.Provider
|
||||
value={{
|
||||
|
|
|
@ -56,7 +56,6 @@ const CheckboxTree = () => {
|
|||
) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const renderTree = (data: TreeElement[]): ReactNode => {
|
||||
return data.map((element) => {
|
||||
const checked = checkedToggles?.[element.id];
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -2,12 +2,12 @@ import { Maybe } from "yup";
|
|||
import {Timestamped} from "./types";
|
||||
import { isDefined } from "./utils/maybe";
|
||||
|
||||
export type DataRecord = Record<string, number>
|
||||
export type DataRecord = Record<string, number | string>
|
||||
|
||||
export type DataPoint = Timestamped<Maybe<DataRecord>>
|
||||
export type RecordSeries = Array<DataPoint>
|
||||
export type PointSeries = Array<Timestamped<Maybe<number>>>
|
||||
export type DataSeries = Array<Maybe<number>>
|
||||
export type PointSeries = Array<Timestamped<Maybe<number| string>>>
|
||||
export type DataSeries = Array<Maybe<number| string>>
|
||||
|
||||
export function getPoints(recordSeries: RecordSeries, series: keyof DataRecord): PointSeries
|
||||
{
|
||||
|
|
|
@ -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> =
|
||||
| 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<T extends Record<string, number>> {
|
||||
private readonly cache: SkipList<Maybe<T>> = new SkipList<Maybe<T>>();
|
||||
private readonly resolution: TimeSpan;
|
||||
|
||||
export default class DataCache<T extends Record<string, number>>
|
||||
{
|
||||
private readonly cache: SkipList<Maybe<T>> = new SkipList<Maybe<T>>()
|
||||
private readonly resolution: TimeSpan;
|
||||
readonly _fetch: (t: UnixTime) => Promise<FetchResult<T>>;
|
||||
|
||||
readonly _fetch: (t: UnixTime) => Promise<FetchResult<T>>;
|
||||
private readonly fetchQueue = createDispatchQueue(6);
|
||||
private readonly fetching: Set<number> = new Set<number>();
|
||||
|
||||
private readonly fetchQueue = createDispatchQueue(6)
|
||||
private readonly fetching: Set<number> = new Set<number>()
|
||||
public readonly gotData: Observable<UnixTime>;
|
||||
|
||||
public readonly gotData: Observable<UnixTime>;
|
||||
constructor(
|
||||
fetch: (t: UnixTime) => Promise<FetchResult<T>>,
|
||||
resolution: TimeSpan
|
||||
) {
|
||||
this._fetch = fetch;
|
||||
this.resolution = resolution;
|
||||
this.gotData = new Subject<UnixTime>();
|
||||
}
|
||||
|
||||
constructor(fetch: (t: UnixTime) => Promise<FetchResult<T>>, resolution: TimeSpan)
|
||||
{
|
||||
this._fetch = fetch;
|
||||
this.resolution = resolution;
|
||||
this.gotData = new Subject<UnixTime>()
|
||||
public prefetch(times: Array<UnixTime>, clear = true) {
|
||||
if (clear) {
|
||||
this.fetching.clear();
|
||||
this.fetchQueue.clear();
|
||||
}
|
||||
|
||||
public prefetch(times: Array<UnixTime>, 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<T> {
|
||||
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<Maybe<T>>, t: number): Maybe<T> {
|
||||
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<Record<string, number>> = {};
|
||||
|
||||
//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<T>) => {
|
||||
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<UnixTime>).next(time);
|
||||
};
|
||||
|
||||
public get(timeStamp: UnixTime, fetch = true): Maybe<T>
|
||||
{
|
||||
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<Maybe<T>>, t: number): Maybe<T>
|
||||
{
|
||||
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<Record<string, number>> = {}
|
||||
|
||||
//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<T>) =>
|
||||
{
|
||||
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<UnixTime>).next(time);
|
||||
}
|
||||
|
||||
return this._fetch(time)
|
||||
.then(d => onSuccess(d), f => onFailure(f))
|
||||
.finally(() => dispatch())
|
||||
};
|
||||
|
||||
this.fetching.add(t)
|
||||
this.fetchQueue.dispatch(() => fetchTask());
|
||||
}
|
||||
|
||||
}
|
||||
this.fetching.add(t);
|
||||
this.fetchQueue.dispatch(() => fetchTask());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue