add working bar charts, before restructureing of code

This commit is contained in:
Sina Blattmann 2023-05-15 13:19:08 +02:00
parent aef9ee4907
commit 0533a86b11
2 changed files with 254 additions and 173 deletions

View File

@ -3,19 +3,25 @@ import { RecordSeries } from "../../../dataCache/data";
import {
GraphCoordinates,
GraphData,
mergeDeep,
flattenBarGraphData,
flattenToggles,
insertTreeElements,
isNumeric,
isText,
parseCsv,
stringToColor,
transformToBarGraphData,
} from "../../../util/graph.util";
import { TimeRange, TimeSpan, UnixTime } from "../../../dataCache/time";
import { useContext, useEffect, useMemo, useState } from "react";
import { BehaviorSubject, startWith, throttleTime, withLatestFrom } from "rxjs";
import { S3Access } from "../../../dataCache/S3/S3Access";
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";
import { Data } from "plotly.js";
export const createTimes = (
range: TimeRange,
@ -39,8 +45,8 @@ const ScalarGraph = () => {
);
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
const [range, setRange] = useState([
timeRange[0].toDate().getTime(),
timeRange[timeRange.length - 1].toDate().getTime(),
timeRange[0].toDate(),
timeRange[timeRange.length - 1].toDate(),
]);
const [uiRevision, setUiRevision] = useState(Math.random());
const [plotTitles, setPlotTitles] = useState<string[]>([]);
@ -54,43 +60,11 @@ const ScalarGraph = () => {
"saliomameiringen",
"sos-ch-dk-2",
"exo.io",
"EXO18e7ae9e53fae71ee55cf35b",
"3Cyonq8gMQ0a3elTH2vP7Yv-czcCj8iE2lBcPB9XhSc",
"EXO464a9ff62fdfa407aa742855",
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g",
""
);
const insert = (
children: TreeElement[] = [],
[head, ...tail]: string[]
): TreeElement[] => {
let child = children.find((child) => child.name === head);
if (!child) {
children.push(
(child = {
id: head,
name: head,
children: [],
})
);
}
if (tail.length > 0) {
insert(child.children, tail);
}
return children;
};
const flattenToggles = (toggles: TreeElement[]): ToggleElement => {
return toggles.reduce((acc, current) => {
if (current.children.length > 0) {
acc[current.id] = false;
return { ...acc, ...flattenToggles(current.children) };
}
acc[current.id] = false;
return acc;
}, {} as ToggleElement);
};
useEffect(() => {
const subscription = cache.gotData
.pipe(
@ -118,7 +92,7 @@ const ScalarGraph = () => {
.slice(1);
})
.reduce(
(children, path) => insert(children, path),
(children, path) => insertTreeElements(children, path),
[] as TreeElement[]
);
setToggles(treeElements);
@ -156,120 +130,154 @@ const ScalarGraph = () => {
[]
);
const transformToGraphData = (timeStampData: RecordSeries) => {
const graphData = timeStampData.reduce((acc, curr) => {
if (isDefined(curr.value)) {
const timeStampObj = Object.keys(curr.value).reduce(
(pathAcc, currPath) => {
if (currPath) {
return {
...pathAcc,
[currPath]: {
x: [new Date(curr.time.ticks * 1000)],
y: [curr.value ? curr.value[currPath] : 0],
},
};
}
return pathAcc;
},
const transformToGraphData = (input: RecordSeries): GraphData => {
const transformedObject: any = {};
input.forEach((item) => {
if (isDefined(item.value)) {
Object.keys(item.value).forEach((key) => {
if (!transformedObject.hasOwnProperty(key)) {
transformedObject[key] = {
x: [],
y: [],
};
}
transformedObject[key].x.push(
new Date(item.time.ticks * 1000).toISOString()
);
transformedObject[key].y.push(item.value?.[key]);
});
}
if (plotTitles.length === 0) {
setPlotTitles(Object.keys(transformedObject));
}
});
return Object.keys(transformedObject).length > 0
? transformedObject
: plotTitles.reduce(
(acc, curr) => ({
...acc,
[curr]: {
x: [],
y: [],
},
}),
{} as GraphData
);
if (plotTitles.length === 0) {
setPlotTitles(Object.keys(curr.value));
}
return mergeDeep(acc, timeStampObj);
}
return acc;
}, {} as GraphData);
if (Object.keys(graphData).length > 0) {
return graphData;
}
return plotTitles.reduce(
(acc, curr) => ({
...acc,
[curr]: {
x: [],
y: [],
},
}),
{} as GraphData
);
};
const renderGraphs = () => {
const coordinateTimeSeries = transformToGraphData(timeSeries);
console.log("coordinates", coordinateTimeSeries);
const graphCoordinates: GraphCoordinates[] = Object.keys(
coordinateTimeSeries
)
.filter((path) => {
return checkedToggles?.[path];
})
.map((path, i) => {
return {
...coordinateTimeSeries[path],
xaxis: "x",
yaxis: i === 0 ? "y" : "y" + (i + 1),
type: "scatter",
};
});
if (checkedToggles && graphCoordinates.length > 0) {
const subplots = graphCoordinates.map((coordinate) => [
(coordinate?.xaxis || "") + (coordinate.yaxis || ""),
]);
return (
<Plot
data={graphCoordinates}
layout={{
title: "Graphs",
uirevision: uiRevision,
showlegend: false,
xaxis: {
autorange: false,
range: range,
type: "date",
showticklabels: true,
},
grid: {
subplots: subplots as any,
xside: "top",
ygap: 0.1,
},
height: graphCoordinates.length * 300,
}}
config={{
modeBarButtonsToRemove: [
"lasso2d",
"select2d",
"pan2d",
"autoScale2d",
],
}}
onRelayout={(params) => {
const xaxisRange0 = params["xaxis.range[0]"];
const xaxisRange1 = params["xaxis.range[1]"];
if (checkedToggles) {
const coordinateTimeSeries = transformToGraphData(timeSeries);
if (plotTitles.length === 0) {
setPlotTitles(Object.keys(coordinateTimeSeries));
}
return Object.keys(coordinateTimeSeries)
.filter((path) => {
return checkedToggles[path];
})
.map((path) => {
const data = coordinateTimeSeries[path] ?? { x: [], y: [] };
const isScalar = isNumeric(data.y[0]);
if (!isScalar) {
const barGraphData = transformToBarGraphData(data);
return (
<Plot
key={path}
data={barGraphData as Data[]}
layout={{
height: 500,
width: 1000,
title: path,
uirevision: uiRevision,
xaxis: {
autorange: false,
range: range,
type: "date",
},
bargap: 0,
barmode: "stack",
barnorm: "percent",
}}
config={{
modeBarButtonsToRemove: [
"lasso2d",
"select2d",
"pan2d",
"autoScale2d",
],
}}
onRelayout={(params) => {
const xaxisRange0 = params["xaxis.range[0]"];
const xaxisRange1 = params["xaxis.range[1]"];
if (xaxisRange0 && xaxisRange1) {
setRange([
new Date(xaxisRange0).getTime(),
new Date(xaxisRange1).getTime(),
]);
setUiRevision(Math.random());
const times = createTimes(
TimeRange.fromTimes(
UnixTime.fromDate(new Date(xaxisRange0)),
UnixTime.fromDate(new Date(xaxisRange1))
),
NUMBER_OF_NODES
);
console.log("times", times);
cache.getSeries(times);
times$.next(times);
}
}}
/>
);
if (xaxisRange0 && xaxisRange1) {
setRange([new Date(xaxisRange0), new Date(xaxisRange1)]);
setUiRevision(Math.random());
const times = createTimes(
TimeRange.fromTimes(
UnixTime.fromDate(new Date(xaxisRange0)),
UnixTime.fromDate(new Date(xaxisRange1))
),
NUMBER_OF_NODES
);
cache.getSeries(times);
times$.next(times);
}
}}
/>
);
}
return (
<Plot
key={path}
data={[
{
...data,
type: isScalar ? "scatter" : "bar",
mode: "lines+markers",
marker: { color: "red" },
},
]}
layout={{
width: 1000,
height: 500,
title: path,
uirevision: uiRevision,
xaxis: {
autorange: false,
range: range,
type: "date",
},
}}
config={{
modeBarButtonsToRemove: [
"lasso2d",
"select2d",
"pan2d",
"autoScale2d",
],
}}
onRelayout={(params) => {
const xaxisRange0 = params["xaxis.range[0]"];
const xaxisRange1 = params["xaxis.range[1]"];
if (xaxisRange0 && xaxisRange1) {
setRange([new Date(xaxisRange0), new Date(xaxisRange1)]);
setUiRevision(Math.random());
const times = createTimes(
TimeRange.fromTimes(
UnixTime.fromDate(new Date(xaxisRange0)),
UnixTime.fromDate(new Date(xaxisRange1))
),
NUMBER_OF_NODES
);
cache.getSeries(times);
times$.next(times);
}
}}
/>
);
});
}
};
return <>{renderGraphs()}</>;

View File

@ -1,30 +1,18 @@
import { Datum, TypedArray } from "plotly.js";
export const mergeDeep = (...objects: any[]) => {
const isObject = (obj: GraphCoordinates) => obj && typeof obj === "object";
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach((key) => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
} else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
} else {
prev[key] = oVal;
}
});
return prev;
}, {} as GraphData);
};
import {
TreeElement,
ToggleElement,
} from "../components/Installations/Log/CheckboxTree";
export interface GraphCoordinates {
x: Datum[] | Datum[][] | TypedArray;
y: Datum[] | Datum[][] | TypedArray;
xaxis?: string;
yaxis?: string;
barmode?: string;
marker?: { color: string };
type?: string;
name?: string;
}
export interface GraphData {
@ -41,19 +29,104 @@ export const parseCsv = (text: string) => {
}
return l.split(";");
});
console.log("text", y);
/* .filter((fields) => !isNaN(parseFloat(fields[1])));
*/
const x = y
.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;
};
export const flattenToggles = (toggles: TreeElement[]): ToggleElement => {
return toggles.reduce((acc, current) => {
if (current.children.length > 0) {
acc[current.id] = false;
return { ...acc, ...flattenToggles(current.children) };
}
acc[current.id] = false;
return acc;
}, {} as ToggleElement);
};
export const insertTreeElements = (
children: TreeElement[] = [],
[head, ...tail]: string[]
): TreeElement[] => {
let child = children.find((child) => child.name === head);
if (!child) {
children.push(
(child = {
id: head,
name: head,
children: [],
})
);
}
if (tail.length > 0) {
insertTreeElements(child.children, tail);
}
return children;
};
export const isText = (data: any): data is string => {
return typeof data === "string";
};
export const flattenBarGraphData = (arr: any): GraphCoordinates[] => {
return arr.reduce((flat: any, toFlatten: any) => {
return flat.concat(
Array.isArray(toFlatten) ? flattenBarGraphData(toFlatten) : toFlatten
);
}, []);
};
export const stringToColor = (str: string) => {
if (str.length === 0) {
return "#FFFFFF";
}
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let colour = "#";
for (var i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
colour += ("02" + value.toString(16)).substr(-2);
}
return colour;
};
export const isNumeric = (value: any) => {
return !isNaN(value) && !isNaN(parseFloat(value));
};
export const transformToBarGraphData = (data: GraphCoordinates) => {
let names: string[] = [];
const barGraphData = data.y.map((text, i) => {
if (isText(text)) {
const splitText = text.split(",");
return splitText.map((split) => {
const foundName = !!names.find((value) => value === split);
if (!foundName) {
names.push(split);
}
return {
x: [data.x[i]],
y: [1 / splitText.length],
barmode: "stack",
marker: { color: stringToColor(split) },
type: "bar",
name: split,
showlegend: !foundName,
} as any;
});
}
return [] as any;
});
return flattenBarGraphData(barGraphData);
};