358 lines
11 KiB
TypeScript
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>
|
|
);
|
|
}
|