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

View File

@ -1,30 +1,18 @@
import { Datum, TypedArray } from "plotly.js"; import { Datum, TypedArray } from "plotly.js";
import {
export const mergeDeep = (...objects: any[]) => { TreeElement,
const isObject = (obj: GraphCoordinates) => obj && typeof obj === "object"; ToggleElement,
return objects.reduce((prev, obj) => { } from "../components/Installations/Log/CheckboxTree";
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);
};
export interface GraphCoordinates { export interface GraphCoordinates {
x: Datum[] | Datum[][] | TypedArray; x: Datum[] | Datum[][] | TypedArray;
y: Datum[] | Datum[][] | TypedArray; y: Datum[] | Datum[][] | TypedArray;
xaxis?: string; xaxis?: string;
yaxis?: string; yaxis?: string;
barmode?: string;
marker?: { color: string };
type?: string;
name?: string;
} }
export interface GraphData { export interface GraphData {
@ -41,19 +29,104 @@ export const parseCsv = (text: string) => {
} }
return l.split(";"); return l.split(";");
}); });
console.log("text", y);
/* .filter((fields) => !isNaN(parseFloat(fields[1])));
*/
const x = y const x = y
.map((fields) => { .map((fields) => {
if (typeof fields[1] === "string") { if (typeof fields[1] === "string") {
console.log("if inside", fields, { [fields[0]]: fields[1] });
return { [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]) }; return { [fields[0]]: parseFloat(fields[1]) };
}) })
.reduce((acc, current) => ({ ...acc, ...current }), {} as any); .reduce((acc, current) => ({ ...acc, ...current }), {} as any);
return x; 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);
};