This commit is contained in:
sebseb7
2025-12-26 00:32:04 +01:00
parent 93e3baa1c5
commit 94a435c6f6
5 changed files with 527 additions and 33 deletions

View File

@@ -1,9 +1,112 @@
import React, { Component } from 'react';
import { Box, Paper, Typography, CircularProgress, IconButton } from '@mui/material';
import { LineChart } from '@mui/x-charts/LineChart';
import { useDrawingArea, useYScale, useXScale } from '@mui/x-charts/hooks';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
// Custom component to render a horizontal band between two y-values
function ReferenceArea({ yMin, yMax, color = 'rgba(76, 175, 80, 0.15)', axisId = 'left' }) {
const { left, width } = useDrawingArea();
const yScale = useYScale(axisId);
if (!yScale) return null;
const y1 = yScale(yMax);
const y2 = yScale(yMin);
if (y1 === undefined || y2 === undefined) return null;
return (
<rect
x={left}
y={Math.min(y1, y2)}
width={width}
height={Math.abs(y2 - y1)}
fill={color}
/>
);
}
// Custom component to render vertical time bands every 6 hours aligned to midnight
function TimeReferenceAreas({ axisStart, axisEnd, colors }) {
const { top, height } = useDrawingArea();
const xScale = useXScale();
if (!xScale) return null;
// Calculate 6-hour bands aligned to midnight
const SIX_HOURS = 6 * 60 * 60 * 1000;
const bands = [];
// Find the first midnight before axisStart
const startDate = new Date(axisStart);
const midnight = new Date(startDate);
midnight.setHours(0, 0, 0, 0);
// Start from that midnight
let bandStart = midnight.getTime();
while (bandStart < axisEnd) {
const bandEnd = bandStart + SIX_HOURS;
// Only render if band overlaps with visible range
if (bandEnd > axisStart && bandStart < axisEnd) {
const visibleStart = Math.max(bandStart, axisStart);
const visibleEnd = Math.min(bandEnd, axisEnd);
const x1 = xScale(new Date(visibleStart));
const x2 = xScale(new Date(visibleEnd));
if (x1 !== undefined && x2 !== undefined) {
// Determine which 6-hour block (0-3) based on hour of day
const hour = new Date(bandStart).getHours();
const blockIndex = Math.floor(hour / 6); // 0, 1, 2, or 3
const color = colors[blockIndex % colors.length];
bands.push(
<rect
key={bandStart}
x={Math.min(x1, x2)}
y={top}
width={Math.abs(x2 - x1)}
height={height}
fill={color}
/>
);
}
}
bandStart = bandEnd;
}
return <>{bands}</>;
}
// Helper function to calculate Simple Moving Average
function calculateSMA(data, channelKey, period) {
if (period <= 1 || data.length === 0) return data;
return data.map((row, i) => {
const newRow = { ...row };
const values = [];
// Look back up to 'period' samples
for (let j = Math.max(0, i - period + 1); j <= i; j++) {
const val = data[j][channelKey];
if (val !== null && val !== undefined && !isNaN(val)) {
values.push(val);
}
}
// Calculate average if we have values
if (values.length > 0) {
newRow[channelKey] = values.reduce((a, b) => a + b, 0) / values.length;
}
return newRow;
});
}
export default class Chart extends Component {
constructor(props) {
super(props);
@@ -115,9 +218,14 @@ export default class Chart extends Component {
// Calculate effective end
let end = explicitEnd;
// If 'until' is null, extend to next point or now
// If 'until' is null, extend to next point or now (but never beyond current time)
const nowTime = Date.now();
if (!end) {
end = nextStart || endTimeVal;
end = nextStart || Math.min(endTimeVal, nowTime);
}
// Never extend data beyond the current time
if (end > nowTime) {
end = nowTime;
}
// Strict Cutoff: Current interval cannot extend past the start of the next interval
@@ -169,7 +277,19 @@ export default class Chart extends Component {
return row;
});
this.setState({ data: denseData, loading: false });
// 4. Apply SMA for channels that have it configured
const { channelConfig } = this.props;
let processedData = denseData;
if (channelConfig) {
channelConfig.forEach(cfg => {
if (cfg.sma && cfg.sma > 1) {
processedData = calculateSMA(processedData, cfg.id, cfg.sma);
}
});
}
this.setState({ data: processedData, loading: false });
})
.catch(err => {
console.error(err);
@@ -305,6 +425,32 @@ export default class Chart extends Component {
const axisEnd = windowEnd ? windowEnd.getTime() : Date.now();
const axisStart = axisEnd - rangeMs;
// Determine if all visible series are Temperature channels
const isTemperatureOnly = visibleChannels.length > 0 && visibleChannels.every(id => {
const lcId = id.toLowerCase();
return lcId.includes('temp') || lcId.includes('temperature');
});
// Determine if all visible series are Humidity channels
const isHumidityOnly = visibleChannels.length > 0 && visibleChannels.every(id => {
const lcId = id.toLowerCase();
return lcId.includes('humid') || lcId.includes('humidity') || lcId.includes('rh');
});
// Determine if all visible series are Light channels
const isLightOnly = visibleChannels.length > 0 && visibleChannels.every(id => {
const lcId = id.toLowerCase();
return lcId.includes('light');
});
// Colors for 6-hour time bands (midnight, 6am, noon, 6pm)
const lightBandColors = [
'rgba(0, 0, 0, 0.1)', // 00:00-06:00 - black (night)
'rgba(135, 206, 250, 0.1)', // 06:00-12:00 - light blue (morning)
'rgba(255, 255, 180, 0.1)', // 12:00-18:00 - light yellow (afternoon)
'rgba(255, 200, 150, 0.1)', // 18:00-24:00 - light orange (evening)
];
return (
<Box sx={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', p: 2, boxSizing: 'border-box' }}>
<Paper sx={{ p: 2, flexGrow: 1, display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}>
@@ -367,7 +513,20 @@ export default class Chart extends Component {
fillOpacity: series.find(s => s.area)?.fillOpacity ?? 0.5,
},
}}
/>
>
{/* Green reference band for temperature charts (20-25°C) */}
{isTemperatureOnly && (
<ReferenceArea yMin={20} yMax={25} color="rgba(76, 175, 80, 0.2)" />
)}
{/* Green reference band for humidity charts (50-70%) */}
{isHumidityOnly && (
<ReferenceArea yMin={50} yMax={70} color="rgba(76, 175, 80, 0.2)" />
)}
{/* Time-based vertical bands for light charts (6-hour intervals) */}
{isLightOnly && (
<TimeReferenceAreas axisStart={axisStart} axisEnd={axisEnd} colors={lightBandColors} />
)}
</LineChart>
</Box>
</Paper>
</Box>