cfei1994 commited on
Commit
9a6ecf5
·
verified ·
1 Parent(s): b33ca75

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +136 -131
index.html CHANGED
@@ -12,7 +12,7 @@
12
  top: 0;
13
  left: 0;
14
  width: 100%;
15
- height: 60px; /* Fixed height for a slim look */
16
  background: rgba(255, 255, 255, 0.95);
17
  display: flex;
18
  align-items: center;
@@ -25,6 +25,14 @@
25
  font-size: 13px;
26
  }
27
 
 
 
 
 
 
 
 
 
28
  .control-group {
29
  display: flex;
30
  flex-direction: column; /* Stack label over slider */
@@ -73,8 +81,8 @@
73
  /* Adjust your Plotly container to make room for the banner */
74
  #plot-container {
75
  display: none; /* Hidden until data is loaded */
76
- margin-top: 60px;
77
- height: calc(100vh - 60px);
78
  }
79
  .slider-label { display: flex; justify-content: space-between; margin-bottom: 8px; font-weight: bold; }
80
  input[type=range] { width: 100%; }
@@ -83,19 +91,34 @@
83
  <body>
84
 
85
  <div id="controls" class="top-banner">
86
- <div class="control-group" style="max-width: 300px; flex-direction: row; align-items: stretch;">
87
- <input type="file" id="file-selector" style="display: none;" onchange="updateFilename(this)">
 
88
 
89
- <label for="file-selector" id="drop-zone"
90
- style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 8px; border: 1px solid #ccc; border-radius: 4px 4px 4px 4px; background: white; cursor: pointer; color: #666; display: flex; align-items: center; overflow: hidden;">
91
- <span id="file-name-display">Click to select or Drop CSV...</span>
92
- </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- <button class="btn" id="process-btn"
95
- style="background: #007bff; color: white; border: 1px solid #007bff; border-radius: 4px 4px 4px 4px; cursor: pointer; white-space: nowrap; margin-left:6px;"
96
- onclick="uploadAndProcess()">
97
- Process File
98
- </button>
99
  </div>
100
 
101
  <div class="control-group">
@@ -107,11 +130,12 @@
107
  <div class="control-group">
108
  <div class="slider-header">
109
  <span>Trajectories:</span>
110
- <span id="percent-val">2%</span>
111
  </div>
112
- <input type="range" id="percent" min="1" max="100" value="2">
113
  </div>
114
 
 
115
  <div class="control-group">
116
  <div class="slider-header">
117
  <span>Opacity:</span>
@@ -120,8 +144,8 @@
120
  <input type="range" id="opacity" min="0" max="0.99" step="0.01" value="0.99">
121
  </div>
122
 
123
- <div class="control-group">
124
- <button id="reset-view">Reset View</button>
125
  </div>
126
 
127
  <div class="info-text">
@@ -136,8 +160,11 @@
136
  <script>
137
 
138
  let allData = [];
139
- let xMin, yMax, zMin;
 
140
  let floorData, wallData, sideData;
 
 
141
 
142
  function handleDrop(event) {
143
  event.preventDefault();
@@ -191,26 +218,36 @@
191
  const allZ = allData.flatMap(t => t.z);
192
 
193
  // Define the boundaries of your "box"
194
- xMin = allX.reduce((a, b) => Math.min(a, b), Infinity);
195
- yMax = allY.reduce((a, b) => Math.max(a, b), -Infinity);
196
- zMin = allZ.reduce((a, b) => Math.min(a, b), Infinity);
197
-
198
- floorData = computeDensityGrid(allX, allY);
199
- wallData = computeDensityGrid(allX, allZ);
200
- sideData = computeDensityGrid(allY, allZ);
 
 
201
 
202
  // 6. Trigger UI
203
  document.getElementById('plot-container').style.display = 'block';
204
- // updatePlot(document.getElementById('percent').value);
205
- //update slider to 2%
206
- document.getElementById('percent').value = 2;
207
- percentVal.innerText = "2%";
208
- updatePlot(2);
209
- console.log("Processed tracks:", allData.length);
 
 
 
 
 
 
 
 
210
  }
211
  } catch (err) {
 
212
  console.error(err);
213
- alert("Upload failed. Check file size.");
214
  } finally {
215
  btn.innerText = "Process File";
216
  btn.style.background = "#3498db";
@@ -218,82 +255,23 @@
218
  }
219
  }
220
 
221
- // async function sendPathToJulia() {
222
- // const path = document.getElementById('path-input').value;
223
- // const btn = document.getElementById('process-btn');
224
-
225
- // if (!path) {
226
- // alert("Please provide a file path.");
227
- // return;
228
- // }
229
-
230
- // btn.innerText = "Processing...";
231
- // btn.style.background = "#6c757d"; // Change to grey
232
- // btn.style.pointerEvents = "none"; // Prevent double-clicking
233
-
234
- // try {
235
- // const response = await fetch('http://localhost:8080/process-path', {
236
- // method: 'POST',
237
- // headers: { 'Content-Type': 'application/json' },
238
- // body: JSON.stringify({ path: path })
239
- // });
240
-
241
- // const data = await response.json();
242
-
243
- // if (data.error) {
244
- // alert("Julia Error: " + data.error);
245
- // } else {
246
- // console.log("Data received:", data);
247
- // allData = data.trajectories;
248
-
249
- // // Re-flatten coordinate arrays for grid calculations
250
- // const allX = allData.flatMap(t => t.x);
251
- // const allY = allData.flatMap(t => t.y);
252
- // const allZ = allData.flatMap(t => t.z);
253
-
254
- // // Define the boundaries of your "box"
255
- // xMin = allX.reduce((a, b) => Math.min(a, b), Infinity);
256
- // yMax = allY.reduce((a, b) => Math.max(a, b), -Infinity);
257
- // zMin = allZ.reduce((a, b) => Math.min(a, b), Infinity);
258
-
259
- // floorData = computeDensityGrid(allX, allY);
260
- // wallData = computeDensityGrid(allX, allZ);
261
- // sideData = computeDensityGrid(allY, allZ);
262
-
263
- // // 6. Trigger UI
264
- // document.getElementById('plot-container').style.display = 'block';
265
- // // updatePlot(document.getElementById('percent').value);
266
- // //update slider to 2%
267
- // document.getElementById('percent').value = 2;
268
- // percentVal.innerText = "2%";
269
- // updatePlot(2);
270
- // console.log("Processed tracks:", allData.length);
271
-
272
- // }
273
- // } catch (err) {
274
- // alert("Could not connect to Julia server. Is server.jl running?");
275
- // console.error(err);
276
- // } finally {
277
- // btn.innerText = "Process File";
278
- // btn.style.background = "#3498db";
279
- // btn.style.pointerEvents = "auto"; // Re-enable button
280
- // }
281
- // }
282
-
283
  // Use the variable name you defined in your Julia export
284
 
285
  const plotDiv = document.getElementById('plot-container');
286
- const slider = document.getElementById('percent');
287
- const percentVal = document.getElementById('percent-val');
288
  const opacitySlider = document.getElementById('opacity');
289
  const opacityVal = document.getElementById('opacity-val');
 
 
 
290
 
291
- function computeDensityGrid(allX, allY, dX = 0.2) {
292
- // 1. Define the grid boundaries
293
- const xMin = allX.reduce((a, b) => Math.min(a, b), Infinity);
294
- const xMax = allX.reduce((a, b) => Math.max(a, b), -Infinity);
295
- const yMin = allY.reduce((a, b) => Math.min(a, b), Infinity);
296
- const yMax = allY.reduce((a, b) => Math.max(a, b), -Infinity);
297
 
298
  // 2. Calculate number of bins based on fixed dX
299
  // We use Math.ceil to ensure we cover the entire range
@@ -324,17 +302,16 @@
324
  return { x: xRange, y: yRange, z: density };
325
  }
326
 
327
-
328
-
329
- let currentOpacity = 0.99;
330
- let heatmapOpacity = 0.0;
331
 
332
- function updatePlot(percent) {
333
 
334
  if (!allData || allData.length === 0) return;
335
-
 
 
 
336
  const plotDiv = document.getElementById('plot-container');
337
-
338
  // 1. Capture the CURRENT camera state
339
  let currentCamera = null;
340
  if (plotDiv && plotDiv.layout && plotDiv.layout.scene) {
@@ -342,13 +319,10 @@
342
  }
343
 
344
  // 1. Calculate how many trajectories to show
345
- const totalToDisplay = Math.floor(allData.length * (percent / 100));
346
- const subset = allData.slice(0, totalToDisplay);
347
-
348
- // console.log("X range:", floorData.x[0], "to", floorData.x[floorData.x.length-1]);
349
- // console.log("Y range:", floorData.y[0], "to", floorData.y[floorData.y.length-1]);
350
 
351
-
352
  // 2. Map data to Plotly traces
353
  const traces = subset.map(t => ({
354
  type: 'scatter3d',
@@ -373,7 +347,7 @@
373
  type: 'surface',
374
  x: floorData.x,
375
  y: floorData.y,
376
- z: floorData.y.map(() => floorData.x.map(() => zMin - 0.1)), // Constant Z
377
  surfacecolor: floorData.z,
378
  colorscale: 'Portland',
379
  opacity: heatmapOpacity,
@@ -393,7 +367,7 @@
393
  x: wallData.y.map(() => wallData.x),
394
 
395
  // Y-matrix: Every single point in the grid is at the same 'yMax' (the wall)
396
- y: wallData.z.map(row => row.map(() => yMax + 0.2)),
397
 
398
  // Z-matrix: Create a column for every X-bin
399
  z: wallData.y.map(zVal => Array(wallData.x.length).fill(zVal)),
@@ -409,7 +383,7 @@
409
  const sideSurface = {
410
  type: 'surface',
411
  // X-matrix: Every single point in the grid is at the same 'xMin' (the side wall)
412
- x: sideData.z.map(row => row.map(() => xMin - 0.1)),
413
 
414
  // Y-matrix: Create a row for every Z-bin
415
  y: sideData.y.map(() => sideData.x),
@@ -431,11 +405,11 @@
431
  camera: currentCamera || {
432
  eye: {x: 1.25, y: 1.25, z: 1.25}
433
  },
434
- xaxis: { title: 'X (m)', backgroundcolor: "#f8f9fa", showbackground: true, showspikes: false, showgrid: true, zeroline: false, gridcolor: '#ffffff', gridwidth: 2 },
435
- yaxis: { title: 'Y (m)', backgroundcolor: "#f8f9fa", showbackground: true, showspikes: false, showgrid: true, zeroline: false, gridcolor: '#ffffff', gridwidth: 2 },
436
- zaxis: { title: 'Z (m)', backgroundcolor: "#f8f9fa", showbackground: true, showspikes: false, showgrid: true, zeroline: false, gridcolor: '#ffffff', gridwidth: 2 },
437
- aspectmode: 'data' // Ensures 1:1:1 spatial scale
438
-
439
  },
440
  margin: { l: 0, r: 0, b: 0, t: 0 },
441
  showlegend: false,
@@ -455,18 +429,51 @@
455
 
456
  // Event Listeners
457
  slider.oninput = function() {
458
- percentVal.innerText = this.value + "%";
459
  };
460
 
461
  slider.onchange = function() {
462
- updatePlot(this.value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  };
464
 
465
  opacitySlider.oninput = function() {
466
  currentOpacity = parseFloat(this.value);
467
  opacityVal.innerText = currentOpacity;
468
  // Trigger a re-render with the new opacity
469
- updatePlot(document.getElementById('percent').value);
470
  };
471
 
472
  // 3. Reset View Listener
@@ -484,14 +491,12 @@
484
  showHeatmaps = this.checked;
485
  // if checked set heatmapOpacity to 0.6 else 0.0
486
  heatmapOpacity = showHeatmaps ? 0.6 : 0.0;
487
- // Get the current trajectory percentage to maintain state
488
- const currentPercent = document.getElementById('percent').value;
489
- updatePlot(currentPercent);
490
  };
491
 
492
 
493
  // Initial render
494
- window.onload = () => updatePlot(2);
495
 
496
  </script>
497
  </body>
 
12
  top: 0;
13
  left: 0;
14
  width: 100%;
15
+ height: 100px; /* Fixed height for a slim look */
16
  background: rgba(255, 255, 255, 0.95);
17
  display: flex;
18
  align-items: center;
 
25
  font-size: 13px;
26
  }
27
 
28
+ /* Container for the sliders to stack them */
29
+ .slider-stack {
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 15px; /* Space between the two sliders */
33
+ min-width: 180px;
34
+ }
35
+
36
  .control-group {
37
  display: flex;
38
  flex-direction: column; /* Stack label over slider */
 
81
  /* Adjust your Plotly container to make room for the banner */
82
  #plot-container {
83
  display: none; /* Hidden until data is loaded */
84
+ margin-top: 100px;
85
+ height: calc(100vh - 100px);
86
  }
87
  .slider-label { display: flex; justify-content: space-between; margin-bottom: 8px; font-weight: bold; }
88
  input[type=range] { width: 100%; }
 
91
  <body>
92
 
93
  <div id="controls" class="top-banner">
94
+ <div class="slider-stack">
95
+ <div class="control-group" style="min-width: 300px; flex-direction: row">
96
+ <input type="file" id="file-selector" style="display: none;" onchange="updateFilename(this)">
97
 
98
+ <label for="file-selector" id="drop-zone"
99
+ style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 8px; border: 1px solid #ccc; border-radius: 4px 4px 4px 4px; background: white; cursor: pointer; color: #666; display: flex; align-items: center; overflow: hidden;">
100
+ <span id="file-name-display">Click to select or Drop CSV...</span>
101
+ </label>
102
+
103
+ <button class="btn" id="process-btn"
104
+ style="background: #007bff; color: white; border: 1px solid #007bff; border-radius: 4px 4px 4px 4px; cursor: pointer; white-space: nowrap; margin-left:6px;"
105
+ onclick="uploadAndProcess()">
106
+ Process File
107
+ </button>
108
+ </div>
109
+ <div class="control-group" style="display: flex; flex-direction: row; gap: 20px; align-items: center; width: 100%;">
110
+ <div class="slider-header" style="min-width: 90px;">
111
+ <span>Start (min):</span>
112
+ <span id="start-val">0</span>
113
+ </div>
114
+ <input type="range" id="start-slider" min="0" max="60" value="0" oninput="document.getElementById('start-val').innerText = this.value">
115
+
116
+ <div class="slider-header" style="min-width: 150px">
117
+ <span>Window (min):</span>
118
+ <input type="number" id="window-input" value="10" min="1" style="width: 45px; border: 1px solid #ccc; border-radius: 3px; font-size: 11px;">
119
+ </div>
120
+ </div>
121
 
 
 
 
 
 
122
  </div>
123
 
124
  <div class="control-group">
 
130
  <div class="control-group">
131
  <div class="slider-header">
132
  <span>Trajectories:</span>
133
+ <span id="trajnum-val">200</span>
134
  </div>
135
+ <input type="range" id="trajnum" min="1" max="1000" value="200" step="1">
136
  </div>
137
 
138
+
139
  <div class="control-group">
140
  <div class="slider-header">
141
  <span>Opacity:</span>
 
144
  <input type="range" id="opacity" min="0" max="0.99" step="0.01" value="0.99">
145
  </div>
146
 
147
+ <div class="control-group" style="max-width: 100px; min-width: 100px">
148
+ <button id="reset-view" style="width: 100%">Reset View</button>
149
  </div>
150
 
151
  <div class="info-text">
 
160
  <script>
161
 
162
  let allData = [];
163
+ let selectData = [];
164
+ let xMinAll, xMaxAll, yMinAll, yMaxAll, zMinAll, zMaxAll;
165
  let floorData, wallData, sideData;
166
+ let currentOpacity = 0.99;
167
+ let heatmapOpacity = 0.0;
168
 
169
  function handleDrop(event) {
170
  event.preventDefault();
 
218
  const allZ = allData.flatMap(t => t.z);
219
 
220
  // Define the boundaries of your "box"
221
+ xMinAll = allX.reduce((a, b) => Math.min(a, b), Infinity);
222
+ xMaxAll = allX.reduce((a, b) => Math.max(a, b), -Infinity);
223
+ yMinAll = allY.reduce((a, b) => Math.min(a, b), Infinity);
224
+ yMaxAll = allY.reduce((a, b) => Math.max(a, b), -Infinity);
225
+ zMinAll = allZ.reduce((a, b) => Math.min(a, b), Infinity);
226
+ zMaxAll = allZ.reduce((a, b) => Math.max(a, b), -Infinity);
227
+ // floorData = computeDensityGrid(allX, allY);
228
+ // wallData = computeDensityGrid(allX, allZ);
229
+ // sideData = computeDensityGrid(allY, allZ);
230
 
231
  // 6. Trigger UI
232
  document.getElementById('plot-container').style.display = 'block';
233
+ //render
234
+ const star_min = document.getElementById('start-slider').value;
235
+ const window_min = document.getElementById('window-input').value;
236
+
237
+ selectData = allData.filter(t => t.start_time >= parseFloat(star_min) * 60 && t.start_time < (parseFloat(star_min) + parseFloat(window_min)) * 60);
238
+ const selectX = selectData.flatMap(t => t.x);
239
+ const selectY = selectData.flatMap(t => t.y);
240
+ const selectZ = selectData.flatMap(t => t.z);
241
+ floorData = computeDensityGrid(selectX, selectY, xMinAll, xMaxAll, yMinAll, yMaxAll);
242
+ wallData = computeDensityGrid(selectX, selectZ, xMinAll, xMaxAll, zMinAll, zMaxAll);
243
+ sideData = computeDensityGrid(selectY, selectZ, yMinAll, yMaxAll, zMinAll, zMaxAll);
244
+
245
+ renderCurrentState();
246
+
247
  }
248
  } catch (err) {
249
+ alert("Could not connect to Julia server. Is server.jl running?");
250
  console.error(err);
 
251
  } finally {
252
  btn.innerText = "Process File";
253
  btn.style.background = "#3498db";
 
255
  }
256
  }
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  // Use the variable name you defined in your Julia export
259
 
260
  const plotDiv = document.getElementById('plot-container');
261
+ const slider = document.getElementById('trajnum');
262
+ const trajVal = document.getElementById('trajnum-val');
263
  const opacitySlider = document.getElementById('opacity');
264
  const opacityVal = document.getElementById('opacity-val');
265
+ const startMinSlider = document.getElementById('start-slider');
266
+ const windowMinInput = document.getElementById('window-input');
267
+ const startMinVal = document.getElementById('start-val');
268
 
269
+ function computeDensityGrid(allX, allY, xMin, xMax, yMin, yMax, dX = 0.2) {
270
+ // // 1. Define the grid boundaries
271
+ // const xMin = allX.reduce((a, b) => Math.min(a, b), Infinity);
272
+ // const xMax = allX.reduce((a, b) => Math.max(a, b), -Infinity);
273
+ // const yMin = allY.reduce((a, b) => Math.min(a, b), Infinity);
274
+ // const yMax = allY.reduce((a, b) => Math.max(a, b), -Infinity);
275
 
276
  // 2. Calculate number of bins based on fixed dX
277
  // We use Math.ceil to ensure we cover the entire range
 
302
  return { x: xRange, y: yRange, z: density };
303
  }
304
 
 
 
 
 
305
 
306
+ function renderCurrentState() {
307
 
308
  if (!allData || allData.length === 0) return;
309
+ if (!selectData || selectData.length === 0) {
310
+ alert("No trajectories found in the specified time window.");
311
+ return;
312
+ }
313
  const plotDiv = document.getElementById('plot-container');
314
+
315
  // 1. Capture the CURRENT camera state
316
  let currentCamera = null;
317
  if (plotDiv && plotDiv.layout && plotDiv.layout.scene) {
 
319
  }
320
 
321
  // 1. Calculate how many trajectories to show
322
+ const trajnum = parseInt(document.getElementById('trajnum').value);
323
+ const totalToDisplay = Math.min(trajnum, selectData.length);
324
+ const subset = selectData.slice(0, totalToDisplay);
 
 
325
 
 
326
  // 2. Map data to Plotly traces
327
  const traces = subset.map(t => ({
328
  type: 'scatter3d',
 
347
  type: 'surface',
348
  x: floorData.x,
349
  y: floorData.y,
350
+ z: floorData.y.map(() => floorData.x.map(() => zMinAll - 0.1)), // Constant Z
351
  surfacecolor: floorData.z,
352
  colorscale: 'Portland',
353
  opacity: heatmapOpacity,
 
367
  x: wallData.y.map(() => wallData.x),
368
 
369
  // Y-matrix: Every single point in the grid is at the same 'yMax' (the wall)
370
+ y: wallData.z.map(row => row.map(() => yMaxAll + 0.2)),
371
 
372
  // Z-matrix: Create a column for every X-bin
373
  z: wallData.y.map(zVal => Array(wallData.x.length).fill(zVal)),
 
383
  const sideSurface = {
384
  type: 'surface',
385
  // X-matrix: Every single point in the grid is at the same 'xMin' (the side wall)
386
+ x: sideData.z.map(row => row.map(() => xMinAll - 0.1)),
387
 
388
  // Y-matrix: Create a row for every Z-bin
389
  y: sideData.y.map(() => sideData.x),
 
405
  camera: currentCamera || {
406
  eye: {x: 1.25, y: 1.25, z: 1.25}
407
  },
408
+ xaxis: { title: 'X (m)', range: [xMinAll - 0.2, xMaxAll], backgroundcolor: "#f8f9fa", showbackground: true, showspikes: false, showgrid: true, zeroline: false, gridcolor: '#ffffff', gridwidth: 2 },
409
+ yaxis: { title: 'Y (m)', range: [yMinAll, yMaxAll + 0.3], backgroundcolor: "#f8f9fa", showbackground: true, showspikes: false, showgrid: true, zeroline: false, gridcolor: '#ffffff', gridwidth: 2 },
410
+ zaxis: { title: 'Z (m)', range: [zMinAll - 0.2, zMaxAll], backgroundcolor: "#f8f9fa", showbackground: true, showspikes: false, showgrid: true, zeroline: false, gridcolor: '#ffffff', gridwidth: 2 },
411
+ aspectmode: 'manual', // Ensures 1:1:1 spatial scale
412
+ aspectratio: {x: 1, z: (zMaxAll - zMinAll + 0.2) / (xMaxAll - xMinAll + 0.2), y: (yMaxAll - yMinAll + 0.3) / (xMaxAll - xMinAll + 0.2)}
413
  },
414
  margin: { l: 0, r: 0, b: 0, t: 0 },
415
  showlegend: false,
 
429
 
430
  // Event Listeners
431
  slider.oninput = function() {
432
+ trajVal.innerText = this.value;
433
  };
434
 
435
  slider.onchange = function() {
436
+ renderCurrentState();
437
+ };
438
+
439
+ startMinSlider.oninput = function() {
440
+ startMinVal.innerText = this.value;
441
+
442
+ const star_min = this.value;
443
+ const window_min = document.getElementById('window-input').value;
444
+
445
+ selectData = allData.filter(t => t.start_time >= parseFloat(star_min) * 60 && t.start_time < (parseFloat(star_min) + parseFloat(window_min)) * 60);
446
+ const selectX = selectData.flatMap(t => t.x);
447
+ const selectY = selectData.flatMap(t => t.y);
448
+ const selectZ = selectData.flatMap(t => t.z);
449
+ floorData = computeDensityGrid(selectX, selectY, xMinAll, xMaxAll, yMinAll, yMaxAll);
450
+ wallData = computeDensityGrid(selectX, selectZ, xMinAll, xMaxAll, zMinAll, zMaxAll);
451
+ sideData = computeDensityGrid(selectY, selectZ, yMinAll, yMaxAll, zMinAll, zMaxAll);
452
+
453
+ renderCurrentState();
454
+ };
455
+
456
+ windowMinInput.onchange = function() {
457
+
458
+ const star_min = document.getElementById('start-slider').value;
459
+ const window_min = this.value;
460
+
461
+ selectData = allData.filter(t => t.start_time >= parseFloat(star_min) * 60 && t.start_time < (parseFloat(star_min) + parseFloat(window_min)) * 60);
462
+ const selectX = selectData.flatMap(t => t.x);
463
+ const selectY = selectData.flatMap(t => t.y);
464
+ const selectZ = selectData.flatMap(t => t.z);
465
+ floorData = computeDensityGrid(selectX, selectY, xMinAll, xMaxAll, yMinAll, yMaxAll);
466
+ wallData = computeDensityGrid(selectX, selectZ, xMinAll, xMaxAll, zMinAll, zMaxAll);
467
+ sideData = computeDensityGrid(selectY, selectZ, yMinAll, yMaxAll, zMinAll, zMaxAll);
468
+
469
+ renderCurrentState();
470
  };
471
 
472
  opacitySlider.oninput = function() {
473
  currentOpacity = parseFloat(this.value);
474
  opacityVal.innerText = currentOpacity;
475
  // Trigger a re-render with the new opacity
476
+ renderCurrentState();
477
  };
478
 
479
  // 3. Reset View Listener
 
491
  showHeatmaps = this.checked;
492
  // if checked set heatmapOpacity to 0.6 else 0.0
493
  heatmapOpacity = showHeatmaps ? 0.6 : 0.0;
494
+ renderCurrentState();
 
 
495
  };
496
 
497
 
498
  // Initial render
499
+ window.onload = () => renderCurrentState(); // Default to showing 2% of trajectories with start_min=0 and window_min=10
500
 
501
  </script>
502
  </body>