Files
dbstorage/dbapp/mainapp/templates/mainapp/statistics.html

487 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'mainapp/base.html' %}
{% load static %}
{% block title %}Статистика{% endblock %}
{% block extra_css %}
<link href="{% static 'css/checkbox-select-multiple.css' %}" rel="stylesheet">
<style>
.stat-card {
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
}
.stat-value {
font-size: 2.5rem;
font-weight: bold;
line-height: 1.2;
}
.stat-label {
font-size: 0.9rem;
color: #6c757d;
}
.satellite-stat-row:hover {
background-color: #f8f9fa;
}
.preset-btn.active {
background-color: #0d6efd;
color: white;
}
#dailyChart {
min-height: 300px;
}
.new-emission-badge {
font-size: 0.75rem;
margin: 2px;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid px-3">
<!-- Header -->
<div class="row mb-3">
<div class="col-12 d-flex justify-content-between align-items-center">
<h2><i class="bi bi-bar-chart-line"></i> Статистика</h2>
<a href="{% url 'mainapp:source_list' %}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> К списку объектов
</a>
</div>
</div>
<!-- Filters -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<form method="get" id="filter-form">
<div class="row g-3 align-items-end">
<!-- Date presets -->
<div class="col-auto">
<label class="form-label">Период:</label>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-primary preset-btn {% if preset == 'week' %}active{% endif %}"
data-preset="week">Неделя</button>
<button type="button" class="btn btn-outline-primary preset-btn {% if preset == 'month' %}active{% endif %}"
data-preset="month">Месяц</button>
<button type="button" class="btn btn-outline-primary preset-btn {% if preset == '3months' %}active{% endif %}"
data-preset="3months">3 месяца</button>
<button type="button" class="btn btn-outline-primary preset-btn {% if preset == '6months' %}active{% endif %}"
data-preset="6months">Полгода</button>
<button type="button" class="btn btn-outline-primary preset-btn {% if preset == 'all' or not preset and not date_from %}active{% endif %}"
data-preset="all">Всё время</button>
</div>
</div>
<!-- Custom date range -->
<div class="col-auto">
<label for="date_from" class="form-label">С:</label>
<input type="date" class="form-control" id="date_from" name="date_from"
value="{{ date_from }}">
</div>
<div class="col-auto">
<label for="date_to" class="form-label">По:</label>
<input type="date" class="form-control" id="date_to" name="date_to"
value="{{ date_to }}">
</div>
<!-- Satellite filter with custom widget -->
<div class="col-md-3">
<label class="form-label">Спутники:</label>
<div class="checkbox-multiselect-wrapper" data-widget-id="satellite_id">
<div class="multiselect-input-container">
<div class="multiselect-tags" id="satellite_id_tags"></div>
<input type="text"
class="multiselect-search form-control"
placeholder="Выберите спутники..."
id="satellite_id_search"
autocomplete="off">
<button type="button" class="multiselect-clear" id="satellite_id_clear" title="Очистить все">×</button>
</div>
<div class="multiselect-dropdown" id="satellite_id_dropdown">
<div class="multiselect-options">
{% for satellite in satellites %}
<label class="multiselect-option">
<input type="checkbox"
name="satellite_id"
value="{{ satellite.id }}"
{% if satellite.id in selected_satellites %}checked{% endif %}
data-label="{{ satellite.name }}">
<span class="option-label">{{ satellite.name }}</span>
</label>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- Submit -->
<div class="col-auto">
<button type="submit" class="btn btn-primary">
Применить
</button>
<a href="{% url 'mainapp:statistics' %}" class="btn btn-secondary">
Сбросить
</a>
</div>
</div>
<input type="hidden" name="preset" id="preset-input" value="{{ preset }}">
</form>
</div>
</div>
</div>
</div>
<!-- Main Statistics Cards -->
<div class="row mb-4">
<!-- Total Points -->
<div class="col-md-4">
<div class="card stat-card h-100 border-primary">
<div class="card-body text-center">
<div class="stat-value text-primary">{{ total_points }}</div>
<div class="stat-label">Точек геолокации</div>
<small class="text-muted">по {{ total_sources }} объектам</small>
</div>
</div>
</div>
<!-- New Emissions -->
<div class="col-md-4">
<div class="card stat-card h-100 border-success">
<div class="card-body text-center">
<div class="stat-value text-success">{{ new_emissions_count }}</div>
<div class="stat-label">Новых уникальных излучений</div>
<small class="text-muted">впервые появившихся за период</small>
</div>
</div>
</div>
<!-- Satellites Count -->
<div class="col-md-4">
<div class="card stat-card h-100 border-info">
<div class="card-body text-center">
<div class="stat-value text-info">{{ satellite_stats|length }}</div>
<div class="stat-label">Спутников с данными</div>
</div>
</div>
</div>
</div>
<!-- New Emissions Table -->
{% if new_emission_objects %}
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-light">
<i class="bi bi-stars"></i> Новые излучения (уникальные имена, появившиеся впервые в выбранном периоде)
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 320px; overflow-y: auto;">
<table class="table table-sm table-hover table-striped mb-0">
<thead class="table-light sticky-top">
<tr>
<th style="width: 5%;" class="text-center"></th>
<th style="width: 45%;">Имя объекта</th>
<th style="width: 25%;">Тип объекта</th>
<th style="width: 25%;">Принадлежность</th>
</tr>
</thead>
<tbody>
{% for obj in new_emission_objects %}
<tr>
<td class="text-center">{{ forloop.counter }}</td>
<td>{{ obj.name }}</td>
<td>{{ obj.info }}</td>
<td>{{ obj.ownership }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="row">
<!-- Daily Chart -->
<div class="col-md-8 mb-4">
<div class="card h-100">
<div class="card-header">
<i class="bi bi-graph-up"></i> Динамика по дням
</div>
<div class="card-body">
<canvas id="dailyChart"></canvas>
</div>
</div>
</div>
<!-- Satellite Statistics -->
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-header">
<i class="bi bi-broadcast"></i> Статистика по спутникам
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-sm table-hover mb-0">
<thead class="table-light sticky-top">
<tr>
<th>Спутник</th>
<th class="text-center">Точек</th>
<th class="text-center">Объектов</th>
</tr>
</thead>
<tbody>
{% for stat in satellite_stats %}
<tr class="satellite-stat-row">
<td>{{ stat.parameter_obj__id_satellite__name }}</td>
<td class="text-center">
<span class="badge bg-primary">{{ stat.points_count }}</span>
</td>
<td class="text-center">
<span class="badge bg-secondary">{{ stat.sources_count }}</span>
</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center text-muted">Нет данных</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Satellite Charts -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<i class="bi bi-pie-chart"></i> Распределение точек по спутникам
</div>
<div class="card-body">
<canvas id="satellitePieChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<i class="bi bi-bar-chart"></i> Топ-10 спутников по количеству точек
</div>
<div class="card-body">
<canvas id="satelliteBarChart"></canvas>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<!-- <script type="module" src="{% static 'chartjs/color.esm.js' %}"></script> -->
<script src="{% static 'chartjs/chart.js' %}"></script>
<script src="{% static 'chartjs/chart-datalabels.js' %}"></script>
<script src="{% static 'js/checkbox-select-multiple.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize multiselect widget
const wrapper = document.querySelector('.checkbox-multiselect-wrapper[data-widget-id="satellite_id"]');
if (wrapper) {
initCheckboxMultiselect(wrapper);
}
// Preset buttons handling
const presetBtns = document.querySelectorAll('.preset-btn');
const presetInput = document.getElementById('preset-input');
const dateFromInput = document.getElementById('date_from');
const dateToInput = document.getElementById('date_to');
presetBtns.forEach(btn => {
btn.addEventListener('click', function() {
presetBtns.forEach(b => b.classList.remove('active'));
this.classList.add('active');
presetInput.value = this.dataset.preset;
// Clear custom dates when using preset
dateFromInput.value = '';
dateToInput.value = '';
// Submit form
document.getElementById('filter-form').submit();
});
});
// Clear preset when custom dates are entered
dateFromInput.addEventListener('change', function() {
presetInput.value = '';
presetBtns.forEach(b => b.classList.remove('active'));
});
dateToInput.addEventListener('change', function() {
presetInput.value = '';
presetBtns.forEach(b => b.classList.remove('active'));
});
// Register datalabels plugin
Chart.register(ChartDataLabels);
// Daily Chart
const dailyData = {{ daily_data|safe }};
const dailyLabels = dailyData.map(d => {
if (d.date) {
const date = new Date(d.date);
return date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year:"2-digit" });
}
return '';
});
const dailyPoints = dailyData.map(d => d.points);
const dailySources = dailyData.map(d => d.sources);
if (dailyData.length > 0) {
new Chart(document.getElementById('dailyChart'), {
type: 'line',
data: {
labels: dailyLabels,
datasets: [{
label: 'Точки ГЛ',
data: dailyPoints,
borderColor: 'rgb(13, 110, 253)',
backgroundColor: 'rgba(13, 110, 253, 0.1)',
fill: false,
// tension: 0.3
}, {
label: 'Объекты',
data: dailySources,
borderColor: 'rgb(25, 135, 84)',
backgroundColor: 'rgba(25, 135, 84, 0.1)',
fill: false,
// tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
},
datalabels: {
display: false
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Satellite Statistics
const satelliteStats = {{ satellite_stats_json|safe }};
// Pie Chart (top 10)
const top10Stats = satelliteStats.slice(0, 10);
const otherPoints = satelliteStats.slice(10).reduce((sum, s) => sum + s.points_count, 0);
const pieLabels = top10Stats.map(s => s.parameter_obj__id_satellite__name);
const pieData = top10Stats.map(s => s.points_count);
if (otherPoints > 0) {
pieLabels.push('Другие');
pieData.push(otherPoints);
}
const colors = [
'#0d6efd', '#198754', '#dc3545', '#ffc107', '#0dcaf0',
'#6f42c1', '#fd7e14', '#20c997', '#6c757d', '#d63384', '#adb5bd'
];
if (pieData.length > 0) {
new Chart(document.getElementById('satellitePieChart'), {
type: 'doughnut',
data: {
labels: pieLabels,
datasets: [{
data: pieData,
backgroundColor: colors.slice(0, pieData.length)
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right',
},
datalabels: {
color: '#fff',
font: {
weight: 'bold',
size: 11
},
formatter: function(value, context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
if (percentage < 5) return '';
return value + '\n(' + percentage + '%)';
},
textAlign: 'center'
}
}
}
});
}
// Bar Chart (top 10) with data labels
if (top10Stats.length > 0) {
new Chart(document.getElementById('satelliteBarChart'), {
type: 'bar',
data: {
labels: top10Stats.map(s => s.parameter_obj__id_satellite__name),
datasets: [{
label: 'Количество точек',
data: top10Stats.map(s => s.points_count),
backgroundColor: colors.slice(0, top10Stats.length)
}]
},
options: {
responsive: true,
indexAxis: 'y',
plugins: {
legend: {
display: false
},
datalabels: {
anchor: 'end',
align: 'end',
color: '#333',
font: {
weight: 'bold',
size: 11
},
formatter: function(value) {
return value;
}
}
},
scales: {
x: {
beginAtZero: true,
grace: '10%'
}
}
}
});
}
});
</script>
{% endblock %}