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

1305 lines
56 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;
}
/* Floating settings button */
.floating-settings {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1050;
opacity: 0.7;
transition: opacity 0.3s ease;
}
.floating-settings:hover {
opacity: 1;
}
.settings-btn {
width: 50px;
height: 50px;
border-radius: 50%;
background: #6c757d;
border: none;
color: white;
font-size: 18px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transition: all 0.3s ease;
}
.settings-btn:hover {
background: #5a6268;
transform: rotate(90deg);
box-shadow: 0 6px 20px rgba(0,0,0,0.25);
}
.settings-btn:focus {
box-shadow: 0 0 0 3px rgba(108, 117, 125, 0.25);
}
/* Block visibility classes */
.stats-block {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.stats-block.hidden {
display: none !important;
}
/* Satellite selection styles */
.satellite-selection-container {
background-color: #f8f9fa;
}
.satellite-selection-container .form-check {
padding: 0.25rem 0.5rem;
margin: 0;
border-radius: 0.25rem;
transition: background-color 0.2s;
}
.satellite-selection-container .form-check:hover {
background-color: #e9ecef;
}
.satellite-selection-container .form-check-label {
font-size: 0.875rem;
cursor: pointer;
width: 100%;
display: flex;
align-items: center;
}
/* Chart zoom hint */
.card-body canvas {
cursor: grab;
}
.card-body canvas:active {
cursor: grabbing;
}
/* Summary rows in satellite table */
.table-warning.fw-bold td {
border-bottom: 2px solid #ffc107 !important;
}
.table-info.fw-bold td {
border-bottom: 2px solid #0dcaf0 !important;
}
.satellite-stat-row:hover {
background-color: #f8f9fa !important;
}
</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>
<!-- Location Place filter -->
<div class="col-auto">
<label class="form-label">Комплекс:</label>
<select name="location_place" class="form-select" multiple size="2">
{% for value, label in location_places %}
<option value="{{ value }}" {% if value in selected_location_places %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
</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 stats-block" id="main-stats-block">
<!-- Total Points -->
<div class="col-md-4 stats-block" id="total-points-card">
<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 stats-block" id="new-emissions-card">
<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 stats-block" id="satellites-count-card">
<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 stats-block" id="new-emissions-block">
<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 stats-block" id="charts-row-block">
<!-- Daily Chart -->
<div class="col-md-8 mb-4 stats-block" id="daily-chart-block">
<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 stats-block" id="satellite-table-block">
<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>
<!-- Total summary rows -->
<tr class="table-info">
<td class="text-left"><strong>Всего </strong></td>
<td class="text-center">
<span class="badge bg-warning text-dark fs-6">{{ total_points }}</span>
</td>
<td class="text-center">
<span class="badge bg-warning text-dark fs-6">{{ total_sources }}</span>
</td>
</tr>
<!-- Individual satellite stats -->
{% 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 stats-block" id="satellite-charts-block">
<div class="col-md-6 stats-block" id="pie-chart-block">
<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 stats-block" id="bar-chart-block">
<div class="card">
<div class="card-header">
<i class="bi bi-bar-chart"></i> Выбранные спутники по количеству точек
</div>
<div class="card-body">
<canvas id="satelliteBarChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Source Objects Charts -->
<div class="row mb-4 stats-block" id="source-charts-block">
<div class="col-md-6 stats-block" id="source-pie-chart-block">
<div class="card">
<div class="card-header">
<i class="bi bi-pie-chart"></i> Распределение объектов по спутникам
</div>
<div class="card-body">
<canvas id="sourcePieChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6 stats-block" id="source-bar-chart-block">
<div class="card">
<div class="card-header">
<i class="bi bi-bar-chart"></i> Выбранные спутники по количеству объектов
</div>
<div class="card-body">
<canvas id="sourceBarChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Floating Settings Button -->
<div class="floating-settings">
<button type="button" class="btn settings-btn" data-bs-toggle="modal" data-bs-target="#settingsModal" title="Настройки отображения">
<i class="bi bi-gear"></i>
</button>
</div>
<!-- Settings Modal -->
<div class="modal fade" id="settingsModal" tabindex="-1" aria-labelledby="settingsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="settingsModalLabel">
<i class="bi bi-gear"></i> Настройки отображения
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<p class="text-muted mb-3">Выберите элементы для отображения на странице:</p>
<!-- Main Statistics Section -->
<div class="mb-4">
<h6 class="text-primary mb-2">
<i class="bi bi-bar-chart-line"></i> Основная статистика
</h6>
<div class="ps-3">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-total-points" checked>
<label class="form-check-label" for="show-total-points">
<i class="bi bi-geo-alt text-primary"></i>
Карточка "Точки геолокации"
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-new-emissions-card" checked>
<label class="form-check-label" for="show-new-emissions-card">
<i class="bi bi-star text-success"></i>
Карточка "Новые излучения"
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-satellites-count" checked>
<label class="form-check-label" for="show-satellites-count">
<i class="bi bi-broadcast text-info"></i>
Карточка "Спутники с данными"
</label>
</div>
</div>
</div>
<!-- New Emissions Table -->
{% if new_emission_objects %}
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="show-new-emissions-table" checked>
<label class="form-check-label" for="show-new-emissions-table">
<i class="bi bi-stars text-success"></i>
<strong>Таблица новых излучений</strong>
<small class="d-block text-muted">Детальная таблица новых уникальных излучений</small>
</label>
</div>
</div>
{% endif %}
<!-- Charts Section -->
<div class="mb-4">
<h6 class="text-info mb-2">
<i class="bi bi-graph-up"></i> Графики и диаграммы
</h6>
<div class="ps-3">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-daily-chart" checked>
<label class="form-check-label" for="show-daily-chart">
<i class="bi bi-graph-up text-info"></i>
График динамики по дням
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-satellite-table" checked>
<label class="form-check-label" for="show-satellite-table">
<i class="bi bi-table text-warning"></i>
Таблица статистики по спутникам
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-pie-chart" checked>
<label class="form-check-label" for="show-pie-chart">
<i class="bi bi-pie-chart text-danger"></i>
Круговая диаграмма распределения
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-bar-chart" checked>
<label class="form-check-label" for="show-bar-chart">
<i class="bi bi-bar-chart text-secondary"></i>
Столбчатая диаграмма точек по спутникам
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-source-pie-chart" checked>
<label class="form-check-label" for="show-source-pie-chart">
<i class="bi bi-pie-chart text-success"></i>
Круговая диаграмма объектов по спутникам
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="show-source-bar-chart" checked>
<label class="form-check-label" for="show-source-bar-chart">
<i class="bi bi-bar-chart text-warning"></i>
Столбчатая диаграмма объектов по спутникам
</label>
</div>
</div>
</div>
<!-- Satellite Selection for Charts -->
<div class="mb-4">
<h6 class="text-warning mb-2">
<i class="bi bi-broadcast"></i> Спутники для диаграмм
</h6>
<div class="ps-3">
<div class="mb-3">
<div class="btn-group w-100 mb-2" role="group">
<button type="button" class="btn btn-outline-success btn-sm" id="select-all-satellites">
<i class="bi bi-check-all"></i> Все спутники
</button>
<button type="button" class="btn btn-outline-warning btn-sm" id="select-top10-satellites">
<i class="bi bi-trophy"></i> Топ-10
</button>
<button type="button" class="btn btn-outline-danger btn-sm" id="clear-satellites">
<i class="bi bi-x-circle"></i> Очистить
</button>
</div>
<div class="satellite-selection-container" style="max-height: 200px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 0.375rem; padding: 1.4rem;">
<div id="satellite-checkboxes">
<!-- Будет заполнено JavaScript -->
</div>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="mb-3">
<h6 class="text-secondary mb-2">
<i class="bi bi-lightning"></i> Быстрые действия
</h6>
<div class="btn-group w-100" role="group">
<button type="button" class="btn btn-outline-success btn-sm" id="select-all">
<i class="bi bi-check-all"></i> Все
</button>
<button type="button" class="btn btn-outline-warning btn-sm" id="select-cards-only">
<i class="bi bi-card-text"></i> Только карточки
</button>
<button type="button" class="btn btn-outline-info btn-sm" id="select-charts-only">
<i class="bi bi-graph-up"></i> Только графики
</button>
<button type="button" class="btn btn-outline-danger btn-sm" id="select-none">
<i class="bi bi-x-circle"></i> Ничего
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" id="reset-settings">
<i class="bi bi-arrow-clockwise"></i> Сбросить
</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">
<i class="bi bi-check-lg"></i> Применить
</button>
</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 plugins
Chart.register(ChartDataLabels);
// Daily Chart with zoom
const dailyData = {{ daily_data|safe }};
const dailyLabels = dailyData.map(d => {
if (d.date) {
const date = new Date(d.date);
const dateStr = date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year:"2-digit" });
const dayStr = date.toLocaleDateString('ru-RU', { weekday: 'short' });
return dateStr + ' (' + dayStr + ')';
}
return '';
});
const dailyPoints = dailyData.map(d => d.points);
const dailySources = dailyData.map(d => d.sources);
let dailyChart = null;
if (dailyData.length > 0) {
dailyChart = 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,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
position: 'top',
},
datalabels: {
display: false
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Satellite Statistics with selection
const satelliteStats = {{ satellite_stats_json|safe }};
const satelliteSettingsKey = 'statistics_satellite_selection';
const colors = [
'#0d6efd', '#198754', '#dc3545', '#ffc107', '#0dcaf0',
'#6f42c1', '#fd7e14', '#20c997', '#6c757d', '#d63384', '#adb5bd'
];
let pieChart = null;
let barChart = null;
// Load satellite selection settings
function loadSatelliteSettings() {
const saved = localStorage.getItem(satelliteSettingsKey);
if (saved) {
try {
return JSON.parse(saved);
} catch (e) {
console.warn('Failed to parse satellite settings:', e);
}
}
// Default: all satellites selected
return satelliteStats.reduce((acc, stat) => {
acc[stat.parameter_obj__id_satellite__name] = true;
return acc;
}, {});
}
// Save satellite selection settings
function saveSatelliteSettings(settings) {
localStorage.setItem(satelliteSettingsKey, JSON.stringify(settings));
}
// Initialize satellite checkboxes
function initSatelliteCheckboxes() {
const container = document.getElementById('satellite-checkboxes');
const settings = loadSatelliteSettings();
container.innerHTML = '';
satelliteStats.forEach((stat, index) => {
const satelliteName = stat.parameter_obj__id_satellite__name;
const isChecked = settings[satelliteName] !== false;
const checkboxHtml = `
<div class="form-check mb-1">
<input class="form-check-input satellite-checkbox" type="checkbox"
id="sat-${index}" value="${satelliteName}" ${isChecked ? 'checked' : ''}>
<label class="form-check-label" for="sat-${index}">
<span class="badge" style="background-color: ${colors[index % colors.length]}; width: 12px; height: 12px; display: inline-block; margin-right: 5px;"></span>
${satelliteName} (${stat.points_count})
</label>
</div>
`;
container.insertAdjacentHTML('beforeend', checkboxHtml);
});
// Add event listeners
container.querySelectorAll('.satellite-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const settings = loadSatelliteSettings();
settings[this.value] = this.checked;
saveSatelliteSettings(settings);
updateCharts();
});
});
}
// Get filtered satellite data based on selection
function getFilteredSatelliteData() {
const settings = loadSatelliteSettings();
return satelliteStats.filter(stat => settings[stat.parameter_obj__id_satellite__name] !== false);
}
// Update charts based on satellite selection
function updateCharts() {
const filteredStats = getFilteredSatelliteData();
// Update pie chart
if (pieChart) {
const pieLabels = filteredStats.map(s => s.parameter_obj__id_satellite__name);
const pieData = filteredStats.map(s => s.points_count);
const pieColors = filteredStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
pieChart.data.labels = pieLabels;
pieChart.data.datasets[0].data = pieData;
pieChart.data.datasets[0].backgroundColor = pieColors;
pieChart.update();
}
// Update bar chart (limit to top 15 for readability)
if (barChart) {
const topStats = filteredStats.slice(0, 15);
const barLabels = topStats.map(s => s.parameter_obj__id_satellite__name);
const barData = topStats.map(s => s.points_count);
const barColors = topStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
barChart.data.labels = barLabels;
barChart.data.datasets[0].data = barData;
barChart.data.datasets[0].backgroundColor = barColors;
barChart.update();
}
// Update source charts
updateSourceCharts();
}
// Initialize charts
function initCharts() {
const filteredStats = getFilteredSatelliteData();
// Pie Chart
if (filteredStats.length > 0) {
const pieLabels = filteredStats.map(s => s.parameter_obj__id_satellite__name);
const pieData = filteredStats.map(s => s.points_count);
const pieColors = filteredStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
pieChart = new Chart(document.getElementById('satellitePieChart'), {
type: 'doughnut',
data: {
labels: pieLabels,
datasets: [{
data: pieData,
backgroundColor: pieColors
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right',
labels: {
boxWidth: 12,
font: {
size: 11
}
}
},
datalabels: {
color: '#fff',
font: {
weight: 'bold',
size: 10
},
formatter: function(value, context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
if (percentage < 3) return '';
return value + '\n(' + percentage + '%)';
},
textAlign: 'center'
}
}
}
});
}
// Bar Chart (top 15)
if (filteredStats.length > 0) {
const topStats = filteredStats.slice(0, 15);
const barLabels = topStats.map(s => s.parameter_obj__id_satellite__name);
const barData = topStats.map(s => s.points_count);
const barColors = topStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
barChart = new Chart(document.getElementById('satelliteBarChart'), {
type: 'bar',
data: {
labels: barLabels,
datasets: [{
label: 'Количество точек',
data: barData,
backgroundColor: barColors
}]
},
options: {
responsive: true,
indexAxis: 'y',
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
display: false
},
datalabels: {
anchor: 'end',
align: 'end',
color: '#333',
font: {
weight: 'bold',
size: 12
},
formatter: function(value, context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return percentage + '%\n(' + value + ')';
}
}
},
scales: {
x: {
beginAtZero: true,
grace: '10%'
}
}
}
});
}
}
// Satellite selection buttons
function initSatelliteButtons() {
const selectAllSatBtn = document.getElementById('select-all-satellites');
const selectTop10Btn = document.getElementById('select-top10-satellites');
const clearSatBtn = document.getElementById('clear-satellites');
if (selectAllSatBtn) {
selectAllSatBtn.addEventListener('click', function() {
const settings = {};
satelliteStats.forEach(stat => {
settings[stat.parameter_obj__id_satellite__name] = true;
});
saveSatelliteSettings(settings);
initSatelliteCheckboxes();
updateCharts();
});
}
if (selectTop10Btn) {
selectTop10Btn.addEventListener('click', function() {
const settings = {};
satelliteStats.forEach((stat, index) => {
settings[stat.parameter_obj__id_satellite__name] = index < 10;
});
saveSatelliteSettings(settings);
initSatelliteCheckboxes();
updateCharts();
});
}
if (clearSatBtn) {
clearSatBtn.addEventListener('click', function() {
const settings = {};
satelliteStats.forEach(stat => {
settings[stat.parameter_obj__id_satellite__name] = false;
});
saveSatelliteSettings(settings);
initSatelliteCheckboxes();
updateCharts();
});
}
}
// Source object charts (similar to satellite charts but for sources count)
let sourcePieChart = null;
let sourceBarChart = null;
// Initialize source charts
function initSourceCharts() {
const filteredStats = getFilteredSatelliteData();
// Source Pie Chart
if (filteredStats.length > 0) {
const sourcePieLabels = filteredStats.map(s => s.parameter_obj__id_satellite__name);
const sourcePieData = filteredStats.map(s => s.sources_count);
const sourcePieColors = filteredStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
sourcePieChart = new Chart(document.getElementById('sourcePieChart'), {
type: 'doughnut',
data: {
labels: sourcePieLabels,
datasets: [{
data: sourcePieData,
backgroundColor: sourcePieColors
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right',
labels: {
boxWidth: 12,
font: {
size: 11
}
}
},
datalabels: {
color: '#fff',
font: {
weight: 'bold',
size: 10
},
formatter: function(value, context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
if (percentage < 3) return '';
return value + '\n(' + percentage + '%)';
},
textAlign: 'center'
}
}
}
});
}
// Source Bar Chart (top 15)
if (filteredStats.length > 0) {
const topSourceStats = filteredStats.slice(0, 15);
const sourceBarLabels = topSourceStats.map(s => s.parameter_obj__id_satellite__name);
const sourceBarData = topSourceStats.map(s => s.sources_count);
const sourceBarColors = topSourceStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
sourceBarChart = new Chart(document.getElementById('sourceBarChart'), {
type: 'bar',
data: {
labels: sourceBarLabels,
datasets: [{
label: 'Количество объектов',
data: sourceBarData,
backgroundColor: sourceBarColors
}]
},
options: {
responsive: true,
indexAxis: 'y',
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
display: false
},
datalabels: {
anchor: 'end',
align: 'end',
color: '#333',
font: {
weight: 'bold',
size: 12
},
formatter: function(value, context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return percentage + '%\n(' + value + ')';
}
}
},
scales: {
x: {
beginAtZero: true,
grace: '10%'
}
}
}
});
}
}
// Update source charts based on satellite selection
function updateSourceCharts() {
const filteredStats = getFilteredSatelliteData();
// Update source pie chart
if (sourcePieChart) {
const sourcePieLabels = filteredStats.map(s => s.parameter_obj__id_satellite__name);
const sourcePieData = filteredStats.map(s => s.sources_count);
const sourcePieColors = filteredStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
sourcePieChart.data.labels = sourcePieLabels;
sourcePieChart.data.datasets[0].data = sourcePieData;
sourcePieChart.data.datasets[0].backgroundColor = sourcePieColors;
sourcePieChart.update();
}
// Update source bar chart (limit to top 15 for readability)
if (sourceBarChart) {
const topSourceStats = filteredStats.slice(0, 15);
const sourceBarLabels = topSourceStats.map(s => s.parameter_obj__id_satellite__name);
const sourceBarData = topSourceStats.map(s => s.sources_count);
const sourceBarColors = topSourceStats.map((s, i) => colors[satelliteStats.indexOf(s) % colors.length]);
sourceBarChart.data.labels = sourceBarLabels;
sourceBarChart.data.datasets[0].data = sourceBarData;
sourceBarChart.data.datasets[0].backgroundColor = sourceBarColors;
sourceBarChart.update();
}
}
// Initialize satellite functionality
initSatelliteCheckboxes();
initSatelliteButtons();
initCharts();
initSourceCharts();
// Note: Zoom functionality temporarily disabled due to plugin loading issues
// Can be re-enabled when zoom plugin is properly configured
// Settings functionality
const settingsKey = 'statistics_display_settings_detailed';
// Detailed block visibility mapping
const blockSettings = {
// Main statistics cards
'show-total-points': 'total-points-card',
'show-new-emissions-card': 'new-emissions-card',
'show-satellites-count': 'satellites-count-card',
// Tables and detailed views
'show-new-emissions-table': 'new-emissions-block',
'show-daily-chart': 'daily-chart-block',
'show-satellite-table': 'satellite-table-block',
// Satellite charts
'show-pie-chart': 'pie-chart-block',
'show-bar-chart': 'bar-chart-block',
// Source object charts
'show-source-pie-chart': 'source-pie-chart-block',
'show-source-bar-chart': 'source-bar-chart-block'
};
// Load settings from localStorage
function loadSettings() {
const saved = localStorage.getItem(settingsKey);
if (saved) {
try {
return JSON.parse(saved);
} catch (e) {
console.warn('Failed to parse settings:', e);
}
}
// Default: all visible
return Object.keys(blockSettings).reduce((acc, key) => {
acc[key] = true;
return acc;
}, {});
}
// Save settings to localStorage
function saveSettings(settings) {
localStorage.setItem(settingsKey, JSON.stringify(settings));
}
// Apply visibility settings
function applySettings(settings) {
Object.entries(blockSettings).forEach(([checkboxId, blockId]) => {
const checkbox = document.getElementById(checkboxId);
const block = document.getElementById(blockId);
if (checkbox && block) {
checkbox.checked = settings[checkboxId] !== false;
if (settings[checkboxId] === false) {
block.classList.add('hidden');
} else {
block.classList.remove('hidden');
}
}
});
// Handle parent containers visibility
updateParentContainers();
}
// Update parent container visibility based on children
function updateParentContainers() {
// Main stats block
const mainStatsVisible = ['show-total-points', 'show-new-emissions-card', 'show-satellites-count']
.some(id => !document.getElementById(blockSettings[id])?.classList.contains('hidden'));
const mainStatsBlock = document.getElementById('main-stats-block');
if (mainStatsBlock) {
if (mainStatsVisible) {
mainStatsBlock.classList.remove('hidden');
} else {
mainStatsBlock.classList.add('hidden');
}
}
// Charts row block
const chartsRowVisible = ['show-daily-chart', 'show-satellite-table']
.some(id => !document.getElementById(blockSettings[id])?.classList.contains('hidden'));
const chartsRowBlock = document.getElementById('charts-row-block');
if (chartsRowBlock) {
if (chartsRowVisible) {
chartsRowBlock.classList.remove('hidden');
} else {
chartsRowBlock.classList.add('hidden');
}
}
// Satellite charts block
const satelliteChartsVisible = ['show-pie-chart', 'show-bar-chart']
.some(id => !document.getElementById(blockSettings[id])?.classList.contains('hidden'));
const satelliteChartsBlock = document.getElementById('satellite-charts-block');
if (satelliteChartsBlock) {
if (satelliteChartsVisible) {
satelliteChartsBlock.classList.remove('hidden');
} else {
satelliteChartsBlock.classList.add('hidden');
}
}
// Source charts block
const sourceChartsVisible = ['show-source-pie-chart', 'show-source-bar-chart']
.some(id => !document.getElementById(blockSettings[id])?.classList.contains('hidden'));
const sourceChartsBlock = document.getElementById('source-charts-block');
if (sourceChartsBlock) {
if (sourceChartsVisible) {
sourceChartsBlock.classList.remove('hidden');
} else {
sourceChartsBlock.classList.add('hidden');
}
}
}
// Initialize settings
let currentSettings = loadSettings();
applySettings(currentSettings);
// Handle checkbox changes
Object.keys(blockSettings).forEach(checkboxId => {
const checkbox = document.getElementById(checkboxId);
if (checkbox) {
checkbox.addEventListener('change', function() {
currentSettings[checkboxId] = this.checked;
saveSettings(currentSettings);
applySettings(currentSettings);
});
}
});
// Quick action buttons
const selectAllBtn = document.getElementById('select-all');
const selectCardsOnlyBtn = document.getElementById('select-cards-only');
const selectChartsOnlyBtn = document.getElementById('select-charts-only');
const selectNoneBtn = document.getElementById('select-none');
if (selectAllBtn) {
selectAllBtn.addEventListener('click', function() {
Object.keys(blockSettings).forEach(key => {
currentSettings[key] = true;
});
saveSettings(currentSettings);
applySettings(currentSettings);
});
}
if (selectCardsOnlyBtn) {
selectCardsOnlyBtn.addEventListener('click', function() {
const cardKeys = ['show-total-points', 'show-new-emissions-card', 'show-satellites-count'];
Object.keys(blockSettings).forEach(key => {
currentSettings[key] = cardKeys.includes(key);
});
saveSettings(currentSettings);
applySettings(currentSettings);
});
}
if (selectChartsOnlyBtn) {
selectChartsOnlyBtn.addEventListener('click', function() {
const chartKeys = ['show-daily-chart', 'show-satellite-table', 'show-pie-chart', 'show-bar-chart', 'show-source-pie-chart', 'show-source-bar-chart'];
Object.keys(blockSettings).forEach(key => {
currentSettings[key] = chartKeys.includes(key);
});
saveSettings(currentSettings);
applySettings(currentSettings);
});
}
if (selectNoneBtn) {
selectNoneBtn.addEventListener('click', function() {
Object.keys(blockSettings).forEach(key => {
currentSettings[key] = false;
});
saveSettings(currentSettings);
applySettings(currentSettings);
});
}
// Reset settings button
const resetBtn = document.getElementById('reset-settings');
if (resetBtn) {
resetBtn.addEventListener('click', function() {
currentSettings = Object.keys(blockSettings).reduce((acc, key) => {
acc[key] = true;
return acc;
}, {});
saveSettings(currentSettings);
applySettings(currentSettings);
});
}
});
</script>
{% endblock %}