|
| 1 | +"use client"; |
1 | 2 | import React, { |
2 | 3 | useEffect, |
3 | 4 | useRef, |
@@ -111,108 +112,103 @@ const Graph: React.FC<GraphProps> = ({ |
111 | 112 | const canvas = canvasRef.current; |
112 | 113 | const container = containerRef.current; |
113 | 114 | if (!canvas || !container) return; |
114 | | - |
| 115 | + |
115 | 116 | if (currentBandPowerData.some(isNaN)) { |
116 | 117 | console.error("NaN values detected in band power data"); |
117 | 118 | return; |
118 | 119 | } |
119 | | - |
| 120 | + |
120 | 121 | const ctx = canvas.getContext("2d"); |
121 | 122 | if (!ctx) return; |
122 | | - |
| 123 | + |
123 | 124 | // Responsive canvas sizing |
124 | 125 | const containerWidth = container.clientWidth; |
125 | 126 | const containerHeight = Math.min(containerWidth * 0.5, 400); // Limit max height |
126 | 127 | canvas.width = containerWidth; |
127 | 128 | canvas.height = containerHeight; |
128 | | - |
| 129 | + |
129 | 130 | const width = canvas.width; |
130 | 131 | const height = canvas.height; |
131 | | - |
| 132 | + |
132 | 133 | ctx.clearRect(0, 0, width, height); |
133 | | - |
134 | | - // Responsive bar sizing and margins |
135 | | - const leftMargin = width < 500 ? 50 : 70; |
136 | | - |
137 | | - const rightMargin = 20; |
138 | | - const bottomMargin = width < 640 ? 40 : 50; // Smaller margin on mobile |
| 134 | + |
| 135 | + // inside drawGraph: |
| 136 | + const topMargin = 30; // space for any in-canvas labels |
| 137 | + const leftMargin = width < 500 ? 60 : 80; |
| 138 | + const bottomMargin = width < 640 ? 70 : 80; |
| 139 | + const rightMargin = 60; |
| 140 | + |
| 141 | + // Draw axes |
| 142 | + const axisColor = theme === "dark" ? "white" : "black"; |
| 143 | + ctx.beginPath(); |
| 144 | + ctx.moveTo(leftMargin, topMargin); |
| 145 | + ctx.lineTo(leftMargin, height - bottomMargin); |
| 146 | + ctx.lineTo(width - rightMargin, height - bottomMargin); |
| 147 | + ctx.strokeStyle = axisColor; |
| 148 | + ctx.stroke(); |
| 149 | + |
139 | 150 | const barWidth = (width - leftMargin - rightMargin) / bandNames.length; |
140 | 151 | const barSpacing = barWidth * 0.2; // Space between bars |
141 | | - |
| 152 | + |
142 | 153 | let minPower = 0; |
143 | 154 | let maxPower = 100; |
144 | | - |
145 | 155 | if (maxPower - minPower < 1) { |
146 | 156 | maxPower = minPower + 1; |
147 | 157 | } |
148 | | - |
149 | | - const axisColor = theme === "dark" ? "white" : "black"; |
150 | | - |
151 | | - // Draw axes |
152 | | - ctx.beginPath(); |
153 | | - ctx.moveTo(leftMargin, 10); |
154 | | - ctx.lineTo(leftMargin, height - bottomMargin); |
155 | | - ctx.lineTo(width - rightMargin, height - bottomMargin); |
156 | | - ctx.strokeStyle = axisColor; |
157 | | - ctx.stroke(); |
158 | | - |
| 158 | + |
159 | 159 | // Draw bars |
160 | | - // Draw bars and beta percentage |
161 | 160 | currentBandPowerData.forEach((power, index) => { |
162 | 161 | const x = leftMargin + index * barWidth; |
163 | 162 | const normalizedHeight = Math.max(0, (power - minPower) / (maxPower - minPower)); |
164 | | - const barHeight = Math.max(0, normalizedHeight * (height - bottomMargin - 10)); |
165 | | - |
| 163 | + const barHeight = Math.max(0, normalizedHeight * (height - bottomMargin - topMargin)); |
| 164 | + |
166 | 165 | const barX = x + barSpacing / 2; |
167 | 166 | const barY = height - bottomMargin - barHeight; |
168 | 167 | const actualBarWidth = barWidth - barSpacing * 1.5; // Make it thinner than before |
169 | | - |
| 168 | + |
170 | 169 | ctx.fillStyle = bandColors[index]; |
171 | 170 | ctx.fillRect(barX, barY, actualBarWidth, barHeight); |
172 | | - |
173 | | - |
174 | 171 | }); |
175 | | - |
176 | | - |
177 | | - // Draw labels |
| 172 | + |
| 173 | + // Y-axis labels |
178 | 174 | ctx.fillStyle = axisColor; |
179 | 175 | const fontSize = width < 640 ? 10 : 12; // Smaller text on mobile |
180 | 176 | ctx.font = `${fontSize}px Arial`; |
181 | | - |
182 | | - // Y-axis labels (log scale) |
183 | 177 | ctx.textAlign = "right"; |
184 | 178 | ctx.textBaseline = "middle"; |
185 | | - const yLabelCount = Math.min(5, Math.floor(height / 50)); // Fewer labels on small screens |
| 179 | + const yLabelCount = Math.min(5, Math.floor(height / 50)); |
186 | 180 | for (let i = 0; i <= yLabelCount; i++) { |
187 | 181 | const value = minPower + (maxPower - minPower) * (i / yLabelCount); |
188 | | - const labelY = height - bottomMargin - (i / yLabelCount) * (height - bottomMargin - 10); |
| 182 | + const labelY = height - bottomMargin - (i / yLabelCount) * (height - bottomMargin - topMargin); |
189 | 183 | ctx.fillText(value.toFixed(1), leftMargin - 5, labelY); |
190 | 184 | } |
191 | | - |
192 | | - // X-axis labels |
| 185 | + |
| 186 | + // X-axis labels, centered under each actual bar |
193 | 187 | ctx.textAlign = "center"; |
194 | 188 | ctx.textBaseline = "top"; |
195 | 189 | bandNames.forEach((band, index) => { |
196 | | - const labelX = leftMargin + index * barWidth + barWidth * 0.5; |
| 190 | + const barX = leftMargin + index * barWidth + barSpacing / 2; |
| 191 | + const barW = barWidth - barSpacing * 1.5; |
| 192 | + const labelX = barX + barW / 2; |
197 | 193 | ctx.fillText(band, labelX, height - bottomMargin + 5); |
198 | 194 | }); |
199 | | - |
200 | | - // EEG Band Power – same as "Frequency (Hz)" |
201 | | - ctx.font = "16px Arial"; |
| 195 | + |
| 196 | + // Title |
| 197 | + ctx.font = "1.2em Arial"; |
202 | 198 | ctx.textAlign = "center"; |
203 | 199 | ctx.fillText("EEG Band Power", (width + leftMargin) / 2, height - 17); |
204 | | - |
205 | | - // Power – same as "Magnitude" |
| 200 | + |
| 201 | + // Power (Y-axis name) inside canvas |
206 | 202 | ctx.save(); |
207 | 203 | ctx.rotate(-Math.PI / 2); |
208 | | - ctx.font = "20px Arial"; |
| 204 | + ctx.font = "1.2em Arial"; |
209 | 205 | ctx.textAlign = "center"; |
210 | 206 | ctx.fillText("Power", -height / 2, 15); |
211 | 207 | ctx.restore(); |
212 | | - |
213 | 208 | }, |
214 | 209 | [theme, bandColors, bandNames] |
215 | 210 | ); |
| 211 | + |
216 | 212 |
|
217 | 213 | // Rest of the component remains the same (animateGraph, useEffect hooks) |
218 | 214 | const animateGraph = useCallback(() => { |
|
0 commit comments