Skip to content

Commit 7eb1aa1

Browse files
authored
Merge pull request #73 from Ritika8081/buttons-update
Improved user interface
2 parents 6b2dbd5 + 4287405 commit 7eb1aa1

File tree

7 files changed

+680
-430
lines changed

7 files changed

+680
-430
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,12 @@ Chords is an application based on Web Serial connection, you can connect [Compat
6868
- [X] **Arduino Uno R4 WiFi Support**: Add support for Arduino Uno R4 WiFi.
6969
- [X] **Giga R1 Support**: Add support for the Arduino Giga R1 board with its 16-bit ADC, offering a range of 0 to 65,535.
7070
- [X] **Raspberry Pi Pico Support**: Release Raspberry Pi Pico support for Chords. It works seamlessly with the new Heart BioAmp Candy. Share your favorite board in the comments, and we'll aim to include it in future updates.
71-
- [ ] **Arduino Nano Support** Add support for Nano board which supports up to 8 channels.
71+
- [X] **Arduino Nano Support** Add support for Nano board which supports up to 8 channels.
7272

73+
- **User Interface** : Improved user interface by following changes:
74+
- [X] **Channel Selection**: Display the available channels in a popover, showing a total of 16 channels. However, the number of enabled channels will be based on the connected board.
75+
- [X] **Zoom Slider**: Adjust zoom to focus on data points or view an overall plot.
76+
- [X] **Time-Base Slider**: Customize the time duration for displaying data per frame, with options ranging from 1 to 10 seconds.
7377

7478
## Contributors
7579

src/components/Canvas.tsx

Lines changed: 132 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import { WebglPlot, ColorRGBA, WebglLine } from "webgl-plot";
1212

1313
interface CanvasProps {
1414
pauseRef: React.RefObject<boolean>;
15-
selectedBits?: BitSelection; // Add `?` to make it optional
15+
selectedBits?: BitSelection;
1616
isDisplay: boolean;
1717
canvasCount?: number;
18+
selectedChannels: number[];
1819
timeBase?: number;
1920
currentSamplingRate: number;
2021
Zoom: number;
@@ -27,11 +28,11 @@ const Canvas = forwardRef(
2728
{
2829
pauseRef,
2930
selectedBits,
30-
isDisplay,
3131
canvasCount = 6, // default value in case not provided
3232
timeBase = 4,
3333
currentSamplingRate,
3434
Zoom,
35+
selectedChannels,
3536
currentSnapshot,
3637
snapShotRef,
3738
}: CanvasProps,
@@ -40,7 +41,7 @@ const Canvas = forwardRef(
4041
const { theme } = useTheme();
4142
let previousCounter: number | null = null; // Variable to store the previous counter value for loss detection
4243
const canvasContainerRef = useRef<HTMLDivElement>(null);
43-
const [numChannels, setNumChannels] = useState<number>(canvasCount);
44+
const [numChannels, setNumChannels] = useState<number>(selectedChannels.length);
4445
const numXRef = useRef<number>(2000); // To track the calculated value
4546
const [canvases, setCanvases] = useState<HTMLCanvasElement[]>([]);
4647
const [wglPlots, setWglPlots] = useState<WebglPlot[]>([]);
@@ -54,6 +55,7 @@ const Canvas = forwardRef(
5455
Array.from({ length: 6 }, () => Array())
5556
)
5657
);
58+
const selectedChannelsRef = useRef(selectedChannels);
5759
const activebuffer = useRef(0); // Initialize useRef with 0
5860
const indicesRef = useRef<number[]>([]); // Use `useRef` for indices
5961

@@ -71,12 +73,15 @@ const Canvas = forwardRef(
7173
}
7274
}, []);
7375

74-
7576
useEffect(() => {
7677
numXRef.current = (currentSamplingRate * timeBase);
7778

7879
}, [timeBase]);
7980

81+
useEffect(() => {
82+
selectedChannelsRef.current = selectedChannels;
83+
}, [selectedChannels]);
84+
8085
const prevCanvasCountRef = useRef<number>(canvasCount);
8186

8287
const processIncomingData = (incomingData: number[]) => {
@@ -113,16 +118,15 @@ const Canvas = forwardRef(
113118
};
114119

115120
useEffect(() => {
116-
setNumChannels(canvasCount);
117-
}, [canvasCount]);
121+
setNumChannels(selectedChannels.length);
122+
}, [selectedChannels]);
118123

119124

120125
useEffect(() => {
121126
// Reset when timeBase changes
122127
currentSweepPos.current = new Array(numChannels).fill(0);
123128
sweepPositions.current = new Array(numChannels).fill(0);
124-
}, [timeBase]);
125-
129+
}, [timeBase, theme]);
126130

127131
useImperativeHandle(
128132
ref,
@@ -155,11 +159,17 @@ const Canvas = forwardRef(
155159
);
156160

157161
const createCanvases = () => {
158-
if (!canvasContainerRef.current) return;
162+
const container = canvasContainerRef.current;
163+
if (!container) {
164+
return; // Exit if the ref is null
165+
}
159166

160-
// Clean up all existing canvases and their WebGL contexts
161-
while (canvasContainerRef.current.firstChild) {
162-
const firstChild = canvasContainerRef.current.firstChild;
167+
currentSweepPos.current = new Array(numChannels).fill(0);
168+
sweepPositions.current = new Array(numChannels).fill(0);
169+
170+
// Clear existing child elements
171+
while (container.firstChild) {
172+
const firstChild = container.firstChild;
163173
if (firstChild instanceof HTMLCanvasElement) {
164174
const gl = firstChild.getContext("webgl");
165175
if (gl) {
@@ -169,130 +179,128 @@ const Canvas = forwardRef(
169179
}
170180
}
171181
}
172-
canvasContainerRef.current.removeChild(firstChild);
182+
container.removeChild(firstChild);
173183
}
174184

175185
setCanvases([]);
176186
setWglPlots([]);
177187
linesRef.current = [];
178-
const newCanvases = [];
179-
const newWglPlots = [];
180-
const newLines = [];
181-
188+
const newCanvases: HTMLCanvasElement[] = [];
189+
const newWglPlots: WebglPlot[] = [];
190+
const newLines: WebglLine[] = [];
182191

183-
// // Create grid lines
192+
// Create grid lines
184193
const canvasWrapper = document.createElement("div");
185-
canvasWrapper.className = "absolute inset-0"; // Make the wrapper fill the parent container
186-
const opacityDarkMajor = "0.2"; // Opacity for every 5th line in dark theme
187-
const opacityDarkMinor = "0.05"; // Opacity for other lines in dark theme
188-
const opacityLightMajor = "0.4"; // Opacity for every 5th line in light theme
189-
const opacityLightMinor = "0.1"; // Opacity for other lines in light theme
194+
canvasWrapper.className = "absolute inset-0";
195+
const opacityDarkMajor = "0.2";
196+
const opacityDarkMinor = "0.05";
197+
const opacityLightMajor = "0.4";
198+
const opacityLightMinor = "0.1";
190199
const distanceminor = samplingRate * 0.04;
191-
const numGridLines = getpoints(selectedBits ?? 10) * 4 / distanceminor;
200+
const numGridLines = (getpoints(selectedBits ?? 10) * 4) / distanceminor;
201+
192202
for (let j = 1; j < numGridLines; j++) {
193203
const gridLineX = document.createElement("div");
194204
gridLineX.className = "absolute bg-[rgb(128,128,128)]";
195205
gridLineX.style.width = "1px";
196206
gridLineX.style.height = "100%";
197-
const divPoint = (j / numGridLines) * 100
198-
const a = parseFloat(divPoint.toFixed(3));
199-
gridLineX.style.left = `${a}%`
200-
gridLineX.style.top = "0";
207+
gridLineX.style.left = `${((j / numGridLines) * 100).toFixed(3)}%`;
201208
gridLineX.style.opacity = j % 5 === 0 ? (theme === "dark" ? opacityDarkMajor : opacityLightMajor) : (theme === "dark" ? opacityDarkMinor : opacityLightMinor);
202-
203-
// Append grid lines to the wrapper
204209
canvasWrapper.appendChild(gridLineX);
205210
}
211+
206212
const horizontalline = 50;
207213
for (let j = 1; j < horizontalline; j++) {
208214
const gridLineY = document.createElement("div");
209215
gridLineY.className = "absolute bg-[rgb(128,128,128)]";
210216
gridLineY.style.height = "1px";
211217
gridLineY.style.width = "100%";
212-
const distance = (j / horizontalline) * 100
213-
const distancetop = parseFloat(distance.toFixed(3));
214-
gridLineY.style.top = `${distancetop}%`;
215-
gridLineY.style.left = "0";
218+
gridLineY.style.top = `${((j / horizontalline) * 100).toFixed(3)}%`;
216219
gridLineY.style.opacity = j % 5 === 0 ? (theme === "dark" ? opacityDarkMajor : opacityLightMajor) : (theme === "dark" ? opacityDarkMinor : opacityLightMinor);
217-
218-
// Append grid lines to the wrapper
219220
canvasWrapper.appendChild(gridLineY);
220221
}
221-
canvasContainerRef.current.appendChild(canvasWrapper);
222-
for (let i = 0; i < numChannels; i++) {
222+
container.appendChild(canvasWrapper);
223+
224+
// Create canvases for each selected channel
225+
selectedChannels.forEach((channelNumber) => {
223226
const canvasWrapper = document.createElement("div");
224-
canvasWrapper.className = "canvas-container relative flex-[1_1_0%]"; // Add relative positioning for absolute grid positioning
227+
canvasWrapper.className = "canvas-container relative flex-[1_1_0%]";
225228

226229
const canvas = document.createElement("canvas");
227-
canvas.id = `canvas${i + 1}`;
228-
canvas.width = canvasContainerRef.current.clientWidth;
229-
const canvasHeight = (canvasContainerRef.current.clientHeight / numChannels);
230-
canvas.height = canvasHeight;
230+
canvas.id = `canvas${channelNumber}`;
231+
canvas.width = container.clientWidth;
232+
canvas.height = container.clientHeight / selectedChannels.length;
231233
canvas.className = "w-full h-full block rounded-xl";
232234

233-
// Create a badge for the channel number
234235
const badge = document.createElement("div");
235236
badge.className = "absolute text-gray-500 text-sm rounded-full p-2 m-2";
236-
badge.innerText = `CH${i + 1}`;
237+
badge.innerText = `CH${channelNumber}`;
237238

238-
// Append the canvas and badge to the container
239239
canvasWrapper.appendChild(badge);
240240
canvasWrapper.appendChild(canvas);
241-
canvasContainerRef.current.appendChild(canvasWrapper);
241+
container.appendChild(canvasWrapper);
242242

243243
newCanvases.push(canvas);
244244
const wglp = new WebglPlot(canvas);
245245
newWglPlots.push(wglp);
246246
wglp.gScaleY = Zoom;
247-
const line = new WebglLine(getLineColor(i, theme), numXRef.current);
247+
const line = new WebglLine(getLineColor(channelNumber, theme), numXRef.current);
248248
wglp.gOffsetY = 0;
249249
line.offsetY = 0;
250250
line.lineSpaceX(-1, 2 / numXRef.current);
251251

252252
wglp.addLine(line);
253253
newLines.push(line);
254-
}
254+
});
255255

256256
linesRef.current = newLines;
257257
setCanvases(newCanvases);
258258
setWglPlots(newWglPlots);
259259
setLines(newLines);
260260
};
261261

262-
263262
const getLineColor = (i: number, theme: string | undefined): ColorRGBA => {
264-
// Define bright colors
263+
// Define the updated dark colors
265264
const colorsDark: ColorRGBA[] = [
266-
new ColorRGBA(1, 0.286, 0.529, 1), // Bright Pink
267-
new ColorRGBA(0.475, 0.894, 0.952, 1), // Light Blue
268-
new ColorRGBA(0, 1, 0.753, 1), // Bright Cyan
269-
new ColorRGBA(0.431, 0.761, 0.031, 1), // Bright Green
270-
new ColorRGBA(0.678, 0.286, 0.882, 1), // Bright Purple
271-
new ColorRGBA(0.914, 0.361, 0.051, 1), // Bright Orange
265+
new ColorRGBA(180 / 255, 70 / 255, 120 / 255, 1), // Darkened #EC6FAA
266+
new ColorRGBA(150 / 255, 70 / 255, 125 / 255, 1), // Darkened #CE6FAC
267+
new ColorRGBA(130 / 255, 90 / 255, 140 / 255, 1), // Darkened #B47EB7
268+
new ColorRGBA(110 / 255, 110 / 255, 160 / 255, 1), // Darkened #9D8DC4
269+
new ColorRGBA(70 / 255, 100 / 255, 150 / 255, 1), // Darkened #689AD2
270+
new ColorRGBA(40 / 255, 110 / 255, 140 / 255, 1), // Darkened #35A5CC
271+
new ColorRGBA(35 / 255, 120 / 255, 130 / 255, 1), // Darkened #30A8B4
272+
new ColorRGBA(35 / 255, 125 / 255, 120 / 255, 1), // Darkened #32ABA2
273+
272274
];
275+
273276
const colorsLight: ColorRGBA[] = [
274-
new ColorRGBA(0.820, 0.000, 0.329, 1), // #D10054 - Bright Pink
275-
new ColorRGBA(0.000, 0.478, 0.549, 1), // #007A8C - Light Blue
276-
new ColorRGBA(0.039, 0.408, 0.278, 1), // #0A6847 - Dark Green
277-
new ColorRGBA(0.404, 0.255, 0.533, 1), // #674188 - Bright Purple
278-
new ColorRGBA(0.902, 0.361, 0.098, 1), // #E65C19 - Bright Orange
279-
new ColorRGBA(0.180, 0.027, 0.247, 1), // #2E073F - Dark Purple
280-
];
277+
new ColorRGBA(236 / 255, 111 / 255, 170 / 255, 0.8), // Slightly transparent #EC6FAA
278+
new ColorRGBA(206 / 255, 111 / 255, 172 / 255, 0.8), // Slightly transparent #CE6FAC
279+
new ColorRGBA(180 / 255, 126 / 255, 183 / 255, 0.8), // Slightly transparent #B47EB7
280+
new ColorRGBA(157 / 255, 141 / 255, 196 / 255, 0.8), // Slightly transparent #9D8DC4
281+
new ColorRGBA(104 / 255, 154 / 255, 210 / 255, 0.8), // Slightly transparent #689AD2
282+
new ColorRGBA(53 / 255, 165 / 255, 204 / 255, 0.8), // Slightly transparent #35A5CC
283+
new ColorRGBA(48 / 255, 168 / 255, 180 / 255, 0.8), // Slightly transparent #30A8B4
284+
new ColorRGBA(50 / 255, 171 / 255, 162 / 255, 0.8), // Slightly transparent #32ABA2
281285

286+
];
282287

283-
// Return color based on the index, cycling through if necessary
288+
// Swap light and dark colors for themes
284289
return theme === "dark"
285-
? colorsDark[i % colorsDark.length]
286-
: colorsLight[i % colorsLight.length];
290+
? colorsLight[i % colorsLight.length] // Use lighter colors in dark theme
291+
: colorsDark[i % colorsDark.length]; // Use darker colors in light theme
287292
};
288293

289294
const updatePlots = useCallback(
290295
(data: number[], Zoom: number) => {
296+
// Access the latest selectedChannels via the ref
297+
const currentSelectedChannels = selectedChannelsRef.current;
291298

299+
// Adjust zoom level for each WebglPlot
292300
wglPlots.forEach((wglp, index) => {
293301
if (wglp) {
294302
try {
295-
wglp.gScaleY = Zoom; // Adjust the zoom value
303+
wglp.gScaleY = Zoom; // Adjust zoom value
296304
} catch (error) {
297305
console.error(
298306
`Error setting gScaleY for WebglPlot instance at index ${index}:`,
@@ -303,28 +311,59 @@ const Canvas = forwardRef(
303311
console.warn(`WebglPlot instance at index ${index} is undefined.`);
304312
}
305313
});
306-
307314
linesRef.current.forEach((line, i) => {
315+
if (!line) {
316+
console.warn(`Line at index ${i} is undefined.`);
317+
return;
318+
}
308319

309-
// Use a separate sweep position for each line
310-
currentSweepPos.current[i] = sweepPositions.current[i];
311-
// Plot the new data at the current sweep position
312-
line.setY(currentSweepPos.current[i] % line.numPoints, data[i + 1]);
320+
// Map channel number from selectedChannels
321+
const channelNumber = currentSelectedChannels[i];
322+
if (channelNumber == null || channelNumber < 0 || channelNumber >= data.length) {
323+
console.warn(`Invalid channel number: ${channelNumber}. Skipping.`);
324+
return;
325+
}
326+
327+
const channelData = data[channelNumber];
313328

314-
// Clear the next point to create a gap (optional, for visual effect)
315-
const clearPosition = Math.ceil((currentSweepPos.current[i] + (numXRef.current / 100)) % line.numPoints);
316-
line.setY(clearPosition, NaN);
329+
// Ensure sweepPositions.current[i] is initialized
330+
if (sweepPositions.current[i] === undefined) {
331+
sweepPositions.current[i] = 0;
332+
}
317333

318-
// Increment the sweep position for the current line
319-
sweepPositions.current[i] = (currentSweepPos.current[i] + 1) % line.numPoints;
334+
// Calculate the current position
335+
const currentPos = sweepPositions.current[i] % line.numPoints;
336+
337+
if (Number.isNaN(currentPos)) {
338+
console.error(`Invalid currentPos at index ${i}. sweepPositions.current[i]:`, sweepPositions.current[i]);
339+
return;
340+
}
341+
342+
// Plot the data
343+
try {
344+
line.setY(currentPos, channelData);
345+
} catch (error) {
346+
console.error(`Error plotting data for line ${i} at position ${currentPos}:`, error);
347+
}
348+
349+
// Clear the next point for visual effect
350+
const clearPosition = Math.ceil((currentPos + numXRef.current / 100) % line.numPoints);
351+
try {
352+
line.setY(clearPosition, NaN);
353+
} catch (error) {
354+
console.error(`Error clearing data at position ${clearPosition} for line ${i}:`, error);
355+
}
356+
357+
// Increment the sweep position
358+
sweepPositions.current[i] = (currentPos + 1) % line.numPoints;
320359
});
321360
},
322-
[lines, wglPlots, numChannels, theme, timeBase]
361+
[linesRef, wglPlots, selectedChannelsRef, numXRef, sweepPositions]
323362
);
324363

325364
useEffect(() => {
326365
createCanvases();
327-
}, [numChannels, theme, timeBase]);
366+
}, [numChannels, theme, timeBase, selectedChannels]);
328367

329368

330369
const animate = useCallback(() => {
@@ -384,6 +423,18 @@ const Canvas = forwardRef(
384423

385424
}, [animate]);
386425

426+
useEffect(() => {
427+
const handleResize = () => {
428+
createCanvases();
429+
430+
};
431+
window.addEventListener("resize", handleResize);
432+
return () => {
433+
window.removeEventListener("resize", handleResize);
434+
};
435+
}, [createCanvases]);
436+
437+
387438
return (
388439
<main className=" flex flex-col flex-[1_1_0%] min-h-80 bg-highlight rounded-2xl m-4 relative"
389440
ref={canvasContainerRef}
@@ -393,4 +444,4 @@ const Canvas = forwardRef(
393444
}
394445
);
395446
Canvas.displayName = "Canvas";
396-
export default Canvas;
447+
export default Canvas;

0 commit comments

Comments
 (0)