Skip to content

Commit cebb596

Browse files
committed
implemented chart save and rendering logic
1 parent 32ac7b0 commit cebb596

File tree

2 files changed

+95
-7
lines changed

2 files changed

+95
-7
lines changed

frontend/components/nodes/signal-graph-node/signal-graph-full.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ interface SignalGraphViewProps {
2828
signal3: number;
2929
signal4: number;
3030
}[];
31+
onTimeframeChange?: (start: string, end: string) => void;
3132
}
3233

3334

3435
const TABLE_PREVIEW_ROWS = 50;
3536

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

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

7575
{/* ---- TOP HALF: CHART ---- */}
@@ -115,6 +115,9 @@ export default function SignalGraphView({ data }: SignalGraphViewProps) {
115115
onChange={(range) => {
116116
if (range.startIndex !== undefined && range.endIndex !== undefined) {
117117
setBrushRange({ start: range.startIndex, end: range.endIndex });
118+
if (data.length > 0 && onTimeframeChange) {
119+
onTimeframeChange(data[range.startIndex].time, data[range.endIndex].time);
120+
}
118121
}
119122
}}
120123
/>

frontend/components/nodes/signal-graph-node/signal-graph-node.tsx

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,64 @@ import {
1515
} from '@/components/ui/dialog';
1616
import SignalGraphView from './signal-graph-full';
1717
import ExportDialog from '@/components/ui/export-dialog';
18+
import { exportEEGData } from '@/lib/eeg-api';
19+
20+
interface SignalDataPoint {
21+
time: string;
22+
signal1: number;
23+
signal2: number;
24+
signal3: number;
25+
signal4: number;
26+
}
27+
28+
function parseEEG(csvContent: string): SignalDataPoint[] {
29+
try {
30+
const lines = csvContent.trim().split('\n');
31+
if (lines.length < 2) return [];
32+
33+
const header = lines[0].split(',').map((h) => h.trim().toLowerCase());
34+
// find time column
35+
const timeColIdx = header.findIndex((h) => h.includes('time') || h === 'timestamp');
36+
if (timeColIdx == -1) return [];
37+
38+
// find signal columns by exact channel number patterns
39+
const signalIndices = [0, 1, 2, 3].map((chNum) => {
40+
const patterns = [`ch${chNum}`, `channel${chNum}`, `signal${chNum + 1}`, `signal_${chNum + 1}`];
41+
return header.findIndex((h) => patterns.includes(h));
42+
});
43+
44+
if (signalIndices.includes(-1)) return [];
45+
46+
const data: SignalDataPoint[] = [];
47+
for (let i = 1; i < lines.length; i++) {
48+
const cols = lines[i].trim().split(',').map((c) => c.trim());
49+
if (!cols[timeColIdx]) continue;
50+
51+
const vals = signalIndices.map((idx) => parseFloat(cols[idx]));
52+
if (vals.every((v) => !isNaN(v))) {
53+
data.push({
54+
time: cols[timeColIdx],
55+
signal1: vals[0],
56+
signal2: vals[1],
57+
signal3: vals[2],
58+
signal4: vals[3],
59+
});
60+
}
61+
}
62+
return data;
63+
} catch (err) {
64+
console.error('Failed to parse EEG CSV:', err);
65+
return [];
66+
}
67+
}
1868

1969
export default function SignalGraphNode({ id }: { id?: string }) {
20-
const { dataStreaming } = useGlobalContext();
70+
const { dataStreaming, activeSessionId } = useGlobalContext();
2171
const { renderData } = useNodeData(500, 10);
22-
23-
const processedData = renderData;
2472
const reactFlowInstance = useReactFlow();
2573
const [isConnected, setIsConnected] = React.useState(false);
26-
const { activeSessionId } = useGlobalContext();
2774
const [isExportOpen, setIsExportOpen] = useState(false);
75+
const [seedData, setSeedData] = React.useState<SignalDataPoint[]>([]);
2876

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

117+
// Fetch EEG data on load
118+
React.useEffect(() => {
119+
if (!id || !activeSessionId) return;
120+
121+
const nodes = reactFlowInstance.getNodes();
122+
const currentNode = nodes.find((n) => n.id === id);
123+
if (!currentNode?.data) return;
124+
125+
const { timeframeStart, timeframeEnd } = currentNode.data as { timeframeStart?: string; timeframeEnd?: string };
126+
if (!timeframeStart || !timeframeEnd) return;
127+
128+
console.log('restored timeframe:', { timeframeStart, timeframeEnd });
129+
(async () => {
130+
try {
131+
const csvContent = await exportEEGData(activeSessionId, { start_time: timeframeStart, end_time: timeframeEnd });
132+
setSeedData(parseEEG(csvContent)); // store as seed data
133+
console.log('loaded saved data');
134+
} catch (err) {
135+
console.debug('export failed', err);
136+
setSeedData([]);
137+
}
138+
})();
139+
}, [id, activeSessionId, reactFlowInstance]);
140+
141+
// Save timeframe in node data
142+
const handleTimeframeChange = (start: string, end: string) => {
143+
if (!id) return;
144+
reactFlowInstance.setNodes((prevNodes) =>
145+
prevNodes.map((n) =>
146+
n.id === id ? { ...n, data: { ...n.data, timeframeStart: start, timeframeEnd: end } } : n
147+
)
148+
);
149+
};
150+
69151
return (
70152
<Dialog>
71153
<Card className="rounded-[30px] border-2 border-[#D3D3D3] shadow-none p-0 overflow-hidden bg-white h-[96px] w-[396px]">
@@ -143,7 +225,10 @@ export default function SignalGraphNode({ id }: { id?: string }) {
143225
<DialogDescription></DialogDescription>
144226
</DialogHeader>
145227
<div className="w-[85vw] h-[90vh]">
146-
<SignalGraphView data={isConnected ? processedData : []} />
228+
<SignalGraphView
229+
data={seedData.length > 0 ? seedData : (isConnected ? renderData : [])}
230+
onTimeframeChange={handleTimeframeChange}
231+
/>
147232
</div>
148233
</DialogContent>
149234
</Card>

0 commit comments

Comments
 (0)