diff --git a/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx b/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx index eae826ab7..f9d658042 100644 --- a/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx +++ b/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx @@ -103,6 +103,7 @@ const CheckboxTree = () => { overflowX: "hidden", position: ["sticky", "-webkit-sticky"], top: 1, + maxHeight: "90vh", }} > {renderTree(toggles)} diff --git a/typescript/Frontend/src/components/Installations/Log/DateRangePicker.tsx b/typescript/Frontend/src/components/Installations/Log/DateRangePicker.tsx index 1bdb2b9b2..24eef4ed4 100644 --- a/typescript/Frontend/src/components/Installations/Log/DateRangePicker.tsx +++ b/typescript/Frontend/src/components/Installations/Log/DateRangePicker.tsx @@ -1,16 +1,9 @@ import * as React from "react"; -import { Button } from "@mui/material"; -import { UnixTime, TimeSpan } from "../../../dataCache/time"; -import { createTimes } from "../../../util/graph.util"; -import { - DatePicker, - DateTimeValidationError, - LocalizationProvider, -} from "@mui/x-date-pickers"; +import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import dayjs from "dayjs"; import { FormattedMessage } from "react-intl"; -import { useState } from "react"; +import ShortcutButton from "./ShortcutButton"; interface DateRangePickerProps { setRange: (value: Date[]) => void; @@ -19,10 +12,6 @@ interface DateRangePickerProps { } const DateRangePicker = (props: DateRangePickerProps) => { const { setRange, range, getCacheSeries } = props; - const [fromDateError, setFromDateError] = - useState(null); - const [toDateError, setToDateError] = - useState(null); const handleChange = (fromDate: Date, toDate: Date) => { setRange([fromDate, toDate]); @@ -31,26 +20,6 @@ const DateRangePicker = (props: DateRangePickerProps) => { return ( <> -
- -
{ handleChange(newValue.toDate(), range[1]); } }} - onError={(err) => setFromDateError(err)} - slotProps={{ - textField: { - variant: "outlined", - error: !!fromDateError, - helperText: fromDateError - ? "From date needs to be before to date" - : "", - }, - }} /> { color: "red", }, }} - onError={(err) => setToDateError(err)} onChange={(newValue) => { if (newValue) { handleChange(range[0], newValue.toDate()); } }} - slotProps={{ - textField: { - variant: "outlined", - error: !!toDateError, - helperText: toDateError - ? "To date needs to be after from date" - : "", - }, - }} /> +
+ + + + + + + + + +
); }; diff --git a/typescript/Frontend/src/components/Installations/Log/Log.tsx b/typescript/Frontend/src/components/Installations/Log/Log.tsx index 93517caab..e5850300e 100644 --- a/typescript/Frontend/src/components/Installations/Log/Log.tsx +++ b/typescript/Frontend/src/components/Installations/Log/Log.tsx @@ -6,6 +6,7 @@ const Log = () => { return ( <> + ); diff --git a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx index 0e1b888bd..a35c95f67 100644 --- a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx +++ b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx @@ -1,6 +1,7 @@ import Plot from "react-plotly.js"; import { RecordSeries } from "../../../dataCache/data"; import { + Csv, GraphData, createTimes, flattenToggles, @@ -19,6 +20,11 @@ import { LogContext } from "../../Context/LogContextProvider"; import { isDefined } from "../../../dataCache/utils/maybe"; import { Data, Layout } from "plotly.js"; import { VariableSizeList as List, areEqual } from "react-window"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import { FormattedMessage } from "react-intl"; +import DateRangePicker from "./DateRangePicker"; +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { Alert } from "@mui/material"; const NUMBER_OF_NODES = 100; @@ -526,9 +532,7 @@ const ScalarGraph = () => { return () => subscription.unsubscribe(); }, [toggles]); - const fetchData = ( - timestamp: UnixTime - ): Promise>> => { + const fetchData = (timestamp: UnixTime): Promise> => { const s3Path = `${timestamp.ticks}.csv`; return s3Access .get(s3Path) @@ -569,7 +573,7 @@ const ScalarGraph = () => { transformedObject[key].x.push( new Date(item.time.ticks * 1000).toISOString() ); - transformedObject[key].y.push(item.value?.[key]); + transformedObject[key].y.push(item.value?.[key].value); }); } if ( @@ -601,6 +605,7 @@ const ScalarGraph = () => { ), NUMBER_OF_NODES ); + console.log("getcacheseries"); cache.getSeries(times); times$.next(times); }; @@ -630,9 +635,6 @@ const ScalarGraph = () => { barnorm: "percent", } : {}; - if (!isScalar) { - console.log("graphData", data[visibleGraphs[index]]); - } return (
{ return null; }, areEqual); - /* const renderGraphs = () => { - if (checkedToggles) { - const coordinateTimeSeries = transformToGraphData(timeSeries); - const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => { - return checkedToggles[path]; - }); - if (visibleGraphs.length > 0) { - return ( - <> - - - - {visibleGraphs.map((path) => { - const isScalar = isNumeric(coordinateTimeSeries[path].y[0]); - const data = isScalar - ? [ - { - ...coordinateTimeSeries[path], - type: "scatter", - mode: "lines+markers", - fill: "tozeroy", - }, - ] - : transformToBarGraphData(coordinateTimeSeries[path]); - const barGraphLayout: Partial = !isScalar - ? { - bargap: 0, - barmode: "stack", - barnorm: "percent", - } - : {}; - return ( - { - 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()); - getCacheSeries(xaxisRange0, xaxisRange1); - } - }} - /> - ); - })} - - ); - } - return ( - - - - ); - } - }; */ - if (checkedToggles) { + if ( + checkedToggles && + Object.keys(checkedToggles).find((toggle) => checkedToggles[toggle]) + ) { const coordinateTimeSeries = transformToGraphData(timeSeries); - console.log( - "length", - Object.keys(checkedToggles).filter((toggle) => checkedToggles[toggle]) - .length - ); return ( - checkedToggles[toggle]) - .length - } - itemSize={() => 500} - width="100%" - itemData={coordinateTimeSeries} - > - {Row} - + <> + + + + checkedToggles[toggle] + ).length + } + itemSize={() => 500} + width="100%" + itemData={coordinateTimeSeries} + > + {Row} + + ); } - return null; + return ( + + + + ); }; export default ScalarGraph; diff --git a/typescript/Frontend/src/components/Installations/Log/ShortcutButton.tsx b/typescript/Frontend/src/components/Installations/Log/ShortcutButton.tsx new file mode 100644 index 000000000..ee3e23a42 --- /dev/null +++ b/typescript/Frontend/src/components/Installations/Log/ShortcutButton.tsx @@ -0,0 +1,36 @@ +import { UnixTime, TimeSpan } from "../../../dataCache/time"; +import { createTimes } from "../../../util/graph.util"; +import InnovenergyButton from "../../Layout/InnovenergyButton"; + +interface ShortcutButtonProps { + setRange: (value: Date[]) => void; + getCacheSeries: (xaxisRange0: number, xaxisRange1: number) => void; + dayRange: number; + children?: React.ReactNode; +} + +const ShortcutButton = (props: ShortcutButtonProps) => { + return ( + { + const weekRange = createTimes( + UnixTime.now().rangeBefore(TimeSpan.fromDays(props.dayRange)), + 100 + ); + props.setRange([ + weekRange[0].toDate(), + weekRange[weekRange.length - 1].toDate(), + ]); + props.getCacheSeries( + weekRange[0].ticks, + weekRange[weekRange.length - 1].ticks + ); + }} + sx={{ mt: 2, mb: 2, mr: 2 }} + > + {props.children} + + ); +}; + +export default ShortcutButton; diff --git a/typescript/Frontend/src/components/Installations/Log/ToplogyBox.tsx b/typescript/Frontend/src/components/Installations/Log/TopologyBox.tsx similarity index 96% rename from typescript/Frontend/src/components/Installations/Log/ToplogyBox.tsx rename to typescript/Frontend/src/components/Installations/Log/TopologyBox.tsx index bf506e71d..e5a0b9728 100644 --- a/typescript/Frontend/src/components/Installations/Log/ToplogyBox.tsx +++ b/typescript/Frontend/src/components/Installations/Log/TopologyBox.tsx @@ -34,6 +34,7 @@ const TopologyBox = (props: TopologyBoxProps) => {
{el.label} {el.values.map((value) => { + console.log("value", value); return (

{ - const length = Math.abs((props.amount ?? 1) * (BOX_SIZE - 20)); + const length = Math.abs((props.amount ?? 1) * BOX_SIZE); return ( - <> +

- {props.data?.map((value) => value.values)}
-
+

+ {props.data?.map((value) => value.values)} +

+
@@ -47,7 +73,7 @@ const TopologyFlow = (props: TopologyFlowProps) => {
- +
); }; diff --git a/typescript/Frontend/src/components/Installations/Log/TopologyView.tsx b/typescript/Frontend/src/components/Installations/Log/TopologyView.tsx index 174b26cf5..c406bc76b 100644 --- a/typescript/Frontend/src/components/Installations/Log/TopologyView.tsx +++ b/typescript/Frontend/src/components/Installations/Log/TopologyView.tsx @@ -18,6 +18,7 @@ const TopologyView = () => { flexDirection: "row", overflow: "auto", padding: 2, + fontFamily: `"Ubuntu", sans-serif`, }} >
@@ -27,9 +28,9 @@ const TopologyView = () => { data: values.acInBus, }} centerConnection={{ - amount: 0.5, + amount: 0.6, data: values.gridToAcIn, - direction: "rightToLeft", + rightToLeft: true, }} />
@@ -40,9 +41,8 @@ const TopologyView = () => { data: values.acInBus, }} centerConnection={{ - amount: 0.5, + amount: 0.3, data: values.gridToAcIn, - direction: "leftToRight", }} />
@@ -55,7 +55,6 @@ const TopologyView = () => { centerConnection={{ amount: 0.5, data: values.gridToAcIn, - direction: "leftToRight", }} />
@@ -66,9 +65,8 @@ const TopologyView = () => { data: values.acOutBus, }} centerConnection={{ - amount: 0.5, + amount: 0.3, data: values.gridToAcIn, - direction: "leftToRight", }} />
@@ -79,7 +77,7 @@ const TopologyView = () => { data: values.acOutBus, }} topConnection={{ - amount: 0.5, + amount: 0.6, data: values.gridToAcIn, }} centerBox={{ @@ -87,9 +85,9 @@ const TopologyView = () => { data: values.acOutBus, }} centerConnection={{ - amount: 0.5, + amount: 0.2, data: values.gridToAcIn, - direction: "rightToLeft", + rightToLeft: true, }} /> @@ -100,7 +98,7 @@ const TopologyView = () => { data: values.acOutBus, }} centerConnection={{ - amount: 0.5, + amount: 0.8, data: values.gridToAcIn, }} /> diff --git a/typescript/Frontend/src/dataCache/data.ts b/typescript/Frontend/src/dataCache/data.ts index 74ffdd1a4..6ee818442 100644 --- a/typescript/Frontend/src/dataCache/data.ts +++ b/typescript/Frontend/src/dataCache/data.ts @@ -1,13 +1,14 @@ import { Maybe } from "yup"; import { Timestamped } from "./types"; import { isDefined } from "./utils/maybe"; +import { CsvEntry } from "../util/graph.util"; -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, diff --git a/typescript/Frontend/src/dataCache/dataCache.ts b/typescript/Frontend/src/dataCache/dataCache.ts index c123658ed..3bfeda572 100644 --- a/typescript/Frontend/src/dataCache/dataCache.ts +++ b/typescript/Frontend/src/dataCache/dataCache.ts @@ -6,7 +6,8 @@ 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"; +import { isNumber } from "./utils/runtimeTypeChecking"; +import { CsvEntry } from "../util/graph.util"; export const FetchResult = { notAvailable: "N/A", @@ -30,7 +31,7 @@ function reverseBits(x: number): number { return x >>> 0; } -export default class DataCache> { +export default class DataCache> { private readonly cache: SkipList> = new SkipList>(); private readonly resolution: TimeSpan; @@ -103,15 +104,20 @@ export default class DataCache> { const n = after.index - t; const pn = p + n; - let interpolated: Partial> = {}; + 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]; + const beforeData = dataBefore[k].value; + const afterData = Number(dataAfter[k].value); + let foo = interpolated[k]; + if (foo) { + foo.value = isNumber(beforeData) + ? (beforeData * n + afterData * p) / pn + : n < p + ? afterData + : beforeData; + } } return interpolated as T; diff --git a/typescript/Frontend/src/util/graph.util.tsx b/typescript/Frontend/src/util/graph.util.tsx index 8a9686483..0f06c4bcb 100644 --- a/typescript/Frontend/src/util/graph.util.tsx +++ b/typescript/Frontend/src/util/graph.util.tsx @@ -6,7 +6,7 @@ import { import { TimeRange, UnixTime } from "../dataCache/time"; import { DataPoint, DataRecord } from "../dataCache/data"; import { isDefined } from "../dataCache/utils/maybe"; -import { BoxData } from "../components/Installations/Log/ToplogyBox"; +import { BoxData } from "../components/Installations/Log/TopologyBox"; export interface GraphCoordinates { x: Datum[] | Datum[][] | TypedArray; @@ -50,22 +50,28 @@ export const extractTopologyValues = ( timeSeriesData: DataPoint ): TopologyValues | null => { const timeSeriesValue = timeSeriesData.value; + let topologyValues: (string | number)[]; if (isDefined(timeSeriesValue)) { - return Object.keys(topologyValues).reduce((acc, topologyKey) => { - const values = topologyValues[topologyKey].map( + return Object.keys(topologyPaths).reduce((acc, topologyKey) => { + const values = topologyPaths[topologyKey].map( (topologyPath) => timeSeriesValue[topologyPath] ); - console.log("values", topologyValues); + switch (topologyKey) { + case "gridToAcIn": + topologyValues = [ + values.reduce((acc, curr) => Number(acc) + Number(curr.value), 0), + ]; + break; + default: + topologyValues = values.map(({ value }) => value); + } return { ...acc, [topologyKey]: [ { - values: - topologyKey === "gridToAcIn" - ? [values.reduce((acc, curr) => Number(acc) + Number(curr))] - : values, - label: topologyValues[topologyKey][0].split("/").pop(), - unit: "V", + values: topologyValues, + label: topologyPaths[topologyKey][0].split("/").pop(), + unit: values[0].unit, } as BoxData, ], }; @@ -74,7 +80,7 @@ export const extractTopologyValues = ( return null; }; -export const topologyValues: { [key: string]: string[] } = { +export const topologyPaths: { [key: string]: string[] } = { gridToAcIn: [ "/GridMeter/Ac/L1/Power/Apparent", "/GridMeter/Ac/L2/Power/Apparent", @@ -101,7 +107,7 @@ export interface Csv { [key: string]: CsvEntry; } -export const parseCsv = (text: string) => { +export const parseCsv = (text: string): Csv => { console.log("split", text.split(/\r?\n/)); const y = text .split(/\r?\n/) @@ -113,11 +119,11 @@ export const parseCsv = (text: string) => { const x = y .map((fields) => { if (typeof fields[1] === "string") { - return { [fields[0]]: fields[1] }; + return { [fields[0]]: { value: fields[1], unit: fields[2] } }; } - return { [fields[0]]: parseFloat(fields[1]) }; + return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } }; }) - .reduce((acc, current) => ({ ...acc, ...current }), {} as any); + .reduce((acc, current) => ({ ...acc, ...current }), {} as Csv); return x; };