frontend/src/gate/GateGraphs.tsx
2025-04-17 15:42:19 -06:00

358 lines
11 KiB
TypeScript

import {
Avatar,
Box,
Button,
Card,
CardHeader,
LinearProgress,
Theme,
Tooltip,
Typography
} from "@mui/material";
import { ZoomOut } from "@mui/icons-material";
import AreaGraph, { AreaData } from "charts/measurementCharts/AreaGraph";
import MultiLineGraph, { LineData, Point } from "charts/measurementCharts/MultiLineGraph";
import { GetDefaultDateRange } from "common/time/DateRange";
import TimeBar from "common/time/TimeBar";
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
import { useThemeType } from "hooks";
import { Component } from "models";
import { Gate } from "models/Gate";
import { UnitMeasurement } from "models/UnitMeasurement";
import moment, { Moment } from "moment";
import { GetComponentIcon } from "pbHelpers/ComponentType";
import { describeMeasurement, MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
import { useGateAPI, useGlobalState } from "providers";
import React, { useEffect, useState } from "react";
import { avg } from "utils";
import GateFlowGraph from "./GateFlowGraph";
import { makeStyles } from "@mui/styles";
interface Props {
gate: Gate;
display: "analytics" | "sensors";
compMap: Map<string, Component>;
device: string | number;
pressure: string;
ambient: string;
setPCAState: (state: boolean) => void;
}
const useStyles = makeStyles((theme: Theme) => ({
card: {
position: "relative",
display: "flex",
height: "100%",
flexDirection: "column",
overflow: "visible"
},
cardHeader: {
padding: theme.spacing(1),
paddingLeft: theme.spacing(2),
marginRight: 10
},
avatarIcon: {
width: 33,
height: 33
}
})
);
export default function GateGraphs(props: Props) {
const { gate, display, compMap, device, pressure, ambient, setPCAState } = props;
const [{ as }] = useGlobalState();
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
const [zoomed, setZoomed] = useState(false);
const defaultDateRange = GetDefaultDateRange();
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
const themeType = useThemeType();
const classes = useStyles();
const [{ user }] = useGlobalState();
const [compMeasurements, setCompMeasurements] = useState<Map<string, UnitMeasurement[]>>(
new Map<string, UnitMeasurement[]>()
);
const gateAPI = useGateAPI();
const [loading, setLoading] = useState(false);
useEffect(() => {
if (loading) return;
setLoading(true);
let measurementMap: Map<string, UnitMeasurement[]> = new Map<string, UnitMeasurement[]>();
gateAPI
.listGateMeasurements(gate.key, startDate.toISOString(), endDate.toISOString(), as)
.then(resp => {
resp.data.measurements.forEach(um => {
let unitMeasurement = UnitMeasurement.any(um, user);
let entry = measurementMap.get(unitMeasurement.componentId);
if (entry) {
entry.push(unitMeasurement);
} else {
measurementMap.set(unitMeasurement.componentId, [unitMeasurement]);
}
});
setLoading(false);
setCompMeasurements(measurementMap);
});
}, [gate, endDate, gateAPI, startDate, as]); // eslint-disable-line react-hooks/exhaustive-deps
const updateDateRange = (newStartDate: any, newEndDate: any) => {
let range = GetDefaultDateRange();
range.start = newStartDate;
range.end = newEndDate;
setStartDate(newStartDate);
setEndDate(newEndDate);
};
const zoomOut = () => {
setXDomain(["dataMin", "dataMax"]);
setZoomed(false);
};
const lineGraph = (
unitMeasurement: UnitMeasurement,
describer: MeasurementDescriber,
averages?: number
) => {
if (unitMeasurement.values.length > 0) {
let firstTime: Moment | undefined;
let lastTime: Moment | undefined;
//build line data to pass to graph
let lineData: LineData[] = [];
let averagedData: LineData[] = [];
let valMap: Map<number, number[]> = new Map<number, number[]>();
unitMeasurement.values.forEach((vals, i) => {
let avgVals: Point[] = [];
let newLineData: LineData = {
timestamp: moment(unitMeasurement.timestamps[i]).valueOf(),
points: vals.values.map((val, i) => ({ value: val, node: i }))
};
lineData.push(newLineData);
if (averages) {
vals.values.forEach((val, k) => {
let entry = valMap.get(k);
if (entry) {
entry.push(val);
if (entry.length >= averages) {
lastTime = moment(unitMeasurement.timestamps[i]);
avgVals.push({
node: k,
value: avg(entry)
});
}
} else {
valMap.set(k, [val]);
firstTime = moment(unitMeasurement.timestamps[i]);
}
});
}
if (firstTime && lastTime) {
averagedData.push({
points: avgVals,
timestamp:
Math.abs(firstTime.diff(lastTime)) / 2 +
Math.min(firstTime.valueOf(), lastTime.valueOf())
});
firstTime = undefined;
lastTime = undefined;
valMap = new Map<number, number[]>();
}
});
return (
<MultiLineGraph
customHeight={300}
key={unitMeasurement.type}
lineData={lineData}
//averagedData={showAveraged ? averagedData : []}
numLines={unitMeasurement.values.length > 1 ? unitMeasurement.values[0].values.length : 0}
describer={describer}
tooltip
newXDomain={xDomain}
multiGraphZoom={domain => {
setXDomain(domain);
setZoomed(true);
}}
multiGraphZoomOut
/>
);
}
};
const areaGraph = (
unitMeasurement: UnitMeasurement,
describer: MeasurementDescriber,
averages?: number
) => {
if (unitMeasurement.timestamps.length > 0) {
let firstTime: Moment | undefined;
let lastTime: Moment | undefined;
//build line data to pass to graph
let areaData: AreaData[] = [];
let avgData: AreaData[] = [];
let maxVals: number[] = [];
let minVals: number[] = [];
unitMeasurement.values.forEach((val, j) => {
let currentMax = Math.min(...val.values);
let currentMin = Math.max(...val.values);
let dataPoint: AreaData = {
timestamp: moment(unitMeasurement.timestamps[j]).valueOf(),
value: [currentMax, currentMin]
};
if (averages) {
if (minVals.length === 0) {
firstTime = moment(unitMeasurement.timestamps[j]);
}
maxVals.push(currentMax);
minVals.push(currentMin);
if (minVals.length >= averages) {
lastTime = moment(unitMeasurement.timestamps[j]);
}
}
if (firstTime && lastTime) {
avgData.push({
timestamp:
Math.abs(firstTime.diff(lastTime)) / 2 +
Math.min(firstTime.valueOf(), lastTime.valueOf()),
value: [avg(maxVals), avg(minVals)]
});
firstTime = undefined;
lastTime = undefined;
maxVals = [];
minVals = [];
}
areaData.push(dataPoint);
});
return (
<AreaGraph
key={unitMeasurement.type}
customHeight={350}
data={areaData}
//averagedData={showAveraged ? avgData : []}
describer={describer}
tooltip
newXDomain={xDomain}
multiGraphZoom={domain => {
setXDomain(domain);
setZoomed(true);
}}
multiGraphZoomOut
/>
);
}
};
const graphHeader = (comp: Component) => {
const componentIcon = GetComponentIcon(comp.settings.type, comp.settings.subtype, themeType);
return (
<CardHeader
avatar={
<Avatar
variant="square"
src={componentIcon}
className={classes.avatarIcon}
alt={comp.name()}
/>
}
title={<Typography style={{ fontSize: 25, fontWeight: 650 }}>{comp.name()}</Typography>}
subheader={
<UnitMeasurementSummary
component={comp}
reading={UnitMeasurement.convertLastMeasurement(
comp.status.measurement.map(um => UnitMeasurement.create(um, user)) ?? []
)}
/>
}
/>
);
};
const sensorGraphs = () => {
return (
<Box>
{Array.from(compMeasurements.values()).map((compMeasurement, i) => {
let c = Component.create();
if (compMeasurement[0]) {
c = compMap.get(compMeasurement[0].componentId) ?? Component.create();
}
if (compMap.get(c.key())) {
return (
<Card raised key={i} style={{ padding: 10, marginBottom: 15 }}>
{graphHeader(c)}
{compMeasurement.map(um => {
if (um.values[0] && um.values[0].values.length > 1) {
return areaGraph(
um,
describeMeasurement(um.type, c.type(), c.subType()),
c.settings.smoothingAverages
);
} else {
return lineGraph(
um,
describeMeasurement(um.type, c.type(), c.subType()),
c.settings.smoothingAverages
);
}
})}
</Card>
);
} else {
return <Box></Box>;
}
})}
</Box>
);
};
const analyticGraphs = () => {
return (
<Box>
<GateFlowGraph
newXDomain={xDomain}
device={device}
start={startDate}
end={endDate}
gate={gate}
ambient={ambient}
pressureComponent={pressure}
setPCAState={(state: boolean) => {
setPCAState(state);
}}
multiGraphZoom={domain => {
setXDomain(domain);
setZoomed(true);
}}
/>
</Box>
);
};
const showGraphs = () => {
switch (display) {
case "sensors":
return sensorGraphs();
case "analytics":
return analyticGraphs();
default:
return <Box>Unknown Graph Type Selected</Box>;
}
};
return (
<React.Fragment>
<Box display="flex" justifyContent="space-between" alignItems="center" marginBottom={2}>
<TimeBar updateDateRange={updateDateRange} startDate={startDate} endDate={endDate} />
{zoomed && (
<Tooltip title="Zoom Out Graphs">
<Button variant="outlined" onClick={zoomOut}>
<ZoomOut />
</Button>
</Tooltip>
)}
</Box>
{loading ? <LinearProgress /> : showGraphs()}
</React.Fragment>
);
}