added and all nodes option for the cards and graphs
This commit is contained in:
parent
99f37f7bda
commit
c3ee4a518c
3 changed files with 141 additions and 39 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { AccessTime, MoreVert } from "@mui/icons-material";
|
||||
import { Avatar, Box, Button, Card, Grid2, IconButton, Menu, MenuItem, Typography, useTheme } from "@mui/material";
|
||||
import { Avatar, Box, Button, Card, Checkbox, FormControlLabel, Grid2, IconButton, Menu, MenuItem, Typography, useTheme } from "@mui/material";
|
||||
import BinSensorCard from "bin/BinSensorCard";
|
||||
import { GetDefaultDateRange } from "common/time/DateRange";
|
||||
import { ExtractMoisture } from "grain";
|
||||
|
|
@ -71,6 +71,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
const defaultDateRange = GetDefaultDateRange();
|
||||
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
|
||||
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
|
||||
const [filterNodes, setFilterNodes] = useState(true)
|
||||
|
||||
//organize the components into the correct 'positions' using the bins preferences
|
||||
useEffect(()=>{
|
||||
|
|
@ -303,21 +304,21 @@ export default function BinSensorsDisplay(props: Props){
|
|||
const rows: { label: string; min: string; avg: string; max: string }[] = [
|
||||
{
|
||||
label: `Temp ${unit}`,
|
||||
min: cable.minTemp(user.tempUnit()).toFixed(2),
|
||||
avg: cable.aveTemp(user.tempUnit()).toFixed(2),
|
||||
max: cable.maxTemp(user.tempUnit()).toFixed(2),
|
||||
min: cable.minTemp(user.tempUnit(), filterNodes).toFixed(2),
|
||||
avg: cable.aveTemp(user.tempUnit(), filterNodes).toFixed(2),
|
||||
max: cable.maxTemp(user.tempUnit(), filterNodes).toFixed(2),
|
||||
},
|
||||
...(hasHumidity ? [{
|
||||
label: "RH %",
|
||||
min: cable.minHumidity().toFixed(2),
|
||||
avg: cable.aveHumidity().toFixed(2),
|
||||
max: cable.maxHumidity().toFixed(2),
|
||||
min: cable.minHumidity(filterNodes).toFixed(2),
|
||||
avg: cable.aveHumidity(filterNodes).toFixed(2),
|
||||
max: cable.maxHumidity(filterNodes).toFixed(2),
|
||||
}] : []),
|
||||
...(hasMoisture ? [{
|
||||
label: "EMC %",
|
||||
min: cable.minMoisture().toFixed(2),
|
||||
avg: cable.aveMoisture().toFixed(2),
|
||||
max: cable.maxMoisture().toFixed(2),
|
||||
min: cable.minMoisture(filterNodes).toFixed(2),
|
||||
avg: cable.aveMoisture(filterNodes).toFixed(2),
|
||||
max: cable.maxMoisture(filterNodes).toFixed(2),
|
||||
}] : []),
|
||||
]
|
||||
|
||||
|
|
@ -695,6 +696,20 @@ export default function BinSensorsDisplay(props: Props){
|
|||
{showGraphs &&
|
||||
<TimeBar updateDateRange={updateDateRange} startDate={startDate} endDate={endDate} />
|
||||
}
|
||||
{cables.length > 0 &&
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterNodes}
|
||||
onChange={(e, checked) => {
|
||||
setFilterNodes(!filterNodes)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={<Typography>Show All Nodes
|
||||
</Typography>}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -718,11 +733,12 @@ export default function BinSensorsDisplay(props: Props){
|
|||
<BinSensorGraph
|
||||
icon={icon}
|
||||
title={cable.name()}
|
||||
tag={"Cable"}
|
||||
tag={"Cable Averages"}
|
||||
device={device}
|
||||
component={cable.key()}
|
||||
component={components.get(cable.key())!}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
allNodes={!filterNodes}
|
||||
onMenuClick={(event) => {
|
||||
setComponentUnnassigned(false);
|
||||
setSelectedComponentType(cable.type());
|
||||
|
|
@ -752,7 +768,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
title={plenum.name()}
|
||||
tag={"Plenum"}
|
||||
device={device}
|
||||
component={plenum.key()}
|
||||
component={components.get(plenum.key())!}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onMenuClick={(event) => {
|
||||
|
|
@ -783,7 +799,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
title={pressure.name()}
|
||||
tag={"Pressure"}
|
||||
device={device}
|
||||
component={pressure.key()}
|
||||
component={components.get(pressure.key())!}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onMenuClick={(event) => {
|
||||
|
|
@ -813,7 +829,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
title={ambient.name()}
|
||||
tag={"Ambient"}
|
||||
device={device}
|
||||
component={ambient.key()}
|
||||
component={components.get(ambient.key())!}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onMenuClick={(event) => {
|
||||
|
|
@ -844,7 +860,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
title={h.name()}
|
||||
tag={"Headspace"}
|
||||
device={device}
|
||||
component={h.key()}
|
||||
component={components.get(h.key())!}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onMenuClick={(event) => {
|
||||
|
|
@ -870,7 +886,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
let icon = GetComponentIcon(co2.type(), co2.subType(), themeType)
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
<BinSensorGraph device={device} component={co2.key()} startDate={startDate} endDate={endDate} />
|
||||
<BinSensorGraph device={device} component={components.get(co2.key())!} startDate={startDate} endDate={endDate} />
|
||||
</Grid2>
|
||||
)
|
||||
}
|
||||
|
|
@ -885,7 +901,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
if(showGraphs && device){
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
<BinSensorGraph device={device} component={lidar.key()} startDate={startDate} endDate={endDate} />
|
||||
<BinSensorGraph device={device} component={components.get(lidar.key())!} startDate={startDate} endDate={endDate} />
|
||||
</Grid2>
|
||||
)
|
||||
}
|
||||
|
|
@ -908,7 +924,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
if(showGraphs && device){
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
<BinSensorGraph device={device} component={heater.key()} startDate={startDate} endDate={endDate} />
|
||||
<BinSensorGraph device={device} component={components.get(heater.key())!} startDate={startDate} endDate={endDate} />
|
||||
</Grid2>
|
||||
)
|
||||
}
|
||||
|
|
@ -923,7 +939,7 @@ export default function BinSensorsDisplay(props: Props){
|
|||
if(showGraphs && device){
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
<BinSensorGraph device={device} component={fan.key()} startDate={startDate} endDate={endDate} />
|
||||
<BinSensorGraph device={device} component={components.get(fan.key())!} startDate={startDate} endDate={endDate} />
|
||||
</Grid2>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { Avatar, Box, Card, CardHeader, CircularProgress, IconButton, Paper, Typography, useTheme } from "@mui/material";
|
||||
import { Avatar, Box, Card, CircularProgress, IconButton, Paper, Typography, useTheme } from "@mui/material";
|
||||
import moment, { Moment } from "moment";
|
||||
import { Component } from "models";
|
||||
import { useComponentAPI, useGlobalState } from "providers";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { UnitMeasurement } from "models/UnitMeasurement";
|
||||
import { quack } from "protobuf-ts/quack";
|
||||
import {
|
||||
Legend,
|
||||
Line,
|
||||
|
|
@ -22,10 +24,45 @@ interface Props {
|
|||
title?: string;
|
||||
tag?: string;
|
||||
onMenuClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
component: string;
|
||||
component: Component;
|
||||
startDate: Moment;
|
||||
endDate: Moment;
|
||||
customHeight?: string | number;
|
||||
/**
|
||||
* this will cause all of the readings for a cable to be used in the average,
|
||||
* by default it will filter any excluded nodes and only use nodes below the top node
|
||||
*/
|
||||
allNodes?: boolean
|
||||
}
|
||||
|
||||
/** Grain-cable nodes to include when averaging (matches GrainCable.filter indexing). */
|
||||
function valuesForAveraging(
|
||||
nodeValues: number[],
|
||||
component: Component,
|
||||
allNodes?: boolean
|
||||
): number[] {
|
||||
if (allNodes) {
|
||||
return nodeValues.filter(v => !isNaN(v));
|
||||
}
|
||||
|
||||
const grainFilledTo = component.settings.grainFilledTo ?? 0;
|
||||
const excludedNodes = component.settings.excludedNodes ?? [];
|
||||
const included: number[] = [];
|
||||
|
||||
nodeValues.forEach((v, index) => {
|
||||
if (isNaN(v)) {
|
||||
return;
|
||||
}
|
||||
if (excludedNodes.includes(index)) {
|
||||
return;
|
||||
}
|
||||
if (grainFilledTo > 0 && index >= grainFilledTo) {
|
||||
return;
|
||||
}
|
||||
included.push(v);
|
||||
});
|
||||
|
||||
return included;
|
||||
}
|
||||
|
||||
interface MeasurementMeta {
|
||||
|
|
@ -54,7 +91,11 @@ function normalizeValue(value: number, min: number, max: number): number {
|
|||
return ((value - min) / (max - min)) * 100;
|
||||
}
|
||||
|
||||
function buildChartData(measurements: UnitMeasurement[]): {
|
||||
function buildChartData(
|
||||
measurements: UnitMeasurement[],
|
||||
component: Component,
|
||||
allNodes?: boolean
|
||||
): {
|
||||
data: ChartPoint[];
|
||||
series: SeriesInfo[];
|
||||
} {
|
||||
|
|
@ -66,7 +107,7 @@ function buildChartData(measurements: UnitMeasurement[]): {
|
|||
if (valArray.error || !um.timestamps[i]) {
|
||||
return;
|
||||
}
|
||||
const nodeValues = valArray.values.filter(v => !isNaN(v));
|
||||
const nodeValues = valuesForAveraging(valArray.values, component, allNodes);
|
||||
if (nodeValues.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -137,7 +178,7 @@ function SensorGraphTooltip(props: TooltipProps<number, string>) {
|
|||
}
|
||||
|
||||
export default function BinSensorGraph(props: Props) {
|
||||
const { device, icon, title, tag, component, startDate, endDate, customHeight = 350, onMenuClick } = props;
|
||||
const { device, icon, title, tag, component, startDate, endDate, customHeight = 350, onMenuClick, allNodes } = props;
|
||||
const [measurements, setMeasurements] = useState<UnitMeasurement[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const componentAPI = useComponentAPI();
|
||||
|
|
@ -148,7 +189,7 @@ export default function BinSensorGraph(props: Props) {
|
|||
useEffect(() => {
|
||||
setLoading(true);
|
||||
componentAPI
|
||||
.sampleUnitMeasurements(device, component, startDate, endDate, 1000)
|
||||
.sampleUnitMeasurements(device, component.key(), startDate, endDate, 1000, undefined, undefined, undefined, undefined, true)
|
||||
.then(res => {
|
||||
setMeasurements(res.data.measurements.map(um => UnitMeasurement.any(um, user)));
|
||||
})
|
||||
|
|
@ -160,7 +201,10 @@ export default function BinSensorGraph(props: Props) {
|
|||
});
|
||||
}, [device, component, startDate, endDate, componentAPI, user]);
|
||||
|
||||
const { data, series } = useMemo(() => buildChartData(measurements), [measurements]);
|
||||
const { data, series } = useMemo(
|
||||
() => buildChartData(measurements, component, allNodes),
|
||||
[measurements, component, allNodes]
|
||||
);
|
||||
|
||||
return (
|
||||
<Card raised sx={{ height: "100%", padding: 1.5 }}>
|
||||
|
|
@ -170,7 +214,7 @@ export default function BinSensorGraph(props: Props) {
|
|||
<Avatar
|
||||
variant="square"
|
||||
src={icon}
|
||||
alt={name + " icon"}
|
||||
alt={(title ?? "sensor") + " icon"}
|
||||
style={{ width: theme.spacing(3), height: theme.spacing(3) }}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,21 +132,43 @@ export class GrainCable {
|
|||
return describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour();
|
||||
}
|
||||
|
||||
public minTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
|
||||
public minTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS, inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
let grainMax = Math.min(...this.filteredNodes(true).temps)
|
||||
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
|
||||
return this.farenheit(grainMax)
|
||||
return grainMax
|
||||
}
|
||||
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
|
||||
return this.farenheit(Math.min(...this.temperatures));
|
||||
return Math.min(...this.temperatures);
|
||||
}
|
||||
|
||||
public minHumidity() {
|
||||
public minHumidity(inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
return Math.max(...this.filteredNodes(true).humids)
|
||||
}
|
||||
return Math.min(...this.humidities);
|
||||
}
|
||||
|
||||
public minMoisture() {
|
||||
public minMoisture(inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
return Math.min(...this.filteredNodes(true).moistures)
|
||||
}
|
||||
return Math.min(...this.grainMoistures);
|
||||
}
|
||||
|
||||
public aveTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
|
||||
/**
|
||||
* Averages the temperatures of the grain cable, if filtered is true it will filter the excluded nodes and nodes above the top node before averaging
|
||||
* @param unit the unit of the temperature
|
||||
* @param inGrain whether to filter the excluded nodes and nodes above the top node before averaging
|
||||
* @returns
|
||||
*/
|
||||
public aveTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS, inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
let filteredNodes = this.filteredNodes(true)
|
||||
return filteredNodes.temps.reduce((p: any, c: any) => p + c, 0) / filteredNodes.temps.length
|
||||
}
|
||||
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
|
||||
return this.farenheit(
|
||||
this.temperatures.reduce((p: any, c: any) => p + c, 0) / this.temperatures.length
|
||||
|
|
@ -154,25 +176,45 @@ export class GrainCable {
|
|||
return this.temperatures.reduce((p: any, c: any) => p + c, 0) / this.temperatures.length;
|
||||
}
|
||||
|
||||
public aveHumidity() {
|
||||
public aveHumidity(inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
let filteredNodes = this.filteredNodes(true)
|
||||
return filteredNodes.humids.reduce((p: any, c: any) => p + c, 0) / filteredNodes.humids.length
|
||||
}
|
||||
return this.humidities.reduce((p: any, c: any) => p + c, 0) / this.humidities.length;
|
||||
}
|
||||
|
||||
public aveMoisture() {
|
||||
public aveMoisture(inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
let filteredNodes = this.filteredNodes(true)
|
||||
return filteredNodes.moistures.reduce((p: any, c: any) => p + c, 0) / filteredNodes.moistures.length
|
||||
}
|
||||
return this.grainMoistures.reduce((p: any, c: any) => p + c, 0) / this.grainMoistures.length;
|
||||
}
|
||||
|
||||
public maxTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
|
||||
public maxTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS, inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
let grainMax = Math.max(...this.filteredNodes(true).temps)
|
||||
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
|
||||
return this.farenheit(grainMax)
|
||||
return grainMax
|
||||
}
|
||||
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
|
||||
return this.farenheit(Math.max(...this.temperatures));
|
||||
return Math.max(...this.temperatures);
|
||||
}
|
||||
|
||||
public maxHumidity() {
|
||||
public maxHumidity(inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
return Math.max(...this.filteredNodes(true).humids)
|
||||
}
|
||||
return Math.max(...this.humidities);
|
||||
}
|
||||
|
||||
public maxMoisture() {
|
||||
public maxMoisture(inGrain?: boolean) {
|
||||
if(inGrain){
|
||||
return Math.max(...this.filteredNodes(true).moistures)
|
||||
}
|
||||
return Math.min(...this.grainMoistures);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue