diff --git a/src/charts/SingleSetAreaChart.tsx b/src/charts/SingleSetAreaChart.tsx index 99dc52a..22477d6 100644 --- a/src/charts/SingleSetAreaChart.tsx +++ b/src/charts/SingleSetAreaChart.tsx @@ -1,9 +1,10 @@ import { useTheme } from "@mui/material"; import moment from "moment"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Area, AreaChart, + Legend, ReferenceArea, ReferenceLine, ResponsiveContainer, @@ -13,25 +14,38 @@ import { YAxis } from "recharts"; import MaterialChartTooltip from "./MaterialChartTooltip"; +import { blue, orange } from "@mui/material/colors"; +import { Payload } from "recharts/types/component/DefaultLegendContent"; export interface SSAreaDataPoint { timestamp: number; value: number; } +export interface ColourData { + colour: string, + label: string +} + interface Props { data: SSAreaDataPoint[]; + tooltipLabel: string + tooltipUnit?: string + yAxisLabel: string maxRef?: number; minRef?: number; newXDomain?: number[] | string[]; multiGraphZoom?: (domain: number[] | string[]) => void; + colourAboveZero?: ColourData + colourBelowZero?: ColourData } export default function SingleSetAreaChart(props: Props) { - const { data, maxRef, minRef, newXDomain, multiGraphZoom } = props; + const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit } = props; const [xDomain, setXDomain] = useState(["dataMin", "dataMax"]); const [refLeft, setRefLeft] = useState(); const [refRight, setRefRight] = useState(); + const [legendPayload, setLegendPayload] = useState([]) const theme = useTheme(); const now = moment(); @@ -41,6 +55,40 @@ export default function SingleSetAreaChart(props: Props) { } }, [newXDomain]); + useEffect(() => { + let legend: Payload[] = [] + if(colourAboveZero){ + legend.push({ + value: colourAboveZero.label, + color: colourAboveZero.colour, + id: colourAboveZero.label, + type: "square" + }) + } + if(colourBelowZero){ + legend.push({ + value: colourBelowZero.label, + color: colourBelowZero.colour, + id: colourBelowZero.label, + type: "square" + }) + } + setLegendPayload(legend) + }, [colourAboveZero, colourBelowZero]) + + const gradientOffset = useMemo(() => { + if (!data.length) return 0; + + const values = data.map(p => p.value); + const max = Math.max(...values); + const min = Math.min(...values); + + if (max <= 0) return 0; + if (min >= 0) return 1; + + return max / (max - min); +}, [data]); + const zoom = () => { let newDomain: number[] | string[] = ["dataMin", "dataMax"]; if (refLeft && refRight && refLeft !== refRight) { @@ -75,12 +123,16 @@ export default function SingleSetAreaChart(props: Props) { setRefRight(undefined); zoom(); }}> + moment(timestamp).format("lll")} content={(props: TooltipProps) => ( - `${value}`} /> + `${value}` + (tooltipUnit ? tooltipUnit : "")} /> )} /> - + + {colourAboveZero && colourBelowZero && + + + + + } + + ([]) + const [{user}] = useGlobalState() + const componentAPI = useComponentAPI(); + const [recent, setRecent] = useState() + const warmingColour = orange["500"] + const coolingColour = blue["500"] + + /** + * the use effect will use the temp component passed in get the measurements, then use the first node and the last node to determine the difference, + * if there is only one node, or only one node is reading due to errors, then it will use that as both the inlet and outlet so the delta will be 0, + * + * //NOT IMPLEMENTED YET// + * if an ambient is passed in it will use the readings from the ambient as the inlet and the temp component as the outlet + */ + useEffect(()=>{ + //get the measurements for the temp component + componentAPI.listUnitMeasurements( + deviceID, + tempComponent.key(), + start.toISOString(), + end.toISOString(), + 500, + 0, + "timestamp", + ).then(resp => { + if(resp.data.measurements){ + let tempCompMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user)); + //if there is an ambient component, then treat the temp component like a single sensor and not a chain, and use the ambient measurements as the inlet + if(ambient){ + //TODO: this is just an idea of how to handle a possible path for the overhaul of gates, if that is the route we take then this needs to be coded + + }else{//else no ambient is passed in treat the temp component like a chain + let deltas: SSAreaDataPoint[] = [] + tempCompMeasurements.forEach(um => { + if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){ + um.values.forEach((nodes, index) => { + if (nodes.values && nodes.values.length > 0 && um.timestamps[index]){ + //use the first node as the inlet + let inletNode = nodes.values[0] + //use the second node as the outlet, if there is an error or there is only one node then it will be used as both and the delta will be 0 + let outletNode = nodes.values[nodes.values.length -1] + let newDelta: SSAreaDataPoint = { + value: Math.round((outletNode - inletNode)*100)/100, + timestamp: moment(um.timestamps[index]).valueOf(), + } + deltas.push(newDelta) + } + }) + } + }) + setData(deltas) + //use the components status to set the recent to show in the header of the card + let recent: SSAreaDataPoint | undefined + if(tempComponent.status.measurement){ + tempComponent.status.measurement.forEach(um => { + if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){ + let clone = cloneDeep(um) + let m = UnitMeasurement.create(clone, user) + if(m.values.length > 0){ + let lastReading = m.values[m.values.length - 1] + if (lastReading.values.length > 0){ + recent = { + value: lastReading.values[lastReading.values.length -1] - lastReading.values[0], + timestamp: moment(m.timestamps[m.values.length - 1]).valueOf() + } + } + } + } + }) + } + setRecent(recent) + } + } + }) + },[tempComponent, ambient, deviceID, start, end]) + + return ( + + Delta Temp} + subheader={ + recent ? + ( + + + + {"Delta Temp: "} + 0 ? warmingColour : coolingColour, fontWeight: 500 }}> + {recent.value.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")} + +
+
+
+ + + {moment(recent.timestamp).fromNow()} + + +
+ ) + : ( + + No Data + + ) + + } + /> + +
+ ) + +} \ No newline at end of file diff --git a/src/gate/GateFlowGraph.tsx b/src/gate/GateFlowGraph.tsx index c2eca79..b9a85b1 100644 --- a/src/gate/GateFlowGraph.tsx +++ b/src/gate/GateFlowGraph.tsx @@ -209,6 +209,8 @@ export default function GateFlowGraph(props: Props) { {flowData.length !== 0 ? ( ; - device: string | number; + device: number; pressure: string; ambient: string; tempChain: string; @@ -297,7 +298,7 @@ export default function GateGraphs(props: Props) { } /** - * This function creates the charts for the gate page using the MAIN components on the gate, it DOES NOT show any other components that could be on the device + * 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 = () => { @@ -309,6 +310,8 @@ export default function GateGraphs(props: Props) { if(pressureComp && pressureReadings){ graphs.push(graphCard(pressureComp, pressureReadings)) } + // maybe also put the delta time graph here as well + //T/H chain let tempComp = compMap.get(tempChain) let tempReadings = compMeasurements.get(tempChain) @@ -337,6 +340,7 @@ export default function GateGraphs(props: Props) { } const analyticGraphs = () => { + let tempComp = compMap.get(tempChain) return ( + {/* add a new chart here for the delta temp over time */} + {tempComp && + + } ); }; diff --git a/src/gate/GateList.tsx b/src/gate/GateList.tsx index 5c7fb0c..b07c113 100644 --- a/src/gate/GateList.tsx +++ b/src/gate/GateList.tsx @@ -311,7 +311,7 @@ export default function GateList(props: Props) { {terminalMap.get(gate.terminal()) ?? "None"} - Conditioning: + Delta Temp: {conditionDisplay(gate)} {/* taking these out for now because we are not sure if the customer wants them */} diff --git a/src/pages/Gate.tsx b/src/pages/Gate.tsx index acaf847..db876e4 100644 --- a/src/pages/Gate.tsx +++ b/src/pages/Gate.tsx @@ -134,7 +134,6 @@ export default function Gate(props: Props) { gateAPI .getGatePageData(id, as) .then(resp => { - console.log(resp.data); let p = new Map(); Object.keys(resp.data.preferences).forEach(k => { let prefKey = parseInt(k);