|
7 | 7 | .plots-container {
|
8 | 8 | display: flex;
|
9 | 9 | justify-content: space-between;
|
10 |
| - gap: 0px; |
| 10 | + gap: 20px; |
11 | 11 | margin-top: 20px;
|
12 | 12 | }
|
13 | 13 |
|
14 | 14 | .plot-column {
|
15 | 15 | flex: 1;
|
16 | 16 | }
|
| 17 | + |
| 18 | + .controls-row { |
| 19 | + margin-bottom: 10px; |
| 20 | + } |
| 21 | + |
| 22 | + .progress-container { |
| 23 | + width: 100%; |
| 24 | + margin-top: 20px; |
| 25 | + display: none; |
| 26 | + } |
| 27 | + |
| 28 | + .progress-bar { |
| 29 | + width: 0%; |
| 30 | + height: 20px; |
| 31 | + background-color: #4CAF50; |
| 32 | + transition: width 0.1s ease-in-out; |
| 33 | + border-radius: 4px; |
| 34 | + } |
17 | 35 | </style>
|
18 | 36 | </head>
|
19 | 37 |
|
|
27 | 45 | <div id="histogram"></div>
|
28 | 46 | </div>
|
29 | 47 | </div>
|
| 48 | + <div class="progress-container"> |
| 49 | + <div class="progress-bar"></div> |
| 50 | + </div> |
30 | 51 |
|
31 | 52 | <script type="module">
|
32 |
| - // Initialize state |
| 53 | + // State initialization |
33 | 54 | const state = {
|
34 |
| - flips: { heads: 0, tails: 0 }, |
35 |
| - histogram: Array(11).fill(0), |
| 55 | + flips: { heads: 0, tails: 0 }, // Only current trial |
| 56 | + histogram: Array(11).fill(0), // Counts of heads (0-10) across trials |
36 | 57 | isPlaying: false,
|
37 | 58 | playInterval: null,
|
38 |
| - probability: 0.5 |
| 59 | + probability: 0.5, |
| 60 | + totalTrials: 0, |
| 61 | + maxTrials: 1000 |
39 | 62 | };
|
40 | 63 |
|
41 | 64 | // Create UI controls
|
42 | 65 | const controlsDiv = document.getElementById('controls');
|
43 | 66 | controlsDiv.innerHTML = `
|
44 |
| - <div id="probabilityControls"> |
45 |
| - <label for="probabilitySelect">Select Probability: </label> |
46 |
| - <select id="probabilitySelect"> |
47 |
| - ${[...Array(9).keys()].map(i => { |
| 67 | + <div class="controls-row"> |
| 68 | + <div id="probabilityControls"> |
| 69 | + <label for="probabilitySelect">Select Probability: </label> |
| 70 | + <select id="probabilitySelect"> |
| 71 | + ${[...Array(9).keys()].map(i => { |
48 | 72 | const prob = (i + 1) / 10;
|
49 | 73 | return `<option value="${prob}" ${prob === 0.5 ? 'selected' : ''}>${prob}</option>`;
|
50 | 74 | }).join('')}
|
51 |
| - </select> |
| 75 | + </select> |
| 76 | + </div> |
| 77 | + </div> |
| 78 | + <div class="controls-row"> |
| 79 | + <button id="flipButton">Flip once</button> |
| 80 | + <button id="playButton">Play</button> |
| 81 | + <button id="resetButton">Reset</button> |
| 82 | + <label> |
| 83 | + <input type="checkbox" id="multiFlipToggle"> Multi-flip |
| 84 | + </label> |
| 85 | + <span id="flipCount">Total flips: 0/10</span> |
| 86 | + <span id="trialCount">Trials: 0/1000</span> |
| 87 | + </div> |
| 88 | + <div class="controls-row"> |
| 89 | + <button id="completeButton">Complete 1000 trials</button> |
52 | 90 | </div>
|
53 |
| - <button id="flipButton">Flip once</button> |
54 |
| - <button id="playButton">Play</button> |
55 |
| - <button id="resetButton">Reset</button> |
56 |
| - <label> |
57 |
| - <input type="checkbox" id="multiFlipToggle"> Multi-flip |
58 |
| - </label> |
59 |
| - <span id="flipCount">Total flips: 0/10</span> |
60 | 91 | `;
|
61 | 92 |
|
62 | 93 | // Add event listeners
|
|
66 | 97 | const multiFlipToggle = document.getElementById('multiFlipToggle');
|
67 | 98 | const flipCountSpan = document.getElementById('flipCount');
|
68 | 99 | const probabilitySelect = document.getElementById('probabilitySelect');
|
| 100 | + const completeButton = document.getElementById('completeButton'); |
| 101 | + const trialCountSpan = document.getElementById('trialCount'); |
| 102 | + const progressContainer = document.querySelector('.progress-container'); |
| 103 | + const progressBar = document.querySelector('.progress-bar'); |
69 | 104 |
|
70 | 105 | probabilitySelect.addEventListener('change', (event) => {
|
71 | 106 | const newProbability = parseFloat(event.target.value);
|
|
120 | 155 | state.isPlaying = false;
|
121 | 156 | state.flips = { heads: 0, tails: 0 };
|
122 | 157 | state.histogram = Array(11).fill(0);
|
| 158 | + state.totalTrials = 0; |
| 159 | + flipButton.disabled = false; |
| 160 | + playButton.disabled = false; |
| 161 | + completeButton.disabled = false; |
| 162 | + playButton.textContent = 'Play'; |
123 | 163 | updateHistogram();
|
| 164 | + updateCurrentRound(); |
| 165 | + updateTrialCount(); |
124 | 166 | flipCountSpan.textContent = 'Total flips: 0/10';
|
125 |
| - playButton.textContent = 'Play'; |
| 167 | + progressContainer.style.display = 'none'; |
| 168 | + progressBar.style.width = '0%'; |
126 | 169 | });
|
127 | 170 |
|
128 | 171 | multiFlipToggle.addEventListener('change', () => {
|
|
134 | 177 | }
|
135 | 178 | });
|
136 | 179 |
|
| 180 | + completeButton.addEventListener('click', () => { |
| 181 | + completeTo1000(); |
| 182 | + }); |
| 183 | + |
137 | 184 | // Create initial plots
|
138 | 185 | const currentRoundTrace = {
|
139 | 186 | x: ['Heads', 'Tails'],
|
|
161 | 208 | }
|
162 | 209 | };
|
163 | 210 |
|
164 |
| - Plotly.newPlot('currentRound', [currentRoundTrace], currentRoundLayout); |
| 211 | + const plotConfig = { |
| 212 | + displayModeBar: false |
| 213 | + }; |
| 214 | + |
| 215 | + Plotly.newPlot('currentRound', [currentRoundTrace], currentRoundLayout, plotConfig); |
165 | 216 |
|
166 | 217 | const histogramTrace = {
|
167 | 218 | x: Array.from({ length: 11 }, (_, i) => i),
|
|
172 | 223 | }
|
173 | 224 | };
|
174 | 225 |
|
| 226 | + // Update initial histogram layout |
175 | 227 | const histogramLayout = {
|
176 | 228 | title: 'Distribution of Heads (Across Completed Rounds)',
|
177 | 229 | xaxis: {
|
|
194 | 246 | }
|
195 | 247 | };
|
196 | 248 |
|
197 |
| - Plotly.newPlot('histogram', [histogramTrace], histogramLayout); |
| 249 | + Plotly.newPlot('histogram', [histogramTrace], histogramLayout, plotConfig); |
198 | 250 |
|
199 | 251 | // Helper function for single flip
|
200 | 252 | function singleFlip() {
|
|
209 | 261 | }
|
210 | 262 | }
|
211 | 263 |
|
212 |
| - // Helper function for multi flip |
| 264 | + // Optimized multiFlip function |
213 | 265 | function multiFlip() {
|
214 | 266 | let newHeads = 0;
|
| 267 | + // Single trial of 10 flips |
215 | 268 | for (let i = 0; i < 10; i++) {
|
216 | 269 | if (Math.random() < state.probability) newHeads++;
|
217 | 270 | }
|
218 |
| - state.flips = { heads: newHeads, tails: 10 - newHeads }; |
| 271 | + // Update histogram directly |
219 | 272 | state.histogram[newHeads]++;
|
| 273 | + state.totalTrials++; |
| 274 | + |
| 275 | + // Update display for current trial |
| 276 | + state.flips = { heads: newHeads, tails: 10 - newHeads }; |
220 | 277 | updateCurrentRound();
|
221 | 278 | updateHistogram();
|
222 | 279 | state.flips = { heads: 0, tails: 0 };
|
| 280 | + |
| 281 | + // Check if max trials reached |
| 282 | + if (state.totalTrials >= state.maxTrials) { |
| 283 | + disableButtons(); |
| 284 | + } |
| 285 | + } |
| 286 | + |
| 287 | + // Optimized completeTo1000 function |
| 288 | + function completeTo1000() { |
| 289 | + const startTrials = state.totalTrials; |
| 290 | + const remaining = state.maxTrials - startTrials; |
| 291 | + progressContainer.style.display = 'block'; |
| 292 | + |
| 293 | + function step(timestamp) { |
| 294 | + const batchSize = 50; // Increased batch size |
| 295 | + for (let i = 0; i < batchSize && state.totalTrials < state.maxTrials; i++) { |
| 296 | + let newHeads = 0; |
| 297 | + for (let j = 0; j < 10; j++) { |
| 298 | + if (Math.random() < state.probability) newHeads++; |
| 299 | + } |
| 300 | + state.histogram[newHeads]++; |
| 301 | + state.totalTrials++; |
| 302 | + } |
| 303 | + |
| 304 | + const progress = ((state.totalTrials - startTrials) / remaining) * 100; |
| 305 | + progressBar.style.width = `${progress}%`; |
| 306 | + updateHistogram(); |
| 307 | + updateTrialCount(); |
| 308 | + |
| 309 | + if (state.totalTrials < state.maxTrials) { |
| 310 | + requestAnimationFrame(step); |
| 311 | + } else { |
| 312 | + progressContainer.style.display = 'none'; |
| 313 | + disableButtons(); |
| 314 | + } |
| 315 | + } |
| 316 | + |
| 317 | + requestAnimationFrame(step); |
| 318 | + } |
| 319 | + |
| 320 | + // Add button state management |
| 321 | + function disableButtons() { |
| 322 | + flipButton.disabled = true; |
| 323 | + playButton.disabled = true; |
| 324 | + completeButton.disabled = true; |
223 | 325 | }
|
224 | 326 |
|
225 | 327 | // Update functions
|
|
234 | 336 | }
|
235 | 337 | }
|
236 | 338 |
|
| 339 | + // Update the updateHistogram function |
237 | 340 | function updateHistogram() {
|
| 341 | + const maxY = Math.max(...state.histogram); |
238 | 342 | const update = {
|
239 | 343 | y: [state.histogram]
|
240 | 344 | };
|
241 |
| - Plotly.update('histogram', update); |
| 345 | + const layout = { |
| 346 | + 'yaxis.dtick': maxY > 10 ? 10 : 1 |
| 347 | + }; |
| 348 | + Plotly.update('histogram', update, layout); |
| 349 | + } |
| 350 | + |
| 351 | + function updateTrialCount() { |
| 352 | + trialCountSpan.textContent = `Trials: ${state.totalTrials}/${state.maxTrials}`; |
| 353 | + } |
| 354 | + |
| 355 | + function updateProgress(progress) { |
| 356 | + progressBar.style.width = `${progress}%`; |
242 | 357 | }
|
243 | 358 | </script>
|
244 | 359 | </body>
|
|
0 commit comments