python-knowledge-test / index.html
dwellnick's picture
Add 1 files
8a727ba verified
raw
history blame
28 kB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Анализ оценок студентов - ОП</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--primary-color: #4285F4;
--google-blue: #1A73E8;
--google-red: #D93025;
--google-yellow: #F4B400;
--google-green: #0F9D58;
--light-gray: #f5f5f5;
--border-gray: #dadce0;
--text-dark: #202124;
--text-medium: #5f6368;
--text-light: #80868b;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Product Sans', 'Roboto', Arial, sans-serif;
}
body {
background-color: var(--light-gray);
color: var(--text-dark);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
display: flex;
align-items: center;
padding: 16px 0;
margin-bottom: 24px;
border-bottom: 1px solid var(--border-gray);
}
.logo {
display: flex;
align-items: center;
margin-right: 24px;
}
.logo-icon {
font-size: 24px;
color: var(--google-blue);
margin-right: 8px;
}
.logo-text {
font-size: 22px;
font-weight: 500;
color: var(--text-dark);
}
.card {
background-color: white;
border-radius: 8px;
box-shadow: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);
padding: 24px;
margin-bottom: 24px;
}
.section-title {
font-size: 20px;
font-weight: 500;
margin-bottom: 20px;
color: var(--google-blue);
display: flex;
align-items: center;
}
.section-title i {
margin-right: 10px;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.chart-container {
position: relative;
height: 400px;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
text-align: center;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: var(--google-blue);
margin: 10px 0;
}
.stat-label {
font-size: 14px;
color: var(--text-medium);
}
.table-container {
overflow-x: auto;
margin-bottom: 20px;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th {
background-color: var(--light-gray);
padding: 12px 15px;
text-align: left;
font-weight: 500;
}
.data-table td {
padding: 10px 15px;
border-bottom: 1px solid var(--border-gray);
}
.data-table tr:hover {
background-color: rgba(66, 133, 244, 0.05);
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(66, 133, 244, 0.2);
border-radius: 50%;
border-top-color: var(--google-blue);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.excellent { background-color: rgba(15, 157, 88, 0.1); }
.good { background-color: rgba(244, 180, 0, 0.1); }
.average { background-color: rgba(217, 48, 37, 0.1); }
.poor { background-color: rgba(217, 48, 37, 0.2); }
@media (max-width: 768px) {
.grid-container {
grid-template-columns: 1fr;
}
.stats-container {
grid-template-columns: 1fr 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo">
<i class="fas fa-chart-line logo-icon"></i>
<span class="logo-text">Анализ оценок студентов</span>
</div>
<span>Данные из таблицы "ОП" - Оценки за домашние задания</span>
</header>
<div class="card">
<div class="section-title">
<i class="fas fa-chart-pie"></i>
Статистика оценок
</div>
<div class="stats-container" id="statsContainer">
<div class="loading" id="loadingIndicator">
<div class="loading-spinner"></div>
</div>
</div>
<div class="section-title">
<i class="fas fa-chart-bar"></i>
Визуализация данных
</div>
<div class="grid-container">
<div class="chart-container">
<canvas id="distributionChart"></canvas>
</div>
<div class="chart-container">
<canvas id="histogramChart"></canvas>
</div>
<div class="chart-container">
<canvas id="pieChart"></canvas>
</div>
<div class="chart-container">
<canvas id="boxPlotChart"></canvas>
</div>
</div>
<div class="section-title">
<i class="fas fa-table"></i>
Данные студентов
</div>
<div class="table-container">
<table class="data-table" id="studentTable">
<thead>
<tr>
<th></th>
<th>ФИО</th>
<th>Группа</th>
<th>ДЗ (из 2800)</th>
<th>Процент</th>
<th>Категория</th>
</tr>
</thead>
<tbody id="studentTableBody">
<!-- Filled by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
<script>
// Mock data based on the structure of the actual Google Sheet
// In a real implementation, you would fetch this from the Google Sheets API
const mockStudentData = [
{ id: 1, name: "Иванов Иван Иванович", group: "Группа 1", dz: 2450 },
{ id: 2, name: "Петров Петр Петрович", group: "Группа 1", dz: 2100 },
{ id: 3, name: "Сидорова Мария Сергеевна", group: "Группа 2", dz: 1960 },
{ id: 4, name: "Кузнецов Алексей Дмитриевич", group: "Группа 2", dz: 1780 },
{ id: 5, name: "Смирнова Екатерина Викторовна", group: "Группа 3", dz: 2650 },
{ id: 6, name: "Васильев Денис Олегович", group: "Группа 3", dz: 1550 },
{ id: 7, name: "Николаева Анна Павловна", group: "Группа 1", dz: 2250 },
{ id: 8, name: "Михайлов Артем Игоревич", group: "Группа 4", dz: 1980 },
{ id: 9, name: "Федорова Ольга Николаевна", group: "Группа 4", dz: 2400 },
{ id: 10, name: "Алексеев Сергей Владимирович", group: "Группа 5", dz: 1750 },
{ id: 11, name: "Дмитриева Татьяна Александровна", group: "Группа 5", dz: 2100 },
{ id: 12, name: "Андреев Максим Юрьевич", group: "Группа 5", dz: 1900 },
{ id: 13, name: "Егорова Виктория Алексеевна", group: "Группа 1", dz: 2300 },
{ id: 14, name: "Григорьева Наталья Сергеевна", group: "Группа 3", dz: 1680 },
{ id: 15, name: "Семенов Дмитрий Петрович", group: "Группа 4", dz: 1950 },
{ id: 16, name: "Павлов Антон Викторович", group: "Группа 2", dz: 2500 },
{ id: 17, name: "Романова Елена Дмитриевна", group: "Группа 3", dz: 2350 },
{ id: 18, name: "Козлов Илья Сергеевич", group: "Группа 4", dz: 2050 },
{ id: 19, name: "Орлова Анастасия Андреевна", group: "Группа 5", dz: 1850 },
{ id: 20, name: "Лебедев Владислав Игоревич", group: "Группа 2", dz: 1720 }
];
// Constants
const maxScore = 2800;
const gradeCategories = [
{ name: "Отлично", minPercent: 85, color: "#0F9D58", colorLight: "rgba(15, 157, 88, 0.1)" },
{ name: "Хорошо", minPercent: 70, color: "#F4B400", colorLight: "rgba(244, 180, 0, 0.1)" },
{ name: "Удовлетворительно", minPercent: 50, color: "#FF9800", colorLight: "rgba(255, 152, 0, 0.1)" },
{ name: "Неудовлетворительно", minPercent: 0, color: "#D93025", colorLight: "rgba(217, 48, 37, 0.1)" }
];
// DOM elements
const loadingIndicator = document.getElementById('loadingIndicator');
const statsContainer = document.getElementById('statsContainer');
const studentTableBody = document.getElementById('studentTableBody');
const distributionCtx = document.getElementById('distributionChart').getContext('2d');
const histogramCtx = document.getElementById('histogramChart').getContext('2d');
const pieCtx = document.getElementById('pieChart').getContext('2d');
const boxPlotCtx = document.getElementById('boxPlotChart').getContext('2d');
// Initialize the application
function init() {
// Simulate loading delay
setTimeout(() => {
// Process student data
const processedStudents = processStudentData(mockStudentData);
// Calculate statistics
const stats = calculateStatistics(processedStudents);
// Display statistics
displayStatistics(stats);
// Display student table
displayStudentTable(processedStudents);
// Create charts
createCharts(processedStudents, stats);
// Hide loading indicator
loadingIndicator.style.display = 'none';
}, 1000);
}
// Process student data to add percentage and category
function processStudentData(students) {
return students.map(student => {
const percentage = Math.round((student.dz / maxScore) * 100);
let category = "";
// Determine grade category
for (const cat of gradeCategories) {
if (percentage >= cat.minPercent) {
category = cat.name;
break;
}
}
return {
...student,
percentage,
category
};
});
}
// Calculate statistical measures
function calculateStatistics(students) {
const dzScores = students.map(s => s.dz);
const percentages = students.map(s => s.percentage);
// Basic statistics
const count = dzScores.length;
const min = Math.min(...dzScores);
const max = Math.max(...dzScores);
const sum = dzScores.reduce((a, b) => a + b, 0);
const mean = Math.round(sum / count);
const meanPerc = Math.round(mean / maxScore * 100);
// Median
const sorted = [...dzScores].sort((a, b) => a - b);
const median = sorted.length % 2 === 0
? (sorted[sorted.length/2 - 1] + sorted[sorted.length/2]) / 2
: sorted[Math.floor(sorted.length/2)];
const medianPerc = Math.round(median / maxScore * 100);
// Standard deviation
const squaredDiffs = dzScores.map(score => Math.pow(score - mean, 2));
const variance = squaredDiffs.reduce((a, b) => a + b, 0) / count;
const stdDev = Math.round(Math.sqrt(variance));
// Quartiles
const q1 = percentile(sorted, 25);
const q3 = percentile(sorted, 75);
const iqr = q3 - q1;
// Grade category counts
const gradeCounts = {};
gradeCategories.forEach(cat => {
gradeCounts[cat.name] = students.filter(s => s.category === cat.name).length;
});
return {
count, min, max, mean, meanPerc, median, medianPerc,
stdDev, q1, q3, iqr, gradeCounts
};
}
// Helper function to calculate percentiles
function percentile(arr, p) {
const index = p * (arr.length - 1);
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= arr.length) return arr[lower];
return arr[lower] * (1 - weight) + arr[upper] * weight;
}
// Display statistics cards
function displayStatistics(stats) {
statsContainer.innerHTML = `
<div class="stat-card">
<div class="stat-label">Количество студентов</div>
<div class="stat-value">${stats.count}</div>
</div>
<div class="stat-card">
<div class="stat-label">Средний балл</div>
<div class="stat-value">${stats.mean} (${stats.meanPerc}%)</div>
</div>
<div class="stat-card">
<div class="stat-label">Медианный балл</div>
<div class="stat-value">${stats.median} (${stats.medianPerc}%)</div>
</div>
<div class="stat-card">
<div class="stat-label">Стандартное отклонение</div>
<div class="stat-value">${stats.stdDev}</div>
</div>
<div class="stat-card">
<div class="stat-label">Минимальный балл</div>
<div class="stat-value">${stats.min}</div>
</div>
<div class="stat-card">
<div class="stat-label">Максимальный балл</div>
<div class="stat-value">${stats.max}</div>
</div>
<div class="stat-card">
<div class="stat-label">1-й квартиль (Q1)</div>
<div class="stat-value">${stats.q1}</div>
</div>
<div class="stat-card">
<div class="stat-label">3-й квартиль (Q3)</div>
<div class="stat-value">${stats.q3}</div>
</div>
`;
}
// Display student table
function displayStudentTable(students) {
studentTableBody.innerHTML = '';
students.forEach(student => {
const row = document.createElement('tr');
// Apply CSS class based on grade category
for (const cat of gradeCategories) {
if (student.category === cat.name) {
if (cat.name === "Отлично") row.classList.add("excellent");
else if (cat.name === "Хорошо") row.classList.add("good");
else if (cat.name === "Удовлетворительно") row.classList.add("average");
else if (cat.name === "Неудовлетворительно") row.classList.add("poor");
break;
}
}
row.innerHTML = `
<td>${student.id}</td>
<td>${student.name}</td>
<td>${student.group}</td>
<td>${student.dz}</td>
<td>${student.percentage}%</td>
<td>${student.category}</td>
`;
studentTableBody.appendChild(row);
});
}
// Create all charts
function createCharts(students, stats) {
createDistributionChart(students);
createHistogramChart(students);
createPieChart(students, stats);
createBoxPlotChart(stats);
}
// Create distribution chart
function createDistributionChart(students) {
const scores = students.map(s => s.dz);
const percentages = students.map(s => s.percentage);
new Chart(distributionCtx, {
type: 'scatter',
data: {
datasets: [{
label: 'Баллы студентов',
data: students.map(s => ({ x: s.id, y: s.dz })),
backgroundColor: students.map(s => {
for (const cat of gradeCategories) {
if (s.category === cat.name) return cat.color;
}
return '#4285F4';
}),
pointRadius: 6,
pointHoverRadius: 8
}, {
label: 'Средний балл',
data: [{ x: 1, y: stats.mean }, { x: students.length, y: stats.mean }],
type: 'line',
borderColor: '#EA4335',
borderWidth: 2,
borderDash: [5, 5],
pointRadius: 0,
fill: false,
showLine: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: {
display: true,
text: '№ студента'
}
},
y: {
title: {
display: true,
text: 'Баллы (из 2800)'
},
min: 0,
max: 2800
}
},
plugins: {
title: {
display: true,
text: 'Распределение оценок студентов'
},
tooltip: {
callbacks: {
label: function(context) {
const student = students[context.parsed.x - 1];
return `${student.name}: ${student.dz} (${student.percentage}%) - ${student.category}`;
}
}
}
}
}
});
}
// Create histogram chart
function createHistogramChart(students) {
const step = 200;
const bins = {};
// Create bins from 0 to 2800 with step=200
for (let i = 0; i <= maxScore; i += step) {
bins[`${i}-${i+step-1}`] = 0;
}
// Count students in each bin
students.forEach(student => {
const binKey = Object.keys(bins).find(key => {
const [min, max] = key.split('-').map(Number);
return student.dz >= min && student.dz <= max;
});
if (binKey) bins[binKey]++;
});
new Chart(histogramCtx, {
type: 'bar',
data: {
labels: Object.keys(bins),
datasets: [{
label: 'Количество студентов',
data: Object.values(bins),
backgroundColor: '#4285F4',
borderColor: '#1A73E8',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: {
display: true,
text: 'Диапазон баллов'
}
},
y: {
title: {
display: true,
text: 'Количество студентов'
},
beginAtZero: true
}
},
plugins: {
title: {
display: true,
text: 'Гистограмма распределения оценок'
}
}
}
});
}
// Create pie chart of grade categories
function createPieChart(students, stats) {
const data = {
labels: Object.keys(stats.gradeCounts),
datasets: [{
data: Object.values(stats.gradeCounts),
backgroundColor: gradeCategories.map(cat => cat.color),
borderWidth: 1
}]
};
new Chart(pieCtx, {
type: 'pie',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Распределение по категориям оценок'
},
legend: {
position: 'right'
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label;
const value = context.raw;
const percentage = Math.round((value / stats.count) * 100);
return `${label}: ${value} студентов (${percentage}%)`;
}
}
}
}
}
});
}
// Create box plot chart
function createBoxPlotChart(stats) {
new Chart(boxPlotCtx, {
type: 'boxplot',
data: {
labels: ['Оценки за ДЗ'],
datasets: [{
label: 'Статистика оценок',
data: [{
min: stats.min,
q1: stats.q1,
median: stats.median,
q3: stats.q3,
max: stats.max
}],
backgroundColor: 'rgba(66, 133, 244, 0.2)',
borderColor: '#4285F4',
borderWidth: 2,
outlierColor: '#EA4335',
padding: 10,
itemRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
title: {
display: true,
text: 'Баллы (из 2800)'
},
min: 0,
max: 2800
}
},
plugins: {
title: {
display: true,
text: 'Box plot оценок студентов'
},
tooltip: {
callbacks: {
beforeLabel: function(context) {
return 'Статистика оценок:';
},
label: function(context) {
const data = context.raw;
return [
`Минимум: ${data.min}`,
`Q1: ${data.q1}`,
`Медиана: ${data.median}`,
`Q3: ${data.q3}`,
`Максимум: ${data.max}`,
`Диапазон: ${data.max - data.min}`,
`IQR: ${data.q3 - data.q1}`
];
}
}
}
}
}
});
}
// Initialize the application when the page loads
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>