Подправил частотный план
This commit is contained in:
@@ -17,12 +17,7 @@
|
|||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow-x: auto;
|
height: 400px;
|
||||||
}
|
|
||||||
|
|
||||||
#frequencyCanvas {
|
|
||||||
display: block;
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend {
|
.legend {
|
||||||
@@ -44,18 +39,16 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transponder-tooltip {
|
.chart-controls {
|
||||||
position: absolute;
|
display: flex;
|
||||||
background: rgba(0, 0, 0, 0.9);
|
gap: 10px;
|
||||||
color: white;
|
margin-bottom: 15px;
|
||||||
padding: 10px;
|
flex-wrap: wrap;
|
||||||
border-radius: 4px;
|
}
|
||||||
font-size: 0.85rem;
|
|
||||||
pointer-events: none;
|
.chart-controls button {
|
||||||
z-index: 1000;
|
padding: 5px 15px;
|
||||||
display: none;
|
font-size: 0.9rem;
|
||||||
max-width: 300px;
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -214,16 +207,28 @@
|
|||||||
|
|
||||||
{% if action == 'update' and transponders %}
|
{% if action == 'update' and transponders %}
|
||||||
<!-- Frequency Plan Visualization -->
|
<!-- Frequency Plan Visualization -->
|
||||||
<!-- <div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4>Частотный план</h4>
|
<h4>Частотный план</h4>
|
||||||
<p class="text-muted">Визуализация транспондеров спутника по частотам (Downlink). Наведите курсор на транспондер для подробной информации.</p>
|
<p class="text-muted">Визуализация транспондеров спутника по частотам (Downlink). Используйте колесико мыши для масштабирования, наведите курсор на полосу для подробной информации.</p>
|
||||||
|
|
||||||
<div class="frequency-plan">
|
<div class="frequency-plan">
|
||||||
|
<div class="chart-controls">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" id="resetZoom">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i> Сбросить масштаб
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="zoomIn">
|
||||||
|
<i class="bi bi-zoom-in"></i> Увеличить
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="zoomOut">
|
||||||
|
<i class="bi bi-zoom-out"></i> Уменьшить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="frequency-chart-container">
|
<div class="frequency-chart-container">
|
||||||
<canvas id="frequencyCanvas"></canvas>
|
<canvas id="frequencyChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
@@ -256,9 +261,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<div class="transponder-tooltip" id="transponderTooltip"></div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -267,7 +270,7 @@
|
|||||||
{% if action == 'update' and transponders %}
|
{% if action == 'update' and transponders %}
|
||||||
<script>
|
<script>
|
||||||
// Transponder data from Django
|
// Transponder data from Django
|
||||||
const transponders = {{ transponders|safe }};
|
const transpondersData = {{ transponders|safe }};
|
||||||
|
|
||||||
// Color mapping for polarizations
|
// Color mapping for polarizations
|
||||||
const polarizationColors = {
|
const polarizationColors = {
|
||||||
@@ -278,88 +281,129 @@ const polarizationColors = {
|
|||||||
'default': '#6c757d'
|
'default': '#6c757d'
|
||||||
};
|
};
|
||||||
|
|
||||||
let canvas, ctx, tooltip;
|
|
||||||
let hoveredTransponder = null;
|
|
||||||
|
|
||||||
function getColor(polarization) {
|
function getColor(polarization) {
|
||||||
return polarizationColors[polarization] || polarizationColors['default'];
|
return polarizationColors[polarization] || polarizationColors['default'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFrequencyPlan() {
|
// Chart state
|
||||||
if (!transponders || transponders.length === 0) {
|
let canvas, ctx, container;
|
||||||
|
let zoomLevel = 1;
|
||||||
|
let panOffset = 0;
|
||||||
|
let isDragging = false;
|
||||||
|
let dragStartX = 0;
|
||||||
|
let dragStartOffset = 0;
|
||||||
|
let hoveredTransponder = null;
|
||||||
|
let transponderRects = [];
|
||||||
|
|
||||||
|
// Frequency range
|
||||||
|
let minFreq, maxFreq, freqRange;
|
||||||
|
let originalMinFreq, originalMaxFreq, originalFreqRange;
|
||||||
|
|
||||||
|
function initializeFrequencyChart() {
|
||||||
|
if (!transpondersData || transpondersData.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas = document.getElementById('frequencyCanvas');
|
canvas = document.getElementById('frequencyChart');
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
container = canvas.parentElement;
|
||||||
ctx = canvas.getContext('2d');
|
ctx = canvas.getContext('2d');
|
||||||
tooltip = document.getElementById('transponderTooltip');
|
|
||||||
|
|
||||||
// Find min and max frequencies
|
// Calculate frequency range
|
||||||
let minFreq = Infinity;
|
minFreq = Infinity;
|
||||||
let maxFreq = -Infinity;
|
maxFreq = -Infinity;
|
||||||
|
|
||||||
transponders.forEach(t => {
|
transpondersData.forEach(t => {
|
||||||
const startFreq = t.downlink - (t.frequency_range / 2);
|
const startFreq = t.downlink - (t.frequency_range / 2);
|
||||||
const endFreq = t.downlink + (t.frequency_range / 2);
|
const endFreq = t.downlink + (t.frequency_range / 2);
|
||||||
minFreq = Math.min(minFreq, startFreq);
|
minFreq = Math.min(minFreq, startFreq);
|
||||||
maxFreq = Math.max(maxFreq, endFreq);
|
maxFreq = Math.max(maxFreq, endFreq);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add padding (5%)
|
// Add 2% padding
|
||||||
const padding = (maxFreq - minFreq) * 0.05;
|
const padding = (maxFreq - minFreq) * 0.02;
|
||||||
minFreq -= padding;
|
minFreq -= padding;
|
||||||
maxFreq += padding;
|
maxFreq += padding;
|
||||||
|
|
||||||
const freqRange = maxFreq - minFreq;
|
// Store original values
|
||||||
|
originalMinFreq = minFreq;
|
||||||
|
originalMaxFreq = maxFreq;
|
||||||
|
originalFreqRange = maxFreq - minFreq;
|
||||||
|
freqRange = originalFreqRange;
|
||||||
|
|
||||||
|
// Setup event listeners
|
||||||
|
canvas.addEventListener('wheel', handleWheel, { passive: false });
|
||||||
|
canvas.addEventListener('mousedown', handleMouseDown);
|
||||||
|
canvas.addEventListener('mousemove', handleMouseMove);
|
||||||
|
canvas.addEventListener('mouseup', handleMouseUp);
|
||||||
|
canvas.addEventListener('mouseleave', handleMouseLeave);
|
||||||
|
|
||||||
|
renderChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderChart() {
|
||||||
|
if (!canvas || !ctx) return;
|
||||||
|
|
||||||
// Set canvas size
|
// Set canvas size
|
||||||
const container = canvas.parentElement;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
const canvasWidth = Math.max(container.clientWidth - 40, 800);
|
const rect = container.getBoundingClientRect();
|
||||||
const rowHeight = 50;
|
const width = rect.width;
|
||||||
const topMargin = 40;
|
const height = rect.height;
|
||||||
const bottomMargin = 60;
|
|
||||||
|
|
||||||
// Group transponders by polarization to stack them
|
canvas.width = width * dpr;
|
||||||
|
canvas.height = height * dpr;
|
||||||
|
canvas.style.width = width + 'px';
|
||||||
|
canvas.style.height = height + 'px';
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
|
||||||
|
// Clear canvas
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// Layout constants
|
||||||
|
const leftMargin = 60;
|
||||||
|
const rightMargin = 20;
|
||||||
|
const topMargin = 40;
|
||||||
|
const bottomMargin = 40;
|
||||||
|
const chartWidth = width - leftMargin - rightMargin;
|
||||||
|
const chartHeight = height - topMargin - bottomMargin;
|
||||||
|
|
||||||
|
// Group transponders by polarization
|
||||||
const polarizationGroups = {};
|
const polarizationGroups = {};
|
||||||
transponders.forEach(t => {
|
transpondersData.forEach(t => {
|
||||||
const pol = t.polarization || 'default';
|
const pol = t.polarization || 'Другая';
|
||||||
if (!polarizationGroups[pol]) {
|
if (!polarizationGroups[pol]) {
|
||||||
polarizationGroups[pol] = [];
|
polarizationGroups[pol] = [];
|
||||||
}
|
}
|
||||||
polarizationGroups[pol].push(t);
|
polarizationGroups[pol].push(t);
|
||||||
});
|
});
|
||||||
|
|
||||||
const numRows = Object.keys(polarizationGroups).length;
|
const polarizations = Object.keys(polarizationGroups);
|
||||||
const canvasHeight = topMargin + (numRows * rowHeight) + bottomMargin;
|
const rowHeight = chartHeight / polarizations.length;
|
||||||
|
|
||||||
// Set canvas dimensions (use device pixel ratio for sharp rendering)
|
// Calculate visible frequency range with zoom and pan
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const visibleFreqRange = freqRange / zoomLevel;
|
||||||
canvas.width = canvasWidth * dpr;
|
const centerFreq = (minFreq + maxFreq) / 2;
|
||||||
canvas.height = canvasHeight * dpr;
|
const visibleMinFreq = centerFreq - visibleFreqRange / 2 + panOffset;
|
||||||
canvas.style.width = canvasWidth + 'px';
|
const visibleMaxFreq = centerFreq + visibleFreqRange / 2 + panOffset;
|
||||||
canvas.style.height = canvasHeight + 'px';
|
|
||||||
ctx.scale(dpr, dpr);
|
|
||||||
|
|
||||||
// Clear canvas
|
|
||||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
||||||
|
|
||||||
// Draw frequency axis
|
// Draw frequency axis
|
||||||
ctx.strokeStyle = '#dee2e6';
|
ctx.strokeStyle = '#dee2e6';
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, topMargin);
|
ctx.moveTo(leftMargin, topMargin);
|
||||||
ctx.lineTo(canvasWidth, topMargin);
|
ctx.lineTo(width - rightMargin, topMargin);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// Draw frequency labels
|
// Draw frequency labels and grid
|
||||||
ctx.fillStyle = '#6c757d';
|
ctx.fillStyle = '#6c757d';
|
||||||
ctx.font = '12px sans-serif';
|
ctx.font = '11px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
|
|
||||||
const numLabels = 10;
|
const numTicks = 10;
|
||||||
for (let i = 0; i <= numLabels; i++) {
|
for (let i = 0; i <= numTicks; i++) {
|
||||||
const freq = minFreq + (freqRange * i / numLabels);
|
const freq = visibleMinFreq + (visibleMaxFreq - visibleMinFreq) * i / numTicks;
|
||||||
const x = (canvasWidth * i / numLabels);
|
const x = leftMargin + chartWidth * i / numTicks;
|
||||||
|
|
||||||
// Draw tick
|
// Draw tick
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@@ -367,114 +411,266 @@ function renderFrequencyPlan() {
|
|||||||
ctx.lineTo(x, topMargin - 5);
|
ctx.lineTo(x, topMargin - 5);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw grid line
|
||||||
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.05)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, topMargin);
|
||||||
|
ctx.lineTo(x, height - bottomMargin);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.strokeStyle = '#dee2e6';
|
||||||
|
|
||||||
// Draw label
|
// Draw label
|
||||||
ctx.fillText(freq.toFixed(1), x, topMargin - 10);
|
ctx.fillText(freq.toFixed(1), x, topMargin - 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw "МГц" label
|
// Draw axis title
|
||||||
ctx.textAlign = 'right';
|
ctx.fillStyle = '#000';
|
||||||
ctx.fillText('МГц', canvasWidth, topMargin - 25);
|
ctx.font = 'bold 12px sans-serif';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('Частота (МГц)', width / 2, topMargin - 25);
|
||||||
|
|
||||||
// Store transponder positions for hover detection
|
// Draw polarization label
|
||||||
const transponderRects = [];
|
ctx.save();
|
||||||
|
ctx.translate(15, height / 2);
|
||||||
|
ctx.rotate(-Math.PI / 2);
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('Поляризация', 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
// Clear transponder rects for hover detection
|
||||||
|
transponderRects = [];
|
||||||
|
|
||||||
// Draw transponders
|
// Draw transponders
|
||||||
let yOffset = topMargin + 10;
|
polarizations.forEach((pol, index) => {
|
||||||
|
|
||||||
Object.keys(polarizationGroups).forEach((pol, groupIndex) => {
|
|
||||||
const group = polarizationGroups[pol];
|
const group = polarizationGroups[pol];
|
||||||
const color = getColor(pol);
|
const color = getColor(pol);
|
||||||
|
const y = topMargin + index * rowHeight;
|
||||||
|
const barHeight = rowHeight * 0.7;
|
||||||
|
const barY = y + (rowHeight - barHeight) / 2;
|
||||||
|
|
||||||
// Draw polarization label
|
// Draw polarization label
|
||||||
ctx.fillStyle = '#000';
|
ctx.fillStyle = '#000';
|
||||||
ctx.font = 'bold 14px sans-serif';
|
ctx.font = 'bold 12px sans-serif';
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'right';
|
||||||
ctx.fillText(`${pol}:`, 5, yOffset + 20);
|
ctx.fillText(pol, leftMargin - 10, barY + barHeight / 2 + 4);
|
||||||
|
|
||||||
|
// Draw transponders
|
||||||
group.forEach(t => {
|
group.forEach(t => {
|
||||||
const startFreq = t.downlink - (t.frequency_range / 2);
|
const startFreq = t.downlink - (t.frequency_range / 2);
|
||||||
const endFreq = t.downlink + (t.frequency_range / 2);
|
const endFreq = t.downlink + (t.frequency_range / 2);
|
||||||
|
|
||||||
const x = ((startFreq - minFreq) / freqRange) * canvasWidth;
|
// Check if transponder is visible
|
||||||
const width = ((endFreq - startFreq) / freqRange) * canvasWidth;
|
if (endFreq < visibleMinFreq || startFreq > visibleMaxFreq) {
|
||||||
const y = yOffset;
|
return;
|
||||||
const height = 30;
|
}
|
||||||
|
|
||||||
// Draw transponder bar
|
// Calculate position
|
||||||
|
const x1 = leftMargin + ((startFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
||||||
|
const x2 = leftMargin + ((endFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
||||||
|
const barWidth = x2 - x1;
|
||||||
|
|
||||||
|
// Skip if too small
|
||||||
|
if (barWidth < 1) return;
|
||||||
|
|
||||||
|
// Draw bar
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
ctx.fillRect(x, y, width, height);
|
ctx.fillRect(x1, barY, barWidth, barHeight);
|
||||||
|
|
||||||
// Draw border
|
// Draw border
|
||||||
ctx.strokeStyle = '#fff';
|
ctx.strokeStyle = '#fff';
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.strokeRect(x, y, width, height);
|
ctx.strokeRect(x1, barY, barWidth, barHeight);
|
||||||
|
|
||||||
// Draw transponder name if there's enough space
|
// Draw name if there's space
|
||||||
if (width > 50) {
|
if (barWidth > 40) {
|
||||||
ctx.fillStyle = pol === 'R' ? '#000' : '#fff';
|
ctx.fillStyle = (pol === 'R') ? '#000' : '#fff';
|
||||||
ctx.font = '11px sans-serif';
|
ctx.font = '10px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText(t.name, x + width / 2, y + height / 2 + 4);
|
ctx.fillText(t.name, x1 + barWidth / 2, barY + barHeight / 2 + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store for hover detection
|
// Store for hover detection
|
||||||
transponderRects.push({
|
transponderRects.push({
|
||||||
x, y, width, height,
|
x: x1,
|
||||||
data: t
|
y: barY,
|
||||||
|
width: barWidth,
|
||||||
|
height: barHeight,
|
||||||
|
transponder: t
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
yOffset += rowHeight;
|
// Draw hover tooltip
|
||||||
|
if (hoveredTransponder) {
|
||||||
|
drawTooltip(hoveredTransponder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTooltip(t) {
|
||||||
|
const startFreq = t.downlink - (t.frequency_range / 2);
|
||||||
|
const endFreq = t.downlink + (t.frequency_range / 2);
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
t.name,
|
||||||
|
'Диапазон: ' + startFreq.toFixed(3) + ' - ' + endFreq.toFixed(3) + ' МГц',
|
||||||
|
'Downlink: ' + t.downlink.toFixed(3) + ' МГц',
|
||||||
|
'Полоса: ' + t.frequency_range.toFixed(3) + ' МГц',
|
||||||
|
'Поляризация: ' + t.polarization,
|
||||||
|
'Зона: ' + t.zone_name
|
||||||
|
];
|
||||||
|
|
||||||
|
// Calculate tooltip size
|
||||||
|
ctx.font = '12px sans-serif';
|
||||||
|
const padding = 10;
|
||||||
|
const lineHeight = 16;
|
||||||
|
let maxWidth = 0;
|
||||||
|
lines.forEach(line => {
|
||||||
|
const width = ctx.measureText(line).width;
|
||||||
|
maxWidth = Math.max(maxWidth, width);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add mouse move event for tooltip
|
const tooltipWidth = maxWidth + padding * 2;
|
||||||
canvas.addEventListener('mousemove', (e) => {
|
const tooltipHeight = lines.length * lineHeight + padding * 2;
|
||||||
|
|
||||||
|
// Position tooltip
|
||||||
|
const mouseX = hoveredTransponder._mouseX || canvas.width / 2;
|
||||||
|
const mouseY = hoveredTransponder._mouseY || canvas.height / 2;
|
||||||
|
let tooltipX = mouseX + 15;
|
||||||
|
let tooltipY = mouseY + 15;
|
||||||
|
|
||||||
|
// Keep tooltip in bounds
|
||||||
|
if (tooltipX + tooltipWidth > canvas.width) {
|
||||||
|
tooltipX = mouseX - tooltipWidth - 15;
|
||||||
|
}
|
||||||
|
if (tooltipY + tooltipHeight > canvas.height) {
|
||||||
|
tooltipY = mouseY - tooltipHeight - 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw tooltip background
|
||||||
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.9)';
|
||||||
|
ctx.fillRect(tooltipX, tooltipY, tooltipWidth, tooltipHeight);
|
||||||
|
|
||||||
|
// Draw tooltip text
|
||||||
|
ctx.fillStyle = '#fff';
|
||||||
|
ctx.font = 'bold 12px sans-serif';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.fillText(lines[0], tooltipX + padding, tooltipY + padding + 12);
|
||||||
|
|
||||||
|
ctx.font = '11px sans-serif';
|
||||||
|
for (let i = 1; i < lines.length; i++) {
|
||||||
|
ctx.fillText(lines[i], tooltipX + padding, tooltipY + padding + 12 + i * lineHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWheel(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
||||||
|
const newZoom = Math.max(1, Math.min(20, zoomLevel * delta));
|
||||||
|
|
||||||
|
if (newZoom !== zoomLevel) {
|
||||||
|
zoomLevel = newZoom;
|
||||||
|
|
||||||
|
// Adjust pan to keep center
|
||||||
|
const maxPan = (originalFreqRange * (zoomLevel - 1)) / (2 * zoomLevel);
|
||||||
|
panOffset = Math.max(-maxPan, Math.min(maxPan, panOffset));
|
||||||
|
|
||||||
|
renderChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseDown(e) {
|
||||||
|
isDragging = true;
|
||||||
|
dragStartX = e.clientX;
|
||||||
|
dragStartOffset = panOffset;
|
||||||
|
canvas.style.cursor = 'grabbing';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseMove(e) {
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
const mouseX = e.clientX - rect.left;
|
const mouseX = e.clientX - rect.left;
|
||||||
const mouseY = e.clientY - rect.top;
|
const mouseY = e.clientY - rect.top;
|
||||||
|
|
||||||
hoveredTransponder = null;
|
if (isDragging) {
|
||||||
|
const dx = e.clientX - dragStartX;
|
||||||
|
const freqPerPixel = (freqRange / zoomLevel) / (rect.width - 80);
|
||||||
|
panOffset = dragStartOffset - dx * freqPerPixel;
|
||||||
|
|
||||||
|
// Limit pan
|
||||||
|
const maxPan = (originalFreqRange * (zoomLevel - 1)) / (2 * zoomLevel);
|
||||||
|
panOffset = Math.max(-maxPan, Math.min(maxPan, panOffset));
|
||||||
|
|
||||||
|
renderChart();
|
||||||
|
} else {
|
||||||
|
// Check hover
|
||||||
|
let found = null;
|
||||||
for (const tr of transponderRects) {
|
for (const tr of transponderRects) {
|
||||||
if (mouseX >= tr.x && mouseX <= tr.x + tr.width &&
|
if (mouseX >= tr.x && mouseX <= tr.x + tr.width &&
|
||||||
mouseY >= tr.y && mouseY <= tr.y + tr.height) {
|
mouseY >= tr.y && mouseY <= tr.y + tr.height) {
|
||||||
hoveredTransponder = tr.data;
|
found = tr.transponder;
|
||||||
|
found._mouseX = mouseX;
|
||||||
|
found._mouseY = mouseY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hoveredTransponder) {
|
if (found !== hoveredTransponder) {
|
||||||
const startFreq = hoveredTransponder.downlink - (hoveredTransponder.frequency_range / 2);
|
hoveredTransponder = found;
|
||||||
const endFreq = hoveredTransponder.downlink + (hoveredTransponder.frequency_range / 2);
|
canvas.style.cursor = found ? 'pointer' : 'default';
|
||||||
|
renderChart();
|
||||||
tooltip.innerHTML = `<strong>${hoveredTransponder.name}</strong>
|
} else if (found) {
|
||||||
Downlink: ${hoveredTransponder.downlink.toFixed(3)} МГц
|
found._mouseX = mouseX;
|
||||||
Полоса: ${hoveredTransponder.frequency_range.toFixed(3)} МГц
|
found._mouseY = mouseY;
|
||||||
Диапазон: ${startFreq.toFixed(3)} - ${endFreq.toFixed(3)} МГц
|
}
|
||||||
Поляризация: ${hoveredTransponder.polarization}
|
|
||||||
Зона: ${hoveredTransponder.zone_name}`;
|
|
||||||
tooltip.style.display = 'block';
|
|
||||||
tooltip.style.left = (e.pageX + 15) + 'px';
|
|
||||||
tooltip.style.top = (e.pageY + 15) + 'px';
|
|
||||||
} else {
|
|
||||||
tooltip.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
canvas.addEventListener('mouseleave', () => {
|
|
||||||
tooltip.style.display = 'none';
|
|
||||||
hoveredTransponder = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render on page load
|
function handleMouseUp() {
|
||||||
document.addEventListener('DOMContentLoaded', renderFrequencyPlan);
|
isDragging = false;
|
||||||
|
canvas.style.cursor = hoveredTransponder ? 'pointer' : 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
isDragging = false;
|
||||||
|
hoveredTransponder = null;
|
||||||
|
canvas.style.cursor = 'default';
|
||||||
|
renderChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetZoom() {
|
||||||
|
zoomLevel = 1;
|
||||||
|
panOffset = 0;
|
||||||
|
renderChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomIn() {
|
||||||
|
zoomLevel = Math.min(20, zoomLevel * 1.2);
|
||||||
|
renderChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomOut() {
|
||||||
|
zoomLevel = Math.max(1, zoomLevel / 1.2);
|
||||||
|
if (zoomLevel === 1) {
|
||||||
|
panOffset = 0;
|
||||||
|
}
|
||||||
|
renderChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initializeFrequencyChart();
|
||||||
|
|
||||||
|
// Control buttons
|
||||||
|
document.getElementById('resetZoom').addEventListener('click', resetZoom);
|
||||||
|
document.getElementById('zoomIn').addEventListener('click', zoomIn);
|
||||||
|
document.getElementById('zoomOut').addEventListener('click', zoomOut);
|
||||||
|
});
|
||||||
|
|
||||||
// Re-render on window resize
|
// Re-render on window resize
|
||||||
let resizeTimeout;
|
let resizeTimeout;
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
clearTimeout(resizeTimeout);
|
clearTimeout(resizeTimeout);
|
||||||
resizeTimeout = setTimeout(renderFrequencyPlan, 250);
|
resizeTimeout = setTimeout(renderChart, 250);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user