import React, { Component } from 'react';
import { Paper, Typography, Box, CircularProgress } from '@mui/material';
import { LineChart } from '@mui/x-charts/LineChart';
export default class Chart extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: true
};
this.intervalId = null;
}
componentDidMount() {
this.fetchData();
this.intervalId = setInterval(this.fetchData, 60000);
}
componentDidUpdate(prevProps) {
// Compare props to see if we need to refetch
const prevEffective = this.getEffectiveChannels(prevProps);
const currEffective = this.getEffectiveChannels(this.props);
if (prevEffective.join(',') !== currEffective.join(',')) {
this.fetchData();
}
}
componentWillUnmount() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
getEffectiveChannels(props) {
return props.channelConfig
? props.channelConfig.map(c => c.id)
: props.selectedChannels;
}
fetchData = () => {
const effectiveChannels = this.getEffectiveChannels(this.props);
// Only fetch if selection exists
if (effectiveChannels.length === 0) {
this.setState({ data: [], loading: false });
return;
}
const selectionStr = effectiveChannels.join(',');
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
fetch(`/api/readings?selection=${encodeURIComponent(selectionStr)}&since=${since}`)
.then(res => res.json())
.then(rows => {
const timeMap = new Map();
rows.forEach(row => {
const id = `${row.device}:${row.channel}`;
if (!effectiveChannels.includes(id)) return;
const time = new Date(row.timestamp).getTime();
if (!timeMap.has(time)) {
timeMap.set(time, { time: new Date(row.timestamp) });
}
const entry = timeMap.get(time);
let val = row.value;
if (row.data_type === 'json' && !val) {
val = null;
}
entry[id] = val;
});
const sortedData = Array.from(timeMap.values()).sort((a, b) => a.time - b.time);
this.setState({ data: sortedData, loading: false });
})
.catch(err => {
console.error("Failed to fetch data", err);
this.setState({ loading: false });
});
};
computeAxisLimits(axisKey, effectiveChannels, series) {
// Collect all data points for this axis
let axisMin = Infinity;
let axisMax = -Infinity;
const axisSeries = series.filter(s => s.yAxisKey === axisKey).map(s => s.dataKey);
if (axisSeries.length === 0) return {}; // No data for this axis
// Check if config exists for this axis
const { axisConfig } = this.props;
let cfgMin = NaN;
let cfgMax = NaN;
if (axisConfig && axisConfig[axisKey]) {
cfgMin = parseFloat(axisConfig[axisKey].min);
cfgMax = parseFloat(axisConfig[axisKey].max);
}
// Optimization: If no config set, just return empty and let chart autoscale fully.
if (isNaN(cfgMin) && isNaN(cfgMax)) return {};
// Calculate data bounds
let hasData = false;
this.state.data.forEach(row => {
axisSeries.forEach(key => {
const val = row[key];
if (val !== null && val !== undefined) {
hasData = true;
if (val < axisMin) axisMin = val;
if (val > axisMax) axisMax = val;
}
});
});
if (!hasData) return {}; // No valid data points
// Apply config soft limits
if (!isNaN(cfgMin)) axisMin = Math.min(axisMin, cfgMin);
if (!isNaN(cfgMax)) axisMax = Math.max(axisMax, cfgMax);
return { min: axisMin, max: axisMax };
}
render() {
const { loading, data } = this.state;
const { channelConfig } = this.props;
const effectiveChannels = this.getEffectiveChannels(this.props);
if (loading) return ;
if (effectiveChannels.length === 0) return No channels selected.;
const series = effectiveChannels.map(id => {
// Find alias and axis if config exists
let label = id;
let yAxisKey = 'left';
if (channelConfig) {
const item = channelConfig.find(c => c.id === id);
if (item) {
if (item.alias) label = item.alias;
if (item.yAxis) yAxisKey = item.yAxis;
}
}
return {
dataKey: id,
label: label,
connectNulls: true,
showMark: false,
yAxisKey: yAxisKey,
};
});
const hasRightAxis = series.some(s => s.yAxisKey === 'right');
const leftLimits = this.computeAxisLimits('left', effectiveChannels, series);
const rightLimits = this.computeAxisLimits('right', effectiveChannels, series);
const yAxes = [
{ id: 'left', scaleType: 'linear', ...leftLimits }
];
if (hasRightAxis) {
yAxes.push({ id: 'right', scaleType: 'linear', ...rightLimits });
}
return (
Last 24 Hours
date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
}]}
yAxis={yAxes}
rightAxis={hasRightAxis ? 'right' : null}
slotProps={{
legend: {
direction: 'row',
position: { vertical: 'top', horizontal: 'middle' },
padding: 0,
},
}}
/>
);
}
}