HI7RAI commited on
Commit
b8afd3e
·
verified ·
1 Parent(s): e61c1c1

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +701 -19
index.html CHANGED
@@ -1,19 +1,701 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>FielDHub Web - Diagonal Arrangement Designer</title>
7
+ <!-- Import Icons -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <!-- Import html2canvas for export -->
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
11
+
12
+ <style>
13
+ :root {
14
+ --primary: #2e7d32;
15
+ --primary-dark: #1b5e20;
16
+ --accent: #81c784;
17
+ --bg: #f5f7fa;
18
+ --surface: #ffffff;
19
+ --text: #333333;
20
+ --text-light: #757575;
21
+ --border: #e0e0e0;
22
+ --plot-check: #ffc107; /* Yellow for checks */
23
+ --plot-treatment: #4caf50; /* Green for treatments */
24
+ --plot-filler: #e0e0e0;
25
+ }
26
+
27
+ * {
28
+ box-sizing: border-box;
29
+ margin: 0;
30
+ padding: 0;
31
+ font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
32
+ }
33
+
34
+ body {
35
+ background-color: var(--bg);
36
+ color: var(--text);
37
+ display: flex;
38
+ flex-direction: column;
39
+ height: 100vh;
40
+ overflow: hidden;
41
+ }
42
+
43
+ /* Header */
44
+ header {
45
+ background: var(--surface);
46
+ border-bottom: 1px solid var(--border);
47
+ padding: 0.75rem 1.5rem;
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: center;
51
+ height: 60px;
52
+ flex-shrink: 0;
53
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
54
+ z-index: 10;
55
+ }
56
+
57
+ .brand {
58
+ font-size: 1.25rem;
59
+ font-weight: 700;
60
+ color: var(--primary);
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 10px;
64
+ }
65
+
66
+ .anycoder-link {
67
+ font-size: 0.8rem;
68
+ color: var(--text-light);
69
+ text-decoration: none;
70
+ border: 1px solid var(--border);
71
+ padding: 4px 8px;
72
+ border-radius: 4px;
73
+ transition: all 0.2s;
74
+ }
75
+
76
+ .anycoder-link:hover {
77
+ background: var(--primary);
78
+ color: white;
79
+ border-color: var(--primary);
80
+ }
81
+
82
+ /* Main Layout */
83
+ main {
84
+ display: flex;
85
+ flex: 1;
86
+ overflow: hidden;
87
+ }
88
+
89
+ /* Sidebar Controls */
90
+ aside {
91
+ width: 320px;
92
+ background: var(--surface);
93
+ border-right: 1px solid var(--border);
94
+ padding: 1.5rem;
95
+ overflow-y: auto;
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: 1.5rem;
99
+ flex-shrink: 0;
100
+ transition: transform 0.3s ease;
101
+ }
102
+
103
+ .control-group {
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 0.5rem;
107
+ }
108
+
109
+ label {
110
+ font-size: 0.9rem;
111
+ font-weight: 600;
112
+ color: var(--text-light);
113
+ }
114
+
115
+ input[type="number"], select {
116
+ padding: 0.6rem;
117
+ border: 1px solid var(--border);
118
+ border-radius: 6px;
119
+ font-size: 0.95rem;
120
+ transition: border 0.2s;
121
+ }
122
+
123
+ input[type="number"]:focus, select:focus {
124
+ outline: none;
125
+ border-color: var(--primary);
126
+ box-shadow: 0 0 0 3px rgba(46, 125, 50, 0.1);
127
+ }
128
+
129
+ .range-wrap {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: 10px;
133
+ }
134
+
135
+ input[type="range"] {
136
+ flex: 1;
137
+ accent-color: var(--primary);
138
+ }
139
+
140
+ .val-display {
141
+ font-weight: bold;
142
+ color: var(--primary);
143
+ width: 40px;
144
+ text-align: right;
145
+ }
146
+
147
+ button.btn-primary {
148
+ background: var(--primary);
149
+ color: white;
150
+ border: none;
151
+ padding: 0.8rem;
152
+ border-radius: 6px;
153
+ font-weight: 600;
154
+ cursor: pointer;
155
+ transition: background 0.2s;
156
+ margin-top: auto;
157
+ }
158
+
159
+ button.btn-primary:hover {
160
+ background: var(--primary-dark);
161
+ }
162
+
163
+ /* Visualization Area */
164
+ #workspace {
165
+ flex: 1;
166
+ padding: 2rem;
167
+ overflow: auto;
168
+ display: flex;
169
+ justify-content: center;
170
+ align-items: center;
171
+ background-image: radial-gradient(#e0e0e0 1px, transparent 1px);
172
+ background-size: 20px 20px;
173
+ position: relative;
174
+ }
175
+
176
+ #field-container {
177
+ background: white;
178
+ padding: 20px;
179
+ border-radius: 8px;
180
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
181
+ display: grid;
182
+ gap: 2px;
183
+ transition: all 0.3s ease;
184
+ }
185
+
186
+ .plot {
187
+ width: 40px;
188
+ height: 40px;
189
+ border-radius: 4px;
190
+ display: flex;
191
+ justify-content: center;
192
+ align-items: center;
193
+ font-size: 0.65rem;
194
+ font-weight: bold;
195
+ color: white;
196
+ position: relative;
197
+ transition: transform 0.2s, opacity 0.3s;
198
+ opacity: 0;
199
+ animation: popIn 0.3s forwards;
200
+ border: 1px solid rgba(0,0,0,0.05);
201
+ }
202
+
203
+ .plot.check {
204
+ background-color: var(--plot-check);
205
+ color: #333;
206
+ z-index: 2;
207
+ box-shadow: 0 0 5px rgba(255, 193, 7, 0.5);
208
+ }
209
+
210
+ .plot.treatment {
211
+ background-color: var(--plot-treatment);
212
+ }
213
+
214
+ .plot.filler {
215
+ background-color: var(--plot-filler);
216
+ color: #999;
217
+ }
218
+
219
+ .plot:hover {
220
+ transform: scale(1.1);
221
+ z-index: 10;
222
+ }
223
+
224
+ /* Tooltip */
225
+ .plot::after {
226
+ content: attr(data-info);
227
+ position: absolute;
228
+ bottom: 100%;
229
+ left: 50%;
230
+ transform: translateX(-50%);
231
+ background: #333;
232
+ color: white;
233
+ padding: 4px 8px;
234
+ border-radius: 4px;
235
+ font-size: 0.7rem;
236
+ white-space: nowrap;
237
+ opacity: 0;
238
+ pointer-events: none;
239
+ transition: opacity 0.2s;
240
+ margin-bottom: 5px;
241
+ }
242
+
243
+ .plot:hover::after {
244
+ opacity: 1;
245
+ }
246
+
247
+ /* Toolbar */
248
+ .toolbar {
249
+ position: absolute;
250
+ bottom: 20px;
251
+ right: 20px;
252
+ background: white;
253
+ padding: 0.5rem;
254
+ border-radius: 50px;
255
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
256
+ display: flex;
257
+ gap: 10px;
258
+ }
259
+
260
+ .tool-btn {
261
+ background: none;
262
+ border: none;
263
+ width: 40px;
264
+ height: 40px;
265
+ border-radius: 50%;
266
+ cursor: pointer;
267
+ color: var(--text-light);
268
+ transition: all 0.2s;
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: center;
272
+ }
273
+
274
+ .tool-btn:hover {
275
+ background: var(--bg);
276
+ color: var(--primary);
277
+ }
278
+
279
+ /* Stats Panel */
280
+ .stats-panel {
281
+ margin-top: 1rem;
282
+ background: var(--bg);
283
+ padding: 1rem;
284
+ border-radius: 6px;
285
+ font-size: 0.85rem;
286
+ }
287
+ .stat-row {
288
+ display: flex;
289
+ justify-content: space-between;
290
+ margin-bottom: 0.5rem;
291
+ }
292
+ .stat-row:last-child { margin-bottom: 0; }
293
+
294
+ /* Animations */
295
+ @keyframes popIn {
296
+ 0% { transform: scale(0); opacity: 0; }
297
+ 80% { transform: scale(1.1); opacity: 1; }
298
+ 100% { transform: scale(1); opacity: 1; }
299
+ }
300
+
301
+ /* Responsive */
302
+ @media (max-width: 768px) {
303
+ aside {
304
+ position: absolute;
305
+ left: 0;
306
+ top: 60px;
307
+ bottom: 0;
308
+ transform: translateX(-100%);
309
+ z-index: 20;
310
+ width: 80%;
311
+ }
312
+ aside.active {
313
+ transform: translateX(0);
314
+ }
315
+ .mobile-toggle {
316
+ display: block;
317
+ font-size: 1.5rem;
318
+ cursor: pointer;
319
+ margin-right: 1rem;
320
+ }
321
+ }
322
+ @media (min-width: 769px) {
323
+ .mobile-toggle { display: none; }
324
+ }
325
+
326
+ .legend {
327
+ display: flex;
328
+ gap: 15px;
329
+ margin-top: 10px;
330
+ font-size: 0.8rem;
331
+ }
332
+ .legend-item { display: flex; align-items: center; gap: 5px; }
333
+ .dot { width: 12px; height: 12px; border-radius: 2px; }
334
+ </style>
335
+ </head>
336
+ <body>
337
+
338
+ <header>
339
+ <div style="display:flex; align-items:center;">
340
+ <i class="fa-solid fa-bars mobile-toggle" onclick="toggleSidebar()"></i>
341
+ <div class="brand">
342
+ <i class="fa-solid fa-seedling"></i>
343
+ FielDHub Web
344
+ </div>
345
+ </div>
346
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">Built with anycoder</a>
347
+ </header>
348
+
349
+ <main>
350
+ <aside id="sidebar">
351
+ <div class="control-group">
352
+ <label>Field Dimensions</label>
353
+ <div class="range-wrap">
354
+ <span>Rows:</span>
355
+ <input type="range" id="nrows" min="5" max="50" value="15">
356
+ <span class="val-display" id="nrows-val">15</span>
357
+ </div>
358
+ <div class="range-wrap">
359
+ <span>Cols:</span>
360
+ <input type="range" id="ncols" min="5" max="50" value="20">
361
+ <span class="val-display" id="ncols-val">20</span>
362
+ </div>
363
+ </div>
364
+
365
+ <div class="control-group">
366
+ <label>Experimental Design</label>
367
+ <div class="range-wrap">
368
+ <span>Checks:</span>
369
+ <input type="range" id="checks" min="1" max="20" value="4">
370
+ <span class="val-display" id="checks-val">4</span>
371
+ </div>
372
+ <div class="range-wrap">
373
+ <span>Lines (Treatments):</span>
374
+ <input type="range" id="lines" min="10" max="500" value="270">
375
+ <span class="val-display" id="lines-val">270</span>
376
+ </div>
377
+ </div>
378
+
379
+ <div class="control-group">
380
+ <label for="planter">Planting Pattern</label>
381
+ <select id="planter">
382
+ <option value="serpentine">Serpentine (Snake)</option>
383
+ <option value="cartesian">Cartesian (Linear)</option>
384
+ </select>
385
+ </div>
386
+
387
+ <div class="control-group">
388
+ <label for="kindExpt">Design Type</label>
389
+ <select id="kindExpt">
390
+ <option value="SUDC">Single Un-replicated Diagonal Checks</option>
391
+ <option value="DBUDC">Decision Blocks Un-replicated</option>
392
+ </select>
393
+ </div>
394
+
395
+ <div class="control-group">
396
+ <label for="seed">Random Seed</label>
397
+ <input type="number" id="seed" value="1987">
398
+ </div>
399
+
400
+ <div class="stats-panel">
401
+ <div class="stat-row"><span>Total Plots:</span> <strong id="stat-total">300</strong></div>
402
+ <div class="stat-row"><span>Check %:</span> <strong id="stat-percent">1.3%</strong></div>
403
+ <div class="stat-row"><span>Fillers:</span> <strong id="stat-fillers">0</strong></div>
404
+ </div>
405
+
406
+ <div class="legend">
407
+ <div class="legend-item"><div class="dot" style="background:var(--plot-check)"></div> Check</div>
408
+ <div class="legend-item"><div class="dot" style="background:var(--plot-treatment)"></div> Treatment</div>
409
+ <div class="legend-item"><div class="dot" style="background:var(--plot-filler)"></div> Empty</div>
410
+ </div>
411
+
412
+ <button class="btn-primary" onclick="generateDesign()">
413
+ <i class="fa-solid fa-wand-magic-sparkles"></i> Generate Design
414
+ </button>
415
+ </aside>
416
+
417
+ <div id="workspace">
418
+ <div id="field-container">
419
+ <!-- Grid will be injected here -->
420
+ </div>
421
+
422
+ <div class="toolbar">
423
+ <button class="tool-btn" title="Download Image" onclick="exportImage()">
424
+ <i class="fa-solid fa-image"></i>
425
+ </button>
426
+ <button class="tool-btn" title="Download CSV" onclick="exportCSV()">
427
+ <i class="fa-solid fa-file-csv"></i>
428
+ </button>
429
+ <button class="tool-btn" title="Zoom In" onclick="adjustZoom(1)">
430
+ <i class="fa-solid fa-magnifying-glass-plus"></i>
431
+ </button>
432
+ <button class="tool-btn" title="Zoom Out" onclick="adjustZoom(-1)">
433
+ <i class="fa-solid fa-magnifying-glass-minus"></i>
434
+ </button>
435
+ </div>
436
+ </div>
437
+ </main>
438
+
439
+ <script>
440
+ // State
441
+ let currentDesign = null;
442
+ let zoomLevel = 1;
443
+
444
+ // DOM Elements
445
+ const inputs = {
446
+ nrows: document.getElementById('nrows'),
447
+ ncols: document.getElementById('ncols'),
448
+ checks: document.getElementById('checks'),
449
+ lines: document.getElementById('lines'),
450
+ planter: document.getElementById('planter'),
451
+ kindExpt: document.getElementById('kindExpt'),
452
+ seed: document.getElementById('seed')
453
+ };
454
+
455
+ const displays = {
456
+ nrows: document.getElementById('nrows-val'),
457
+ ncols: document.getElementById('ncols-val'),
458
+ checks: document.getElementById('checks-val'),
459
+ lines: document.getElementById('lines-val'),
460
+ total: document.getElementById('stat-total'),
461
+ percent: document.getElementById('stat-percent'),
462
+ fillers: document.getElementById('stat-fillers')
463
+ };
464
+
465
+ // Event Listeners for Sliders
466
+ Object.keys(inputs).forEach(key => {
467
+ if(inputs[key].type === 'range') {
468
+ inputs[key].addEventListener('input', (e) => {
469
+ displays[key].innerText = e.target.value;
470
+ // Auto update stats for total
471
+ if(key === 'nrows' || key === 'ncols') {
472
+ const total = inputs.nrows.value * inputs.ncols.value;
473
+ displays.total.innerText = total;
474
+ }
475
+ });
476
+ }
477
+ });
478
+
479
+ // Toggle Sidebar
480
+ function toggleSidebar() {
481
+ document.getElementById('sidebar').classList.toggle('active');
482
+ }
483
+
484
+ // Zoom Functionality
485
+ function adjustZoom(direction) {
486
+ zoomLevel += (direction * 0.1);
487
+ if(zoomLevel < 0.5) zoomLevel = 0.5;
488
+ if(zoomLevel > 2.0) zoomLevel = 2.0;
489
+
490
+ const container = document.getElementById('field-container');
491
+ // Adjust plot size based on zoom
492
+ const plots = document.querySelectorAll('.plot');
493
+ plots.forEach(p => {
494
+ p.style.width = `${40 * zoomLevel}px`;
495
+ p.style.height = `${40 * zoomLevel}px`;
496
+ p.style.fontSize = `${0.65 * zoomLevel}rem`;
497
+ });
498
+ }
499
+
500
+ // --- CORE LOGIC PORTED FROM R ---
501
+
502
+ function generateDesign() {
503
+ const params = {
504
+ nrows: parseInt(inputs.nrows.value),
505
+ ncols: parseInt(inputs.ncols.value),
506
+ lines: parseInt(inputs.lines.value),
507
+ checks: parseInt(inputs.checks.value),
508
+ planter: inputs.planter.value,
509
+ kindExpt: inputs.kindExpt.value,
510
+ seed: parseInt(inputs.seed.value)
511
+ };
512
+
513
+ // Simple Seeded Random
514
+ const seedRandom = (seed) => {
515
+ let x = Math.sin(seed++) * 10000;
516
+ return x - Math.floor(x);
517
+ };
518
+
519
+ // 1. Initialize Grid
520
+ let grid = Array(params.nrows).fill().map(() => Array(params.ncols).fill(null));
521
+
522
+ // 2. Determine Check Placement (Diagonal Logic)
523
+ // In R, this is complex optimization. For Web UI, we use a heuristic diagonal placement.
524
+ // We want checks to be somewhat evenly distributed.
525
+
526
+ const totalPlots = params.nrows * params.ncols;
527
+ const totalNeeded = params.lines + params.checks;
528
+
529
+ let fillers = 0;
530
+ if (totalNeeded > totalPlots) {
531
+ alert("Lines + Checks exceed Field Capacity!");
532
+ return;
533
+ }
534
+
535
+ // Calculate Fillers (Empty plots if lines+checks < total)
536
+ fillers = totalPlots - totalNeeded;
537
+ displays.fillers.innerText = fillers;
538
+
539
+ // Place Checks on Diagonals
540
+ // Strategy: Iterate rows. Place check if (row + col) % stride == 0
541
+ // We need to place exactly 'checks' number of checks.
542
+ // We will distribute them across the field.
543
+
544
+ let checkPositions = [];
545
+ let stride = Math.floor(totalPlots / params.checks);
546
+ if (stride < 1) stride = 1;
547
+
548
+ // To make it look like the R "Diagonal" design, we often place checks every k steps in a serpentine path
549
+ // or on geometric diagonals. Let's try geometric diagonals first as per name.
550
+
551
+ // Heuristic: Place checks on main diagonals shifted
552
+ let placedChecks = 0;
553
+ for(let r=0; r<params.nrows; r++) {
554
+ for(let c=0; c<params.ncols; c++) {
555
+ // Simple diagonal check: (r + c) % k == offset
556
+ // We need to tune k to get roughly 'checks' amount.
557
+ // Let's try a simpler approach: Randomly select 'checks' spots, but ensure no two are adjacent (spatial constraint)
558
+ // Actually, R code implies specific diagonal patterns.
559
+ // Let's implement a "Serpentine Diagonal":
560
+ // If we lay out the field linearly 0..N, place checks at indices i*stride.
561
+ }
562
+ }
563
+
564
+ // Let's use the Serpentine Indexing to place checks, then map back to Grid
565
+ // This mimics the R logic where randomization often happens on the linear index first.
566
+
567
+ let linearIndices = [];
568
+ for(let i=0; i<totalPlots; i++) linearIndices.push(i);
569
+
570
+ // Shuffle with seed
571
+ for (let i = linearIndices.length - 1; i > 0; i--) {
572
+ const j = Math.floor(seedRandom(params.seed + i) * (i + 1));
573
+ [linearIndices[i], linearIndices[j]] = [linearIndices[j], linearIndices[i]];
574
+ }
575
+
576
+ // Select first 'checks' indices for checks
577
+ let checkIndices = linearIndices.slice(0, params.checks);
578
+ let treatmentIndices = linearIndices.slice(params.checks, params.checks + params.lines);
579
+ // Rest are fillers (implicitly null)
580
+
581
+ // Map linear index to Grid (Row, Col) based on Planter mode
582
+ const getCoords = (index, mode, rows, cols) => {
583
+ let r = Math.floor(index / cols);
584
+ let c = index % cols;
585
+
586
+ if (mode === 'serpentine') {
587
+ if (r % 2 !== 0) { // Odd row (0-indexed), reverse column
588
+ c = cols - 1 - c;
589
+ }
590
+ }
591
+ return {r, c};
592
+ };
593
+
594
+ // Populate Grid
595
+ let plotData = []; // Store for export
596
+
597
+ checkIndices.forEach(idx => {
598
+ const {r, c} = getCoords(idx, params.planter, params.nrows, params.ncols);
599
+ grid[r][c] = { type: 'check', value: `CH-${idx+1}` };
600
+ plotData.push({row: r+1, col: c+1, type: 'Check', name: `CH-${idx+1}`, plotNum: idx+1});
601
+ });
602
+
603
+ treatmentIndices.forEach((idx, i) => {
604
+ const {r, c} = getCoords(idx, params.planter, params.nrows, params.ncols);
605
+ grid[r][c] = { type: 'treatment', value: `G-${i+1}` };
606
+ plotData.push({row: r+1, col: c+1, type: 'Line', name: `G-${i+1}`, plotNum: idx+1});
607
+ });
608
+
609
+ // Fillers remain null in grid, but we can count them
610
+ displays.percent.innerText = ((params.checks / totalPlots) * 100).toFixed(1) + '%';
611
+
612
+ currentDesign = { grid, plotData, params };
613
+ renderGrid(grid, params);
614
+ }
615
+
616
+ function renderGrid(grid, params) {
617
+ const container = document.getElementById('field-container');
618
+ container.innerHTML = '';
619
+
620
+ // Set Grid CSS
621
+ container.style.gridTemplateColumns = `repeat(${params.ncols}, 1fr)`;
622
+
623
+ // Animation delay counter
624
+ let delay = 0;
625
+
626
+ for(let r=0; r<params.nrows; r++) {
627
+ for(let c=0; c<params.ncols; c++) {
628
+ const cell = grid[r][c];
629
+ const div = document.createElement('div');
630
+ div.className = 'plot';
631
+
632
+ // Apply zoom immediately
633
+ div.style.width = `${40 * zoomLevel}px`;
634
+ div.style.height = `${40 * zoomLevel}px`;
635
+ div.style.fontSize = `${0.65 * zoomLevel}rem`;
636
+
637
+ if (cell) {
638
+ div.classList.add(cell.type);
639
+ div.innerText = cell.value.replace(/[^0-9]/g, ''); // Show number only for clean look
640
+ div.setAttribute('data-info', `${cell.value}\nRow: ${r+1}, Col: ${c+1}`);
641
+ } else {
642
+ div.classList.add('filler');
643
+ div.setAttribute('data-info', `Empty Plot\nRow: ${r+1}, Col: ${c+1}`);
644
+ }
645
+
646
+ // Staggered animation
647
+ div.style.animationDelay = `${delay}ms`;
648
+ delay += 5;
649
+ if(delay > 300) delay = 0; // Cap delay for large grids
650
+
651
+ container.appendChild(div);
652
+ }
653
+ }
654
+ }
655
+
656
+ // Export Functions
657
+ function exportImage() {
658
+ const element = document.getElementById("field-container");
659
+ // Temporarily remove animation for clean capture
660
+ const plots = document.querySelectorAll('.plot');
661
+ plots.forEach(p => p.style.animation = 'none');
662
+
663
+ html2canvas(element, {
664
+ backgroundColor: "#ffffff",
665
+ scale: 2 // Higher resolution
666
+ }).then(canvas => {
667
+ const link = document.createElement('a');
668
+ link.download = `field_design_${inputs.kindExpt.value}.png`;
669
+ link.href = canvas.toDataURL();
670
+ link.click();
671
+
672
+ // Restore animation
673
+ plots.forEach(p => p.style.animation = 'popIn 0.3s forwards');
674
+ });
675
+ }
676
+
677
+ function exportCSV() {
678
+ if(!currentDesign) return;
679
+
680
+ let csvContent = "data:text/csv;charset=utf-8,";
681
+ csvContent += "Row,Column,Type,Name,PlotID\n";
682
+
683
+ currentDesign.plotData.forEach(row => {
684
+ csvContent += `${row.row},${row.col},${row.type},${row.name},${row.plotNum}\n`;
685
+ });
686
+
687
+ const encodedUri = encodeURI(csvContent);
688
+ const link = document.createElement("a");
689
+ link.setAttribute("href", encodedUri);
690
+ link.setAttribute("download", "field_book.csv");
691
+ document.body.appendChild(link);
692
+ link.click();
693
+ document.body.removeChild(link);
694
+ }
695
+
696
+ // Initialize
697
+ generateDesign();
698
+
699
+ </script>
700
+ </body>
701
+ </html>