[WIP] add color and style to liveview, add new connections, refactor components, nearly finished
This commit is contained in:
parent
4b9f3e6a84
commit
9e56dffb27
|
@ -53,10 +53,13 @@ const UsersWithInheritedAccess = () => {
|
||||||
<Link
|
<Link
|
||||||
id={"inherited-access-user-link-" + user.id}
|
id={"inherited-access-user-link-" + user.id}
|
||||||
to={
|
to={
|
||||||
routes.groups + routes.manageAccess + user.parentId
|
routes.installations +
|
||||||
|
routes.tree +
|
||||||
|
routes.manageAccess +
|
||||||
|
user.parentId
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{folderName}
|
{` ${folderName}`}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import ShortcutButton from "./ShortcutButton";
|
import ShortcutButton from "./ShortcutButton";
|
||||||
|
import { createTimes } from "../../../util/graph.util";
|
||||||
|
import { TimeRange, UnixTime } from "../../../dataCache/time";
|
||||||
|
|
||||||
interface DateRangePickerProps {
|
interface DateRangePickerProps {
|
||||||
setRange: (value: Date[]) => void;
|
setRange: (value: Date[]) => void;
|
||||||
|
@ -14,8 +16,15 @@ const DateRangePicker = (props: DateRangePickerProps) => {
|
||||||
const { setRange, range, getCacheSeries } = props;
|
const { setRange, range, getCacheSeries } = props;
|
||||||
|
|
||||||
const handleChange = (fromDate: Date, toDate: Date) => {
|
const handleChange = (fromDate: Date, toDate: Date) => {
|
||||||
setRange([fromDate, toDate]);
|
const timeRange = createTimes(
|
||||||
getCacheSeries(fromDate.getMilliseconds(), toDate.getMilliseconds());
|
TimeRange.fromTimes(
|
||||||
|
UnixTime.fromDate(fromDate),
|
||||||
|
UnixTime.fromDate(toDate)
|
||||||
|
),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
setRange([timeRange[0].toDate(), timeRange[timeRange.length - 1].toDate()]);
|
||||||
|
getCacheSeries(timeRange[0].ticks, timeRange[timeRange.length - 1].ticks);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,7 +6,6 @@ const Log = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopologyView />
|
<TopologyView />
|
||||||
|
|
||||||
<ScalarGraph />
|
<ScalarGraph />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import Plot from "react-plotly.js";
|
import Plot from "react-plotly.js";
|
||||||
import { RecordSeries } from "../../../dataCache/data";
|
import { DataRecord, RecordSeries } from "../../../dataCache/data";
|
||||||
import {
|
import {
|
||||||
Csv,
|
|
||||||
GraphData,
|
GraphData,
|
||||||
createTimes,
|
createTimes,
|
||||||
flattenToggles,
|
flattenToggles,
|
||||||
|
@ -481,12 +480,43 @@ export const testData = `/AcDc/SystemControl/Alarms;;
|
||||||
/Config/HoldSocZone;1;
|
/Config/HoldSocZone;1;
|
||||||
/Config/ControllerPConstant;0.5;
|
/Config/ControllerPConstant;0.5;
|
||||||
/SystemState/Message;Panic: Unknown State!;
|
/SystemState/Message;Panic: Unknown State!;
|
||||||
/SystemState/Id;100;`;
|
/SystemState/Id;100;
|
||||||
|
/LoadOnDc/Power;100;`;
|
||||||
|
|
||||||
|
const s3Access = new S3Access(
|
||||||
|
"saliomameiringen",
|
||||||
|
"sos-ch-dk-2",
|
||||||
|
"exo.io",
|
||||||
|
"EXO464a9ff62fdfa407aa742855",
|
||||||
|
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
export const fetchData = (
|
||||||
|
timestamp: UnixTime
|
||||||
|
): Promise<FetchResult<DataRecord>> => {
|
||||||
|
const s3Path = `${timestamp.ticks}.csv`;
|
||||||
|
return s3Access
|
||||||
|
.get(s3Path)
|
||||||
|
.then(async (r) => {
|
||||||
|
if (r.status === 404) {
|
||||||
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
|
} else if (r.status === 200) {
|
||||||
|
const text = await r.text();
|
||||||
|
return parseCsv(text);
|
||||||
|
} else {
|
||||||
|
console.error("unexpected status code");
|
||||||
|
return Promise.resolve(FetchResult.notAvailable);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
return Promise.resolve(FetchResult.tryLater);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const ScalarGraph = () => {
|
const ScalarGraph = () => {
|
||||||
const timeRange = createTimes(
|
const timeRange = createTimes(
|
||||||
UnixTime.now() /* .fromTicks(1682085650) */
|
UnixTime.now() /* .fromTicks(1682085650) */
|
||||||
.rangeBefore(TimeSpan.fromDays(7)),
|
.rangeBefore(TimeSpan.fromHours(5)),
|
||||||
NUMBER_OF_NODES
|
NUMBER_OF_NODES
|
||||||
);
|
);
|
||||||
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
|
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
|
||||||
|
@ -502,15 +532,6 @@ const ScalarGraph = () => {
|
||||||
|
|
||||||
const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
|
const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
|
||||||
|
|
||||||
const s3Access = new S3Access(
|
|
||||||
"saliomameiringen",
|
|
||||||
"sos-ch-dk-2",
|
|
||||||
"exo.io",
|
|
||||||
"EXO464a9ff62fdfa407aa742855",
|
|
||||||
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = cache.gotData
|
const subscription = cache.gotData
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@ -532,26 +553,6 @@ const ScalarGraph = () => {
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
}, [toggles]);
|
}, [toggles]);
|
||||||
|
|
||||||
const fetchData = (timestamp: UnixTime): Promise<FetchResult<Csv>> => {
|
|
||||||
const s3Path = `${timestamp.ticks}.csv`;
|
|
||||||
return s3Access
|
|
||||||
.get(s3Path)
|
|
||||||
.then(async (r) => {
|
|
||||||
if (r.status === 404) {
|
|
||||||
return Promise.resolve(FetchResult.notAvailable);
|
|
||||||
} else if (r.status === 200) {
|
|
||||||
const text = await r.text();
|
|
||||||
return parseCsv(text);
|
|
||||||
} else {
|
|
||||||
console.error("unexpected status code");
|
|
||||||
return Promise.resolve(FetchResult.notAvailable);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
return Promise.resolve(FetchResult.tryLater);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const cache = useMemo(
|
const cache = useMemo(
|
||||||
() => new DataCache(fetchData, TimeSpan.fromSeconds(2)),
|
() => new DataCache(fetchData, TimeSpan.fromSeconds(2)),
|
||||||
[]
|
[]
|
||||||
|
@ -570,9 +571,7 @@ const ScalarGraph = () => {
|
||||||
marker: { color: stringToColor(key) },
|
marker: { color: stringToColor(key) },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
transformedObject[key].x.push(
|
transformedObject[key].x.push(new Date(item.time.ticks * 1000));
|
||||||
new Date(item.time.ticks * 1000).toISOString()
|
|
||||||
);
|
|
||||||
transformedObject[key].y.push(item.value?.[key].value);
|
transformedObject[key].y.push(item.value?.[key].value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -600,12 +599,11 @@ const ScalarGraph = () => {
|
||||||
const getCacheSeries = (xaxisRange0: number, xaxisRange1: number) => {
|
const getCacheSeries = (xaxisRange0: number, xaxisRange1: number) => {
|
||||||
const times = createTimes(
|
const times = createTimes(
|
||||||
TimeRange.fromTimes(
|
TimeRange.fromTimes(
|
||||||
UnixTime.fromDate(new Date(xaxisRange0)),
|
UnixTime.fromTicks(xaxisRange0),
|
||||||
UnixTime.fromDate(new Date(xaxisRange1))
|
UnixTime.fromTicks(xaxisRange1)
|
||||||
),
|
),
|
||||||
NUMBER_OF_NODES
|
NUMBER_OF_NODES
|
||||||
);
|
);
|
||||||
console.log("getcacheseries");
|
|
||||||
cache.getSeries(times);
|
cache.getSeries(times);
|
||||||
times$.next(times);
|
times$.next(times);
|
||||||
};
|
};
|
||||||
|
@ -615,7 +613,6 @@ const ScalarGraph = () => {
|
||||||
const visibleGraphs = Object.keys(data).filter((path) => {
|
const visibleGraphs = Object.keys(data).filter((path) => {
|
||||||
return checkedToggles ? checkedToggles[path] : false;
|
return checkedToggles ? checkedToggles[path] : false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data[visibleGraphs[index]]) {
|
if (data[visibleGraphs[index]]) {
|
||||||
const isScalar = isNumeric(data[visibleGraphs[index]].y[0]);
|
const isScalar = isNumeric(data[visibleGraphs[index]].y[0]);
|
||||||
const graphData = isScalar
|
const graphData = isScalar
|
||||||
|
@ -685,7 +682,9 @@ const ScalarGraph = () => {
|
||||||
checkedToggles &&
|
checkedToggles &&
|
||||||
Object.keys(checkedToggles).find((toggle) => checkedToggles[toggle])
|
Object.keys(checkedToggles).find((toggle) => checkedToggles[toggle])
|
||||||
) {
|
) {
|
||||||
|
console.log("timeseries", timeSeries);
|
||||||
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
||||||
|
console.log("timeSeries", timeSeries);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
|
|
|
@ -17,6 +17,7 @@ const ShortcutButton = (props: ShortcutButtonProps) => {
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(props.dayRange)),
|
UnixTime.now().rangeBefore(TimeSpan.fromDays(props.dayRange)),
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
|
console.log("weekrange", weekRange[0].ticks);
|
||||||
props.setRange([
|
props.setRange([
|
||||||
weekRange[0].toDate(),
|
weekRange[0].toDate(),
|
||||||
weekRange[weekRange.length - 1].toDate(),
|
weekRange[weekRange.length - 1].toDate(),
|
||||||
|
|
|
@ -8,45 +8,61 @@ export type BoxData = {
|
||||||
|
|
||||||
export type TopologyBoxProps = {
|
export type TopologyBoxProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
data?: BoxData[];
|
data?: BoxData;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isInt = (value: number) => {
|
const isInt = (value: number) => {
|
||||||
return value % 1 === 0;
|
return value % 1 === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BOX_SIZE = 150;
|
export const BOX_SIZE = 85;
|
||||||
const TopologyBox = (props: TopologyBoxProps) => {
|
const TopologyBox = (props: TopologyBoxProps) => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: "1px solid grey",
|
|
||||||
visibility: props.title ? "visible" : "hidden",
|
visibility: props.title ? "visible" : "hidden",
|
||||||
height: BOX_SIZE + "px",
|
height: BOX_SIZE + "px",
|
||||||
width: BOX_SIZE + "px",
|
width: BOX_SIZE + "px",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
|
color: "white",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ padding: "5px" }}>
|
<p
|
||||||
<p style={{ marginBlockStart: "2px" }}>{props.title}</p>
|
style={{
|
||||||
{props.data &&
|
marginBlockStart: "0",
|
||||||
props.data.map((el) => (
|
marginBlockEnd: "0",
|
||||||
<div>
|
backgroundColor: "#e74c3c",
|
||||||
{el.label}
|
padding: "5px",
|
||||||
{el.values.map((value) => {
|
borderTopLeftRadius: "4px",
|
||||||
console.log("value", value);
|
borderTopRightRadius: "4px",
|
||||||
return (
|
}}
|
||||||
<p
|
>
|
||||||
style={{ marginBlockStart: "2px", marginBlockEnd: "2px" }}
|
{props.title}
|
||||||
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
</p>
|
||||||
>{`${
|
<div
|
||||||
!isInt(Number(value)) ? Number(value).toPrecision(8) : value
|
style={{
|
||||||
}${el.unit}`}</p>
|
backgroundColor: "#c0392b",
|
||||||
);
|
borderBottomLeftRadius: "4px",
|
||||||
})}
|
borderBottomRightRadius: "4px",
|
||||||
</div>
|
padding: "5px",
|
||||||
))}
|
height: "52px",
|
||||||
</Box>
|
}}
|
||||||
|
>
|
||||||
|
{props.data && (
|
||||||
|
<>
|
||||||
|
{props.data.values.map((value) => {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
style={{ marginBlockStart: "0", marginBlockEnd: "2px" }}
|
||||||
|
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
||||||
|
>{`${
|
||||||
|
!isInt(Number(value)) ? Number(value).toPrecision(4) : value
|
||||||
|
}${props.data?.unit}`}</p>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 130px;
|
width: 150px;
|
||||||
height: 130px;
|
height: 150px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,12 @@ import "./TopologyFlow.scss";
|
||||||
export type TopologyFlowProps = {
|
export type TopologyFlowProps = {
|
||||||
orientation?: "vertical" | "horizontal";
|
orientation?: "vertical" | "horizontal";
|
||||||
amount?: number;
|
amount?: number;
|
||||||
rightToLeft?: boolean;
|
data?: BoxData;
|
||||||
data?: BoxData[];
|
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
};
|
};
|
||||||
const TopologyFlow = (props: TopologyFlowProps) => {
|
const TopologyFlow = (props: TopologyFlowProps) => {
|
||||||
const length = Math.abs((props.amount ?? 1) * BOX_SIZE);
|
const length = Math.abs((props.amount ?? 1) * BOX_SIZE);
|
||||||
|
const values = props.data?.values;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -28,38 +28,36 @@ const TopologyFlow = (props: TopologyFlowProps) => {
|
||||||
backgroundColor: "#f4b3504d",
|
backgroundColor: "#f4b3504d",
|
||||||
visibility: props.hidden || !props.data ? "hidden" : "visible",
|
visibility: props.hidden || !props.data ? "hidden" : "visible",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
position: "relative",
|
||||||
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{values?.map(
|
||||||
|
(value) => `${Math.round(value as number)} ${props.data?.unit}`
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
<div
|
<div
|
||||||
className="container"
|
className="container"
|
||||||
style={{
|
style={{
|
||||||
transform:
|
transform:
|
||||||
props.orientation === "vertical"
|
props.orientation === "vertical"
|
||||||
? "rotate(90deg)"
|
? "rotate(90deg)"
|
||||||
: props.rightToLeft
|
: Number(values?.[0]) < 0
|
||||||
? "rotate(180deg)"
|
? "rotate(180deg)"
|
||||||
: "",
|
: "",
|
||||||
overflow: "hidden",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: BOX_SIZE,
|
alignItems: "center",
|
||||||
width: BOX_SIZE,
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p
|
<div className="data-flow">
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
zIndex: 1,
|
|
||||||
transform:
|
|
||||||
props.orientation === "vertical"
|
|
||||||
? "rotate(-90deg)"
|
|
||||||
: props.rightToLeft
|
|
||||||
? "rotate(-180deg)"
|
|
||||||
: "",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.data?.map((value) => value.values)}
|
|
||||||
</p>
|
|
||||||
<div className="data-flow" style={{ overflow: "hidden" }}>
|
|
||||||
<div className="dot"></div>
|
<div className="dot"></div>
|
||||||
<div className="dot"></div>
|
<div className="dot"></div>
|
||||||
<div className="dot"></div>
|
<div className="dot"></div>
|
||||||
|
|
|
@ -1,60 +1,119 @@
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import TopologyColumn from "./TopologyColumn";
|
import TopologyColumn from "./TopologyColumn";
|
||||||
import { UnixTime } from "../../../dataCache/time";
|
import { TimeSpan, UnixTime } from "../../../dataCache/time";
|
||||||
import { extractTopologyValues, parseCsv } from "../../../util/graph.util";
|
import {
|
||||||
import { testData } from "./ScalarGraph";
|
TopologyValues,
|
||||||
|
extractTopologyValues,
|
||||||
|
getAmount,
|
||||||
|
getHighestConnectionValue,
|
||||||
|
} from "../../../util/graph.util";
|
||||||
|
import { fetchData } from "./ScalarGraph";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { FetchResult } from "../../../dataCache/dataCache";
|
||||||
|
|
||||||
const TopologyView = () => {
|
const TopologyView = () => {
|
||||||
const values = extractTopologyValues({
|
const [values, setValues] = useState<TopologyValues | null>(null);
|
||||||
time: UnixTime.fromTicks(192384239),
|
|
||||||
value: parseCsv(testData),
|
useEffect(() => {
|
||||||
});
|
const interval = setInterval(() => {
|
||||||
console.log("csvValue", parseCsv(testData));
|
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(15));
|
||||||
|
fetchData(now.round(2))
|
||||||
|
.then((res) => {
|
||||||
|
if (
|
||||||
|
res !== FetchResult.notAvailable &&
|
||||||
|
res !== FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
setValues(
|
||||||
|
extractTopologyValues({
|
||||||
|
time: now,
|
||||||
|
value: res,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err));
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (values) {
|
if (values) {
|
||||||
|
const highestConnectionValue = getHighestConnectionValue(values);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
overflow: "auto",
|
|
||||||
padding: 2,
|
padding: 2,
|
||||||
fontFamily: `"Ubuntu", sans-serif`,
|
fontFamily: `"Ubuntu", sans-serif`,
|
||||||
|
fontSize: "12px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: "Grid",
|
title: "Grid",
|
||||||
data: values.acInBus,
|
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.6,
|
amount: getAmount(
|
||||||
data: values.gridToAcIn,
|
highestConnectionValue,
|
||||||
rightToLeft: true,
|
values.gridToAcInConnection.values
|
||||||
|
),
|
||||||
|
data: values.gridToAcInConnection,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: "AcInBus",
|
title: "GridBus",
|
||||||
data: values.acInBus,
|
data: values.islandBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.3,
|
amount: 0.3,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcInConnection,
|
||||||
|
}}
|
||||||
|
topConnection={{
|
||||||
|
amount: getAmount(
|
||||||
|
highestConnectionValue,
|
||||||
|
values.gridBusToPvOnGridbusConnection.values
|
||||||
|
),
|
||||||
|
data: values.gridBusToPvOnGridbusConnection,
|
||||||
|
}}
|
||||||
|
topBox={{
|
||||||
|
title: "PvOnGridBus",
|
||||||
|
}}
|
||||||
|
bottomConnection={{
|
||||||
|
amount: getAmount(
|
||||||
|
highestConnectionValue,
|
||||||
|
values.gridBusToLoadOnGridBusConnection.values
|
||||||
|
),
|
||||||
|
data: values.gridBusToLoadOnGridBusConnection,
|
||||||
|
}}
|
||||||
|
bottomBox={{
|
||||||
|
title: "LoadOnGridBus",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: "AcOutBus",
|
title: "IslandBus",
|
||||||
data: values.acOutBus,
|
data: values.islandBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.5,
|
amount: 0.5,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcInConnection,
|
||||||
|
}}
|
||||||
|
bottomBox={{
|
||||||
|
title: "LoadOnIslandBus",
|
||||||
|
}}
|
||||||
|
bottomConnection={{
|
||||||
|
amount: getAmount(
|
||||||
|
highestConnectionValue,
|
||||||
|
values.islandBusToLoadOnIslandBusConnection.values
|
||||||
|
),
|
||||||
|
data: values.islandBusToLoadOnIslandBusConnection,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,32 +121,40 @@ const TopologyView = () => {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: "Inverter",
|
title: "Inverter",
|
||||||
data: values.acOutBus,
|
data: values.islandBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.3,
|
amount: 0.3,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcInConnection,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
topBox={{
|
topBox={{
|
||||||
title: "MPPT",
|
title: "PvOnDc",
|
||||||
data: values.acOutBus,
|
|
||||||
}}
|
}}
|
||||||
topConnection={{
|
topConnection={{
|
||||||
amount: 0.6,
|
amount: 0.6,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcInConnection,
|
||||||
}}
|
}}
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: "DcBus",
|
title: "DcBus",
|
||||||
data: values.acOutBus,
|
data: values.dcBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
|
amount: getAmount(
|
||||||
|
highestConnectionValue,
|
||||||
|
values.dcBusToDcDcConnection.values
|
||||||
|
),
|
||||||
|
data: values.dcBusToDcDcConnection,
|
||||||
|
}}
|
||||||
|
bottomConnection={{
|
||||||
amount: 0.2,
|
amount: 0.2,
|
||||||
data: values.gridToAcIn,
|
data: values.dcBusToLoadOnDcConnection,
|
||||||
rightToLeft: true,
|
}}
|
||||||
|
bottomBox={{
|
||||||
|
title: "LoadOnDc",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,11 +162,14 @@ const TopologyView = () => {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: "DcDc",
|
title: "DcDc",
|
||||||
data: values.acOutBus,
|
data: values.islandBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.8,
|
amount: getAmount(
|
||||||
data: values.gridToAcIn,
|
highestConnectionValue,
|
||||||
|
values.dcDCToBatteryConnection.values
|
||||||
|
),
|
||||||
|
data: values.dcDCToBatteryConnection,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,7 +177,7 @@ const TopologyView = () => {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: "Battery",
|
title: "Battery",
|
||||||
data: values.acOutBus,
|
data: values.battery,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,29 +35,60 @@ export interface GraphData {
|
||||||
[path: string]: GraphCoordinates;
|
[path: string]: GraphCoordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// connections must have the word Connection in the prop name, so the topology works correctly
|
||||||
export type TopologyValues = {
|
export type TopologyValues = {
|
||||||
gridToAcIn: BoxData[];
|
gridToAcInConnection: BoxData;
|
||||||
acInBus: BoxData[];
|
gridBus: BoxData;
|
||||||
acOutBus: BoxData[];
|
islandBus: BoxData;
|
||||||
dcBus: BoxData[];
|
dcBus: BoxData;
|
||||||
mpptToDcBus: BoxData[];
|
dcBusToDcDcConnection: BoxData;
|
||||||
dcBusToDcDc: BoxData[];
|
dcDCToBatteryConnection: BoxData;
|
||||||
dcDCToBattery: BoxData[];
|
battery: BoxData;
|
||||||
battery: BoxData[];
|
dcBusToLoadOnDcConnection: BoxData;
|
||||||
|
islandBusToLoadOnIslandBusConnection: BoxData;
|
||||||
|
gridBusToPvOnGridbusConnection: BoxData;
|
||||||
|
gridBusToLoadOnGridBusConnection: 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",
|
||||||
|
],
|
||||||
|
gridBus: ["/GridMeter/Ac/L2/Voltage"],
|
||||||
|
islandBus: [
|
||||||
|
"/AcDc/Ac/L1/Voltage",
|
||||||
|
"/AcDc/Ac/L2/Voltage",
|
||||||
|
"/AcDc/Ac/L3/Voltage",
|
||||||
|
],
|
||||||
|
dcBus: ["/AcDc/Dc/Voltage"],
|
||||||
|
dcBusToDcDcConnection: ["/DcDc/Dc/Link/Power"],
|
||||||
|
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"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBoxColor = () => {};
|
||||||
|
|
||||||
export const extractTopologyValues = (
|
export const extractTopologyValues = (
|
||||||
timeSeriesData: DataPoint
|
timeSeriesData: DataPoint
|
||||||
): TopologyValues | null => {
|
): TopologyValues | null => {
|
||||||
const timeSeriesValue = timeSeriesData.value;
|
const timeSeriesValue = timeSeriesData.value;
|
||||||
let topologyValues: (string | number)[];
|
|
||||||
if (isDefined(timeSeriesValue)) {
|
if (isDefined(timeSeriesValue)) {
|
||||||
return Object.keys(topologyPaths).reduce((acc, topologyKey) => {
|
return Object.keys(topologyPaths).reduce((acc, topologyKey) => {
|
||||||
const values = topologyPaths[topologyKey].map(
|
let topologyValues: (string | number)[];
|
||||||
|
const values = topologyPaths[topologyKey as keyof TopologyValues].map(
|
||||||
(topologyPath) => timeSeriesValue[topologyPath]
|
(topologyPath) => timeSeriesValue[topologyPath]
|
||||||
);
|
);
|
||||||
switch (topologyKey) {
|
console.log("AAA", topologyKey);
|
||||||
case "gridToAcIn":
|
switch (topologyKey as keyof TopologyValues) {
|
||||||
|
case "gridToAcInConnection":
|
||||||
topologyValues = [
|
topologyValues = [
|
||||||
values.reduce((acc, curr) => Number(acc) + Number(curr.value), 0),
|
values.reduce((acc, curr) => Number(acc) + Number(curr.value), 0),
|
||||||
];
|
];
|
||||||
|
@ -65,50 +96,45 @@ export const extractTopologyValues = (
|
||||||
default:
|
default:
|
||||||
topologyValues = values.map(({ value }) => value);
|
topologyValues = values.map(({ value }) => value);
|
||||||
}
|
}
|
||||||
|
console.log("topologyValues", topologyValues);
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[topologyKey]: [
|
[topologyKey]: {
|
||||||
{
|
values: topologyValues,
|
||||||
values: topologyValues,
|
label: topologyPaths[topologyKey as keyof TopologyValues][0]
|
||||||
label: topologyPaths[topologyKey][0].split("/").pop(),
|
.split("/")
|
||||||
unit: values[0].unit,
|
.pop(),
|
||||||
} as BoxData,
|
unit: values[0].unit,
|
||||||
],
|
} as BoxData,
|
||||||
};
|
};
|
||||||
}, {} as TopologyValues);
|
}, {} as TopologyValues);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const topologyPaths: { [key: string]: string[] } = {
|
export const getHighestConnectionValue = (values: TopologyValues) =>
|
||||||
gridToAcIn: [
|
Object.keys(values)
|
||||||
"/GridMeter/Ac/L1/Power/Apparent",
|
.filter((value) => value.includes("Connection"))
|
||||||
"/GridMeter/Ac/L2/Power/Apparent",
|
.reduce((acc, curr) => {
|
||||||
"/GridMeter/Ac/L3/Power/Apparent",
|
const value = values[curr as keyof TopologyValues].values[0] as number;
|
||||||
],
|
console.log("reduce", value, acc);
|
||||||
acInBus: ["/GridMeter/Ac/L2/Voltage"],
|
return value > acc ? value : acc;
|
||||||
acOutBus: [
|
}, 0);
|
||||||
"/AcDc/Ac/L1/Voltage",
|
|
||||||
"/AcDc/Ac/L2/Voltage",
|
export const getAmount = (
|
||||||
"/AcDc/Ac/L3/Voltage",
|
highestConnectionValue: number,
|
||||||
],
|
values: (string | number)[]
|
||||||
dcBus: ["/AcDc/Dc/Voltage"],
|
) => {
|
||||||
mpptToDcBus: ["/Mppt/Dc/Power"],
|
console.log("amount", values[0] as number, highestConnectionValue);
|
||||||
dcBusToDcDc: ["/DcDc/Dc/Link/Power"],
|
return (values[0] as number) / highestConnectionValue;
|
||||||
dcDCToBattery: ["/DcDc/Dc/Battery/Power"],
|
|
||||||
battery: ["/Battery/Soc", "/Battery/Dc/Voltage"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CsvEntry {
|
export interface CsvEntry {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
unit: string;
|
unit: string;
|
||||||
}
|
}
|
||||||
export interface Csv {
|
|
||||||
[key: string]: CsvEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parseCsv = (text: string): Csv => {
|
export const parseCsv = (text: string): DataRecord => {
|
||||||
console.log("split", text.split(/\r?\n/));
|
|
||||||
const y = text
|
const y = text
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.filter((split) => split.length > 0)
|
.filter((split) => split.length > 0)
|
||||||
|
@ -118,12 +144,12 @@ export const parseCsv = (text: string): Csv => {
|
||||||
|
|
||||||
const x = y
|
const x = y
|
||||||
.map((fields) => {
|
.map((fields) => {
|
||||||
if (typeof fields[1] === "string") {
|
if (isNaN(Number(fields[1]))) {
|
||||||
return { [fields[0]]: { value: fields[1], unit: fields[2] } };
|
return { [fields[0]]: { value: fields[1], unit: fields[2] } };
|
||||||
}
|
}
|
||||||
return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
|
return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
|
||||||
})
|
})
|
||||||
.reduce((acc, current) => ({ ...acc, ...current }), {} as Csv);
|
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue