feat: add problem name distribution and problem×class matrix to statistics
This commit is contained in:
@@ -432,6 +432,25 @@ def get_student_statistics():
|
|||||||
"problem_count": len(class_problems)
|
"problem_count": len(class_problems)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 各问题名称分布
|
||||||
|
problem_name_dist = {}
|
||||||
|
for p in problems:
|
||||||
|
name = p.problem.name if p.problem else f"问题{p.problem_id}"
|
||||||
|
if name not in problem_name_dist:
|
||||||
|
problem_name_dist[name] = 0
|
||||||
|
problem_name_dist[name] += 1
|
||||||
|
# 排序:数量多的在前
|
||||||
|
problem_name_dist = dict(sorted(problem_name_dist.items(), key=lambda x: x[1], reverse=True))
|
||||||
|
|
||||||
|
# 问题×班级矩阵
|
||||||
|
problem_class_matrix = []
|
||||||
|
for p_name in problem_name_dist.keys():
|
||||||
|
row = {"problem_name": p_name}
|
||||||
|
for c in classes:
|
||||||
|
count = sum(1 for p in problems if (p.problem.name if p.problem else f"问题{p.problem_id}") == p_name and p.student.class_id == c.id)
|
||||||
|
row[f"class_{c.id}"] = count
|
||||||
|
problem_class_matrix.append(row)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"total_students": total_students,
|
"total_students": total_students,
|
||||||
"total_problems": len(problems),
|
"total_problems": len(problems),
|
||||||
@@ -440,4 +459,7 @@ def get_student_statistics():
|
|||||||
"class_student_count": class_student_count,
|
"class_student_count": class_student_count,
|
||||||
"class_problem_count": class_problem_count,
|
"class_problem_count": class_problem_count,
|
||||||
"problem_level_matrix": matrix,
|
"problem_level_matrix": matrix,
|
||||||
|
"problem_name_distribution": problem_name_dist,
|
||||||
|
"problem_class_matrix": problem_class_matrix,
|
||||||
|
"classes": [{"id": c.id, "name": c.name} for c in classes],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -81,6 +81,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 问题分布 -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="bi bi-bar-chart"></i> 各类问题分布</h5>
|
||||||
|
<canvas id="problemNameChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="bi bi-grid-3x3-gap"></i> 问题×班级矩阵</h5>
|
||||||
|
<div class="table-responsive" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
<table class="table table-sm table-bordered text-center small" id="problemClassMatrix">
|
||||||
|
<thead id="problemClassMatrixHead"></thead>
|
||||||
|
<tbody id="problemClassMatrixBody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 班级统计 -->
|
<!-- 班级统计 -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -105,7 +130,7 @@
|
|||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
let severityChart, levelChart, classStudentChart, classProblemChart;
|
let severityChart, levelChart, classStudentChart, classProblemChart, problemNameChart;
|
||||||
|
|
||||||
async function loadStatistics() {
|
async function loadStatistics() {
|
||||||
try {
|
try {
|
||||||
@@ -191,6 +216,58 @@ async function loadStatistics() {
|
|||||||
html += `<td><strong>${grandTotal}</strong></td></tr>`;
|
html += `<td><strong>${grandTotal}</strong></td></tr>`;
|
||||||
matrixBody.innerHTML = html;
|
matrixBody.innerHTML = html;
|
||||||
|
|
||||||
|
// 问题名称分布图
|
||||||
|
const problemNames = Object.keys(data.problem_name_distribution || {});
|
||||||
|
const problemCounts = Object.values(data.problem_name_distribution || {});
|
||||||
|
const pnCtx = document.getElementById('problemNameChart').getContext('2d');
|
||||||
|
if (problemNameChart) problemNameChart.destroy();
|
||||||
|
problemNameChart = new Chart(pnCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: problemNames,
|
||||||
|
datasets: [{
|
||||||
|
label: '出现次数',
|
||||||
|
data: problemCounts,
|
||||||
|
backgroundColor: '#0d6efd',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
indexAxis: 'y',
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { beginAtZero: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 问题×班级矩阵表
|
||||||
|
const classes = data.classes || [];
|
||||||
|
const pcmHead = document.getElementById('problemClassMatrixHead');
|
||||||
|
let headHtml = '<tr><th>问题</th>';
|
||||||
|
for (const c of classes) {
|
||||||
|
headHtml += `<th>${c.name}</th>`;
|
||||||
|
}
|
||||||
|
headHtml += '<th>合计</th></tr>';
|
||||||
|
pcmHead.innerHTML = headHtml;
|
||||||
|
|
||||||
|
const pcmBody = document.getElementById('problemClassMatrixBody');
|
||||||
|
let bodyHtml = '';
|
||||||
|
const pcmData = data.problem_class_matrix || [];
|
||||||
|
for (const row of pcmData) {
|
||||||
|
let rowTotal = 0;
|
||||||
|
bodyHtml += `<tr><td>${row.problem_name}</td>`;
|
||||||
|
for (const c of classes) {
|
||||||
|
const count = row[`class_${c.id}`] || 0;
|
||||||
|
bodyHtml += `<td>${count}</td>`;
|
||||||
|
rowTotal += count;
|
||||||
|
}
|
||||||
|
bodyHtml += `<td><strong>${rowTotal}</strong></td></tr>`;
|
||||||
|
}
|
||||||
|
pcmBody.innerHTML = bodyHtml;
|
||||||
|
|
||||||
// 班级学员条形图
|
// 班级学员条形图
|
||||||
const csCtx = document.getElementById('classStudentChart').getContext('2d');
|
const csCtx = document.getElementById('classStudentChart').getContext('2d');
|
||||||
if (classStudentChart) classStudentChart.destroy();
|
if (classStudentChart) classStudentChart.destroy();
|
||||||
|
|||||||
Reference in New Issue
Block a user