Поправил ошибку. Чутка поменял статистику.

This commit is contained in:
2025-12-11 09:48:38 +03:00
parent 41e8dc30fd
commit cf3c7ee01a
3 changed files with 309 additions and 130 deletions

View File

@@ -737,25 +737,6 @@
</div>
<div id="modalErrorMessage" class="alert alert-danger" style="display: none;"></div>
<div id="modalContent" style="display: none;">
<!-- Marks Section -->
<div id="marksSection" class="mb-3" style="display: none;">
<h6 class="mb-2">Наличие сигнала объекта (<span id="marksCount">0</span>):</h6>
<div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr>
<th style="width: 20%;">Наличие сигнала</th>
<th style="width: 40%;">Дата и время</th>
<th style="width: 40%;">Пользователь</th>
</tr>
</thead>
<tbody id="marksTableBody">
<!-- Marks will be loaded here -->
</tbody>
</table>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Связанные точки (<span id="objitemCount">0</span>):</h6>
<div class="dropdown">
@@ -1764,33 +1745,6 @@ function showSourceDetails(sourceId) {
// Hide loading spinner
document.getElementById('modalLoadingSpinner').style.display = 'none';
// Show marks if available
if (data.marks && data.marks.length > 0) {
document.getElementById('marksSection').style.display = 'block';
document.getElementById('marksCount').textContent = data.marks.length;
const marksTableBody = document.getElementById('marksTableBody');
marksTableBody.innerHTML = '';
data.marks.forEach(mark => {
const row = document.createElement('tr');
let markBadge = '<span class="badge bg-secondary">-</span>';
if (mark.mark === true) {
markBadge = '<span class="badge bg-success">Есть</span>';
} else if (mark.mark === false) {
markBadge = '<span class="badge bg-danger">Нет</span>';
}
row.innerHTML = '<td class="text-center">' + markBadge + '</td>' +
'<td>' + mark.timestamp + '</td>' +
'<td>' + mark.created_by + '</td>';
marksTableBody.appendChild(row);
});
} else {
document.getElementById('marksSection').style.display = 'none';
}
if (data.objitems && data.objitems.length > 0) {
// Show content
document.getElementById('modalContent').style.display = 'block';

View File

@@ -80,6 +80,39 @@
.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;
}
</style>
{% endblock %}
@@ -331,7 +364,7 @@
<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> Топ-10 спутников по количеству точек
<i class="bi bi-bar-chart"></i> Выбранные спутники по количеству точек
</div>
<div class="card-body">
<canvas id="satelliteBarChart"></canvas>
@@ -441,6 +474,33 @@
</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">
@@ -520,10 +580,10 @@ document.addEventListener('DOMContentLoaded', function() {
presetBtns.forEach(b => b.classList.remove('active'));
});
// Register datalabels plugin
// Register plugins
Chart.register(ChartDataLabels);
// Daily Chart
// Daily Chart with zoom
const dailyData = {{ daily_data|safe }};
const dailyLabels = dailyData.map(d => {
if (d.date) {
@@ -535,8 +595,9 @@ document.addEventListener('DOMContentLoaded', function() {
const dailyPoints = dailyData.map(d => d.points);
const dailySources = dailyData.map(d => d.sources);
let dailyChart = null;
if (dailyData.length > 0) {
new Chart(document.getElementById('dailyChart'), {
dailyChart = new Chart(document.getElementById('dailyChart'), {
type: 'line',
data: {
labels: dailyLabels,
@@ -559,6 +620,10 @@ document.addEventListener('DOMContentLoaded', function() {
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
position: 'top',
@@ -576,103 +641,263 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
// Satellite Statistics
// Satellite Statistics with selection
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 satelliteSettingsKey = 'statistics_satellite_selection';
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'
}
}
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();
});
});
}
// 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
// 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();
}
}
// 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
}
}
},
formatter: function(value) {
return value;
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
}]
},
scales: {
x: {
beginAtZero: true,
grace: '10%'
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: 11
},
formatter: function(value) {
return 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();
});
}
}
// Initialize satellite functionality
initSatelliteCheckboxes();
initSatelliteButtons();
initCharts();
// 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';