diff --git a/src/bin/3dView/Data/BuildCableData.ts b/src/bin/3dView/Data/BuildCableData.ts index 7fce813..9d65297 100644 --- a/src/bin/3dView/Data/BuildCableData.ts +++ b/src/bin/3dView/Data/BuildCableData.ts @@ -163,7 +163,7 @@ export function BuildCableData(bin: Bin, dims: BinDims): CableData[] { const layout = getLayoutFromDiameter(dims.diameter); const distributed = distributeCables(cables.length, layout); - const cableRadius = 5; + const cableRadius = 3; const result: CableData[] = []; diff --git a/src/bin/3dView/Data/BuildNodeData.ts b/src/bin/3dView/Data/BuildNodeData.ts index fd3f5dd..c1d88a8 100644 --- a/src/bin/3dView/Data/BuildNodeData.ts +++ b/src/bin/3dView/Data/BuildNodeData.ts @@ -21,7 +21,7 @@ export interface NodeData { } export function BuildNodeData(cables: CableData[]): NodeData[] { - const nodeRadius = 15 + const nodeRadius = 10 let nodeData: NodeData[] = [] cables.forEach((cable, cableIndex) => { diff --git a/src/bin/3dView/Systems/Cables/CableNode.tsx b/src/bin/3dView/Systems/Cables/CableNode.tsx index 08fa571..faca893 100644 --- a/src/bin/3dView/Systems/Cables/CableNode.tsx +++ b/src/bin/3dView/Systems/Cables/CableNode.tsx @@ -24,9 +24,10 @@ export default function CableNode(props: Props) { const { camera } = useThree(); const [{ user }] = useGlobalState(); // const [showLabel, setShowLabel] = useState(false); - const ROW_HEIGHT = 45; - const PADDING = 15; - const LABEL_WIDTH = 220; + const ROW_HEIGHT = 35; + const PADDING = 5; + const LABEL_WIDTH = 200; + const FONT_SIZE = 25; const flagRef = React.useRef(null); useFrame(() => { @@ -170,7 +171,7 @@ export default function CableNode(props: Props) { c.key); const buckets = assignMeasurementsToCheckpoints( cableKeys, @@ -259,8 +268,26 @@ function buildStatusCheckpoints( carriedByCable.set(key, merged); return buildGrainCableFromReadings(template, merged, time); }); - return { time, cables }; - }); + return { time, cables, timeString: time.toISOString() }; + }).filter(checkpoint => //filter the checkpoints to only include ones that have readings + checkpoint.cables.some(cable => + cable.celcius.length > 0 || + cable.relativeHumidity.length > 0 || + cable.moisture.length > 0 + )); +} + + +/** + * Calculates the number of checkpoints to create based on the start and end dates. + * Is hard coded to space the checkpoints 6 hours apart. + * @param start - The start date + * @param end - The end date + * @returns The number of checkpoints to create + */ +function checkpointCountFromRange(start: Moment, end: Moment): number { + const hours = end.diff(start, 'hours'); + return Math.max(1, Math.floor(hours / 6) + 1); } /** @@ -272,14 +299,52 @@ function buildStatusCheckpoints( export default function BinPlayer(props: Props) { const { bin, componentDevices, setBin } = props; const componentAPI = useComponentAPI(); - const defaultDateRange = GetDefaultDateRange(); - const [startDate, setStartDate] = useState(defaultDateRange.start); - const [endDate, setEndDate] = useState(defaultDateRange.end); -// const [statusCheckpoints, setStatusCheckpoints] = useState([]); + //the default start and end dates are 1 week ago and now + const [dateSelect, setDateSelect] = useState(0); + const [startDate, setStartDate] = useState(moment().subtract(1, 'week')); + const [endDate, setEndDate] = useState(moment); + const [dateRangeDialog, setDateRangeDialog] = useState(false); + const [tempStartDate, setTempStartDate] = useState(moment().subtract(1, 'week')); + const [tempEndDate, setTempEndDate] = useState(moment()); + const [currentCheckpointTime, setCurrentCheckpointTime] = useState(undefined) + const [displayProgress, setDisplayProgress] = useState(0); + const totalCheckpoints = useRef(0); + const checkpointStartTime = useRef(0); + const currentCheckpointIndex = useRef(0); const isPlaying = useRef(false); + const PLAYBACK_INTERVAL = 2000 //playback time between checkpoints + const [isPlayingState, setIsPlayingState] = useState(false); const stopPlayer = () => { isPlaying.current = false; }; const originalBin = useRef(null); + useEffect(() => { + if (!isPlayingState) return; + + const fps = 30; + const interval = 1000 / fps; + const stepDuration = PLAYBACK_INTERVAL; // must match your setTimeout delay + + const timer = setInterval(() => { + if (!isPlayingState) { + clearInterval(timer); + setDisplayProgress(0); + return; + } + const total = totalCheckpoints.current; + if (total === 0) return; + + const idx = currentCheckpointIndex.current; + // and change the progress formula to account for the last checkpoint reaching 100%: + const elapsed = Date.now() - checkpointStartTime.current; + const stepProgress = Math.min(elapsed / stepDuration, 1); + // use (total - 1) as denominator so the last checkpoint goes from ~end to 100% + const raw = total <= 1 ? 100 : ((idx + stepProgress) / (total - 1)) * 100; + setDisplayProgress(Math.min(raw, 100)); + }, interval); + + return () => clearInterval(timer); + }, [isPlayingState]); // re-run when playing state changes + /** * Fetches sampled unit measurements for every grain cable in the current bin status, * then builds `statusCheckpoints` for playback over [startDate, endDate]. @@ -289,10 +354,11 @@ const startPlayer = async () => { console.log(endDate.toISOString()) originalBin.current = cloneDeep(bin); // capture before async work begins isPlaying.current = true; + setIsPlayingState(true) const sampleRequests = bin.status.grainCables.map(cable => { const deviceID = componentDevices.get(cable.key) ?? 0; - return componentAPI.sampleUnitMeasurements(deviceID, cable.key, startDate, endDate, 100); + return componentAPI.sampleUnitMeasurements(deviceID, cable.key, startDate, endDate, 100, undefined, undefined, undefined, undefined, true); }); const responses = await Promise.all(sampleRequests); @@ -300,29 +366,162 @@ const startPlayer = async () => { bin.status.grainCables, responses.map(r => r.data), startDate, - endDate + endDate, + checkpointCountFromRange(startDate, endDate) ); console.log(responses) console.log(checkpoints) - for (const checkpoint of checkpoints) { + for (let i = 0; i < checkpoints.length; i++) { if (!isPlaying.current) { setBin(originalBin.current!); + setCurrentCheckpointTime(undefined); + setIsPlayingState(false) return; } + currentCheckpointIndex.current = i; + totalCheckpoints.current = checkpoints.length; + checkpointStartTime.current = Date.now(); const newBin = cloneDeep(originalBin.current!); - newBin.status.grainCables = checkpoint.cables; + newBin.status.grainCables = checkpoints[i].cables; setBin(newBin); - await new Promise(res => setTimeout(res, 2000)); + setCurrentCheckpointTime(checkpoints[i].time) + await new Promise(res => setTimeout(res, PLAYBACK_INTERVAL)); console.log("next step") } isPlaying.current = false; + setIsPlayingState(false) setBin(originalBin.current!); + setCurrentCheckpointTime(undefined); }; +const datePickerDialog = () => { + return ( + setDateRangeDialog(false)} + aria-labelledby="date-range-dialog"> + + { + setTempStartDate(moment(e.target.value)); + }} + /> + { + setTempEndDate(moment(e.target.value)); + }} + /> + + + + + + + ); +}; + +const dateSelector = () => { + return ( + + + + + {dateSelect === 3 && ( + + )} + + ) +} + return ( - + {datePickerDialog()} + + {/* start/stop button */} + + {isPlayingState ? ( + + ) : ( + + )} + + + {/* progress bar and date display */} + + {currentCheckpointTime ? currentCheckpointTime.format("MMM D, YYYY h:mm A") : moment().format("MMM D, YYYY h:mm A")} + + + {startDate.format("MMM D, YYYY")} + {endDate.format("MMM D, YYYY")} + + + {/* date range selector */} + + {dateSelector()} + + ); } diff --git a/src/bin/bin3dVisualizer.tsx b/src/bin/bin3dVisualizer.tsx index 0ff9766..54a99ac 100644 --- a/src/bin/bin3dVisualizer.tsx +++ b/src/bin/bin3dVisualizer.tsx @@ -1,4 +1,4 @@ -import { Box, Checkbox, FormControl, MenuItem, List, ListItem, InputLabel, Select, Typography, Stack } from "@mui/material"; +import { Box, Checkbox, FormControl, MenuItem, List, ListItem, InputLabel, Select, Typography, Stack, FormControlLabel } from "@mui/material"; import Bin3dView from "bin/3dView/Scene/Bin3dView"; import { Bin, Component, Device } from "models"; import { Controller } from "models/Controller"; @@ -42,6 +42,7 @@ export default function bin3dVisualizer(props: Props){ const [openNodeControls, setOpenNodeControls] = useState(false) const [devMap, setDevMap] = useState>(new Map()) const [modeChangeInProgress, setModeChangeInProgress] = useState(false) + const [showLabels, setShowLabels] = useState(true) const isMobile = useMobile() useEffect(()=>{ @@ -102,7 +103,21 @@ export default function bin3dVisualizer(props: Props){ const heatmapDisplay = () => { return ( - Heatmap Display + + Heatmap Display + { + setShowLabels(checked) + }} + /> + } + label={Show Values} + /> + {/* Display */}