Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ interface SignalGraphViewProps {
signal3: number;
signal4: number;
}[];
onTimeframeChange?: (start: string, end: string) => void;
}


const TABLE_PREVIEW_ROWS = 50;

export default function SignalGraphView({ data }: SignalGraphViewProps) {
export default function SignalGraphView({ data, onTimeframeChange }: SignalGraphViewProps) {
const { dataStreaming, setDataStreaming } = useGlobalContext();
const [selectedSignal, setSelectedSignal] = useState<string | null>(null);

Expand Down Expand Up @@ -69,7 +70,6 @@ export default function SignalGraphView({ data }: SignalGraphViewProps) {
>
{dataStreaming ? 'Stop Data Stream' : 'Start Data Stream'}
</Button>
<Button className='bg-[#2E7B75] text-white'> Save </Button>
</div>

{/* ---- TOP HALF: CHART ---- */}
Expand Down Expand Up @@ -115,6 +115,9 @@ export default function SignalGraphView({ data }: SignalGraphViewProps) {
onChange={(range) => {
if (range.startIndex !== undefined && range.endIndex !== undefined) {
setBrushRange({ start: range.startIndex, end: range.endIndex });
if (data.length > 0 && onTimeframeChange) {
onTimeframeChange(data[range.startIndex].time, data[range.endIndex].time);
}
}
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,64 @@ import {
} from '@/components/ui/dialog';
import SignalGraphView from './signal-graph-full';
import ExportDialog from '@/components/ui/export-dialog';
import { exportEEGData } from '@/lib/eeg-api';

interface SignalDataPoint {
time: string;
signal1: number;
signal2: number;
signal3: number;
signal4: number;
}

function parseEEG(csvContent: string): SignalDataPoint[] {
try {
const lines = csvContent.trim().split('\n');
if (lines.length < 2) return [];

const header = lines[0].split(',').map((h) => h.trim().toLowerCase());
// find time column
const timeColIdx = header.findIndex((h) => h.includes('time') || h === 'timestamp');
if (timeColIdx == -1) return [];

// find signal columns by exact channel number patterns
const signalIndices = [0, 1, 2, 3].map((chNum) => {
const patterns = [`ch${chNum}`, `channel${chNum}`, `signal${chNum + 1}`, `signal_${chNum + 1}`];
return header.findIndex((h) => patterns.includes(h));
});

if (signalIndices.includes(-1)) return [];

const data: SignalDataPoint[] = [];
for (let i = 1; i < lines.length; i++) {
const cols = lines[i].trim().split(',').map((c) => c.trim());
if (!cols[timeColIdx]) continue;

const vals = signalIndices.map((idx) => parseFloat(cols[idx]));
if (vals.every((v) => !isNaN(v))) {
data.push({
time: cols[timeColIdx],
signal1: vals[0],
signal2: vals[1],
signal3: vals[2],
signal4: vals[3],
});
}
}
return data;
} catch (err) {
console.error('Failed to parse EEG CSV:', err);
return [];
}
}

export default function SignalGraphNode({ id }: { id?: string }) {
const { dataStreaming } = useGlobalContext();
const { dataStreaming, activeSessionId } = useGlobalContext();
const { renderData } = useNodeData(500, 10);

const processedData = renderData;
const reactFlowInstance = useReactFlow();
const [isConnected, setIsConnected] = React.useState(false);
const { activeSessionId } = useGlobalContext();
const [isExportOpen, setIsExportOpen] = useState(false);
const [seedData, setSeedData] = React.useState<SignalDataPoint[]>([]);

// Determine if this Chart View node has an upstream path from a Source
const checkConnectionStatus = React.useCallback(() => {
Expand Down Expand Up @@ -66,6 +114,40 @@ export default function SignalGraphNode({ id }: { id?: string }) {
};
}, [checkConnectionStatus]);

// Fetch EEG data on load
React.useEffect(() => {
if (!id || !activeSessionId) return;

const nodes = reactFlowInstance.getNodes();
const currentNode = nodes.find((n) => n.id === id);
if (!currentNode?.data) return;

const { timeframeStart, timeframeEnd } = currentNode.data as { timeframeStart?: string; timeframeEnd?: string };
if (!timeframeStart || !timeframeEnd) return;

console.log('restored timeframe:', { timeframeStart, timeframeEnd });
(async () => {
try {
const csvContent = await exportEEGData(activeSessionId, { start_time: timeframeStart, end_time: timeframeEnd });
setSeedData(parseEEG(csvContent)); // store as seed data
console.log('loaded saved data');
} catch (err) {
console.debug('export failed', err);
setSeedData([]);
}
})();
}, [id, activeSessionId, reactFlowInstance]);

// Save timeframe in node data
const handleTimeframeChange = (start: string, end: string) => {
if (!id) return;
reactFlowInstance.setNodes((prevNodes) =>
prevNodes.map((n) =>
n.id === id ? { ...n, data: { ...n.data, timeframeStart: start, timeframeEnd: end } } : n
)
);
};

return (
<Dialog>
<Card className="rounded-[30px] border-2 border-[#D3D3D3] shadow-none p-0 overflow-hidden bg-white h-[96px] w-[396px]">
Expand Down Expand Up @@ -143,7 +225,10 @@ export default function SignalGraphNode({ id }: { id?: string }) {
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="w-[85vw] h-[90vh]">
<SignalGraphView data={isConnected ? processedData : []} />
<SignalGraphView
data={seedData.length > 0 ? seedData : (isConnected ? renderData : [])}
onTimeframeChange={handleTimeframeChange}
/>
</div>
</DialogContent>
</Card>
Expand Down
Loading