fix interpolate bug, fix graph bugs, remove modebar buttons
This commit is contained in:
parent
531c296a15
commit
a81220f2f0
|
@ -57,7 +57,8 @@
|
|||
"@formatjs/cli": "^6.0.3",
|
||||
"@types/react-plotly.js": "^2.6.0",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"eslint-plugin-formatjs": "^4.10.1"
|
||||
"eslint-plugin-formatjs": "^4.10.1",
|
||||
"prettier": "^2.8.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
|
@ -19743,6 +19744,21 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
@ -39706,6 +39722,12 @@
|
|||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
|
||||
"integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg=="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
"@formatjs/cli": "^6.0.3",
|
||||
"@types/react-plotly.js": "^2.6.0",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"eslint-plugin-formatjs": "^4.10.1"
|
||||
"eslint-plugin-formatjs": "^4.10.1",
|
||||
"prettier": "^2.8.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import { S3Access } from "../../../dataCache/S3/S3Access";
|
|||
import DataCache, { FetchResult } from "../../../dataCache/dataCache";
|
||||
import { LogContext } from "../../Context/LogContextProvider";
|
||||
import { isDefined } from "../../../dataCache/utils/maybe";
|
||||
import { Data, Layout, PlotRelayoutEvent } from "plotly.js";
|
||||
import { Data, Icons, Layout, PlotRelayoutEvent, relayout } from "plotly.js";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import DateRangePicker from "./DateRangePicker";
|
||||
|
@ -45,6 +45,7 @@ export const fetchData = (
|
|||
return Promise.resolve(FetchResult.notAvailable);
|
||||
} else if (r.status === 200) {
|
||||
const text = await r.text();
|
||||
console.log("parsecsv", text, parseCsv(text));
|
||||
return parseCsv(text);
|
||||
} else {
|
||||
console.error("unexpected status code");
|
||||
|
@ -153,6 +154,7 @@ const ScalarGraph = () => {
|
|||
|
||||
const handleRelayout = useCallback(
|
||||
(params: PlotRelayoutEvent) => {
|
||||
console.log("relayout");
|
||||
const xaxisRange0 = params["xaxis.range[0]"];
|
||||
const xaxisRange1 = params["xaxis.range[1]"];
|
||||
if (xaxisRange0 && xaxisRange1) {
|
||||
|
@ -169,11 +171,8 @@ const ScalarGraph = () => {
|
|||
if (checkedToggles.length > 0) {
|
||||
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
||||
const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => {
|
||||
console.log('checkedToggles',coordinateTimeSeries, checkedToggles)
|
||||
return checkedToggles.find((toggle) => toggle === path)
|
||||
}
|
||||
);
|
||||
console.log('visibleGraphs', visibleGraphs)
|
||||
return checkedToggles.find((toggle) => toggle === path);
|
||||
});
|
||||
if (visibleGraphs.length > 0) {
|
||||
return (
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
|
@ -186,6 +185,8 @@ const ScalarGraph = () => {
|
|||
</LocalizationProvider>
|
||||
{visibleGraphs.map((path) => {
|
||||
const isScalar = isNumeric(coordinateTimeSeries[path].y[0]);
|
||||
console.log("graphdata", timeSeries, coordinateTimeSeries);
|
||||
|
||||
const data = isScalar
|
||||
? [
|
||||
{
|
||||
|
@ -203,7 +204,6 @@ const ScalarGraph = () => {
|
|||
barnorm: "percent",
|
||||
}
|
||||
: {};
|
||||
console.log("graphdata", coordinateTimeSeries);
|
||||
return (
|
||||
<Plot
|
||||
key={path}
|
||||
|
@ -228,6 +228,8 @@ const ScalarGraph = () => {
|
|||
"select2d",
|
||||
"pan2d",
|
||||
"autoScale2d",
|
||||
"resetScale2d",
|
||||
"zoom2d",
|
||||
],
|
||||
}}
|
||||
onRelayout={handleRelayout}
|
||||
|
|
|
@ -1,79 +1,86 @@
|
|||
import {Box} from "@mui/material";
|
||||
import {getBoxColor} from "../../../util/graph.util";
|
||||
import { Box } from "@mui/material";
|
||||
import { getBoxColor } from "../../../util/graph.util";
|
||||
|
||||
export interface BoxDataValue {
|
||||
unit: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export type BoxData = {
|
||||
label: string;
|
||||
values: (string | number)[];
|
||||
unit: string;
|
||||
label: string;
|
||||
values: BoxDataValue[];
|
||||
};
|
||||
|
||||
export type TopologyBoxProps = {
|
||||
title?: string;
|
||||
data?: BoxData;
|
||||
title?: string;
|
||||
data?: BoxData;
|
||||
};
|
||||
|
||||
const isInt = (value: number) => {
|
||||
return value % 1 === 0;
|
||||
return value % 1 === 0;
|
||||
};
|
||||
|
||||
export const BOX_SIZE = 85;
|
||||
const TopologyBox = (props: TopologyBoxProps) => {
|
||||
const {titleColor, boxColor} = getBoxColor(props.title);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
visibility: props.title ? "visible" : "hidden",
|
||||
height: BOX_SIZE + "px",
|
||||
width: BOX_SIZE + "px",
|
||||
borderRadius: "4px",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
marginBlockStart: "0",
|
||||
marginBlockEnd: "0",
|
||||
backgroundColor: titleColor,
|
||||
padding: "5px",
|
||||
borderTopLeftRadius: "4px",
|
||||
borderTopRightRadius: "4px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{props.title}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: boxColor,
|
||||
borderBottomLeftRadius: "4px",
|
||||
borderBottomRightRadius: "4px",
|
||||
padding: "5px",
|
||||
height: "51px",
|
||||
}}
|
||||
>
|
||||
{props.data && (
|
||||
<>
|
||||
{props.data.values.map((value, index) => {
|
||||
return (
|
||||
<p
|
||||
key={props.title + '-' + index}
|
||||
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>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
const { titleColor, boxColor } = getBoxColor(props.title);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
visibility: props.title ? "visible" : "hidden",
|
||||
height: BOX_SIZE + "px",
|
||||
width: BOX_SIZE + "px",
|
||||
borderRadius: "4px",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
marginBlockStart: "0",
|
||||
marginBlockEnd: "0",
|
||||
backgroundColor: titleColor,
|
||||
padding: "5px",
|
||||
borderTopLeftRadius: "4px",
|
||||
borderTopRightRadius: "4px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{props.title}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: boxColor,
|
||||
borderBottomLeftRadius: "4px",
|
||||
borderBottomRightRadius: "4px",
|
||||
padding: "5px",
|
||||
height: "51px",
|
||||
}}
|
||||
>
|
||||
{props.data && (
|
||||
<>
|
||||
{props.data.values.map((boxData, index) => {
|
||||
console.log("boxData", boxData);
|
||||
return (
|
||||
<p
|
||||
key={props.title + "-" + index}
|
||||
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(boxData.value))
|
||||
? Number(boxData.value).toPrecision(4)
|
||||
: boxData.value
|
||||
}${boxData.unit}`}</p>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopologyBox;
|
||||
|
|
|
@ -43,9 +43,9 @@ const TopologyFlow = (props: TopologyFlowProps) => {
|
|||
}}
|
||||
>
|
||||
{values
|
||||
?.filter((value) => (value as number) !== 0)
|
||||
?.filter((boxData) => (boxData.value as number) !== 0)
|
||||
.map(
|
||||
(value) => `${Math.round(value as number)} ${props.data?.unit}`
|
||||
(boxData) => `${Math.round(boxData.value as number)} ${boxData.unit}`
|
||||
)}
|
||||
</p>
|
||||
<div
|
||||
|
|
|
@ -5,7 +5,7 @@ import { SkipList } from "./skipList/skipList";
|
|||
import { createDispatchQueue } from "./promiseQueue";
|
||||
import { SkipListNode } from "./skipList/skipListNode";
|
||||
import { RecordSeries } from "./data";
|
||||
import { Maybe, isUndefined } from "./utils/maybe";
|
||||
import { isUndefined, Maybe } from "./utils/maybe";
|
||||
import { isNumber } from "./utils/runtimeTypeChecking";
|
||||
import { CsvEntry } from "../util/graph.util";
|
||||
|
||||
|
@ -104,20 +104,24 @@ export default class DataCache<T extends Record<string, CsvEntry>> {
|
|||
const n = after.index - t;
|
||||
const pn = p + n;
|
||||
|
||||
let interpolated: Partial<Record<string, CsvEntry>> = {};
|
||||
let interpolated: Record<string, CsvEntry> = {};
|
||||
|
||||
//What about string nodes? like Alarms
|
||||
for (const k of Object.keys(dataBefore)) {
|
||||
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;
|
||||
const valueBefore = dataBefore[k].value;
|
||||
const valueAfter = dataAfter[k]?.value as Maybe<number | string>;
|
||||
|
||||
let value: number | string;
|
||||
|
||||
if (isUndefined(valueAfter)) {
|
||||
value = valueBefore;
|
||||
} else if (isNumber(valueBefore) && isNumber(valueAfter)) {
|
||||
value = (valueBefore * n + valueAfter * p) / pn;
|
||||
} else {
|
||||
value = n < p ? valueAfter : valueBefore;
|
||||
}
|
||||
|
||||
interpolated[k] = { value, unit: dataBefore[k].unit };
|
||||
}
|
||||
|
||||
return interpolated as T;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { Datum, TypedArray } from "plotly.js";
|
||||
import {
|
||||
TreeElement,
|
||||
} from "../components/Installations/Log/CheckboxTree";
|
||||
import { TreeElement } from "../components/Installations/Log/CheckboxTree";
|
||||
import { TimeRange, UnixTime } from "../dataCache/time";
|
||||
import { DataPoint, DataRecord } from "../dataCache/data";
|
||||
import { isDefined } from "../dataCache/utils/maybe";
|
||||
import { BoxData } from "../components/Installations/Log/TopologyBox";
|
||||
import {
|
||||
BoxData,
|
||||
BoxDataValue,
|
||||
} from "../components/Installations/Log/TopologyBox";
|
||||
|
||||
export interface GraphCoordinates {
|
||||
x: Datum[] | Datum[][] | TypedArray;
|
||||
|
@ -30,6 +31,7 @@ export const createTimes = (
|
|||
);
|
||||
return roundedRange.sample(oneSpan);
|
||||
};
|
||||
|
||||
export interface GraphData {
|
||||
[path: string]: GraphCoordinates;
|
||||
}
|
||||
|
@ -95,6 +97,7 @@ interface BoxColor {
|
|||
titleColor: string;
|
||||
boxColor: string;
|
||||
}
|
||||
|
||||
export const getBoxColor = (boxTitle?: string): BoxColor => {
|
||||
switch (boxTitle) {
|
||||
case "Grid":
|
||||
|
@ -138,18 +141,33 @@ export const extractTopologyValues = (
|
|||
const timeSeriesValue = timeSeriesData.value;
|
||||
if (isDefined(timeSeriesValue)) {
|
||||
return Object.keys(topologyPaths).reduce((acc, topologyKey) => {
|
||||
let topologyValues: (string | number)[];
|
||||
let topologyValues: BoxDataValue[];
|
||||
const values = topologyPaths[topologyKey as keyof TopologyValues].map(
|
||||
(topologyPath) => timeSeriesValue[topologyPath]
|
||||
);
|
||||
switch (topologyKey as keyof TopologyValues) {
|
||||
case "gridToAcInConnection":
|
||||
topologyValues = [
|
||||
values.reduce((acc, curr) => Number(acc) + Number(curr.value), 0),
|
||||
values.reduce(
|
||||
(acc, curr) => {
|
||||
return {
|
||||
value: Number(acc.value) + Number(curr.value),
|
||||
unit: acc.unit,
|
||||
};
|
||||
},
|
||||
{ value: 0, unit: values[0].unit }
|
||||
),
|
||||
];
|
||||
break;
|
||||
default:
|
||||
topologyValues = values.map((element) => element.value);
|
||||
topologyValues = values
|
||||
.filter((element) => element)
|
||||
.map((element) => {
|
||||
if (element) {
|
||||
return { value: element.value, unit: element.unit };
|
||||
}
|
||||
return { value: 0, unit: "" };
|
||||
});
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
|
@ -158,7 +176,6 @@ export const extractTopologyValues = (
|
|||
label: topologyPaths[topologyKey as keyof TopologyValues][0]
|
||||
.split("/")
|
||||
.pop(),
|
||||
unit: values[0].unit,
|
||||
} as BoxData,
|
||||
};
|
||||
}, {} as TopologyValues);
|
||||
|
@ -171,16 +188,22 @@ export const getHighestConnectionValue = (values: TopologyValues) =>
|
|||
.filter((value) => value.includes("Connection"))
|
||||
.reduce((acc, curr) => {
|
||||
const value = Math.abs(
|
||||
values[curr as keyof TopologyValues].values[0] as number
|
||||
values[curr as keyof TopologyValues].values[0].value as number
|
||||
);
|
||||
return value > acc ? value : acc;
|
||||
}, 0);
|
||||
|
||||
export const getAmount = (
|
||||
highestConnectionValue: number,
|
||||
values: (string | number)[]
|
||||
values: BoxDataValue[]
|
||||
) => {
|
||||
return Math.abs(values[0] as number) / highestConnectionValue;
|
||||
console.log(
|
||||
"getamount",
|
||||
Math.abs(values[0].value as number) / highestConnectionValue,
|
||||
Math.abs(values[0].value as number),
|
||||
highestConnectionValue
|
||||
);
|
||||
return Math.abs(values[0].value as number) / highestConnectionValue;
|
||||
};
|
||||
|
||||
export interface CsvEntry {
|
||||
|
@ -189,23 +212,26 @@ export interface CsvEntry {
|
|||
}
|
||||
|
||||
export const parseCsv = (text: string): DataRecord => {
|
||||
console.log("parseText", text);
|
||||
const y = text
|
||||
.split(/\r?\n/)
|
||||
.filter((split) => split.length > 0)
|
||||
.map((l) => {
|
||||
return l.split(";");
|
||||
});
|
||||
const x = y
|
||||
return y
|
||||
.map((fields) => {
|
||||
if (fields[0] === "/AcDc/Warnings")
|
||||
console.log("warnings", fields[1], isNaN(+fields[1]));
|
||||
if (fields[0].includes("LoadOnAcIsland")) {
|
||||
console.log("fields", fields, {
|
||||
[fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] },
|
||||
});
|
||||
}
|
||||
if (isNaN(Number(fields[1])) || fields[1] === "") {
|
||||
return { [fields[0]]: { value: fields[1], unit: fields[2] } };
|
||||
}
|
||||
return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
|
||||
})
|
||||
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
||||
return x;
|
||||
};
|
||||
|
||||
export const insertTreeElements = (
|
||||
|
|
Loading…
Reference in New Issue