From b7c443fc93c05727b0e004050ee26e5d9c4ae3be Mon Sep 17 00:00:00 2001 From: Sina Blattmann Date: Tue, 2 May 2023 11:08:08 +0200 Subject: [PATCH] round graph times correctly --- .../Installations/Log/ScalarGraph.tsx | 153 +++++++++++++++--- typescript/Frontend/src/util/graph.util.tsx | 1 - 2 files changed, 134 insertions(+), 20 deletions(-) diff --git a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx index 655eb220f..95c5c57cf 100644 --- a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx +++ b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx @@ -1,23 +1,42 @@ import Plot from "react-plotly.js"; import { RecordSeries } from "../../../dataCache/data"; import { parseCsv, transformToGraphData } from "../../../util/graph.util"; -import { TimeSpan, UnixTime } from "../../../dataCache/time"; -import { useEffect, useMemo, useState } from "react"; +import { TimeRange, TimeSpan, UnixTime } from "../../../dataCache/time"; +import { ReactNode, 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 { TreeItem, TreeView } from "@mui/lab"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; +import { Checkbox } from "@mui/material"; + +export const createTimes = ( + range: TimeRange, + numberOfNodes: number +): UnixTime[] => { + const oneSpan = range.duration.divide(numberOfNodes); + const roundedRange = TimeRange.fromTimes( + range.start.round(oneSpan), + range.end.round(oneSpan) + ); + return roundedRange.sample(oneSpan); +}; + +const NUMBER_OF_NODES = 100; const ScalarGraph = () => { - const timeRange = UnixTime.fromTicks(1682085650) - .rangeBefore(TimeSpan.fromDays(1)) - .sample(TimeSpan.fromMinutes(30)); + const timeRange = createTimes( + UnixTime.fromTicks(1682085650).rangeBefore(TimeSpan.fromDays(1)), + NUMBER_OF_NODES + ); const [timeSeries, setTimeSeries] = useState([]); const [uiRevision, setUiRevision] = useState(Math.random()); const [range, setRange] = useState([ timeRange[0].toDate(), timeRange[timeRange.length - 1].toDate(), ]); - const [toggles, setToggles] = useState(null); + const [toggles, setToggles] = useState(null); const times$ = useMemo(() => new BehaviorSubject(timeRange), []); @@ -30,6 +49,24 @@ const ScalarGraph = () => { "" ); + function insert( + children: TreeElement[] = [], + [head, ...tail]: string[] + ): TreeElement[] { + let child = children.find((child) => child.name === head); + if (!child) + children.push( + (child = { + id: head + tail.join("_"), + name: head, + checked: false, + children: [], + }) + ); + if (tail.length > 0) insert(child.children, tail); + return children; + } + useEffect(() => { const subscription = cache.gotData .pipe( @@ -39,15 +76,76 @@ const ScalarGraph = () => { ) .subscribe(([_, times]) => { const timeSeries = cache.getSeries(times); - console.log("GOT DATA", _, times, timeSeries); setTimeSeries(timeSeries); - if (toggles === null) setToggles(timeSeries); + const toggleValues = timeSeries.find((timeStamp) => timeStamp.value); + + if (toggles === null && toggleValues && toggleValues.value) { + setToggles( + Object.keys(toggleValues.value) + .map((path) => path.split("/").slice(1)) + .reduce( + (children, path) => insert(children, path), + [] as TreeElement[] + ) + ); + } }); return () => subscription.unsubscribe(); }, []); - const roundToNearest30 = (date: Date) => { + interface TreeElement { + id: string; + name: string; + children: TreeElement[]; + checked: boolean; + } + + const getNodes = (element: TreeElement): null | ReactNode => { + return element.children.length > 0 ? renderTree(element.children) : null; + }; + + const renderTree = (data: TreeElement[]): ReactNode => { + return data.map((element) => { + return ( + + { + if (toggles) { + const togglesValue = toggles; + const index = toggles?.findIndex((toggle) => { + return element.id === toggle.id; + }); + if (index > 0) { + togglesValue[index] = { + ...element, + checked: !element.checked, + }; + setToggles(togglesValue); + } + } + }} + /> + {element.name} + + } + sx={{ + ".MuiTreeItem-content": { paddingY: "12px" }, + }} + > + {getNodes(element)} + + ); + }); + }; + + const round = (date: Date) => { const minutes = 30; const ms = 1000 * 60 * minutes; @@ -72,7 +170,6 @@ const ScalarGraph = () => { } }) .catch((e) => { - console.log(e); return Promise.resolve(FetchResult.tryLater); }); }; @@ -116,20 +213,19 @@ const ScalarGraph = () => { ], }} onRelayout={(params) => { - console.log("PARAMS", params); const xaxisRange0 = params["xaxis.range[0]"]; const xaxisRange1 = params["xaxis.range[1]"]; if (xaxisRange0 && xaxisRange1) { - const epoch0 = - roundToNearest30(new Date(xaxisRange0)).getTime() / 1000; - const epoch1 = - roundToNearest30(new Date(xaxisRange1)).getTime() / 1000; setRange([new Date(xaxisRange0), new Date(xaxisRange1)]); setUiRevision(Math.random()); - const times = UnixTime.fromTicks(epoch1) - .rangeBefore(TimeSpan.fromSeconds(epoch1 - epoch0)) - .sample(TimeSpan.fromMinutes(30)); + const times = createTimes( + TimeRange.fromTimes( + UnixTime.fromDate(new Date(xaxisRange0)), + UnixTime.fromDate(new Date(xaxisRange1)) + ), + NUMBER_OF_NODES + ); cache.getSeries(times); times$.next(times); } @@ -139,6 +235,25 @@ const ScalarGraph = () => { }); }; - return <>{renderGraphs()}; + return ( + <> + {renderGraphs()} + {toggles !== null && ( + } + defaultExpandIcon={} + sx={{ + height: 480, + flexGrow: 1, + overflow: "auto", + overflowX: "hidden", + }} + > + {renderTree(toggles)} + + )} + + ); }; export default ScalarGraph; diff --git a/typescript/Frontend/src/util/graph.util.tsx b/typescript/Frontend/src/util/graph.util.tsx index b1a56f793..040c091dd 100644 --- a/typescript/Frontend/src/util/graph.util.tsx +++ b/typescript/Frontend/src/util/graph.util.tsx @@ -40,7 +40,6 @@ export const transformToGraphData = (timeStampData: RecordSeries) => { }, {} as GraphData ); - console.log("acc", acc, timeStampObj); return mergeDeep(acc, timeStampObj); } return acc;