dwellnick commited on
Commit
c91594a
·
verified ·
1 Parent(s): 8a727ba

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +716 -579
index.html CHANGED
@@ -1,192 +1,321 @@
1
  <!DOCTYPE html>
2
- <html lang="ru">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Анализ оценок студентов - ОП</title>
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
  <style>
10
  :root {
11
- --primary-color: #4285F4;
12
- --google-blue: #1A73E8;
13
- --google-red: #D93025;
14
- --google-yellow: #F4B400;
15
- --google-green: #0F9D58;
16
- --light-gray: #f5f5f5;
17
- --border-gray: #dadce0;
18
- --text-dark: #202124;
19
- --text-medium: #5f6368;
20
- --text-light: #80868b;
21
  }
22
 
23
  * {
24
  box-sizing: border-box;
25
  margin: 0;
26
  padding: 0;
27
- font-family: 'Product Sans', 'Roboto', Arial, sans-serif;
28
  }
29
 
30
  body {
31
- background-color: var(--light-gray);
32
- color: var(--text-dark);
33
  line-height: 1.6;
34
  }
35
 
36
  .container {
37
- max-width: 1200px;
38
  margin: 0 auto;
39
  padding: 20px;
40
  }
41
 
42
  header {
43
- display: flex;
44
- align-items: center;
45
- padding: 16px 0;
46
- margin-bottom: 24px;
47
- border-bottom: 1px solid var(--border-gray);
 
 
48
  }
49
 
50
- .logo {
51
- display: flex;
52
- align-items: center;
53
- margin-right: 24px;
54
  }
55
 
56
- .logo-icon {
57
- font-size: 24px;
58
- color: var(--google-blue);
59
- margin-right: 8px;
60
  }
61
 
62
- .logo-text {
63
- font-size: 22px;
64
- font-weight: 500;
65
- color: var(--text-dark);
66
  }
67
 
68
- .card {
69
  background-color: white;
70
  border-radius: 8px;
71
- box-shadow: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);
72
- padding: 24px;
73
- margin-bottom: 24px;
74
  }
75
 
76
- .section-title {
77
- font-size: 20px;
78
- font-weight: 500;
 
79
  margin-bottom: 20px;
80
- color: var(--google-blue);
81
  display: flex;
 
82
  align-items: center;
83
  }
84
 
85
- .section-title i {
86
- margin-right: 10px;
 
 
 
 
 
87
  }
88
 
89
- .grid-container {
90
- display: grid;
91
- grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
92
- gap: 20px;
93
- margin-bottom: 20px;
94
  }
95
 
96
- .chart-container {
97
- position: relative;
98
- height: 400px;
99
- background: white;
100
- padding: 20px;
101
- border-radius: 8px;
102
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
103
  }
104
 
105
- .stats-container {
106
  display: grid;
107
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
108
- gap: 15px;
109
- margin-bottom: 30px;
110
  }
111
 
112
- .stat-card {
113
- background: white;
114
  padding: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  border-radius: 8px;
116
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
 
117
  text-align: center;
118
  }
119
 
120
- .stat-value {
121
- font-size: 28px;
122
- font-weight: bold;
123
- color: var(--google-blue);
124
- margin: 10px 0;
125
  }
126
 
127
- .stat-label {
128
- font-size: 14px;
129
- color: var(--text-medium);
130
  }
131
 
132
- .table-container {
133
- overflow-x: auto;
134
- margin-bottom: 20px;
135
  }
136
 
137
- .data-table {
138
- width: 100%;
139
- border-collapse: collapse;
140
  }
141
 
142
- .data-table th {
143
- background-color: var(--light-gray);
144
- padding: 12px 15px;
145
- text-align: left;
146
- font-weight: 500;
147
  }
148
 
149
- .data-table td {
150
- padding: 10px 15px;
151
- border-bottom: 1px solid var(--border-gray);
152
  }
153
 
154
- .data-table tr:hover {
155
- background-color: rgba(66, 133, 244, 0.05);
 
 
156
  }
157
 
158
- .loading {
159
- display: flex;
160
- justify-content: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  align-items: center;
162
- height: 200px;
163
  }
164
 
165
- .loading-spinner {
166
- width: 40px;
167
- height: 40px;
168
- border: 3px solid rgba(66, 133, 244, 0.2);
169
- border-radius: 50%;
170
- border-top-color: var(--google-blue);
171
- animation: spin 1s ease-in-out infinite;
 
 
 
 
 
 
 
 
 
172
  }
173
 
174
- @keyframes spin {
175
- to { transform: rotate(360deg); }
 
 
176
  }
177
 
178
- .excellent { background-color: rgba(15, 157, 88, 0.1); }
179
- .good { background-color: rgba(244, 180, 0, 0.1); }
180
- .average { background-color: rgba(217, 48, 37, 0.1); }
181
- .poor { background-color: rgba(217, 48, 37, 0.2); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
  @media (max-width: 768px) {
184
- .grid-container {
185
- grid-template-columns: 1fr;
186
  }
187
-
188
- .stats-container {
189
- grid-template-columns: 1fr 1fr;
 
 
 
 
 
 
 
 
190
  }
191
  }
192
  </style>
@@ -194,524 +323,532 @@
194
  <body>
195
  <div class="container">
196
  <header>
197
- <div class="logo">
198
- <i class="fas fa-chart-line logo-icon"></i>
199
- <span class="logo-text">Анализ оценок студентов</span>
200
  </div>
201
- <span>Данные из таблицы "ОП" - Оценки за домашние задания</span>
 
202
  </header>
203
 
204
- <div class="card">
205
- <div class="section-title">
206
- <i class="fas fa-chart-pie"></i>
207
- Статистика оценок
 
 
208
  </div>
209
-
210
- <div class="stats-container" id="statsContainer">
211
- <div class="loading" id="loadingIndicator">
212
- <div class="loading-spinner"></div>
 
 
 
 
213
  </div>
 
214
  </div>
215
-
216
- <div class="section-title">
217
- <i class="fas fa-chart-bar"></i>
218
- Визуализация данных
 
 
 
 
 
 
 
219
  </div>
220
-
221
- <div class="grid-container">
222
- <div class="chart-container">
223
- <canvas id="distributionChart"></canvas>
224
- </div>
225
- <div class="chart-container">
226
- <canvas id="histogramChart"></canvas>
227
- </div>
228
- <div class="chart-container">
229
- <canvas id="pieChart"></canvas>
230
- </div>
231
- <div class="chart-container">
232
- <canvas id="boxPlotChart"></canvas>
233
- </div>
234
  </div>
 
 
 
235
 
236
- <div class="section-title">
237
- <i class="fas fa-table"></i>
238
- Данные студентов
 
 
239
  </div>
240
 
241
- <div class="table-container">
242
- <table class="data-table" id="studentTable">
243
- <thead>
244
- <tr>
245
- <th>№</th>
246
- <th>ФИО</th>
247
- <th>Группа</th>
248
- <th>ДЗ (из 2800)</th>
249
- <th>Процент</th>
250
- <th>Категория</th>
251
- </tr>
252
- </thead>
253
- <tbody id="studentTableBody">
254
- <!-- Filled by JavaScript -->
255
- </tbody>
256
- </table>
257
- </div>
258
  </div>
259
  </div>
260
 
261
  <script>
262
- // Mock data based on the structure of the actual Google Sheet
263
- // In a real implementation, you would fetch this from the Google Sheets API
264
- const mockStudentData = [
265
- { id: 1, name: "Иванов Иван Иванович", group: "Группа 1", dz: 2450 },
266
- { id: 2, name: "Петров Петр Петрович", group: "Группа 1", dz: 2100 },
267
- { id: 3, name: "Сидорова Мария Сергеевна", group: "Группа 2", dz: 1960 },
268
- { id: 4, name: "Кузнецов Алексей Дмитриевич", group: "Группа 2", dz: 1780 },
269
- { id: 5, name: "Смирнова Екатерина Викторовна", group: "Группа 3", dz: 2650 },
270
- { id: 6, name: "Васильев Денис Олегович", group: "Группа 3", dz: 1550 },
271
- { id: 7, name: "Николаева Анна Павловна", group: "Группа 1", dz: 2250 },
272
- { id: 8, name: "Михайлов Артем Игоревич", group: "Группа 4", dz: 1980 },
273
- { id: 9, name: "Федорова Ольга Николаевна", group: "Группа 4", dz: 2400 },
274
- { id: 10, name: "Алексеев Сергей Владимирович", group: "Группа 5", dz: 1750 },
275
- { id: 11, name: "Дмитриева Татьяна Александровна", group: "Группа 5", dz: 2100 },
276
- { id: 12, name: "Андреев Максим Юрьевич", group: "Группа 5", dz: 1900 },
277
- { id: 13, name: "Егорова Виктория Алексеевна", group: "Группа 1", dz: 2300 },
278
- { id: 14, name: "Григорьева Наталья Сергеевна", group: "Группа 3", dz: 1680 },
279
- { id: 15, name: "Семенов Дмитрий Петрович", group: "Группа 4", dz: 1950 },
280
- { id: 16, name: "Павлов Антон Викторович", group: "Группа 2", dz: 2500 },
281
- { id: 17, name: "Романова Елена Дмитриевна", group: "Группа 3", dz: 2350 },
282
- { id: 18, name: "Козлов Илья Сергеевич", group: "Группа 4", dz: 2050 },
283
- { id: 19, name: "Орлова Анастасия Андреевна", group: "Группа 5", dz: 1850 },
284
- { id: 20, name: "Лебедев Владислав Игоревич", group: "Группа 2", dz: 1720 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  ];
286
 
287
- // Constants
288
- const maxScore = 2800;
289
- const gradeCategories = [
290
- { name: "Отлично", minPercent: 85, color: "#0F9D58", colorLight: "rgba(15, 157, 88, 0.1)" },
291
- { name: "Хорошо", minPercent: 70, color: "#F4B400", colorLight: "rgba(244, 180, 0, 0.1)" },
292
- { name: "Удовлетворительно", minPercent: 50, color: "#FF9800", colorLight: "rgba(255, 152, 0, 0.1)" },
293
- { name: "Неудовлетворительно", minPercent: 0, color: "#D93025", colorLight: "rgba(217, 48, 37, 0.1)" }
294
- ];
295
 
296
  // DOM elements
297
- const loadingIndicator = document.getElementById('loadingIndicator');
298
- const statsContainer = document.getElementById('statsContainer');
299
- const studentTableBody = document.getElementById('studentTableBody');
300
- const distributionCtx = document.getElementById('distributionChart').getContext('2d');
301
- const histogramCtx = document.getElementById('histogramChart').getContext('2d');
302
- const pieCtx = document.getElementById('pieChart').getContext('2d');
303
- const boxPlotCtx = document.getElementById('boxPlotChart').getContext('2d');
304
-
305
- // Initialize the application
306
- function init() {
307
- // Simulate loading delay
308
- setTimeout(() => {
309
- // Process student data
310
- const processedStudents = processStudentData(mockStudentData);
311
-
312
- // Calculate statistics
313
- const stats = calculateStatistics(processedStudents);
314
-
315
- // Display statistics
316
- displayStatistics(stats);
317
-
318
- // Display student table
319
- displayStudentTable(processedStudents);
320
-
321
- // Create charts
322
- createCharts(processedStudents, stats);
323
-
324
- // Hide loading indicator
325
- loadingIndicator.style.display = 'none';
326
- }, 1000);
327
- }
328
-
329
- // Process student data to add percentage and category
330
- function processStudentData(students) {
331
- return students.map(student => {
332
- const percentage = Math.round((student.dz / maxScore) * 100);
333
- let category = "";
334
-
335
- // Determine grade category
336
- for (const cat of gradeCategories) {
337
- if (percentage >= cat.minPercent) {
338
- category = cat.name;
339
- break;
340
- }
341
  }
342
 
343
- return {
344
- ...student,
345
- percentage,
346
- category
347
- };
 
 
 
 
 
 
 
 
 
 
 
348
  });
 
 
 
 
 
 
 
349
  }
350
 
351
- // Calculate statistical measures
352
- function calculateStatistics(students) {
353
- const dzScores = students.map(s => s.dz);
354
- const percentages = students.map(s => s.percentage);
355
-
356
- // Basic statistics
357
- const count = dzScores.length;
358
- const min = Math.min(...dzScores);
359
- const max = Math.max(...dzScores);
360
- const sum = dzScores.reduce((a, b) => a + b, 0);
361
- const mean = Math.round(sum / count);
362
- const meanPerc = Math.round(mean / maxScore * 100);
363
-
364
- // Median
365
- const sorted = [...dzScores].sort((a, b) => a - b);
366
- const median = sorted.length % 2 === 0
367
- ? (sorted[sorted.length/2 - 1] + sorted[sorted.length/2]) / 2
368
- : sorted[Math.floor(sorted.length/2)];
369
- const medianPerc = Math.round(median / maxScore * 100);
370
-
371
- // Standard deviation
372
- const squaredDiffs = dzScores.map(score => Math.pow(score - mean, 2));
373
- const variance = squaredDiffs.reduce((a, b) => a + b, 0) / count;
374
- const stdDev = Math.round(Math.sqrt(variance));
375
-
376
- // Quartiles
377
- const q1 = percentile(sorted, 25);
378
- const q3 = percentile(sorted, 75);
379
- const iqr = q3 - q1;
380
-
381
- // Grade category counts
382
- const gradeCounts = {};
383
- gradeCategories.forEach(cat => {
384
- gradeCounts[cat.name] = students.filter(s => s.category === cat.name).length;
385
  });
 
 
 
 
386
 
387
- return {
388
- count, min, max, mean, meanPerc, median, medianPerc,
389
- stdDev, q1, q3, iqr, gradeCounts
390
- };
391
- }
392
-
393
- // Helper function to calculate percentiles
394
- function percentile(arr, p) {
395
- const index = p * (arr.length - 1);
396
- const lower = Math.floor(index);
397
- const upper = lower + 1;
398
- const weight = index % 1;
399
 
400
- if (upper >= arr.length) return arr[lower];
401
- return arr[lower] * (1 - weight) + arr[upper] * weight;
 
 
 
 
 
 
 
 
402
  }
403
 
404
- // Display statistics cards
405
- function displayStatistics(stats) {
406
- statsContainer.innerHTML = `
407
- <div class="stat-card">
408
- <div class="stat-label">Количество студентов</div>
409
- <div class="stat-value">${stats.count}</div>
410
- </div>
411
- <div class="stat-card">
412
- <div class="stat-label">Средний балл</div>
413
- <div class="stat-value">${stats.mean} (${stats.meanPerc}%)</div>
414
- </div>
415
- <div class="stat-card">
416
- <div class="stat-label">Медианный балл</div>
417
- <div class="stat-value">${stats.median} (${stats.medianPerc}%)</div>
418
- </div>
419
- <div class="stat-card">
420
- <div class="stat-label">Стандартное отклонение</div>
421
- <div class="stat-value">${stats.stdDev}</div>
422
- </div>
423
- <div class="stat-card">
424
- <div class="stat-label">Минимальный балл</div>
425
- <div class="stat-value">${stats.min}</div>
426
- </div>
427
- <div class="stat-card">
428
- <div class="stat-label">Максимальный балл</div>
429
- <div class="stat-value">${stats.max}</div>
430
- </div>
431
- <div class="stat-card">
432
- <div class="stat-label">1-й квартиль (Q1)</div>
433
- <div class="stat-value">${stats.q1}</div>
434
- </div>
435
- <div class="stat-card">
436
- <div class="stat-label">3-й квартиль (Q3)</div>
437
- <div class="stat-value">${stats.q3}</div>
438
- </div>
439
- `;
440
  }
441
 
442
- // Display student table
443
- function displayStudentTable(students) {
444
- studentTableBody.innerHTML = '';
 
 
 
 
 
445
 
446
- students.forEach(student => {
447
- const row = document.createElement('tr');
448
-
449
- // Apply CSS class based on grade category
450
- for (const cat of gradeCategories) {
451
- if (student.category === cat.name) {
452
- if (cat.name === "Отлично") row.classList.add("excellent");
453
- else if (cat.name === "Хорошо") row.classList.add("good");
454
- else if (cat.name === "Удовлетворительно") row.classList.add("average");
455
- else if (cat.name === "Неудовлетворительно") row.classList.add("poor");
456
- break;
457
- }
458
- }
459
-
460
- row.innerHTML = `
461
- <td>${student.id}</td>
462
- <td>${student.name}</td>
463
- <td>${student.group}</td>
464
- <td>${student.dz}</td>
465
- <td>${student.percentage}%</td>
466
- <td>${student.category}</td>
467
- `;
468
-
469
- studentTableBody.appendChild(row);
470
- });
471
  }
472
 
473
- // Create all charts
474
- function createCharts(students, stats) {
475
- createDistributionChart(students);
476
- createHistogramChart(students);
477
- createPieChart(students, stats);
478
- createBoxPlotChart(stats);
 
 
479
  }
480
 
481
- // Create distribution chart
482
- function createDistributionChart(students) {
483
- const scores = students.map(s => s.dz);
484
- const percentages = students.map(s => s.percentage);
 
485
 
486
- new Chart(distributionCtx, {
487
- type: 'scatter',
488
- data: {
489
- datasets: [{
490
- label: 'Баллы студентов',
491
- data: students.map(s => ({ x: s.id, y: s.dz })),
492
- backgroundColor: students.map(s => {
493
- for (const cat of gradeCategories) {
494
- if (s.category === cat.name) return cat.color;
495
- }
496
- return '#4285F4';
497
- }),
498
- pointRadius: 6,
499
- pointHoverRadius: 8
500
- }, {
501
- label: 'Средний балл',
502
- data: [{ x: 1, y: stats.mean }, { x: students.length, y: stats.mean }],
503
- type: 'line',
504
- borderColor: '#EA4335',
505
- borderWidth: 2,
506
- borderDash: [5, 5],
507
- pointRadius: 0,
508
- fill: false,
509
- showLine: true
510
- }]
511
- },
512
- options: {
513
- responsive: true,
514
- maintainAspectRatio: false,
515
- scales: {
516
- x: {
517
- title: {
518
- display: true,
519
- text: '№ студента'
520
- }
521
- },
522
- y: {
523
- title: {
524
- display: true,
525
- text: 'Баллы (из 2800)'
526
- },
527
- min: 0,
528
- max: 2800
529
- }
530
- },
531
- plugins: {
532
- title: {
533
- display: true,
534
- text: 'Распределение оценок студентов'
535
- },
536
- tooltip: {
537
- callbacks: {
538
- label: function(context) {
539
- const student = students[context.parsed.x - 1];
540
- return `${student.name}: ${student.dz} (${student.percentage}%) - ${student.category}`;
541
- }
542
- }
543
- }
544
- }
545
  }
546
  });
547
- }
548
-
549
- // Create histogram chart
550
- function createHistogramChart(students) {
551
- const step = 200;
552
- const bins = {};
553
 
554
- // Create bins from 0 to 2800 with step=200
555
- for (let i = 0; i <= maxScore; i += step) {
556
- bins[`${i}-${i+step-1}`] = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  }
558
 
559
- // Count students in each bin
560
- students.forEach(student => {
561
- const binKey = Object.keys(bins).find(key => {
562
- const [min, max] = key.split('-').map(Number);
563
- return student.dz >= min && student.dz <= max;
564
- });
565
- if (binKey) bins[binKey]++;
566
- });
567
 
568
- new Chart(histogramCtx, {
569
- type: 'bar',
570
- data: {
571
- labels: Object.keys(bins),
572
- datasets: [{
573
- label: 'Количество студентов',
574
- data: Object.values(bins),
575
- backgroundColor: '#4285F4',
576
- borderColor: '#1A73E8',
577
- borderWidth: 1
578
- }]
579
- },
580
- options: {
581
- responsive: true,
582
- maintainAspectRatio: false,
583
- scales: {
584
- x: {
585
- title: {
586
- display: true,
587
- text: 'Диапазон баллов'
588
- }
589
- },
590
- y: {
591
- title: {
592
- display: true,
593
- text: 'Количество студентов'
594
- },
595
- beginAtZero: true
596
- }
597
- },
598
- plugins: {
599
- title: {
600
- display: true,
601
- text: 'Гистограмма распределения оценок'
602
- }
603
- }
604
- }
605
- });
606
  }
607
 
608
- // Create pie chart of grade categories
609
- function createPieChart(students, stats) {
610
- const data = {
611
- labels: Object.keys(stats.gradeCounts),
612
- datasets: [{
613
- data: Object.values(stats.gradeCounts),
614
- backgroundColor: gradeCategories.map(cat => cat.color),
615
- borderWidth: 1
616
- }]
617
- };
618
 
619
- new Chart(pieCtx, {
620
- type: 'pie',
621
- data: data,
622
- options: {
623
- responsive: true,
624
- maintainAspectRatio: false,
625
- plugins: {
626
- title: {
627
- display: true,
628
- text: 'Распределение по категориям оценок'
629
- },
630
- legend: {
631
- position: 'right'
632
- },
633
- tooltip: {
634
- callbacks: {
635
- label: function(context) {
636
- const label = context.label;
637
- const value = context.raw;
638
- const percentage = Math.round((value / stats.count) * 100);
639
- return `${label}: ${value} студентов (${percentage}%)`;
640
- }
641
- }
642
- }
643
- }
644
- }
645
- });
646
  }
647
 
648
- // Create box plot chart
649
- function createBoxPlotChart(stats) {
650
- new Chart(boxPlotCtx, {
651
- type: 'boxplot',
652
- data: {
653
- labels: ['Оценки за ДЗ'],
654
- datasets: [{
655
- label: 'Статистика оценок',
656
- data: [{
657
- min: stats.min,
658
- q1: stats.q1,
659
- median: stats.median,
660
- q3: stats.q3,
661
- max: stats.max
662
- }],
663
- backgroundColor: 'rgba(66, 133, 244, 0.2)',
664
- borderColor: '#4285F4',
665
- borderWidth: 2,
666
- outlierColor: '#EA4335',
667
- padding: 10,
668
- itemRadius: 0
669
- }]
670
- },
671
- options: {
672
- responsive: true,
673
- maintainAspectRatio: false,
674
- scales: {
675
- y: {
676
- title: {
677
- display: true,
678
- text: 'Баллы (из 2800)'
679
- },
680
- min: 0,
681
- max: 2800
682
- }
683
- },
684
- plugins: {
685
- title: {
686
- display: true,
687
- text: 'Box plot оценок студентов'
688
- },
689
- tooltip: {
690
- callbacks: {
691
- beforeLabel: function(context) {
692
- return 'Статистика оценок:';
693
- },
694
- label: function(context) {
695
- const data = context.raw;
696
- return [
697
- `Минимум: ${data.min}`,
698
- `Q1: ${data.q1}`,
699
- `Медиана: ${data.median}`,
700
- `Q3: ${data.q3}`,
701
- `Максимум: ${data.max}`,
702
- `Диапазон: ${data.max - data.min}`,
703
- `IQR: ${data.q3 - data.q1}`
704
- ];
705
- }
706
- }
707
- }
708
- }
709
- }
710
- });
711
- }
712
 
713
- // Initialize the application when the page loads
714
- document.addEventListener('DOMContentLoaded', init);
715
  </script>
716
  </body>
717
- </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>Python Knowledge Test</title>
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
8
  <style>
9
  :root {
10
+ --primary-color: #3776ab;
11
+ --secondary-color: #ffd43b;
12
+ --dark-color: #306998;
13
+ --light-color: #f8f9fa;
14
+ --success-color: #28a745;
15
+ --danger-color: #dc3545;
16
+ --warning-color: #ffc107;
17
+ --info-color: #17a2b8;
 
 
18
  }
19
 
20
  * {
21
  box-sizing: border-box;
22
  margin: 0;
23
  padding: 0;
24
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
  }
26
 
27
  body {
28
+ background-color: #f5f5f5;
29
+ color: #333;
30
  line-height: 1.6;
31
  }
32
 
33
  .container {
34
+ max-width: 800px;
35
  margin: 0 auto;
36
  padding: 20px;
37
  }
38
 
39
  header {
40
+ background-color: var(--primary-color);
41
+ color: white;
42
+ padding: 20px 0;
43
+ text-align: center;
44
+ border-radius: 8px 8px 0 0;
45
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
46
+ margin-bottom: 2rem;
47
  }
48
 
49
+ header h1 {
50
+ font-size: 2.5rem;
51
+ margin-bottom: 10px;
 
52
  }
53
 
54
+ header p {
55
+ font-size: 1.1rem;
56
+ opacity: 0.9;
 
57
  }
58
 
59
+ .python-icon {
60
+ font-size: 3rem;
61
+ margin-bottom: 10px;
62
+ color: var(--secondary-color);
63
  }
64
 
65
+ .quiz-container {
66
  background-color: white;
67
  border-radius: 8px;
68
+ padding: 30px;
69
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
70
+ margin-bottom: 2rem;
71
  }
72
 
73
+ .question-counter {
74
+ font-size: 1.1rem;
75
+ font-weight: bold;
76
+ color: var(--primary-color);
77
  margin-bottom: 20px;
 
78
  display: flex;
79
+ justify-content: space-between;
80
  align-items: center;
81
  }
82
 
83
+ .progress-bar {
84
+ height: 8px;
85
+ background-color: #e9ecef;
86
+ border-radius: 4px;
87
+ overflow: hidden;
88
+ flex-grow: 1;
89
+ margin-left: 20px;
90
  }
91
 
92
+ .progress {
93
+ height: 100%;
94
+ background-color: var(--primary-color);
95
+ width: 0%;
96
+ transition: width 0.3s ease;
97
  }
98
 
99
+ .question {
100
+ font-size: 1.2rem;
101
+ font-weight: 500;
102
+ margin-bottom: 1.5rem;
 
 
 
103
  }
104
 
105
+ .options {
106
  display: grid;
107
+ grid-template-columns: 1fr;
108
+ gap: 12px;
109
+ margin-bottom: 1.5rem;
110
  }
111
 
112
+ .option {
113
+ background-color: var(--light-color);
114
  padding: 15px;
115
+ border-radius: 6px;
116
+ cursor: pointer;
117
+ transition: all 0.3s ease;
118
+ border: 2px solid transparent;
119
+ display: flex;
120
+ align-items: center;
121
+ }
122
+
123
+ .option:hover {
124
+ background-color: #e2e6ea;
125
+ transform: translateY(-2px);
126
+ }
127
+
128
+ .option.selected {
129
+ background-color: #e3f2fd;
130
+ border-color: var(--primary-color);
131
+ }
132
+
133
+ .option.correct {
134
+ background-color: #d4edda;
135
+ border-color: var(--success-color);
136
+ }
137
+
138
+ .option.incorrect {
139
+ background-color: #f8d7da;
140
+ border-color: var(--danger-color);
141
+ }
142
+
143
+ .option input {
144
+ margin-right: 10px;
145
+ cursor: pointer;
146
+ }
147
+
148
+ .option-label {
149
+ font-weight: 500;
150
+ flex-grow: 1;
151
+ }
152
+
153
+ .navigation {
154
+ display: flex;
155
+ justify-content: space-between;
156
+ margin-top: 2rem;
157
+ }
158
+
159
+ .btn {
160
+ padding: 12px 24px;
161
+ border: none;
162
+ border-radius: 6px;
163
+ cursor: pointer;
164
+ font-weight: 600;
165
+ transition: all 0.3s ease;
166
+ display: inline-flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ }
170
+
171
+ .btn-primary {
172
+ background-color: var(--primary-color);
173
+ color: white;
174
+ }
175
+
176
+ .btn-primary:hover {
177
+ background-color: var(--dark-color);
178
+ transform: translateY(-2px);
179
+ }
180
+
181
+ .btn-secondary {
182
+ background-color: #6c757d;
183
+ color: white;
184
+ }
185
+
186
+ .btn-secondary:hover {
187
+ background-color: #5a6268;
188
+ transform: translateY(-2px);
189
+ }
190
+
191
+ .btn:disabled {
192
+ opacity: 0.5;
193
+ cursor: not-allowed;
194
+ transform: none !important;
195
+ }
196
+
197
+ .result-container {
198
+ display: none;
199
+ background-color: white;
200
  border-radius: 8px;
201
+ padding: 30px;
202
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
203
  text-align: center;
204
  }
205
 
206
+ .result-icon {
207
+ font-size: 5rem;
208
+ margin-bottom: 20px;
 
 
209
  }
210
 
211
+ .excellent {
212
+ color: var(--success-color);
 
213
  }
214
 
215
+ .good {
216
+ color: var(--primary-color);
 
217
  }
218
 
219
+ .average {
220
+ color: var(--warning-color);
 
221
  }
222
 
223
+ .poor {
224
+ color: var(--danger-color);
 
 
 
225
  }
226
 
227
+ h2 {
228
+ margin-bottom: 20px;
229
+ color: var(--primary-color);
230
  }
231
 
232
+ .score {
233
+ font-size: 3rem;
234
+ font-weight: bold;
235
+ margin-bottom: 20px;
236
  }
237
 
238
+ .assessment {
239
+ font-size: 1.2rem;
240
+ margin-bottom: 25px;
241
+ max-width: 600px;
242
+ margin-left: auto;
243
+ margin-right: auto;
244
+ }
245
+
246
+ .restart-btn {
247
+ background-color: var(--primary-color);
248
+ color: white;
249
+ padding: 12px 30px;
250
+ border-radius: 6px;
251
+ border: none;
252
+ font-weight: 600;
253
+ cursor: pointer;
254
+ transition: all 0.3s ease;
255
+ display: inline-flex;
256
  align-items: center;
 
257
  }
258
 
259
+ .restart-btn i {
260
+ margin-right: 8px;
261
+ }
262
+
263
+ .restart-btn:hover {
264
+ background-color: var(--dark-color);
265
+ transform: translateY(-2px);
266
+ }
267
+
268
+ .explanation {
269
+ background-color: #f8f9fa;
270
+ border-left: 4px solid var(--primary-color);
271
+ padding: 15px;
272
+ margin-top: 20px;
273
+ border-radius: 0 6px 6px 0;
274
+ display: none;
275
  }
276
 
277
+ .explanation-title {
278
+ font-weight: bold;
279
+ margin-bottom: 8px;
280
+ color: var(--primary-color);
281
  }
282
 
283
+ .wrong-answers {
284
+ margin-top: 20px;
285
+ text-align: left;
286
+ max-width: 600px;
287
+ margin-left: auto;
288
+ margin-right: auto;
289
+ }
290
+
291
+ .wrong-answers-title {
292
+ font-weight: bold;
293
+ margin-bottom: 10px;
294
+ color: var(--danger-color);
295
+ }
296
+
297
+ .wrong-answer-item {
298
+ margin-bottom: 8px;
299
+ padding: 8px;
300
+ background-color: #f8f9fa;
301
+ border-radius: 4px;
302
+ }
303
 
304
  @media (max-width: 768px) {
305
+ .container {
306
+ padding: 10px;
307
  }
308
+
309
+ header h1 {
310
+ font-size: 2rem;
311
+ }
312
+
313
+ .quiz-container, .result-container {
314
+ padding: 20px;
315
+ }
316
+
317
+ .question {
318
+ font-size: 1.1rem;
319
  }
320
  }
321
  </style>
 
323
  <body>
324
  <div class="container">
325
  <header>
326
+ <div class="python-icon">
327
+ <i class="fab fa-python"></i>
 
328
  </div>
329
+ <h1>Python Knowledge Test</h1>
330
+ <p>Test your Python programming skills with this 20-question quiz</p>
331
  </header>
332
 
333
+ <div class="quiz-container" id="quiz">
334
+ <div class="question-counter">
335
+ <span>Question <span id="current-question">1</span> of 20</span>
336
+ <div class="progress-bar">
337
+ <div class="progress" id="progress"></div>
338
+ </div>
339
  </div>
340
+
341
+ <div class="question" id="question"></div>
342
+
343
+ <div class="options" id="options"></div>
344
+
345
+ <div class="explanation" id="explanation">
346
+ <div class="explanation-title">
347
+ <i class="fas fa-info-circle"></i> Explanation
348
  </div>
349
+ <div id="explanation-text"></div>
350
  </div>
351
+
352
+ <div class="navigation">
353
+ <button class="btn btn-secondary" id="prev-btn" disabled>
354
+ <i class="fas fa-arrow-left"></i> Previous
355
+ </button>
356
+ <button class="btn btn-primary" id="next-btn">
357
+ Next <i class="fas fa-arrow-right"></i>
358
+ </button>
359
+ <button class="btn btn-primary" id="submit-btn" style="display: none;">
360
+ Submit <i class="fas fa-paper-plane"></i>
361
+ </button>
362
  </div>
363
+ </div>
364
+
365
+ <div class="result-container" id="result-container">
366
+ <div class="result-icon" id="result-icon">
367
+ <i class="fas fa-trophy"></i>
 
 
 
 
 
 
 
 
 
368
  </div>
369
+ <h2>Your Results</h2>
370
+ <div class="score" id="score"></div>
371
+ <div class="assessment" id="assessment"></div>
372
 
373
+ <div class="wrong-answers" id="wrong-answers">
374
+ <div class="wrong-answers-title">
375
+ <i class="fas fa-times-circle"></i> Incorrect Answers
376
+ </div>
377
+ <div id="wrong-answers-list"></div>
378
  </div>
379
 
380
+ <button class="restart-btn" id="restart-btn">
381
+ <i class="fas fa-redo"></i> Take Again
382
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  </div>
384
  </div>
385
 
386
  <script>
387
+ // Questions array with options, correct answer, and explanations
388
+ const questions = [
389
+ {
390
+ question: "Which of the following is the correct way to declare a variable in Python?",
391
+ options: [
392
+ "var name = 'John'",
393
+ "name := 'John'",
394
+ "name = 'John'",
395
+ "let name = 'John'"
396
+ ],
397
+ correct: 2,
398
+ explanation: "In Python, variables are declared by simply assigning a value to a name using the equals sign (=). The other syntax shown is used in other languages like JavaScript or more recent Python versions for different purposes."
399
+ },
400
+ {
401
+ question: "What does the 'range()' function return in Python 3?",
402
+ options: [
403
+ "A list of numbers",
404
+ "A tuple of numbers",
405
+ "A range object",
406
+ "A set of numbers"
407
+ ],
408
+ correct: 2,
409
+ explanation: "In Python 3, range() returns an immutable sequence type (range object), not a list. This is more memory efficient as it generates numbers on demand rather than storing them all in memory."
410
+ },
411
+ {
412
+ question: "Which of these collection types in Python is immutable?",
413
+ options: [
414
+ "List",
415
+ "Dictionary",
416
+ "Set",
417
+ "Tuple"
418
+ ],
419
+ correct: 3,
420
+ explanation: "Tuples are immutable in Python, meaning they cannot be changed after creation. Lists, dictionaries, and sets are all mutable types."
421
+ },
422
+ {
423
+ question: "What is the output of '3 * 'ab' + 'ba' in Python?",
424
+ options: [
425
+ "'abababba'",
426
+ "'3abba'",
427
+ "'abababa'",
428
+ "Error"
429
+ ],
430
+ correct: 0,
431
+ explanation: "In Python, multiplying a string by an integer n concatenates the string with itself n times. So 3 * 'ab' becomes 'ababab', then adding 'ba' gives 'abababba'."
432
+ },
433
+ {
434
+ question: "What is the purpose of the '__init__' method in Python classes?",
435
+ options: [
436
+ "To destroy an object",
437
+ "To initialize the object's attributes",
438
+ "To create a new class",
439
+ "To import modules"
440
+ ],
441
+ correct: 1,
442
+ explanation: "The __init__ method is the constructor in Python classes. It's automatically called when an object is created and is used to initialize the object's state (attributes)."
443
+ },
444
+ {
445
+ question: "Which statement is used for exception handling in Python?",
446
+ options: [
447
+ "try-catch",
448
+ "try-except",
449
+ "try-exception",
450
+ "try-handle"
451
+ ],
452
+ correct: 1,
453
+ explanation: "Python uses try-except blocks for exception handling, unlike some other languages that use try-catch. The except block captures and handles exceptions that occur in the try block."
454
+ },
455
+ {
456
+ question: "What is the output of 'bool('False')' in Python?",
457
+ options: [
458
+ "False",
459
+ "True",
460
+ "Error",
461
+ "None"
462
+ ],
463
+ correct: 1,
464
+ explanation: "The bool() function converts values to boolean. Any non-empty string evaluates to True in Python, even if the string is 'False'. Only empty strings evaluate to False."
465
+ },
466
+ {
467
+ question: "Which method would you use to remove an item from a list and return it?",
468
+ options: [
469
+ "remove()",
470
+ "delete()",
471
+ "pop()",
472
+ "discard()"
473
+ ],
474
+ correct: 2,
475
+ explanation: "The pop() method removes and returns an item at the given index (or the last item if no index is specified). remove() deletes the first matching value, delete is a keyword, and discard() is a set method."
476
+ },
477
+ {
478
+ question: "What does the 'pass' statement do in Python?",
479
+ options: [
480
+ "Throws an error",
481
+ "Exits the program",
482
+ "Does nothing, acts as a placeholder",
483
+ "Continues to the next iteration in a loop"
484
+ ],
485
+ correct: 2,
486
+ explanation: "The pass statement is a null operation in Python - it does nothing. It's used when syntax requires a statement but no action is needed, often as a placeholder in empty functions, classes, or conditionals."
487
+ },
488
+ {
489
+ question: "How do you open a file for both reading and writing in Python?",
490
+ options: [
491
+ "open('file.txt', 'r')",
492
+ "open('file.txt', 'w')",
493
+ "open('file.txt', 'rw')",
494
+ "open('file.txt', 'r+')"
495
+ ],
496
+ correct: 3,
497
+ explanation: "The 'r+' mode opens a file for both reading and writing. 'r' is read-only, 'w' is write-only (overwrites file), and 'rw' is not a valid mode."
498
+ },
499
+ {
500
+ question: "What is the output of '[n**2 for n in range(5)]'?",
501
+ options: [
502
+ "[0, 1, 4, 9, 16]",
503
+ "[1, 2, 3, 4, 5]",
504
+ "[0, 1, 4, 9, 16, 25]",
505
+ "[1, 4, 9, 16, 25]"
506
+ ],
507
+ correct: 0,
508
+ explanation: "This is a list comprehension that squares each number in range(5), which produces numbers 0 to 4. The result is [0, 1, 4, 9, 16]."
509
+ },
510
+ {
511
+ question: "Which decorator is commonly used to define a method as a class method?",
512
+ options: [
513
+ "@staticmethod",
514
+ "@classmethod",
515
+ "@method",
516
+ "@property"
517
+ ],
518
+ correct: 1,
519
+ explanation: "The @classmethod decorator is used to define a method as a class method. It receives the class as the first argument (cls) rather than the instance (self)."
520
+ },
521
+ {
522
+ question: "What does the 'yield' keyword do in Python?",
523
+ options: [
524
+ "Returns a value from a function",
525
+ "Pauses function execution and returns a value",
526
+ "Terminates a function",
527
+ "Raises an exception"
528
+ ],
529
+ correct: 1,
530
+ explanation: "The yield keyword pauses function execution and returns a value to the caller, but retains enough state to resume where it left off. Functions containing yield are generator functions."
531
+ },
532
+ {
533
+ question: "How do you create a virtual environment in Python?",
534
+ options: [
535
+ "python -m venv myenv",
536
+ "python create virtualenv myenv",
537
+ "python -m virtualenv create myenv",
538
+ "python environment myenv"
539
+ ],
540
+ correct: 0,
541
+ explanation: "The standard way to create a virtual environment in Python is using 'python -m venv myenv'. This creates a new virtual environment in the 'myenv' directory."
542
+ },
543
+ {
544
+ question: "What is the purpose of the '__name__' variable in Python?",
545
+ options: [
546
+ "Stores the name of the current user",
547
+ "Holds the name of the current function",
548
+ "Contains the name of the module being executed",
549
+ "Is always '__main__'"
550
+ ],
551
+ correct: 2,
552
+ explanation: "The __name__ variable holds the name of the current module. When a module is run directly (not imported), __name__ is set to '__main__', which is a common pattern for code that should only run when the script is executed directly."
553
+ },
554
+ {
555
+ question: "Which of these is NOT a valid dictionary key in Python?",
556
+ options: [
557
+ "Integer",
558
+ "String",
559
+ "Tuple",
560
+ "List"
561
+ ],
562
+ correct: 3,
563
+ explanation: "In Python, dictionary keys must be immutable types. Lists are mutable and cannot be used as dictionary keys. Integers, strings, and tuples (if they contain only immutable elements) can be dictionary keys."
564
+ },
565
+ {
566
+ question: "What does the '//' operator do in Python?",
567
+ options: [
568
+ "Performs integer (floor) division",
569
+ "Is used for comments",
570
+ "Performs floating point division",
571
+ "Is the exponentiation operator"
572
+ ],
573
+ correct: 0,
574
+ explanation: "The // operator performs floor division, returning the largest integer less than or equal to the division result. For example, 5 // 2 returns 2, not 2.5."
575
+ },
576
+ {
577
+ question: "How do you check if an object is an instance of a particular class?",
578
+ options: [
579
+ "isinstance()",
580
+ "type()",
581
+ "classof()",
582
+ "Both isinstance() and type()"
583
+ ],
584
+ correct: 0,
585
+ explanation: "The isinstance() function checks if an object is an instance of a class or its subclasses, which is the preferred way to check types in Python. type() returns the exact type but doesn't account for inheritance."
586
+ },
587
+ {
588
+ question: "What is the output of 'print('Hello, World!'[::-1])'?",
589
+ options: [
590
+ "'Hello, World!'",
591
+ "'!dlroW ,olleH'",
592
+ "Error",
593
+ "'Hello, World!' printed multiple times"
594
+ ],
595
+ correct: 1,
596
+ explanation: "The [::-1] slicing creates a reversed copy of the string by stepping backward through the string. So 'Hello, World!' reversed is '!dlroW ,olleH'."
597
+ },
598
+ {
599
+ question: "Which module would you use for working with dates and times in Python?",
600
+ options: [
601
+ "datetime",
602
+ "time",
603
+ "cal",
604
+ "All of the above"
605
+ ],
606
+ correct: 0,
607
+ explanation: "The datetime module is the primary module for working with dates and times in Python. While the time module exists, it's lower-level, and there is no standard 'cal' module."
608
+ }
609
  ];
610
 
611
+ // Quiz state
612
+ let currentQuestion = 0;
613
+ let score = 0;
614
+ let selectedOption = null;
615
+ let userAnswers = Array(questions.length).fill(null);
 
 
 
616
 
617
  // DOM elements
618
+ const quizContainer = document.getElementById('quiz');
619
+ const resultContainer = document.getElementById('result-container');
620
+ const questionElement = document.getElementById('question');
621
+ const optionsElement = document.getElementById('options');
622
+ const currentQuestionElement = document.getElementById('current-question');
623
+ const progressElement = document.getElementById('progress');
624
+ const prevBtn = document.getElementById('prev-btn');
625
+ const nextBtn = document.getElementById('next-btn');
626
+ const submitBtn = document.getElementById('submit-btn');
627
+ const explanationElement = document.getElementById('explanation');
628
+ const explanationTextElement = document.getElementById('explanation-text');
629
+ const scoreElement = document.getElementById('score');
630
+ const assessmentElement = document.getElementById('assessment');
631
+ const resultIconElement = document.getElementById('result-icon');
632
+ const restartBtn = document.getElementById('restart-btn');
633
+ const wrongAnswersList = document.getElementById('wrong-answers-list');
634
+
635
+ // Initialize the quiz
636
+ function initQuiz() {
637
+ showQuestion();
638
+ updateNavigationButtons();
639
+ }
640
+
641
+ // Display the current question
642
+ function showQuestion() {
643
+ const question = questions[currentQuestion];
644
+ questionElement.textContent = question.question;
645
+ optionsElement.innerHTML = '';
646
+
647
+ currentQuestionElement.textContent = currentQuestion + 1;
648
+ progressElement.style.width = `${((currentQuestion + 1) / questions.length) * 100}%`;
649
+
650
+ // Create options
651
+ question.options.forEach((option, index) => {
652
+ const optionElement = document.createElement('div');
653
+ optionElement.className = 'option';
654
+ if (userAnswers[currentQuestion] === index) {
655
+ optionElement.classList.add('selected');
656
+ selectedOption = index;
 
 
 
 
 
657
  }
658
 
659
+ const input = document.createElement('input');
660
+ input.type = 'radio';
661
+ input.name = 'option';
662
+ input.id = `option-${index}`;
663
+ input.value = index;
664
+ input.checked = userAnswers[currentQuestion] === index;
665
+ input.addEventListener('change', () => selectOption(index));
666
+
667
+ const label = document.createElement('label');
668
+ label.className = 'option-label';
669
+ label.htmlFor = `option-${index}`;
670
+ label.textContent = option;
671
+
672
+ optionElement.appendChild(input);
673
+ optionElement.appendChild(label);
674
+ optionsElement.appendChild(optionElement);
675
  });
676
+
677
+ // Show explanation if this question was already answered
678
+ if (userAnswers[currentQuestion] !== null) {
679
+ showExplanation();
680
+ } else {
681
+ hideExplanation();
682
+ }
683
  }
684
 
685
+ // Handle option selection
686
+ function selectOption(index) {
687
+ // Remove selected class from all options
688
+ document.querySelectorAll('.option').forEach(option => {
689
+ option.classList.remove('selected');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690
  });
691
+
692
+ // Add selected class to clicked option
693
+ const selectedOptionElement = document.querySelector(`.option input[value="${index}"]`).parentNode;
694
+ selectedOptionElement.classList.add('selected');
695
 
696
+ selectedOption = index;
697
+ userAnswers[currentQuestion] = index;
 
 
 
 
 
 
 
 
 
 
698
 
699
+ // If it's the last question, show the submit button
700
+ if (currentQuestion === questions.length - 1 && userAnswers[currentQuestion] !== null) {
701
+ nextBtn.style.display = 'none';
702
+ submitBtn.style.display = 'inline-block';
703
+ } else {
704
+ nextBtn.style.display = 'inline-block';
705
+ submitBtn.style.display = 'none';
706
+ }
707
+
708
+ showExplanation();
709
  }
710
 
711
+ // Show explanation for the current question
712
+ function showExplanation() {
713
+ if (userAnswers[currentQuestion] !== null) {
714
+ explanationTextElement.textContent = questions[currentQuestion].explanation;
715
+ explanationElement.style.display = 'block';
716
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
  }
718
 
719
+ // Hide the explanation
720
+ function hideExplanation() {
721
+ explanationElement.style.display = 'none';
722
+ }
723
+
724
+ // Update navigation buttons state
725
+ function updateNavigationButtons() {
726
+ prevBtn.disabled = currentQuestion === 0;
727
 
728
+ // For the last question, show submit button if answered
729
+ if (currentQuestion === questions.length - 1) {
730
+ nextBtn.style.display = userAnswers[currentQuestion] === null ? 'inline-block' : 'none';
731
+ submitBtn.style.display = userAnswers[currentQuestion] !== null ? 'inline-block' : 'none';
732
+ } else {
733
+ nextBtn.style.display = 'inline-block';
734
+ submitBtn.style.display = 'none';
735
+ }
736
+ }
737
+
738
+ // Go to the next question
739
+ function nextQuestion() {
740
+ if (currentQuestion < questions.length - 1) {
741
+ currentQuestion++;
742
+ selectedOption = null;
743
+ showQuestion();
744
+ updateNavigationButtons();
745
+ }
 
 
 
 
 
 
 
746
  }
747
 
748
+ // Go to the previous question
749
+ function prevQuestion() {
750
+ if (currentQuestion > 0) {
751
+ currentQuestion--;
752
+ selectedOption = userAnswers[currentQuestion];
753
+ showQuestion();
754
+ updateNavigationButtons();
755
+ }
756
  }
757
 
758
+ // Submit the quiz and show results
759
+ function submitQuiz() {
760
+ // Calculate score and collect wrong answers
761
+ score = 0;
762
+ const wrongAnswers = [];
763
 
764
+ questions.forEach((question, index) => {
765
+ if (userAnswers[index] === question.correct) {
766
+ score++;
767
+ } else if (userAnswers[index] !== null) {
768
+ wrongAnswers.push({
769
+ questionNumber: index + 1,
770
+ question: question.question,
771
+ yourAnswer: question.options[userAnswers[index]],
772
+ correctAnswer: question.options[question.correct]
773
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
774
  }
775
  });
 
 
 
 
 
 
776
 
777
+ // Display results
778
+ quizContainer.style.display = 'none';
779
+ resultContainer.style.display = 'block';
780
+ scoreElement.textContent = `${score}/${questions.length}`;
781
+
782
+ // Calculate percentage
783
+ const percentage = (score / questions.length) * 100;
784
+
785
+ // Determine assessment
786
+ let assessment, iconClass, feedback;
787
+
788
+ if (percentage >= 90) {
789
+ assessment = "Python Expert!";
790
+ iconClass = "excellent";
791
+ feedback = "Outstanding! Your Python knowledge is comprehensive and detailed. You've demonstrated a deep understanding of Python concepts and syntax. Consider sharing your expertise with others!";
792
+ } else if (percentage >= 75) {
793
+ assessment = "Advanced Python User";
794
+ iconClass = "good";
795
+ feedback = "Great job! You have strong Python knowledge with just a few gaps. With a bit more practice, you'll master all the advanced concepts. Consider working on more complex projects to refine your skills.";
796
+ } else if (percentage >= 50) {
797
+ assessment = "Intermediate Python Programmer";
798
+ iconClass = "average";
799
+ feedback = "Good effort! You understand the basics and some intermediate concepts of Python. Focus on learning about functions, classes, and Python's standard library to improve your skills.";
800
+ } else {
801
+ assessment = "Python Beginner";
802
+ iconClass = "poor";
803
+ feedback = "You're just starting your Python journey! Focus on the fundamentals first - variables, data types, loops, and functions. Practice writing simple programs and gradually increase complexity.";
804
  }
805
 
806
+ assessmentElement.textContent = feedback;
807
+ document.querySelector('#assessment').previousElementSibling.textContent = assessment;
808
+ document.querySelector('.result-icon i').className = `fas ${percentage >= 90 ? 'fa-trophy' : percentage >= 75 ? 'fa-award' : percentage >= 50 ? 'fa-star' : 'fa-book'}`;
809
+ resultIconElement.className = `result-icon ${iconClass}`;
 
 
 
 
810
 
811
+ // Display wrong answers if any
812
+ if (wrongAnswers.length > 0) {
813
+ wrongAnswersList.innerHTML = '';
814
+ wrongAnswers.forEach(item => {
815
+ const wrongAnswerElement = document.createElement('div');
816
+ wrongAnswerElement.className = 'wrong-answer-item';
817
+ wrongAnswerElement.innerHTML = `
818
+ <strong>Question ${item.questionNumber}:</strong> ${item.question}<br>
819
+ <span style="color: var(--danger-color);">Your answer: ${item.yourAnswer}</span><br>
820
+ <span style="color: var(--success-color);">Correct answer: ${item.correctAnswer}</span>
821
+ `;
822
+ wrongAnswersList.appendChild(wrongAnswerElement);
823
+ });
824
+ document.getElementById('wrong-answers').style.display = 'block';
825
+ } else {
826
+ document.getElementById('wrong-answers').style.display = 'none';
827
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
  }
829
 
830
+ // Restart the quiz
831
+ function restartQuiz() {
832
+ currentQuestion = 0;
833
+ score = 0;
834
+ selectedOption = null;
835
+ userAnswers = Array(questions.length).fill(null);
 
 
 
 
836
 
837
+ quizContainer.style.display = 'block';
838
+ resultContainer.style.display = 'none';
839
+
840
+ showQuestion();
841
+ updateNavigationButtons();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
842
  }
843
 
844
+ // Event listeners
845
+ nextBtn.addEventListener('click', nextQuestion);
846
+ prevBtn.addEventListener('click', prevQuestion);
847
+ submitBtn.addEventListener('click', submitQuiz);
848
+ restartBtn.addEventListener('click', restartQuiz);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
849
 
850
+ // Initialize the quiz when the page loads
851
+ window.addEventListener('DOMContentLoaded', initQuiz);
852
  </script>
853
  </body>
854
+ </html>