upgraded the line charts

This commit is contained in:
2025-07-24 05:05:28 +03:00
parent e4d83e64de
commit 1f837dfdb0
5 changed files with 694 additions and 1802 deletions

View File

@@ -3,19 +3,9 @@ import { Box, Paper, Typography, Fade, useTheme, Grid, AppBar, Toolbar, Circular
import RefreshIcon from '@mui/icons-material/Refresh';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CancelIcon from '@mui/icons-material/Cancel';
import Plot from 'react-plotly.js';
import { Layout, PlotData, Config } from 'plotly.js';
import { LineChart } from '@mui/x-charts/LineChart';
import { config } from '../config/env';
// Extend the Window interface to include Google Charts
declare global {
interface Window {
google?: {
charts?: any;
};
}
}
// Define the structure of our data
interface ChartData {
power: string;
@@ -36,8 +26,6 @@ const Temperature = () => {
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const [refreshing, setRefreshing] = useState(false);
const [decisionLoading, setDecisionLoading] = useState(false);
const [powerZoom, setPowerZoom] = useState<{xRange?: [number, number]; yRange?: [number, number]}>({});
const [tempZoom, setTempZoom] = useState<{xRange?: [number, number]; yRange?: [number, number]}>({});
const [alert, setAlert] = useState<{ open: boolean; message: string; severity: 'success' | 'error' }>({
open: false,
message: '',
@@ -45,7 +33,7 @@ const Temperature = () => {
});
// Use refs to keep track of the interval
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const updateIntervalMs = 5000; // 5 seconds refresh rate
// Create a memoized fetchData function with useCallback
@@ -125,216 +113,46 @@ const Temperature = () => {
};
}, [fetchData]);
// Process data for Plotly charts
const preparePlotlyData = () => {
if (!data || data.length === 0) return { powerData: [], tempData: [] };
const currentTimestamps = data.map(item => new Date(item.now_timestamp));
const futureTimestamps = data.map(item => new Date(item.future_timestamp));
const currentPower = data.map(item => parseFloat(item.power));
const predictedPower = data.map(item => parseFloat(item.power_future_min));
const currentTemp = data.map(item => parseFloat(item.env_temp_cur));
const predictedTemp = data.map(item => parseFloat(item.env_temp_min));
// Calculate min and max values for range sliders
const powerMin = Math.min(...currentPower, ...predictedPower);
const powerMax = Math.max(...currentPower, ...predictedPower);
const tempMin = Math.min(...currentTemp, ...predictedTemp);
const tempMax = Math.max(...currentTemp, ...predictedTemp);
const powerData: Partial<PlotData>[] = [
{
x: currentTimestamps,
y: currentPower,
type: 'scatter',
mode: 'lines+markers',
name: 'Current Power',
line: { color: theme.palette.primary.main, width: 2 },
marker: { size: 6 }
},
{
x: futureTimestamps,
y: predictedPower,
type: 'scatter',
mode: 'lines+markers',
name: 'Predicted Power',
line: { color: theme.palette.success.main, width: 2, dash: 'dash' },
marker: { size: 6 }
},
// Range slider trace
{
x: [...currentTimestamps, ...futureTimestamps],
y: Array(currentTimestamps.length + futureTimestamps.length).fill(0.5),
type: 'scatter',
mode: 'lines',
name: 'Y Range',
yaxis: 'y2',
line: { color: 'transparent' },
showlegend: false,
hoverinfo: 'skip' as const
}
];
const tempData: Partial<PlotData>[] = [
{
x: currentTimestamps,
y: currentTemp,
type: 'scatter',
mode: 'lines+markers',
name: 'Current Temperature',
line: { color: theme.palette.primary.main, width: 2 },
marker: { size: 6 }
},
{
x: futureTimestamps,
y: predictedTemp,
type: 'scatter',
mode: 'lines+markers',
name: 'Predicted Temperature',
line: { color: theme.palette.success.main, width: 2, dash: 'dash' },
marker: { size: 6 }
},
// Range slider trace
{
x: [...currentTimestamps, ...futureTimestamps],
y: Array(currentTimestamps.length + futureTimestamps.length).fill(0.5),
type: 'scatter',
mode: 'lines',
name: 'Y Range',
yaxis: 'y2',
line: { color: 'transparent' },
showlegend: false,
hoverinfo: 'skip' as const
}
];
return {
powerData,
tempData,
ranges: {
power: { min: powerMin, max: powerMax },
temp: { min: tempMin, max: tempMax }
}
};
};
const { powerData, tempData, ranges } = preparePlotlyData();
// Common layout settings for both charts
const commonLayoutSettings: Partial<Layout> = {
showlegend: true,
legend: {
orientation: 'h',
y: -0.2,
x: 0.5,
xanchor: 'center',
yanchor: 'top',
font: {
size: 12,
family: theme.typography.fontFamily,
color: theme.palette.text.secondary
},
bgcolor: 'rgba(255, 255, 255, 0)',
bordercolor: 'rgba(255, 255, 255, 0)'
},
margin: { t: 60, b: 100, l: 60, r: 60 }, // Increased right margin for Y-axis range slider
plot_bgcolor: 'rgba(0,0,0,0)',
paper_bgcolor: 'rgba(0,0,0,0)',
hovermode: 'closest',
xaxis: {
type: 'date',
gridcolor: theme.palette.divider,
tickfont: { size: 12, color: theme.palette.text.secondary },
showgrid: true,
rangeslider: { visible: true }
},
yaxis2: {
overlaying: 'y',
side: 'right',
showgrid: false,
zeroline: false,
showticklabels: false,
range: [0, 1],
rangeslider: {
visible: true,
thickness: 0.1,
bgcolor: 'rgba(0,0,0,0)',
bordercolor: theme.palette.divider
}
// Process data for charts
const prepareChartData = () => {
if (!data || data.length === 0) {
console.log('No data available, using fallback data');
// Fallback data for testing
const now = new Date();
const fallbackData = Array.from({ length: 10 }, (_, i) => ({
currentTimestamp: new Date(now.getTime() - (9 - i) * 60000), // 1 minute intervals
futureTimestamp: new Date(now.getTime() - (9 - i) * 60000 + 3 * 60000), // 3 minutes in the future
currentPower: 95 + Math.random() * 10,
predictedPower: 115 + Math.random() * 10,
currentTemp: 25 + Math.random() * 5,
predictedTemp: 28 + Math.random() * 5,
}));
return fallbackData;
}
return data.map(item => ({
currentTimestamp: new Date(item.now_timestamp),
futureTimestamp: new Date(item.future_timestamp),
currentPower: parseFloat(item.power),
predictedPower: parseFloat(item.power_future_min),
currentTemp: parseFloat(item.env_temp_cur),
predictedTemp: parseFloat(item.env_temp_min),
}));
};
// Add handlers for zoom events
const handlePowerZoom = (event: any) => {
if (event['xaxis.range[0]']) {
setPowerZoom({
xRange: [new Date(event['xaxis.range[0]']).getTime(), new Date(event['xaxis.range[1]']).getTime()],
yRange: [event['yaxis.range[0]'], event['yaxis.range[1]']]
});
}
};
const chartData = prepareChartData();
const handleTempZoom = (event: any) => {
if (event['xaxis.range[0]']) {
setTempZoom({
xRange: [new Date(event['xaxis.range[0]']).getTime(), new Date(event['xaxis.range[1]']).getTime()],
yRange: [event['yaxis.range[0]'], event['yaxis.range[1]']]
});
}
};
// Extract data arrays for charts
const currentTimestamps = chartData.map(item => item.currentTimestamp);
const futureTimestamps = chartData.map(item => item.futureTimestamp);
const currentPowerData = chartData.map(item => item.currentPower);
const predictedPowerData = chartData.map(item => item.predictedPower);
const currentTempData = chartData.map(item => item.currentTemp);
const predictedTempData = chartData.map(item => item.predictedTemp);
// Modify the power layout to use preserved zoom
const powerLayout: Partial<Layout> = {
...commonLayoutSettings,
yaxis: {
title: 'Power (W)',
gridcolor: theme.palette.divider,
tickfont: { size: 12, color: theme.palette.text.secondary },
titlefont: { size: 14, color: theme.palette.text.primary },
showgrid: true,
rangemode: 'tozero',
fixedrange: false,
range: powerZoom.yRange || (ranges ? [ranges.power.min * 0.9, ranges.power.max * 1.1] : undefined)
},
xaxis: {
...commonLayoutSettings.xaxis,
range: powerZoom.xRange ? [new Date(powerZoom.xRange[0]), new Date(powerZoom.xRange[1])] : undefined
}
};
// Modify the temperature layout to use preserved zoom
const tempLayout: Partial<Layout> = {
...commonLayoutSettings,
yaxis: {
title: 'Temperature (°C)',
gridcolor: theme.palette.divider,
tickfont: { size: 12, color: theme.palette.text.secondary },
titlefont: { size: 14, color: theme.palette.text.primary },
showgrid: true,
rangemode: 'tozero',
fixedrange: false,
range: tempZoom.yRange || (ranges ? [ranges.temp.min * 0.9, ranges.temp.max * 1.1] : undefined)
},
xaxis: {
...commonLayoutSettings.xaxis,
range: tempZoom.xRange ? [new Date(tempZoom.xRange[0]), new Date(tempZoom.xRange[1])] : undefined
}
};
// Common Plotly config with additional modebar buttons
const plotConfig: Partial<Config> = {
responsive: true,
displayModeBar: true,
displaylogo: false,
modeBarButtonsToRemove: ['lasso2d', 'select2d'] as ('lasso2d' | 'select2d')[],
toImageButtonOptions: {
format: 'png' as const,
filename: 'temperature_monitoring',
height: 1000,
width: 1500,
scale: 2
}
};
// Debug logging
console.log('Chart data:', chartData);
console.log('Raw data length:', data.length);
// Handle temperature decision
const handleTemperatureDecision = async (approval: boolean) => {
@@ -378,7 +196,7 @@ const Temperature = () => {
return (
<Box sx={{ flexGrow: 1, bgcolor: theme.palette.background.default }}>
<AppBar
<AppBar
position="static"
elevation={0}
sx={{
@@ -545,18 +363,46 @@ const Temperature = () => {
Loading power data...
</Typography>
</Box>
) : data.length === 0 ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '400px' }}>
<Typography variant="h6" color="text.secondary">No power data available</Typography>
</Box>
) : (
<Box sx={{ height: 400 }}>
<Plot
data={powerData}
layout={powerLayout}
config={plotConfig}
style={{ width: '100%', height: '100%' }}
onRelayout={handlePowerZoom}
<LineChart
height={400}
skipAnimation={false}
series={[
{
data: predictedPowerData,
label: 'Predicted Power (3min)',
color: '#ff9800',
showMark: false,
curve: 'linear'
},
{
data: currentPowerData,
label: 'Current Power',
color: '#028a4a', // B'GREEN brand color
showMark: false
},
]}
xAxis={[
{
scaleType: 'time',
data: [...currentTimestamps, ...futureTimestamps],
valueFormatter: (value: Date) => value.toLocaleTimeString(),
},
]}
yAxis={[
{
width: 50,
valueFormatter: (value: number) => `${value} W`,
}
]}
margin={{ right: 24 }}
slotProps={{
legend: {
direction: 'horizontal',
position: { vertical: 'top', horizontal: 'center' },
},
}}
/>
</Box>
)}
@@ -626,18 +472,45 @@ const Temperature = () => {
Loading temperature data...
</Typography>
</Box>
) : data.length === 0 ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '400px' }}>
<Typography variant="h6" color="text.secondary">No temperature data available</Typography>
</Box>
) : (
<Box sx={{ height: 400 }}>
<Plot
data={tempData}
layout={tempLayout}
config={plotConfig}
style={{ width: '100%', height: '100%' }}
onRelayout={handleTempZoom}
<LineChart
height={400}
skipAnimation={false}
series={[
{
data: predictedTempData,
label: 'Predicted Temperature (3min)',
color: '#ff9800',
showMark: false
},
{
data: currentTempData,
label: 'Current Temperature',
color: '#028a4a', // B'GREEN brand color
showMark: false
},
]}
xAxis={[
{
scaleType: 'time',
data: [...currentTimestamps, ...futureTimestamps],
valueFormatter: (value: Date) => value.toLocaleTimeString(),
},
]}
yAxis={[
{
width: 50,
valueFormatter: (value: number) => `${value} °C`,
}
]}
margin={{ right: 24 }}
slotProps={{
legend: {
direction: 'horizontal',
position: { vertical: 'top', horizontal: 'center' },
},
}}
/>
</Box>
)}