[WIP] bugfixing for graphs

This commit is contained in:
Sina Blattmann 2023-06-26 11:36:14 +02:00
parent b256e9bd75
commit 84697743c1
16 changed files with 214 additions and 97 deletions

View File

@ -1,11 +0,0 @@
import { FormattedMessage } from "react-intl";
const Alarms = () => {
return (
<div>
<FormattedMessage id="alarms" defaultMessage="Alarms" />
</div>
);
};
export default Alarms;

View File

@ -42,7 +42,7 @@ const InstallationList = (props: InstallationListProps) => {
const routeMatch = useRouteMatch([
routes.installations + routes.list + routes.installation + ":id",
routes.installations + routes.list + routes.alarms + ":id",
routes.installations + routes.list + routes.liveView + ":id",
routes.installations + routes.list + routes.log + ":id",
]);

View File

@ -9,7 +9,7 @@ import { AntTabs, StyledTab } from "../../util/installation.util";
const InstallationTabs = () => {
const routeMatch = useRouteMatch([
routes.installations + routes.list + routes.installation + ":id",
routes.installations + routes.list + routes.alarms + ":id",
routes.installations + routes.list + routes.liveView + ":id",
routes.installations + routes.list + routes.log + ":id",
]);
@ -40,14 +40,16 @@ const InstallationTabs = () => {
to={routes.installation + id}
/>
<StyledTab
id={"installation-tab-alarms"}
id={"installation-tab-liveView"}
label={intl.formatMessage({
id: "alarms",
defaultMessage: "Alarms",
id: "liveView",
defaultMessage: "Live view",
})}
value={routes.installations + routes.list + routes.alarms + ":id"}
value={
routes.installations + routes.list + routes.liveView + ":id"
}
component={Link}
to={routes.alarms + id}
to={routes.liveView + id}
/>
<StyledTab
id={"installation-tab-log"}

View File

@ -1,6 +1,6 @@
import { Grid } from "@mui/material";
import { Routes, Route } from "react-router";
import Alarms from "./Alarms";
import LiveView from "./LiveView";
import InstallationTabs from "./InstallationTabs";
import Log from "./Log/Log";
import routes from "../../routes.json";
@ -36,7 +36,7 @@ const Installations = () => {
element={<Installation />}
index
/>
<Route path={routes.alarms + ":id"} element={<Alarms />} />
<Route path={routes.liveView + ":id"} element={<LiveView />} />
<Route path={routes.log + ":id"} element={<Log />} />
</Routes>
</Grid>

View File

@ -0,0 +1,7 @@
import TopologyView from "./Log/TopologyView";
const LiveView = () => {
return <TopologyView />;
};
export default LiveView;

View File

@ -10,7 +10,7 @@ import { TimeRange, UnixTime } from "../../../dataCache/time";
interface DateRangePickerProps {
setRange: (value: Date[]) => void;
range: Date[];
getCacheSeries: (xaxisRange0: number, xaxisRange1: number) => void;
getCacheSeries: (xaxisRange0: Date, xaxisRange1: Date) => void;
}
const DateRangePicker = (props: DateRangePickerProps) => {
const { setRange, range, getCacheSeries } = props;
@ -23,8 +23,11 @@ const DateRangePicker = (props: DateRangePickerProps) => {
),
100
);
setRange([timeRange[0].toDate(), timeRange[timeRange.length - 1].toDate()]);
getCacheSeries(timeRange[0].ticks, timeRange[timeRange.length - 1].ticks);
const timeRange0 = timeRange[0].toDate();
const timeRange1 = timeRange[timeRange.length - 1].toDate();
setRange([timeRange0, timeRange1]);
getCacheSeries(timeRange0, timeRange1);
};
return (

View File

@ -1,11 +1,9 @@
import React from "react";
import ScalarGraph from "./ScalarGraph";
import TopologyView from "./TopologyView";
const Log = () => {
return (
<>
<TopologyView />
<ScalarGraph />
</>
);

View File

@ -491,6 +491,7 @@ const s3Access = new S3Access(
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g",
""
);
export const fetchData = (
timestamp: UnixTime
): Promise<FetchResult<DataRecord>> => {
@ -516,7 +517,7 @@ export const fetchData = (
const ScalarGraph = () => {
const timeRange = createTimes(
UnixTime.now() /* .fromTicks(1682085650) */
.rangeBefore(TimeSpan.fromHours(5)),
.rangeBefore(TimeSpan.fromDays(1)),
NUMBER_OF_NODES
);
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
@ -596,11 +597,11 @@ const ScalarGraph = () => {
);
};
const getCacheSeries = (xaxisRange0: number, xaxisRange1: number) => {
const getCacheSeries = (xaxisRange0: Date, xaxisRange1: Date) => {
const times = createTimes(
TimeRange.fromTimes(
UnixTime.fromTicks(xaxisRange0),
UnixTime.fromTicks(xaxisRange1)
UnixTime.fromDate(xaxisRange0),
UnixTime.fromDate(xaxisRange1)
),
NUMBER_OF_NODES
);
@ -661,14 +662,29 @@ const ScalarGraph = () => {
"autoScale2d",
],
}}
onUpdate={(params) => {
console.log("event onupdate", params);
}}
onSliderChange={(params) => {
console.log("event sliderchange", params);
}}
onRestyle={(params) => {
console.log("event restyle", params);
}}
onRelayout={(params) => {
console.log("event relayout", params);
const xaxisRange0 = params["xaxis.range[0]"];
const xaxisRange1 = params["xaxis.range[1]"];
console.log("relayout", xaxisRange0, xaxisRange1);
if (xaxisRange0 && xaxisRange1) {
setRange([new Date(xaxisRange0), new Date(xaxisRange1)]);
console.log("relayout", xaxisRange0, xaxisRange1);
const range0 = new Date(xaxisRange0);
const range1 = new Date(xaxisRange1);
setRange([range0, range1]);
setUiRevision(Math.random());
getCacheSeries(xaxisRange0, xaxisRange1);
getCacheSeries(range0, range1);
}
}}
/>
@ -680,13 +696,13 @@ const ScalarGraph = () => {
if (
checkedToggles &&
Object.keys(checkedToggles).find((toggle) => checkedToggles[toggle])
Object.keys(checkedToggles).find((toggle) => {
return checkedToggles[toggle];
})
) {
console.log("timeseries", timeSeries);
const coordinateTimeSeries = transformToGraphData(timeSeries);
console.log("timeSeries", timeSeries);
return (
<>
<div style={{ marginTop: "20px" }}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateRangePicker
setRange={setRange}
@ -699,7 +715,7 @@ const ScalarGraph = () => {
itemCount={
Object.keys(checkedToggles).filter(
(toggle) => checkedToggles[toggle]
).length
).length - 1
}
itemSize={() => 500}
width="100%"
@ -707,9 +723,10 @@ const ScalarGraph = () => {
>
{Row}
</List>
</>
</div>
);
}
return (
<Alert sx={{ mt: 2 }} severity="info">
<FormattedMessage

View File

@ -4,7 +4,7 @@ import InnovenergyButton from "../../Layout/InnovenergyButton";
interface ShortcutButtonProps {
setRange: (value: Date[]) => void;
getCacheSeries: (xaxisRange0: number, xaxisRange1: number) => void;
getCacheSeries: (xaxisRange0: Date, xaxisRange1: Date) => void;
dayRange: number;
children?: React.ReactNode;
}
@ -13,19 +13,15 @@ const ShortcutButton = (props: ShortcutButtonProps) => {
return (
<InnovenergyButton
onClick={() => {
const weekRange = createTimes(
const range = createTimes(
UnixTime.now().rangeBefore(TimeSpan.fromDays(props.dayRange)),
100
);
console.log("weekrange", weekRange[0].ticks);
props.setRange([
weekRange[0].toDate(),
weekRange[weekRange.length - 1].toDate(),
]);
props.getCacheSeries(
weekRange[0].ticks,
weekRange[weekRange.length - 1].ticks
);
const range0 = range[0].toDate();
const range1 = range[range.length - 1].toDate();
props.setRange([range0, range1]);
props.getCacheSeries(range0, range1);
}}
sx={{ mt: 2, mb: 2, mr: 2 }}
>

View File

@ -1,4 +1,5 @@
import { Box } from "@mui/material";
import { getBoxColor } from "../../../util/graph.util";
export type BoxData = {
label: string;
@ -17,6 +18,8 @@ const isInt = (value: number) => {
export const BOX_SIZE = 85;
const TopologyBox = (props: TopologyBoxProps) => {
const { titleColor, boxColor } = getBoxColor(props.title);
if (props.title === "Battery") console.log(props.data?.values, "data");
return (
<Box
sx={{
@ -31,31 +34,37 @@ const TopologyBox = (props: TopologyBoxProps) => {
style={{
marginBlockStart: "0",
marginBlockEnd: "0",
backgroundColor: "#e74c3c",
backgroundColor: titleColor,
padding: "5px",
borderTopLeftRadius: "4px",
borderTopRightRadius: "4px",
display: "flex",
justifyContent: "center",
}}
>
{props.title}
</p>
<div
style={{
backgroundColor: "#c0392b",
backgroundColor: boxColor,
borderBottomLeftRadius: "4px",
borderBottomRightRadius: "4px",
padding: "5px",
height: "52px",
height: "51px",
}}
>
{props.data && (
<>
{props.data.values.map((value) => {
{props.data.values.map((value, index) => {
return (
<p
style={{ marginBlockStart: "0", marginBlockEnd: "2px" }}
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
>{`${
props.data && props.data.values.length === 3
? "L" + (index + 1) + " "
: ""
}${
!isInt(Number(value)) ? Number(value).toPrecision(4) : value
}${props.data?.unit}`}</p>
);

View File

@ -9,6 +9,7 @@ export type TopologyFlowProps = {
hidden?: boolean;
};
const TopologyFlow = (props: TopologyFlowProps) => {
console.log("amount", props.amount, props.data?.values);
const length = Math.abs((props.amount ?? 1) * BOX_SIZE);
const values = props.data?.values;
return (
@ -25,7 +26,7 @@ const TopologyFlow = (props: TopologyFlowProps) => {
sx={{
width: props.orientation === "horizontal" ? BOX_SIZE : length,
height: props.orientation === "vertical" ? BOX_SIZE : length,
backgroundColor: "#f4b3504d",
backgroundColor: "#FFEBCD",
visibility: props.hidden || !props.data ? "hidden" : "visible",
display: "flex",
alignItems: "center",
@ -37,11 +38,16 @@ const TopologyFlow = (props: TopologyFlowProps) => {
style={{
position: "absolute",
zIndex: 1,
width: BOX_SIZE,
display: "flex",
justifyContent: "center",
}}
>
{values?.map(
(value) => `${Math.round(value as number)} ${props.data?.unit}`
)}
{values
?.filter((value) => (value as number) !== 0)
.map(
(value) => `${Math.round(value as number)} ${props.data?.unit}`
)}
</p>
<div
className="container"

View File

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Alert, Box } from "@mui/material";
import TopologyColumn from "./TopologyColumn";
import { TimeSpan, UnixTime } from "../../../dataCache/time";
import {
@ -10,6 +10,7 @@ import {
import { fetchData } from "./ScalarGraph";
import { useEffect, useState } from "react";
import { FetchResult } from "../../../dataCache/dataCache";
import { FormattedMessage } from "react-intl";
const TopologyView = () => {
const [values, setValues] = useState<TopologyValues | null>(null);
@ -31,7 +32,7 @@ const TopologyView = () => {
);
}
})
.catch((err) => console.log(err));
.catch((err) => console.error(err));
}, 2000);
return () => clearInterval(interval);
@ -66,12 +67,15 @@ const TopologyView = () => {
<div>
<TopologyColumn
centerBox={{
title: "GridBus",
data: values.islandBus,
title: "Grid Bus",
data: values.gridBus,
}}
centerConnection={{
amount: 0.3,
data: values.gridToAcInConnection,
amount: getAmount(
highestConnectionValue,
values.gridBusToIslandBusConnection.values
),
data: values.gridBusToIslandBusConnection,
}}
topConnection={{
amount: getAmount(
@ -81,7 +85,7 @@ const TopologyView = () => {
data: values.gridBusToPvOnGridbusConnection,
}}
topBox={{
title: "PvOnGridBus",
title: "Pv",
}}
bottomConnection={{
amount: getAmount(
@ -91,22 +95,35 @@ const TopologyView = () => {
data: values.gridBusToLoadOnGridBusConnection,
}}
bottomBox={{
title: "LoadOnGridBus",
title: "Load",
}}
/>
</div>
<div>
<TopologyColumn
topBox={{
title: "Pv",
}}
topConnection={{
amount: getAmount(
highestConnectionValue,
values.pvOnIslandBusToIslandBusConnection.values
),
data: values.pvOnIslandBusToIslandBusConnection,
}}
centerBox={{
title: "IslandBus",
title: "Island Bus",
data: values.islandBus,
}}
centerConnection={{
amount: 0.5,
data: values.gridToAcInConnection,
amount: getAmount(
highestConnectionValue,
values.islandBusToInverter.values
),
data: values.islandBusToInverter,
}}
bottomBox={{
title: "LoadOnIslandBus",
title: "Load",
}}
bottomConnection={{
amount: getAmount(
@ -121,25 +138,31 @@ const TopologyView = () => {
<TopologyColumn
centerBox={{
title: "Inverter",
data: values.islandBus,
data: values.inverter,
}}
centerConnection={{
amount: 0.3,
data: values.gridToAcInConnection,
amount: getAmount(
highestConnectionValue,
values.inverterToDcBus.values
),
data: values.inverterToDcBus,
}}
/>
</div>
<div>
<TopologyColumn
topBox={{
title: "PvOnDc",
title: "Pv",
}}
topConnection={{
amount: 0.6,
data: values.gridToAcInConnection,
amount: getAmount(
highestConnectionValue,
values.pvOnDcBusToDcBusConnection.values
),
data: values.pvOnDcBusToDcBusConnection,
}}
centerBox={{
title: "DcBus",
title: "Dc Bus",
data: values.dcBus,
}}
centerConnection={{
@ -150,11 +173,14 @@ const TopologyView = () => {
data: values.dcBusToDcDcConnection,
}}
bottomConnection={{
amount: 0.2,
amount: getAmount(
highestConnectionValue,
values.dcBusToLoadOnDcConnection.values
),
data: values.dcBusToLoadOnDcConnection,
}}
bottomBox={{
title: "LoadOnDc",
title: "Load",
}}
/>
</div>
@ -162,7 +188,7 @@ const TopologyView = () => {
<TopologyColumn
centerBox={{
title: "DcDc",
data: values.islandBus,
data: values.dcDc,
}}
centerConnection={{
amount: getAmount(
@ -184,7 +210,14 @@ const TopologyView = () => {
</Box>
);
}
return null;
return (
<Alert severity="info" sx={{ mt: 1 }}>
<FormattedMessage
id="noData"
defaultMessage="Couldn't find any live data"
/>
</Alert>
);
};
export default TopologyView;

View File

@ -1,5 +1,5 @@
{
"alarms": "Alarme",
"liveView": "Live Ansicht",
"allInstallations": "Alle Installationen",
"applyChanges": "Änderungen übernehmen",
"country": "Land",

View File

@ -1,5 +1,5 @@
{
"alarms": "Alarms",
"liveView": "Live view",
"allInstallations": "All installations",
"applyChanges": "Apply changes",
"country": "Country",

View File

@ -1,6 +1,6 @@
{
"installation": "installation/",
"alarms": "alarms/",
"liveView": "liveView/",
"users": "/users/",
"log": "log/",
"installations": "/installations/",

View File

@ -48,17 +48,28 @@ export type TopologyValues = {
islandBusToLoadOnIslandBusConnection: BoxData;
gridBusToPvOnGridbusConnection: BoxData;
gridBusToLoadOnGridBusConnection: BoxData;
inverter: BoxData;
dcDc: BoxData;
islandBusToInverter: BoxData;
inverterToDcBus: BoxData;
gridBusToIslandBusConnection: BoxData;
pvOnDcBusToDcBusConnection: BoxData;
pvOnIslandBusToIslandBusConnection: BoxData;
};
type TopologyPaths = { [key in keyof TopologyValues]: string[] };
export const topologyPaths: TopologyPaths = {
gridToAcInConnection: [
"/GridMeter/Ac/L1/Power/Apparent",
"/GridMeter/Ac/L2/Power/Apparent",
"/GridMeter/Ac/L3/Power/Apparent",
"/GridMeter/Ac/L1/Power/Active",
"/GridMeter/Ac/L2/Power/Active",
"/GridMeter/Ac/L3/Power/Active",
],
gridBus: [
"/GridMeter/Ac/L1/Voltage",
"/GridMeter/Ac/L2/Voltage",
"/GridMeter/Ac/L3/Voltage",
],
gridBus: ["/GridMeter/Ac/L2/Voltage"],
islandBus: [
"/AcDc/Ac/L1/Voltage",
"/AcDc/Ac/L2/Voltage",
@ -69,12 +80,58 @@ export const topologyPaths: TopologyPaths = {
dcDCToBatteryConnection: ["/DcDc/Dc/Battery/Power"],
battery: ["/Battery/Soc", "/Battery/Dc/Voltage"],
dcBusToLoadOnDcConnection: ["/LoadOnDc/Power"],
islandBusToLoadOnIslandBusConnection: ["/LoadOnAcIsland/Ac/Power/Apparent"],
gridBusToPvOnGridbusConnection: ["/PvOnAcGrid/Power/Apparent"],
gridBusToLoadOnGridBusConnection: ["/LoadOnAcGrid/Power/Apparent"],
islandBusToLoadOnIslandBusConnection: ["/LoadOnAcIsland/Ac/Power/Active"],
gridBusToPvOnGridbusConnection: ["/PvOnAcGrid/Power/Active"],
gridBusToLoadOnGridBusConnection: ["/LoadOnAcGrid/Power/Active"],
inverter: ["/AcDc/Devices/1/Status/Ac/Power/Active"],
dcDc: ["/DcDc/Devices/1/Status/Dc/Battery/Voltage"],
islandBusToInverter: ["/AcDc/Ac/Power/Active"],
inverterToDcBus: ["/AcDc/Dc/Power"],
gridBusToIslandBusConnection: ["/AcGridToAcIsland/Power/Active"],
pvOnDcBusToDcBusConnection: ["/PvOnDc/Dc/Power"],
pvOnIslandBusToIslandBusConnection: ["/PvOnAcIsland/Power/Active"],
};
const getBoxColor = () => {};
interface BoxColor {
titleColor: string;
boxColor: string;
}
export const getBoxColor = (boxTitle?: string): BoxColor => {
switch (boxTitle) {
case "Grid":
return {
titleColor: "#e74c3c",
boxColor: "#c0392b",
};
case "Island Bus":
case "Grid Bus":
return {
titleColor: "#adadad",
boxColor: "#8e8e8e",
};
case "Inverter":
return {
titleColor: "#4789d0",
boxColor: "#4789d0",
};
case "Pv":
return {
titleColor: "#f4b350",
boxColor: "#f39c12",
};
case "Load":
return {
titleColor: "#2ecc71",
boxColor: "#27ae60",
};
default:
return {
titleColor: "#e74c3c",
boxColor: "#c0392b",
};
}
};
export const extractTopologyValues = (
timeSeriesData: DataPoint
@ -86,7 +143,7 @@ export const extractTopologyValues = (
const values = topologyPaths[topologyKey as keyof TopologyValues].map(
(topologyPath) => timeSeriesValue[topologyPath]
);
console.log("AAA", topologyKey);
console.log("values", values, topologyKey);
switch (topologyKey as keyof TopologyValues) {
case "gridToAcInConnection":
topologyValues = [
@ -94,7 +151,7 @@ export const extractTopologyValues = (
];
break;
default:
topologyValues = values.map(({ value }) => value);
topologyValues = values.map((element) => element.value);
}
console.log("topologyValues", topologyValues);
return {
@ -116,8 +173,9 @@ export const getHighestConnectionValue = (values: TopologyValues) =>
Object.keys(values)
.filter((value) => value.includes("Connection"))
.reduce((acc, curr) => {
const value = values[curr as keyof TopologyValues].values[0] as number;
console.log("reduce", value, acc);
const value = Math.abs(
values[curr as keyof TopologyValues].values[0] as number
);
return value > acc ? value : acc;
}, 0);
@ -125,8 +183,7 @@ export const getAmount = (
highestConnectionValue: number,
values: (string | number)[]
) => {
console.log("amount", values[0] as number, highestConnectionValue);
return (values[0] as number) / highestConnectionValue;
return Math.abs(values[0] as number) / highestConnectionValue;
};
export interface CsvEntry {