[WIP] modify pology, style it a bit
This commit is contained in:
parent
3a4c768074
commit
4b9f3e6a84
|
@ -103,6 +103,7 @@ const CheckboxTree = () => {
|
||||||
overflowX: "hidden",
|
overflowX: "hidden",
|
||||||
position: ["sticky", "-webkit-sticky"],
|
position: ["sticky", "-webkit-sticky"],
|
||||||
top: 1,
|
top: 1,
|
||||||
|
maxHeight: "90vh",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderTree(toggles)}
|
{renderTree(toggles)}
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Button } from "@mui/material";
|
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
|
||||||
import { UnixTime, TimeSpan } from "../../../dataCache/time";
|
|
||||||
import { createTimes } from "../../../util/graph.util";
|
|
||||||
import {
|
|
||||||
DatePicker,
|
|
||||||
DateTimeValidationError,
|
|
||||||
LocalizationProvider,
|
|
||||||
} from "@mui/x-date-pickers";
|
|
||||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
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 { useState } from "react";
|
import ShortcutButton from "./ShortcutButton";
|
||||||
|
|
||||||
interface DateRangePickerProps {
|
interface DateRangePickerProps {
|
||||||
setRange: (value: Date[]) => void;
|
setRange: (value: Date[]) => void;
|
||||||
|
@ -19,10 +12,6 @@ interface DateRangePickerProps {
|
||||||
}
|
}
|
||||||
const DateRangePicker = (props: DateRangePickerProps) => {
|
const DateRangePicker = (props: DateRangePickerProps) => {
|
||||||
const { setRange, range, getCacheSeries } = props;
|
const { setRange, range, getCacheSeries } = props;
|
||||||
const [fromDateError, setFromDateError] =
|
|
||||||
useState<DateTimeValidationError | null>(null);
|
|
||||||
const [toDateError, setToDateError] =
|
|
||||||
useState<DateTimeValidationError | null>(null);
|
|
||||||
|
|
||||||
const handleChange = (fromDate: Date, toDate: Date) => {
|
const handleChange = (fromDate: Date, toDate: Date) => {
|
||||||
setRange([fromDate, toDate]);
|
setRange([fromDate, toDate]);
|
||||||
|
@ -31,26 +20,6 @@ const DateRangePicker = (props: DateRangePickerProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
const weekRange = createTimes(
|
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(7)),
|
|
||||||
100
|
|
||||||
);
|
|
||||||
setRange([
|
|
||||||
weekRange[0].toDate(),
|
|
||||||
weekRange[weekRange.length - 1].toDate(),
|
|
||||||
]);
|
|
||||||
getCacheSeries(
|
|
||||||
weekRange[0].ticks,
|
|
||||||
weekRange[weekRange.length - 1].ticks
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="lastWeek" defaultMessage="Last week" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
disableFuture
|
disableFuture
|
||||||
|
@ -62,16 +31,6 @@ const DateRangePicker = (props: DateRangePickerProps) => {
|
||||||
handleChange(newValue.toDate(), range[1]);
|
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"
|
|
||||||
: "",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
disableFuture
|
disableFuture
|
||||||
|
@ -83,23 +42,36 @@ const DateRangePicker = (props: DateRangePickerProps) => {
|
||||||
color: "red",
|
color: "red",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onError={(err) => setToDateError(err)}
|
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
handleChange(range[0], newValue.toDate());
|
handleChange(range[0], newValue.toDate());
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
|
||||||
textField: {
|
|
||||||
variant: "outlined",
|
|
||||||
error: !!toDateError,
|
|
||||||
helperText: toDateError
|
|
||||||
? "To date needs to be after from date"
|
|
||||||
: "",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
|
<div>
|
||||||
|
<ShortcutButton
|
||||||
|
dayRange={1}
|
||||||
|
setRange={setRange}
|
||||||
|
getCacheSeries={getCacheSeries}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="today" defaultMessage="Today" />
|
||||||
|
</ShortcutButton>
|
||||||
|
<ShortcutButton
|
||||||
|
dayRange={7}
|
||||||
|
setRange={setRange}
|
||||||
|
getCacheSeries={getCacheSeries}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="lastWeek" defaultMessage="Last week" />
|
||||||
|
</ShortcutButton>
|
||||||
|
<ShortcutButton
|
||||||
|
dayRange={30}
|
||||||
|
setRange={setRange}
|
||||||
|
getCacheSeries={getCacheSeries}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="lastMonth" defaultMessage="Last month" />
|
||||||
|
</ShortcutButton>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,7 @@ const Log = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopologyView />
|
<TopologyView />
|
||||||
|
|
||||||
<ScalarGraph />
|
<ScalarGraph />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Plot from "react-plotly.js";
|
import Plot from "react-plotly.js";
|
||||||
import { RecordSeries } from "../../../dataCache/data";
|
import { RecordSeries } from "../../../dataCache/data";
|
||||||
import {
|
import {
|
||||||
|
Csv,
|
||||||
GraphData,
|
GraphData,
|
||||||
createTimes,
|
createTimes,
|
||||||
flattenToggles,
|
flattenToggles,
|
||||||
|
@ -19,6 +20,11 @@ import { LogContext } from "../../Context/LogContextProvider";
|
||||||
import { isDefined } from "../../../dataCache/utils/maybe";
|
import { isDefined } from "../../../dataCache/utils/maybe";
|
||||||
import { Data, Layout } from "plotly.js";
|
import { Data, Layout } from "plotly.js";
|
||||||
import { VariableSizeList as List, areEqual } from "react-window";
|
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;
|
const NUMBER_OF_NODES = 100;
|
||||||
|
|
||||||
|
@ -526,9 +532,7 @@ const ScalarGraph = () => {
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
}, [toggles]);
|
}, [toggles]);
|
||||||
|
|
||||||
const fetchData = (
|
const fetchData = (timestamp: UnixTime): Promise<FetchResult<Csv>> => {
|
||||||
timestamp: UnixTime
|
|
||||||
): Promise<FetchResult<Record<string, number>>> => {
|
|
||||||
const s3Path = `${timestamp.ticks}.csv`;
|
const s3Path = `${timestamp.ticks}.csv`;
|
||||||
return s3Access
|
return s3Access
|
||||||
.get(s3Path)
|
.get(s3Path)
|
||||||
|
@ -569,7 +573,7 @@ const ScalarGraph = () => {
|
||||||
transformedObject[key].x.push(
|
transformedObject[key].x.push(
|
||||||
new Date(item.time.ticks * 1000).toISOString()
|
new Date(item.time.ticks * 1000).toISOString()
|
||||||
);
|
);
|
||||||
transformedObject[key].y.push(item.value?.[key]);
|
transformedObject[key].y.push(item.value?.[key].value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -601,6 +605,7 @@ const ScalarGraph = () => {
|
||||||
),
|
),
|
||||||
NUMBER_OF_NODES
|
NUMBER_OF_NODES
|
||||||
);
|
);
|
||||||
|
console.log("getcacheseries");
|
||||||
cache.getSeries(times);
|
cache.getSeries(times);
|
||||||
times$.next(times);
|
times$.next(times);
|
||||||
};
|
};
|
||||||
|
@ -630,9 +635,6 @@ const ScalarGraph = () => {
|
||||||
barnorm: "percent",
|
barnorm: "percent",
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
if (!isScalar) {
|
|
||||||
console.log("graphData", data[visibleGraphs[index]]);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
<Plot
|
<Plot
|
||||||
|
@ -679,116 +681,43 @@ const ScalarGraph = () => {
|
||||||
return null;
|
return null;
|
||||||
}, areEqual);
|
}, areEqual);
|
||||||
|
|
||||||
/* const renderGraphs = () => {
|
if (
|
||||||
if (checkedToggles) {
|
checkedToggles &&
|
||||||
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
Object.keys(checkedToggles).find((toggle) => checkedToggles[toggle])
|
||||||
const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => {
|
) {
|
||||||
return checkedToggles[path];
|
|
||||||
});
|
|
||||||
if (visibleGraphs.length > 0) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
|
||||||
<DateRangePicker
|
|
||||||
setRange={setRange}
|
|
||||||
range={range}
|
|
||||||
getCacheSeries={getCacheSeries}
|
|
||||||
/>
|
|
||||||
</LocalizationProvider>
|
|
||||||
{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<Layout> = !isScalar
|
|
||||||
? {
|
|
||||||
bargap: 0,
|
|
||||||
barmode: "stack",
|
|
||||||
barnorm: "percent",
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
return (
|
|
||||||
<Plot
|
|
||||||
key={path}
|
|
||||||
data={data as Data[]}
|
|
||||||
layout={{
|
|
||||||
width: 1000,
|
|
||||||
height: 500,
|
|
||||||
title: path,
|
|
||||||
uirevision: uiRevision,
|
|
||||||
xaxis: {
|
|
||||||
autorange: false,
|
|
||||||
range: range,
|
|
||||||
type: "date",
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
rangemode: "tozero",
|
|
||||||
},
|
|
||||||
...barGraphLayout,
|
|
||||||
}}
|
|
||||||
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());
|
|
||||||
getCacheSeries(xaxisRange0, xaxisRange1);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Alert sx={{ mt: 2 }} severity="info">
|
|
||||||
<FormattedMessage
|
|
||||||
id="makeASelection"
|
|
||||||
defaultMessage="Please make a selection on the left"
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}; */
|
|
||||||
if (checkedToggles) {
|
|
||||||
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
||||||
console.log(
|
|
||||||
"length",
|
|
||||||
Object.keys(checkedToggles).filter((toggle) => checkedToggles[toggle])
|
|
||||||
.length
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<List
|
<>
|
||||||
height={1000}
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
itemCount={
|
<DateRangePicker
|
||||||
Object.keys(checkedToggles).filter((toggle) => checkedToggles[toggle])
|
setRange={setRange}
|
||||||
.length
|
range={range}
|
||||||
}
|
getCacheSeries={getCacheSeries}
|
||||||
itemSize={() => 500}
|
/>
|
||||||
width="100%"
|
</LocalizationProvider>
|
||||||
itemData={coordinateTimeSeries}
|
<List
|
||||||
>
|
height={1000}
|
||||||
{Row}
|
itemCount={
|
||||||
</List>
|
Object.keys(checkedToggles).filter(
|
||||||
|
(toggle) => checkedToggles[toggle]
|
||||||
|
).length
|
||||||
|
}
|
||||||
|
itemSize={() => 500}
|
||||||
|
width="100%"
|
||||||
|
itemData={coordinateTimeSeries}
|
||||||
|
>
|
||||||
|
{Row}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return (
|
||||||
|
<Alert sx={{ mt: 2 }} severity="info">
|
||||||
|
<FormattedMessage
|
||||||
|
id="makeASelection"
|
||||||
|
defaultMessage="Please make a selection on the left"
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default ScalarGraph;
|
export default ScalarGraph;
|
||||||
|
|
|
@ -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 (
|
||||||
|
<InnovenergyButton
|
||||||
|
onClick={() => {
|
||||||
|
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}
|
||||||
|
</InnovenergyButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShortcutButton;
|
|
@ -34,6 +34,7 @@ const TopologyBox = (props: TopologyBoxProps) => {
|
||||||
<div>
|
<div>
|
||||||
{el.label}
|
{el.label}
|
||||||
{el.values.map((value) => {
|
{el.values.map((value) => {
|
||||||
|
console.log("value", value);
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
style={{ marginBlockStart: "2px", marginBlockEnd: "2px" }}
|
style={{ marginBlockStart: "2px", marginBlockEnd: "2px" }}
|
|
@ -1,5 +1,5 @@
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import TopologyBox, { TopologyBoxProps } from "./ToplogyBox";
|
import TopologyBox, { TopologyBoxProps } from "./TopologyBox";
|
||||||
import TopologyFlow, { TopologyFlowProps } from "./TopologyFlow";
|
import TopologyFlow, { TopologyFlowProps } from "./TopologyFlow";
|
||||||
|
|
||||||
type TopologyColumnProps = {
|
type TopologyColumnProps = {
|
||||||
|
|
|
@ -1,39 +1,65 @@
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { BOX_SIZE, BoxData } from "./ToplogyBox";
|
import { BOX_SIZE, BoxData } from "./TopologyBox";
|
||||||
import "./TopologyFlow.scss";
|
import "./TopologyFlow.scss";
|
||||||
|
|
||||||
export type TopologyFlowProps = {
|
export type TopologyFlowProps = {
|
||||||
orientation?: "vertical" | "horizontal";
|
orientation?: "vertical" | "horizontal";
|
||||||
amount?: number;
|
amount?: number;
|
||||||
direction?: "leftToRight" | "rightToLeft";
|
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 - 20));
|
const length = Math.abs((props.amount ?? 1) * BOX_SIZE);
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: BOX_SIZE,
|
||||||
|
width: BOX_SIZE,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: props.orientation === "horizontal" ? BOX_SIZE - 20 : length,
|
width: props.orientation === "horizontal" ? BOX_SIZE : length,
|
||||||
height: props.orientation === "vertical" ? BOX_SIZE - 20 : length,
|
height: props.orientation === "vertical" ? BOX_SIZE : length,
|
||||||
backgroundColor: "#f4b3504d",
|
backgroundColor: "#f4b3504d",
|
||||||
visibility: props.hidden || !props.data ? "hidden" : "visible",
|
visibility: props.hidden || !props.data ? "hidden" : "visible",
|
||||||
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.data?.map((value) => value.values)}
|
|
||||||
<div
|
<div
|
||||||
className="container"
|
className="container"
|
||||||
style={{
|
style={{
|
||||||
transform:
|
transform:
|
||||||
props.orientation === "vertical"
|
props.orientation === "vertical"
|
||||||
? "rotate(90deg)"
|
? "rotate(90deg)"
|
||||||
: props.direction === "rightToLeft"
|
: props.rightToLeft
|
||||||
? "rotate(180deg)"
|
? "rotate(180deg)"
|
||||||
: "",
|
: "",
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
height: BOX_SIZE,
|
||||||
|
width: BOX_SIZE,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="data-flow">
|
<p
|
||||||
|
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>
|
||||||
|
@ -47,7 +73,7 @@ const TopologyFlow = (props: TopologyFlowProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ const TopologyView = () => {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
padding: 2,
|
padding: 2,
|
||||||
|
fontFamily: `"Ubuntu", sans-serif`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
@ -27,9 +28,9 @@ const TopologyView = () => {
|
||||||
data: values.acInBus,
|
data: values.acInBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.5,
|
amount: 0.6,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcIn,
|
||||||
direction: "rightToLeft",
|
rightToLeft: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,9 +41,8 @@ const TopologyView = () => {
|
||||||
data: values.acInBus,
|
data: values.acInBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.5,
|
amount: 0.3,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcIn,
|
||||||
direction: "leftToRight",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,7 +55,6 @@ const TopologyView = () => {
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.5,
|
amount: 0.5,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcIn,
|
||||||
direction: "leftToRight",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,9 +65,8 @@ const TopologyView = () => {
|
||||||
data: values.acOutBus,
|
data: values.acOutBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.5,
|
amount: 0.3,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcIn,
|
||||||
direction: "leftToRight",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,7 +77,7 @@ const TopologyView = () => {
|
||||||
data: values.acOutBus,
|
data: values.acOutBus,
|
||||||
}}
|
}}
|
||||||
topConnection={{
|
topConnection={{
|
||||||
amount: 0.5,
|
amount: 0.6,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcIn,
|
||||||
}}
|
}}
|
||||||
centerBox={{
|
centerBox={{
|
||||||
|
@ -87,9 +85,9 @@ const TopologyView = () => {
|
||||||
data: values.acOutBus,
|
data: values.acOutBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.5,
|
amount: 0.2,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcIn,
|
||||||
direction: "rightToLeft",
|
rightToLeft: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -100,7 +98,7 @@ const TopologyView = () => {
|
||||||
data: values.acOutBus,
|
data: values.acOutBus,
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
amount: 0.5,
|
amount: 0.8,
|
||||||
data: values.gridToAcIn,
|
data: values.gridToAcIn,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Maybe } from "yup";
|
import { Maybe } from "yup";
|
||||||
import { Timestamped } from "./types";
|
import { Timestamped } from "./types";
|
||||||
import { isDefined } from "./utils/maybe";
|
import { isDefined } from "./utils/maybe";
|
||||||
|
import { CsvEntry } from "../util/graph.util";
|
||||||
|
|
||||||
export type DataRecord = Record<string, number | string>;
|
export type DataRecord = Record<string, CsvEntry>;
|
||||||
|
|
||||||
export type DataPoint = Timestamped<Maybe<DataRecord>>;
|
export type DataPoint = Timestamped<Maybe<DataRecord>>;
|
||||||
export type RecordSeries = Array<DataPoint>;
|
export type RecordSeries = Array<DataPoint>;
|
||||||
export type PointSeries = Array<Timestamped<Maybe<number | string>>>;
|
export type PointSeries = Array<Timestamped<Maybe<CsvEntry>>>;
|
||||||
export type DataSeries = Array<Maybe<number | string>>;
|
export type DataSeries = Array<Maybe<CsvEntry>>;
|
||||||
|
|
||||||
export function getPoints(
|
export function getPoints(
|
||||||
recordSeries: RecordSeries,
|
recordSeries: RecordSeries,
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { createDispatchQueue } from "./promiseQueue";
|
||||||
import { SkipListNode } from "./skipList/skipListNode";
|
import { SkipListNode } from "./skipList/skipListNode";
|
||||||
import { RecordSeries } from "./data";
|
import { RecordSeries } from "./data";
|
||||||
import { Maybe, isUndefined } from "./utils/maybe";
|
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 = {
|
export const FetchResult = {
|
||||||
notAvailable: "N/A",
|
notAvailable: "N/A",
|
||||||
|
@ -30,7 +31,7 @@ function reverseBits(x: number): number {
|
||||||
return x >>> 0;
|
return x >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DataCache<T extends Record<string, number>> {
|
export default class DataCache<T extends Record<string, CsvEntry>> {
|
||||||
private readonly cache: SkipList<Maybe<T>> = new SkipList<Maybe<T>>();
|
private readonly cache: SkipList<Maybe<T>> = new SkipList<Maybe<T>>();
|
||||||
private readonly resolution: TimeSpan;
|
private readonly resolution: TimeSpan;
|
||||||
|
|
||||||
|
@ -103,15 +104,20 @@ export default class DataCache<T extends Record<string, number>> {
|
||||||
const n = after.index - t;
|
const n = after.index - t;
|
||||||
const pn = p + n;
|
const pn = p + n;
|
||||||
|
|
||||||
let interpolated: Partial<Record<string, number>> = {};
|
let interpolated: Partial<Record<string, CsvEntry>> = {};
|
||||||
|
|
||||||
//What about string nodes? like Alarms
|
//What about string nodes? like Alarms
|
||||||
for (const k of Object.keys(dataBefore)) {
|
for (const k of Object.keys(dataBefore)) {
|
||||||
interpolated[k] = isNumber(dataBefore[k])
|
const beforeData = dataBefore[k].value;
|
||||||
? (dataBefore[k] * n + dataAfter[k] * p) / pn
|
const afterData = Number(dataAfter[k].value);
|
||||||
: n < p
|
let foo = interpolated[k];
|
||||||
? dataAfter[k]
|
if (foo) {
|
||||||
: dataBefore[k];
|
foo.value = isNumber(beforeData)
|
||||||
|
? (beforeData * n + afterData * p) / pn
|
||||||
|
: n < p
|
||||||
|
? afterData
|
||||||
|
: beforeData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return interpolated as T;
|
return interpolated as T;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import { TimeRange, UnixTime } from "../dataCache/time";
|
import { TimeRange, UnixTime } from "../dataCache/time";
|
||||||
import { DataPoint, DataRecord } from "../dataCache/data";
|
import { DataPoint, DataRecord } from "../dataCache/data";
|
||||||
import { isDefined } from "../dataCache/utils/maybe";
|
import { isDefined } from "../dataCache/utils/maybe";
|
||||||
import { BoxData } from "../components/Installations/Log/ToplogyBox";
|
import { BoxData } from "../components/Installations/Log/TopologyBox";
|
||||||
|
|
||||||
export interface GraphCoordinates {
|
export interface GraphCoordinates {
|
||||||
x: Datum[] | Datum[][] | TypedArray;
|
x: Datum[] | Datum[][] | TypedArray;
|
||||||
|
@ -50,22 +50,28 @@ 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(topologyValues).reduce((acc, topologyKey) => {
|
return Object.keys(topologyPaths).reduce((acc, topologyKey) => {
|
||||||
const values = topologyValues[topologyKey].map(
|
const values = topologyPaths[topologyKey].map(
|
||||||
(topologyPath) => timeSeriesValue[topologyPath]
|
(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 {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[topologyKey]: [
|
[topologyKey]: [
|
||||||
{
|
{
|
||||||
values:
|
values: topologyValues,
|
||||||
topologyKey === "gridToAcIn"
|
label: topologyPaths[topologyKey][0].split("/").pop(),
|
||||||
? [values.reduce((acc, curr) => Number(acc) + Number(curr))]
|
unit: values[0].unit,
|
||||||
: values,
|
|
||||||
label: topologyValues[topologyKey][0].split("/").pop(),
|
|
||||||
unit: "V",
|
|
||||||
} as BoxData,
|
} as BoxData,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -74,7 +80,7 @@ export const extractTopologyValues = (
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const topologyValues: { [key: string]: string[] } = {
|
export const topologyPaths: { [key: string]: string[] } = {
|
||||||
gridToAcIn: [
|
gridToAcIn: [
|
||||||
"/GridMeter/Ac/L1/Power/Apparent",
|
"/GridMeter/Ac/L1/Power/Apparent",
|
||||||
"/GridMeter/Ac/L2/Power/Apparent",
|
"/GridMeter/Ac/L2/Power/Apparent",
|
||||||
|
@ -101,7 +107,7 @@ export interface Csv {
|
||||||
[key: string]: CsvEntry;
|
[key: string]: CsvEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseCsv = (text: string) => {
|
export const parseCsv = (text: string): Csv => {
|
||||||
console.log("split", text.split(/\r?\n/));
|
console.log("split", text.split(/\r?\n/));
|
||||||
const y = text
|
const y = text
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
|
@ -113,11 +119,11 @@ export const parseCsv = (text: string) => {
|
||||||
const x = y
|
const x = y
|
||||||
.map((fields) => {
|
.map((fields) => {
|
||||||
if (typeof fields[1] === "string") {
|
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;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue