frontend/src/gate/GateGraphs.tsx

400 lines
12 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";
import { cloneDeep } from "lodash";
import GateDeltaTempGraph from "./GateDeltaTempGraph";
interface Props {
gate: Gate;
display: "analytics" | "sensors";
compMap: Map<string, Component>;
device: number;
pressure: string;
ambient: string;
tempChain: string;
redKey: string;
greenKey: 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, tempChain, greenKey, redKey } = 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);
console.log(comp)
let measurement = cloneDeep(comp.status.measurement)
let umArray = measurement.map(um => UnitMeasurement.create(um, user))
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(umArray)}
/>
}
/>
);
};
const graphCard = (component: Component, unitMeasurements: UnitMeasurement[]) => {
return (
<Card raised key={component.key()} style={{ padding: 10, marginBottom: 15 }}>
{graphHeader(component)}
{unitMeasurements.map(um => {
if (um.values[0] && um.values[0].values.length > 1) {
return areaGraph(
um,
describeMeasurement(um.type, component.type(), component.subType()),
component.settings.smoothingAverages
);
} else {
return lineGraph(
um,
describeMeasurement(um.type, component.type(), component.subType()),
component.settings.smoothingAverages
);
}
})}
</Card>
);
}
/**
* This function creates the charts for the gate page using the MAIN components on the gate, and delta time, it DOES NOT show any other components that could be on the device
* @returns list of cards for the charts
*/
const sensorGraphs = () => {
const tempComp = compMap.get(tempChain)
let graphs: JSX.Element[] = []
//pressure
let pressureComp = compMap.get(pressure)
let pressureReadings = compMeasurements.get(pressure)
if(pressureComp && pressureReadings){
graphs.push(graphCard(pressureComp, pressureReadings))
}
if(tempComp){
graphs.push(
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp}/>
)
}
//T/H chain
let tempReadings = compMeasurements.get(tempChain)
if(tempComp && tempReadings){
graphs.push(graphCard(tempComp, tempReadings))
}
//ambient
let ambientComp = compMap.get(ambient)
let ambientReadings = compMeasurements.get(ambient)
if (ambientComp && ambientReadings){
graphs.push(graphCard(ambientComp, ambientReadings))
}
//green
let greenComp = compMap.get(greenKey)
let greenReadings = compMeasurements.get(greenKey)
if (greenComp && greenReadings){
graphs.push(graphCard(greenComp, greenReadings))
}
//red
let redComp = compMap.get(redKey)
let redReadings = compMeasurements.get(redKey)
if (redComp && redReadings){
graphs.push(graphCard(redComp, redReadings))
}
return graphs
}
const analyticGraphs = () => {
let tempComp = compMap.get(tempChain)
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);
}}
/>
{/* add a new chart here for the delta temp over time */}
{tempComp &&
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp}/>
}
</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>
);
}