diff --git a/gui_dev/src/components/BandPowerGraph.jsx b/gui_dev/src/components/BandPowerGraph.jsx
index 00059d70..26db7db1 100644
--- a/gui_dev/src/components/BandPowerGraph.jsx
+++ b/gui_dev/src/components/BandPowerGraph.jsx
@@ -121,6 +121,7 @@ export const BandPowerGraph = () => {
height: 350,
paper_bgcolor: "#333",
plot_bgcolor: "#333",
+ hovermode: false, // Add this line to disable hovermode
xaxis: {
title: { text: "Frequency Band", font: { color: "#f4f4f4" } },
color: "#cccccc",
@@ -141,6 +142,7 @@ export const BandPowerGraph = () => {
x: data.features,
y: data.values,
type: "bar",
+ hoverinfo: 'skip',
name: data.channelName,
marker: { color: barColors },
};
diff --git a/gui_dev/src/components/DecodingGraph.jsx b/gui_dev/src/components/DecodingGraph.jsx
index e757bacc..cbea6793 100644
--- a/gui_dev/src/components/DecodingGraph.jsx
+++ b/gui_dev/src/components/DecodingGraph.jsx
@@ -33,7 +33,7 @@ export const DecodingGraph = ({
}) => {
//const graphData = useSocketStore((state) => state.graphData);
const graphDecodingData = useSocketStore((state) => state.graphDecodingData);
-
+ const samplingRate = useSessionStore((state) => state.streamParameters.samplingRateFeatures);
//const channels = useSessionStore((state) => state.channels, shallow);
//const usedChannels = useMemo(
@@ -68,6 +68,7 @@ export const DecodingGraph = ({
height: 400,
paper_bgcolor: "#333",
plot_bgcolor: "#333",
+ hovermode: false, // Add this line to disable hovermode
margin: {
l: 50,
r: 50,
@@ -110,7 +111,7 @@ export const DecodingGraph = ({
};
const handleMaxDataPointsChangeDecoding = (event, newValue) => {
- setMaxDataPointsDecoding(newValue);
+ setMaxDataPointsDecoding(newValue * samplingRate); // Convert seconds to number of samples
};
//useEffect(() => {
@@ -218,13 +219,14 @@ export const DecodingGraph = ({
const traces = selectedDecodingOutputs.map((decodingOutput, idx) => {
const yData = decodingData[decodingOutput] || [];
const y = yData.slice().reverse();
- const x = Array.from({ length: y.length }, (_, i) => i);
+ const x = Array.from({ length: y.length }, (_, i) => i / samplingRate); // Convert to time in seconds
return {
x,
y,
type: "scattergl",
mode: "lines",
+ hoverinfo: 'skip',
name: decodingOutput,
line: { simplify: false, color: colors[idx] },
yaxis: idx === 0 ? "y" : `y${idx + 1}`,
@@ -236,9 +238,13 @@ export const DecodingGraph = ({
xaxis: {
...layoutRef.current.xaxis,
autorange: "reversed",
- range: [0, maxDataPointsDecoding],
+ range: [0, maxDataPointsDecoding / samplingRate], // Adjust range to time in seconds
domain: [0, 1],
anchor: totalDecodingOutputs === 1 ? "y" : `y${totalDecodingOutputs}`,
+ title: {
+ text: "Time [s]", // Update x-axis title
+ font: { color: "#f4f4f4" },
+ },
},
...yAxes,
height: 350, // TODO height autoadjust to screen
@@ -254,7 +260,7 @@ export const DecodingGraph = ({
.catch((error) => {
console.error("Plotly error:", error);
});
- }, [decodingData, selectedDecodingOutputs, yAxisMaxValue, maxDataPointsDecoding]);
+ }, [decodingData, selectedDecodingOutputs, yAxisMaxValue, maxDataPointsDecoding, samplingRate]);
return (
@@ -310,20 +316,20 @@ export const DecodingGraph = ({
+ title="Window Size (s)" defaultExpanded={true} id="BoxDecoding">
- Current Value: {maxDataPointsDecoding}
+ Current Value: {(maxDataPointsDecoding / samplingRate).toFixed(1)} s
diff --git a/gui_dev/src/components/HeatmapGraph.jsx b/gui_dev/src/components/HeatmapGraph.jsx
index 8cc80e71..504ce317 100644
--- a/gui_dev/src/components/HeatmapGraph.jsx
+++ b/gui_dev/src/components/HeatmapGraph.jsx
@@ -9,15 +9,13 @@ import {
FormControlLabel,
Radio,
Checkbox,
+ Slider,
} from '@mui/material';
import { CollapsibleBox } from './CollapsibleBox';
import { getChannelAndFeature } from './utils';
import { shallow } from 'zustand/shallow';
-const maxTimeWindow = 10;
-
export const HeatmapGraph = () => {
-
const channels = useSessionStore((state) => state.channels, shallow);
const usedChannels = useMemo(
@@ -30,35 +28,41 @@ export const HeatmapGraph = () => {
[usedChannels]
);
- const [selectedChannel, setSelectedChannel] = useState(''); // TODO: Switch this maybe multiple?
+ const [selectedChannel, setSelectedChannel] = useState('');
const [features, setFeatures] = useState([]);
- const [fftFeatures, setFftFeatures] = useState([]);
+ const [fftFeatures, setFftFeatures] = useState([]);
const [otherFeatures, setOtherFeatures] = useState([]);
const [selectedFeatures, setSelectedFeatures] = useState([]);
const [heatmapData, setHeatmapData] = useState({ x: [], z: [] });
const [isDataStale, setIsDataStale] = useState(false);
const [lastDataTime, setLastDataTime] = useState(null);
- const [lastDataTimestamp, setLastDataTimestamp] = useState(null);
- const hasInitialized = useRef(Date.now());
const graphData = useSocketStore((state) => state.graphData);
+ const [maxDataPoints, setMaxDataPoints] = useState(100);
+
+ const handleMaxDataPointsChange = (event, newValue) => {
+ setMaxDataPoints(newValue);
+ };
+
const handleChannelToggle = (event) => {
setSelectedChannel(event.target.value);
};
- // Added handler for fft_psd_xyz features toggle TODO: also welch/ other features?
const handleFftFeaturesToggle = () => {
- const allFftFeaturesSelected = fftFeatures.every((feature) => selectedFeatures.includes(feature));
+ const allFftFeaturesSelected = fftFeatures.every((feature) =>
+ selectedFeatures.includes(feature)
+ );
if (allFftFeaturesSelected) {
setSelectedFeatures((prevSelected) =>
prevSelected.filter((feature) => !fftFeatures.includes(feature))
);
} else {
- setSelectedFeatures((prevSelected) =>
- [...prevSelected, ...fftFeatures.filter((feature) => !prevSelected.includes(feature))]
- );
+ setSelectedFeatures((prevSelected) => [
+ ...prevSelected,
+ ...fftFeatures.filter((feature) => !prevSelected.includes(feature)),
+ ]);
}
};
@@ -86,47 +90,45 @@ export const HeatmapGraph = () => {
const featureKeys = dataKeys.filter(
(key) => key.startsWith(channelPrefix) && key !== 'time'
);
- const newFeatures = featureKeys.map((key) => key.substring(channelPrefix.length));
+ const newFeatures = featureKeys.map((key) =>
+ key.substring(channelPrefix.length)
+ );
if (JSON.stringify(newFeatures) !== JSON.stringify(features)) {
console.log('Updating features:', newFeatures);
setFeatures(newFeatures);
- // TODO: currently all psd selectable together. should we also do this for welch and other features?
- const fftFeatures = newFeatures.filter((feature) => feature.startsWith('fft_psd_'));
- const otherFeatures = newFeatures.filter((feature) => !feature.startsWith('fft_psd_'));
+ const fftFeatures = newFeatures.filter((feature) =>
+ feature.startsWith('fft_psd_')
+ );
+ const otherFeatures = newFeatures.filter(
+ (feature) => !feature.startsWith('fft_psd_')
+ );
setFftFeatures(fftFeatures);
setOtherFeatures(otherFeatures);
setSelectedFeatures(newFeatures);
- setHeatmapData({ x: [], z: [] }); // Reset heatmap data when features change
+ setHeatmapData({ x: [], z: [] });
setIsDataStale(false);
setLastDataTime(null);
- setLastDataTimestamp(null);
}
}, [graphData, selectedChannel, features]);
useEffect(() => {
- if (!graphData || !selectedChannel || features.length === 0 || selectedFeatures.length === 0) return;
-
- // TODO: Always data in ms? (Time conversion here always necessary?)
- // Timon: yes, let's define that the stream's time is always in ms
- let timestamp = graphData.time;
- if (timestamp === undefined) {
- timestamp = (Date.now() - hasInitialized.current) / 1000;
- } else {
- timestamp = timestamp / 1000;
- }
+ if (
+ !graphData ||
+ !selectedChannel ||
+ features.length === 0 ||
+ selectedFeatures.length === 0
+ )
+ return;
setLastDataTime(Date.now());
setIsDataStale(false);
- let x = [...heatmapData.x, timestamp];
-
let z;
- // Initialize 'z' for selected features
if (heatmapData.z && heatmapData.z.length === selectedFeatures.length) {
z = heatmapData.z.map((row) => [...row]);
} else {
@@ -137,27 +139,30 @@ export const HeatmapGraph = () => {
const key = `${selectedChannel}_${featureName}`;
const value = graphData[key];
const numericValue = typeof value === 'number' && !isNaN(value) ? value : 0;
- z[idx].push(numericValue);
- });
-
- const currentTime = timestamp;
- const minTime = currentTime - maxTimeWindow; // TODO: What should be the visible window frame? adjustable? 10s?
- // Timon: Would be amazing if it's adjustable
- const validIndices = x.reduce((indices, time, index) => {
- if (time >= minTime) {
- indices.push(index);
+ // Shift existing data to the left if necessary
+ if (z[idx].length >= maxDataPoints) {
+ z[idx].shift();
}
- return indices;
- }, []);
- x = validIndices.map((index) => x[index]);
- z = z.map((row) => validIndices.map((index) => row[index]));
+ // Append the new data
+ z[idx].push(numericValue);
+ });
+
+ // Update x based on the length of z[0] (assuming all rows are the same length)
+ const dataLength = z[0]?.length || 0;
+ const x = Array.from({ length: dataLength }, (_, i) => i);
setHeatmapData({ x, z });
- }, [graphData, selectedChannel, features, selectedFeatures]);
-
- // Check if data is stale (no new data in the last second) -> TODO: Find better solution debug this
+ }, [
+ graphData,
+ selectedChannel,
+ features,
+ selectedFeatures,
+ maxDataPoints,
+ ]);
+
+ // Check if data is stale (no new data in the last second)
useEffect(() => {
if (!lastDataTime) return;
@@ -172,27 +177,20 @@ export const HeatmapGraph = () => {
return () => clearInterval(interval);
}, [lastDataTime]);
- // TODO: Adjustment of x-axis -> this currently is a bit buggy
- const xRange = isDataStale && heatmapData.x.length > 0
- ? [heatmapData.x[0], heatmapData.x[heatmapData.x.length - 1]]
- : undefined;
-
const layout = {
- // title: { text: 'Heatmap', font: { color: '#f4f4f4' } },
- height: 600,
+ height: 350,
paper_bgcolor: '#333',
plot_bgcolor: '#333',
autosize: true,
xaxis: {
- tickformat: '.2f',
- title: { text: 'Time (s)', font: { color: '#f4f4f4' } },
+ title: { text: 'Nr. of Samples', font: { color: '#f4f4f4' } },
color: '#cccccc',
tickfont: {
color: '#cccccc',
},
automargin: false,
- autorange: !isDataStale,
- range: xRange,
+ hovermode: false
+ // autorange: 'reversed'
},
yaxis: {
title: { text: 'Features', font: { color: '#f4f4f4' } },
@@ -213,18 +211,14 @@ export const HeatmapGraph = () => {
Heatmap
-
+
-
+
{usedChannels.map((channel, index) => (
} // TODO: Should we make multiple selectable? // Timon: No, let's keep with one
+ control={}
label={channel.name}
/>
))}
@@ -232,7 +226,7 @@ export const HeatmapGraph = () => {
-
+
@@ -240,10 +234,16 @@ export const HeatmapGraph = () => {
selectedFeatures.includes(feature))}
+ checked={fftFeatures.every((feature) =>
+ selectedFeatures.includes(feature)
+ )}
indeterminate={
- fftFeatures.some((feature) => selectedFeatures.includes(feature)) &&
- !fftFeatures.every((feature) => selectedFeatures.includes(feature))
+ fftFeatures.some((feature) =>
+ selectedFeatures.includes(feature)
+ ) &&
+ !fftFeatures.every((feature) =>
+ selectedFeatures.includes(feature)
+ )
}
onChange={handleFftFeaturesToggle}
color="primary"
@@ -269,25 +269,43 @@ export const HeatmapGraph = () => {
+
+
+ Current Value: {maxDataPoints}
+
+
+
- {heatmapData.x.length > 0 && selectedFeatures.length > 0 && heatmapData.z.length > 0 && (
-
- )}
+ {heatmapData.x.length > 0 &&
+ selectedFeatures.length > 0 &&
+ heatmapData.z.length > 0 && (
+
+ )}
);
-};
+};
\ No newline at end of file
diff --git a/gui_dev/src/components/PSDGraph.jsx b/gui_dev/src/components/PSDGraph.jsx
index 91438f29..d365ff9d 100644
--- a/gui_dev/src/components/PSDGraph.jsx
+++ b/gui_dev/src/components/PSDGraph.jsx
@@ -135,6 +135,7 @@ export const PSDGraph = () => {
height: 350,
paper_bgcolor: "#333",
plot_bgcolor: "#333",
+ hovermode: false, // Add this line to disable hovermode
xaxis: {
title: { text: "Feature Index", font: { color: "#f4f4f4" } },
color: "#cccccc",
@@ -156,6 +157,7 @@ export const PSDGraph = () => {
y: data.values,
type: "scattergl",
mode: "lines",
+ hoverinfo: "skip",
name: data.channelName,
line: { simplify: false, color: colors[idx] },
}));
diff --git a/gui_dev/src/components/RawDataGraph.jsx b/gui_dev/src/components/RawDataGraph.jsx
index 62087e4f..32f3dde0 100644
--- a/gui_dev/src/components/RawDataGraph.jsx
+++ b/gui_dev/src/components/RawDataGraph.jsx
@@ -28,13 +28,14 @@ const generateColors = (numColors) => {
export const RawDataGraph = ({
title = "Raw Data",
- xAxisTitle = "Nr. of Samples",
+ xAxisTitle = "Time [s]",
yAxisTitle = "Value",
}) => {
//const graphData = useSocketStore((state) => state.graphData);
const graphRawData = useSocketStore((state) => state.graphRawData);
const channels = useSessionStore((state) => state.channels, shallow);
+ const samplingRate = useSessionStore((state) => state.streamParameters.samplingRate);
const usedChannels = useMemo(
() => channels.filter((channel) => channel.used === 1),
@@ -63,6 +64,7 @@ export const RawDataGraph = ({
height: 400,
paper_bgcolor: "#333",
plot_bgcolor: "#333",
+ hovermode: false, // Add this line to disable hovermode
margin: {
l: 50,
r: 50,
@@ -100,12 +102,12 @@ export const RawDataGraph = ({
});
};
- const handleYAxisMaxValueChange = (event) => {
- setYAxisMaxValue(event.target.value);
+ const handleYAxisMaxValueChange = (event, newValue) => {
+ setYAxisMaxValue(newValue);
};
const handleMaxDataPointsChange = (event, newValue) => {
- setMaxDataPoints(newValue);
+ setMaxDataPoints(newValue * samplingRate); // Convert seconds to samples
};
useEffect(() => {
@@ -206,7 +208,7 @@ export const RawDataGraph = ({
const traces = selectedChannels.map((channelName, idx) => {
const yData = rawData[channelName] || [];
const y = yData.slice().reverse();
- const x = Array.from({ length: y.length }, (_, i) => i);
+ const x = Array.from({ length: y.length }, (_, i) => i / samplingRate); // Convert samples to negative seconds
return {
x,
@@ -214,6 +216,7 @@ export const RawDataGraph = ({
type: "scattergl",
mode: "lines",
name: channelName,
+ hoverinfo: 'skip',
line: { simplify: false, color: colors[idx] },
yaxis: idx === 0 ? "y" : `y${idx + 1}`,
};
@@ -224,12 +227,13 @@ export const RawDataGraph = ({
xaxis: {
...layoutRef.current.xaxis,
autorange: "reversed",
- range: [0, maxDataPoints],
+ range: [maxDataPoints / samplingRate, 0], // Adjust range to negative seconds
domain: [0, 1],
anchor: totalChannels === 1 ? "y" : `y${totalChannels}`,
},
...yAxes,
height: 350, // TODO height autoadjust to screen
+ hovermode: false, // Add this line to disable hovermode in the trace
};
Plotly.react(graphRef.current, traces, layout, {
@@ -280,37 +284,34 @@ export const RawDataGraph = ({
-
-
- } label="Auto" />
- } label="5" />
- } label="10" />
- } label="20" />
- } label="50" />
- } label="100" />
- } label="500" />
-
-
+
- Current Value: {maxDataPoints}
+ Current Value: {maxDataPoints / samplingRate}
diff --git a/gui_dev/src/pages/Dashboard.jsx b/gui_dev/src/pages/Dashboard.jsx
index f7b07dcc..0402aed0 100644
--- a/gui_dev/src/pages/Dashboard.jsx
+++ b/gui_dev/src/pages/Dashboard.jsx
@@ -3,9 +3,10 @@ import { PSDGraph } from '@/components/PSDGraph';
import { DecodingGraph } from '@/components/DecodingGraph';
import { HeatmapGraph } from '@/components/HeatmapGraph';
import { BandPowerGraph } from '@/components/BandPowerGraph';
-import { Box, Button } from '@mui/material';
+import { Box, Button, ToggleButton, ToggleButtonGroup } from '@mui/material';
import { useSessionStore } from "@/stores";
import { useSocketStore } from '@/stores';
+import { useState } from 'react';
export const Dashboard = () => {
@@ -16,74 +17,86 @@ export const Dashboard = () => {
const startStream = useSessionStore((state) => state.startStream);
const stopStream = useSessionStore((state) => state.stopStream);
-
+ const [enabledGraphs, setEnabledGraphs] = useState({
+ rawData: true,
+ psdPlot: true,
+ heatmap: true,
+ bandPowerGraph: true,
+ decodingGraph: true,
+ });
+
+ const handleGraphToggle = (event, newEnabledGraphs) => {
+ setEnabledGraphs((prev) => ({
+ ...prev,
+ [event.target.value]: newEnabledGraphs.includes(event.target.value),
+ }));
+ };
+
+ const graphComponents = {
+ rawData: RawDataGraph,
+ psdPlot: PSDGraph,
+ heatmap: HeatmapGraph,
+ bandPowerGraph: BandPowerGraph,
+ decodingGraph: DecodingGraph,
+ };
return (
<>
- {/* */}
-
-
+
+
+
+
+
+ enabledGraphs[key])}
+ onChange={handleGraphToggle}
+ aria-label="graph toggle"
+ >
+
+ Raw Data
+
+
+ PSD Plot
+
+
+ Heatmap
+
+
+ Band Power Graph
+
+
+ Decoding Graph
+
+
+
- {/* Top Row - RawDataGraph and PSDGraph */}
-
- {/* RawDataGraph */}
-
-
-
-
-
-
- {/* PSDGraph */}
-
-
-
-
-
-
- {/* HeatmapGraph */}
-
-
-
-
-
-
-
-
- {/* BandPowerGraph */}
-
-
-
-
-
- {/* DecoddingGraph */}
-
-
-
-
-
+ {Object.keys(enabledGraphs).filter((key) => enabledGraphs[key]).map((key) => {
+ const GraphComponent = graphComponents[key];
+ return (
+
+
+
+
+
+ );
+ })}
+
>
);