Поправил частотный план
This commit is contained in:
@@ -23,14 +23,12 @@ from .models import (
|
|||||||
Polarization,
|
Polarization,
|
||||||
Modulation,
|
Modulation,
|
||||||
Standard,
|
Standard,
|
||||||
SigmaParMark,
|
|
||||||
ObjectMark,
|
ObjectMark,
|
||||||
ObjectInfo,
|
ObjectInfo,
|
||||||
ObjectOwnership,
|
ObjectOwnership,
|
||||||
SigmaParameter,
|
SigmaParameter,
|
||||||
Parameter,
|
Parameter,
|
||||||
Satellite,
|
Satellite,
|
||||||
Mirror,
|
|
||||||
Geo,
|
Geo,
|
||||||
ObjItem,
|
ObjItem,
|
||||||
CustomUser,
|
CustomUser,
|
||||||
@@ -359,14 +357,14 @@ class ObjectMarkAdmin(BaseAdmin):
|
|||||||
autocomplete_fields = ("source",)
|
autocomplete_fields = ("source",)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SigmaParMark)
|
# @admin.register(SigmaParMark)
|
||||||
class SigmaParMarkAdmin(BaseAdmin):
|
# class SigmaParMarkAdmin(BaseAdmin):
|
||||||
"""Админ-панель для модели SigmaParMark."""
|
# """Админ-панель для модели SigmaParMark."""
|
||||||
|
|
||||||
list_display = ("mark", "timestamp")
|
# list_display = ("mark", "timestamp")
|
||||||
search_fields = ("mark",)
|
# search_fields = ("mark",)
|
||||||
ordering = ("-timestamp",)
|
# ordering = ("-timestamp",)
|
||||||
list_filter = (("timestamp", DateRangeQuickSelectListFilterBuilder()),)
|
# list_filter = (("timestamp", DateRangeQuickSelectListFilterBuilder()),)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Polarization)
|
@admin.register(Polarization)
|
||||||
@@ -417,7 +415,6 @@ class ObjectOwnershipAdmin(BaseAdmin):
|
|||||||
class SigmaParameterInline(admin.StackedInline):
|
class SigmaParameterInline(admin.StackedInline):
|
||||||
model = SigmaParameter
|
model = SigmaParameter
|
||||||
extra = 0
|
extra = 0
|
||||||
autocomplete_fields = ["mark"]
|
|
||||||
readonly_fields = (
|
readonly_fields = (
|
||||||
"datetime_begin",
|
"datetime_begin",
|
||||||
"datetime_end",
|
"datetime_end",
|
||||||
@@ -561,7 +558,6 @@ class SigmaParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
|
|||||||
"modulation__name",
|
"modulation__name",
|
||||||
"standard__name",
|
"standard__name",
|
||||||
)
|
)
|
||||||
autocomplete_fields = ("mark",)
|
|
||||||
ordering = ("-frequency",)
|
ordering = ("-frequency",)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
@@ -589,13 +585,13 @@ class SatelliteAdmin(BaseAdmin):
|
|||||||
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
|
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Mirror)
|
# @admin.register(Mirror)
|
||||||
class MirrorAdmin(ImportExportActionModelAdmin, BaseAdmin):
|
# class MirrorAdmin(ImportExportActionModelAdmin, BaseAdmin):
|
||||||
"""Админ-панель для модели Mirror с поддержкой импорта/экспорта."""
|
# """Админ-панель для модели Mirror с поддержкой импорта/экспорта."""
|
||||||
|
|
||||||
list_display = ("name",)
|
# list_display = ("name",)
|
||||||
search_fields = ("name",)
|
# search_fields = ("name",)
|
||||||
ordering = ("name",)
|
# ordering = ("name",)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Geo)
|
@admin.register(Geo)
|
||||||
|
|||||||
@@ -172,63 +172,63 @@ class ObjectMark(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
# Для обратной совместимости с SigmaParameter
|
# Для обратной совместимости с SigmaParameter
|
||||||
class SigmaParMark(models.Model):
|
# class SigmaParMark(models.Model):
|
||||||
"""
|
# """
|
||||||
Модель отметки о наличии сигнала (для Sigma).
|
# Модель отметки о наличии сигнала (для Sigma).
|
||||||
|
|
||||||
Используется для фиксации моментов времени когда сигнал был обнаружен или потерян.
|
# Используется для фиксации моментов времени когда сигнал был обнаружен или потерян.
|
||||||
"""
|
# """
|
||||||
|
|
||||||
# Основные поля
|
# # Основные поля
|
||||||
mark = models.BooleanField(
|
# mark = models.BooleanField(
|
||||||
null=True,
|
# null=True,
|
||||||
blank=True,
|
# blank=True,
|
||||||
verbose_name="Наличие сигнала",
|
# verbose_name="Наличие сигнала",
|
||||||
help_text="True - сигнал обнаружен, False - сигнал отсутствует",
|
# help_text="True - сигнал обнаружен, False - сигнал отсутствует",
|
||||||
)
|
# )
|
||||||
timestamp = models.DateTimeField(
|
# timestamp = models.DateTimeField(
|
||||||
null=True,
|
# null=True,
|
||||||
blank=True,
|
# blank=True,
|
||||||
verbose_name="Время",
|
# verbose_name="Время",
|
||||||
db_index=True,
|
# db_index=True,
|
||||||
help_text="Время фиксации отметки",
|
# help_text="Время фиксации отметки",
|
||||||
)
|
# )
|
||||||
|
|
||||||
def __str__(self):
|
# def __str__(self):
|
||||||
if self.timestamp:
|
# if self.timestamp:
|
||||||
timestamp = self.timestamp.strftime("%d.%m.%Y %H:%M")
|
# timestamp = self.timestamp.strftime("%d.%m.%Y %H:%M")
|
||||||
return f"+ {timestamp}" if self.mark else f"- {timestamp}"
|
# return f"+ {timestamp}" if self.mark else f"- {timestamp}"
|
||||||
return "Отметка без времени"
|
# return "Отметка без времени"
|
||||||
|
|
||||||
class Meta:
|
# class Meta:
|
||||||
verbose_name = "Отметка сигнала"
|
# verbose_name = "Отметка сигнала"
|
||||||
verbose_name_plural = "Отметки сигналов"
|
# verbose_name_plural = "Отметки сигналов"
|
||||||
ordering = ["-timestamp"]
|
# ordering = ["-timestamp"]
|
||||||
|
|
||||||
|
|
||||||
class Mirror(models.Model):
|
# class Mirror(models.Model):
|
||||||
"""
|
# """
|
||||||
Модель зеркала антенны.
|
# Модель зеркала антенны.
|
||||||
|
|
||||||
Представляет физическое зеркало антенны для приема спутникового сигнала.
|
# Представляет физическое зеркало антенны для приема спутникового сигнала.
|
||||||
"""
|
# """
|
||||||
|
|
||||||
# Основные поля
|
# # Основные поля
|
||||||
name = models.CharField(
|
# name = models.CharField(
|
||||||
max_length=30,
|
# max_length=30,
|
||||||
unique=True,
|
# unique=True,
|
||||||
verbose_name="Имя зеркала",
|
# verbose_name="Имя зеркала",
|
||||||
db_index=True,
|
# db_index=True,
|
||||||
help_text="Уникальное название зеркала антенны",
|
# help_text="Уникальное название зеркала антенны",
|
||||||
)
|
# )
|
||||||
|
|
||||||
def __str__(self):
|
# def __str__(self):
|
||||||
return self.name
|
# return self.name
|
||||||
|
|
||||||
class Meta:
|
# class Meta:
|
||||||
verbose_name = "Зеркало"
|
# verbose_name = "Зеркало"
|
||||||
verbose_name_plural = "Зеркала"
|
# verbose_name_plural = "Зеркала"
|
||||||
ordering = ["name"]
|
# ordering = ["name"]
|
||||||
|
|
||||||
|
|
||||||
class Polarization(models.Model):
|
class Polarization(models.Model):
|
||||||
@@ -1027,7 +1027,7 @@ class SigmaParameter(models.Model):
|
|||||||
verbose_name="Время окончания измерения",
|
verbose_name="Время окончания измерения",
|
||||||
help_text="Дата и время окончания измерения",
|
help_text="Дата и время окончания измерения",
|
||||||
)
|
)
|
||||||
mark = models.ManyToManyField(SigmaParMark, verbose_name="Отметка", blank=True)
|
# mark = models.ManyToManyField(SigmaParMark, verbose_name="Отметка", blank=True)
|
||||||
parameter = models.ForeignKey(
|
parameter = models.ForeignKey(
|
||||||
Parameter,
|
Parameter,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
|||||||
@@ -212,26 +212,26 @@
|
|||||||
<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">Визуализация транспондеров спутника по частотам. <span style="color: #0d6efd;">■</span> Downlink (синий), <span style="color: #fd7e14;">■</span> Uplink (оранжевый). Используйте колесико мыши для масштабирования, наведите курсор на полосу для подробной информации.</p>
|
||||||
|
|
||||||
<div class="frequency-plan">
|
<div class="frequency-plan">
|
||||||
<div class="chart-controls">
|
<div class="chart-controls">
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" id="resetZoom">
|
<button type="button" class="btn btn-sm btn-outline-primary" id="resetZoom">
|
||||||
<i class="bi bi-arrow-clockwise"></i> Сбросить масштаб
|
<i class="bi bi-arrow-clockwise"></i> Сбросить масштаб
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="zoomIn">
|
<!-- <button type="button" class="btn btn-sm btn-outline-secondary" id="zoomIn">
|
||||||
<i class="bi bi-zoom-in"></i> Увеличить
|
<i class="bi bi-zoom-in"></i> Увеличить
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="zoomOut">
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="zoomOut">
|
||||||
<i class="bi bi-zoom-out"></i> Уменьшить
|
<i class="bi bi-zoom-out"></i> Уменьшить
|
||||||
</button>
|
</button> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="frequency-chart-container">
|
<div class="frequency-chart-container">
|
||||||
<canvas id="frequencyChart"></canvas>
|
<canvas id="frequencyChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legend">
|
<!-- <div class="legend">
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<div class="legend-color" style="background-color: #0d6efd;"></div>
|
<div class="legend-color" style="background-color: #0d6efd;"></div>
|
||||||
<span>H - Горизонтальная</span>
|
<span>H - Горизонтальная</span>
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
<div class="legend-color" style="background-color: #6c757d;"></div>
|
<div class="legend-color" style="background-color: #6c757d;"></div>
|
||||||
<span>Другая</span>
|
<span>Другая</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<p><strong>Всего транспондеров:</strong> {{ transponder_count }}</p>
|
<p><strong>Всего транспондеров:</strong> {{ transponder_count }}</p>
|
||||||
@@ -310,19 +310,28 @@ function initializeFrequencyChart() {
|
|||||||
container = canvas.parentElement;
|
container = canvas.parentElement;
|
||||||
ctx = canvas.getContext('2d');
|
ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
// Calculate frequency range
|
// Calculate frequency range (including both downlink and uplink)
|
||||||
minFreq = Infinity;
|
minFreq = Infinity;
|
||||||
maxFreq = -Infinity;
|
maxFreq = -Infinity;
|
||||||
|
|
||||||
transpondersData.forEach(t => {
|
transpondersData.forEach(t => {
|
||||||
const startFreq = t.downlink - (t.frequency_range / 2);
|
// Downlink
|
||||||
const endFreq = t.downlink + (t.frequency_range / 2);
|
const dlStartFreq = t.downlink - (t.frequency_range / 2);
|
||||||
minFreq = Math.min(minFreq, startFreq);
|
const dlEndFreq = t.downlink + (t.frequency_range / 2);
|
||||||
maxFreq = Math.max(maxFreq, endFreq);
|
minFreq = Math.min(minFreq, dlStartFreq);
|
||||||
|
maxFreq = Math.max(maxFreq, dlEndFreq);
|
||||||
|
|
||||||
|
// Uplink (if exists)
|
||||||
|
if (t.uplink) {
|
||||||
|
const ulStartFreq = t.uplink - (t.frequency_range / 2);
|
||||||
|
const ulEndFreq = t.uplink + (t.frequency_range / 2);
|
||||||
|
minFreq = Math.min(minFreq, ulStartFreq);
|
||||||
|
maxFreq = Math.max(maxFreq, ulEndFreq);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add 2% padding
|
// Add 2% padding
|
||||||
const padding = (maxFreq - minFreq) * 0.02;
|
const padding = (maxFreq - minFreq) * 0.04;
|
||||||
minFreq -= padding;
|
minFreq -= padding;
|
||||||
maxFreq += padding;
|
maxFreq += padding;
|
||||||
|
|
||||||
@@ -368,10 +377,12 @@ function renderChart() {
|
|||||||
const chartWidth = width - leftMargin - rightMargin;
|
const chartWidth = width - leftMargin - rightMargin;
|
||||||
const chartHeight = height - topMargin - bottomMargin;
|
const chartHeight = height - topMargin - bottomMargin;
|
||||||
|
|
||||||
// Group transponders by polarization
|
// Group transponders by polarization (use first letter only)
|
||||||
const polarizationGroups = {};
|
const polarizationGroups = {};
|
||||||
transpondersData.forEach(t => {
|
transpondersData.forEach(t => {
|
||||||
const pol = t.polarization || 'Другая';
|
let pol = t.polarization || '-';
|
||||||
|
// Take only first letter for abbreviation
|
||||||
|
pol = pol.charAt(0).toUpperCase();
|
||||||
if (!polarizationGroups[pol]) {
|
if (!polarizationGroups[pol]) {
|
||||||
polarizationGroups[pol] = [];
|
polarizationGroups[pol] = [];
|
||||||
}
|
}
|
||||||
@@ -379,7 +390,8 @@ function renderChart() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const polarizations = Object.keys(polarizationGroups);
|
const polarizations = Object.keys(polarizationGroups);
|
||||||
const rowHeight = chartHeight / polarizations.length;
|
// Each polarization gets 2 rows (downlink + uplink)
|
||||||
|
const rowHeight = chartHeight / (polarizations.length * 2);
|
||||||
|
|
||||||
// Calculate visible frequency range with zoom and pan
|
// Calculate visible frequency range with zoom and pan
|
||||||
const visibleFreqRange = freqRange / zoomLevel;
|
const visibleFreqRange = freqRange / zoomLevel;
|
||||||
@@ -443,18 +455,41 @@ function renderChart() {
|
|||||||
// Draw transponders
|
// Draw transponders
|
||||||
polarizations.forEach((pol, index) => {
|
polarizations.forEach((pol, index) => {
|
||||||
const group = polarizationGroups[pol];
|
const group = polarizationGroups[pol];
|
||||||
const color = getColor(pol);
|
const downlinkColor = '#0000ff'; //getColor(pol);
|
||||||
const y = topMargin + index * rowHeight;
|
const uplinkColor = '#fd7e14';
|
||||||
const barHeight = rowHeight * 0.7;
|
|
||||||
const barY = y + (rowHeight - barHeight) / 2;
|
|
||||||
|
|
||||||
// Draw polarization label
|
// Downlink row
|
||||||
|
const downlinkY = topMargin + (index * 2) * rowHeight;
|
||||||
|
const downlinkBarHeight = rowHeight * 0.8;
|
||||||
|
const downlinkBarY = downlinkY + (rowHeight - downlinkBarHeight) / 2;
|
||||||
|
|
||||||
|
// Uplink row
|
||||||
|
const uplinkY = topMargin + (index * 2 + 1) * rowHeight;
|
||||||
|
const uplinkBarHeight = rowHeight * 0.8;
|
||||||
|
const uplinkBarY = uplinkY + (rowHeight - uplinkBarHeight) / 2;
|
||||||
|
|
||||||
|
// Draw polarization label (centered between downlink and uplink)
|
||||||
ctx.fillStyle = '#000';
|
ctx.fillStyle = '#000';
|
||||||
ctx.font = 'bold 12px sans-serif';
|
ctx.font = 'bold 14px sans-serif';
|
||||||
ctx.textAlign = 'right';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText(pol, leftMargin - 10, barY + barHeight / 2 + 4);
|
const labelY = downlinkY + rowHeight;
|
||||||
|
ctx.fillText(pol, leftMargin - 25, labelY);
|
||||||
|
|
||||||
// Draw transponders
|
// Draw "DL" and "UL" labels
|
||||||
|
ctx.font = '10px sans-serif';
|
||||||
|
ctx.fillStyle = '#666';
|
||||||
|
ctx.fillText('DL', leftMargin - 5, downlinkBarY + downlinkBarHeight / 2 + 3);
|
||||||
|
ctx.fillText('UL', leftMargin - 5, uplinkBarY + uplinkBarHeight / 2 + 3);
|
||||||
|
|
||||||
|
// Draw separator line between DL and UL
|
||||||
|
ctx.strokeStyle = '#dee2e6';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(leftMargin, uplinkY);
|
||||||
|
ctx.lineTo(width - rightMargin, uplinkY);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw downlink 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);
|
||||||
@@ -464,6 +499,52 @@ function renderChart() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const x1 = leftMargin + ((startFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
||||||
|
const x2 = leftMargin + ((endFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
||||||
|
const barWidth = x2 - x1;
|
||||||
|
|
||||||
|
if (barWidth < 1) return;
|
||||||
|
|
||||||
|
// Draw downlink bar
|
||||||
|
ctx.fillStyle = downlinkColor;
|
||||||
|
ctx.fillRect(x1, downlinkBarY, barWidth, downlinkBarHeight);
|
||||||
|
|
||||||
|
// Draw border
|
||||||
|
ctx.strokeStyle = '#fff';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeRect(x1, downlinkBarY, barWidth, downlinkBarHeight);
|
||||||
|
|
||||||
|
// Draw name if there's space
|
||||||
|
if (barWidth > 40) {
|
||||||
|
ctx.fillStyle = (pol === 'R') ? '#000' : '#fff';
|
||||||
|
ctx.font = '9px sans-serif';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(t.name, x1 + barWidth / 2, downlinkBarY + downlinkBarHeight / 2 + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store for hover detection
|
||||||
|
transponderRects.push({
|
||||||
|
x: x1,
|
||||||
|
y: downlinkBarY,
|
||||||
|
width: barWidth,
|
||||||
|
height: downlinkBarHeight,
|
||||||
|
transponder: t,
|
||||||
|
type: 'downlink'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw uplink transponders
|
||||||
|
group.forEach(t => {
|
||||||
|
if (!t.uplink) return; // Skip if no uplink data
|
||||||
|
|
||||||
|
const startFreq = t.uplink - (t.frequency_range / 2);
|
||||||
|
const endFreq = t.uplink + (t.frequency_range / 2);
|
||||||
|
|
||||||
|
// Check if transponder is visible
|
||||||
|
if (endFreq < visibleMinFreq || startFreq > visibleMaxFreq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate position
|
// Calculate position
|
||||||
const x1 = leftMargin + ((startFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
const x1 = leftMargin + ((startFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
||||||
const x2 = leftMargin + ((endFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
const x2 = leftMargin + ((endFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth;
|
||||||
@@ -472,32 +553,44 @@ function renderChart() {
|
|||||||
// Skip if too small
|
// Skip if too small
|
||||||
if (barWidth < 1) return;
|
if (barWidth < 1) return;
|
||||||
|
|
||||||
// Draw bar
|
// Draw uplink bar
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = uplinkColor;
|
||||||
ctx.fillRect(x1, barY, barWidth, barHeight);
|
ctx.fillRect(x1, uplinkBarY, barWidth, uplinkBarHeight);
|
||||||
|
|
||||||
// Draw border
|
// Draw border
|
||||||
ctx.strokeStyle = '#fff';
|
ctx.strokeStyle = '#fff';
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 1;
|
||||||
ctx.strokeRect(x1, barY, barWidth, barHeight);
|
ctx.strokeRect(x1, uplinkBarY, barWidth, uplinkBarHeight);
|
||||||
|
|
||||||
// Draw name if there's space
|
// Draw name if there's space
|
||||||
if (barWidth > 40) {
|
if (barWidth > 40) {
|
||||||
ctx.fillStyle = (pol === 'R') ? '#000' : '#fff';
|
ctx.fillStyle = '#fff';
|
||||||
ctx.font = '10px sans-serif';
|
ctx.font = '9px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText(t.name, x1 + barWidth / 2, barY + barHeight / 2 + 3);
|
ctx.fillText(t.name, x1 + barWidth / 2, uplinkBarY + uplinkBarHeight / 2 + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store for hover detection
|
// Store for hover detection
|
||||||
transponderRects.push({
|
transponderRects.push({
|
||||||
x: x1,
|
x: x1,
|
||||||
y: barY,
|
y: uplinkBarY,
|
||||||
width: barWidth,
|
width: barWidth,
|
||||||
height: barHeight,
|
height: uplinkBarHeight,
|
||||||
transponder: t
|
transponder: t,
|
||||||
|
type: 'uplink'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Draw separator line after each polarization group (except last)
|
||||||
|
if (index < polarizations.length - 1) {
|
||||||
|
const separatorY = topMargin + (index * 2 + 2) * rowHeight;
|
||||||
|
ctx.strokeStyle = '#adb5bd';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(leftMargin, separatorY);
|
||||||
|
ctx.lineTo(width - rightMargin, separatorY);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Draw hover tooltip
|
// Draw hover tooltip
|
||||||
@@ -506,19 +599,29 @@ function renderChart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTooltip(t) {
|
function drawTooltip(rectInfo) {
|
||||||
const startFreq = t.downlink - (t.frequency_range / 2);
|
const t = rectInfo.transponder;
|
||||||
const endFreq = t.downlink + (t.frequency_range / 2);
|
const isUplink = rectInfo.type === 'uplink';
|
||||||
|
const freq = isUplink ? t.uplink : t.downlink;
|
||||||
|
const startFreq = freq - (t.frequency_range / 2);
|
||||||
|
const endFreq = freq + (t.frequency_range / 2);
|
||||||
|
|
||||||
const lines = [
|
const lines = [
|
||||||
t.name,
|
t.name,
|
||||||
|
'Тип: ' + (isUplink ? 'Uplink' : 'Downlink'),
|
||||||
'Диапазон: ' + startFreq.toFixed(3) + ' - ' + endFreq.toFixed(3) + ' МГц',
|
'Диапазон: ' + startFreq.toFixed(3) + ' - ' + endFreq.toFixed(3) + ' МГц',
|
||||||
'Downlink: ' + t.downlink.toFixed(3) + ' МГц',
|
'Центр: ' + freq.toFixed(3) + ' МГц',
|
||||||
'Полоса: ' + t.frequency_range.toFixed(3) + ' МГц',
|
'Полоса: ' + t.frequency_range.toFixed(3) + ' МГц',
|
||||||
'Поляризация: ' + t.polarization,
|
'Поляризация: ' + t.polarization,
|
||||||
'Зона: ' + t.zone_name
|
'Зона: ' + t.zone_name
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add frequency conversion info for uplink
|
||||||
|
if (isUplink && t.downlink && t.uplink) {
|
||||||
|
const conversion = t.downlink - t.uplink;
|
||||||
|
lines.push('Перенос: ' + conversion.toFixed(3) + ' МГц');
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate tooltip size
|
// Calculate tooltip size
|
||||||
ctx.font = '12px sans-serif';
|
ctx.font = '12px sans-serif';
|
||||||
const padding = 10;
|
const padding = 10;
|
||||||
@@ -533,8 +636,8 @@ function drawTooltip(t) {
|
|||||||
const tooltipHeight = lines.length * lineHeight + padding * 2;
|
const tooltipHeight = lines.length * lineHeight + padding * 2;
|
||||||
|
|
||||||
// Position tooltip
|
// Position tooltip
|
||||||
const mouseX = hoveredTransponder._mouseX || canvas.width / 2;
|
const mouseX = rectInfo._mouseX || canvas.width / 2;
|
||||||
const mouseY = hoveredTransponder._mouseY || canvas.height / 2;
|
const mouseY = rectInfo._mouseY || canvas.height / 2;
|
||||||
let tooltipX = mouseX + 15;
|
let tooltipX = mouseX + 15;
|
||||||
let tooltipY = mouseY + 15;
|
let tooltipY = mouseY + 15;
|
||||||
|
|
||||||
@@ -607,7 +710,7 @@ function handleMouseMove(e) {
|
|||||||
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) {
|
||||||
found = tr.transponder;
|
found = tr;
|
||||||
found._mouseX = mouseX;
|
found._mouseX = mouseX;
|
||||||
found._mouseY = mouseY;
|
found._mouseY = mouseY;
|
||||||
break;
|
break;
|
||||||
@@ -662,8 +765,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Control buttons
|
// Control buttons
|
||||||
document.getElementById('resetZoom').addEventListener('click', resetZoom);
|
document.getElementById('resetZoom').addEventListener('click', resetZoom);
|
||||||
document.getElementById('zoomIn').addEventListener('click', zoomIn);
|
// document.getElementById('zoomIn').addEventListener('click', zoomIn);
|
||||||
document.getElementById('zoomOut').addEventListener('click', zoomOut);
|
// document.getElementById('zoomOut').addEventListener('click', zoomOut);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-render on window resize
|
// Re-render on window resize
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from mapsapp.models import Transponders
|
|||||||
from .models import (
|
from .models import (
|
||||||
CustomUser,
|
CustomUser,
|
||||||
Geo,
|
Geo,
|
||||||
Mirror,
|
|
||||||
Modulation,
|
Modulation,
|
||||||
ObjItem,
|
ObjItem,
|
||||||
Parameter,
|
Parameter,
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ class SatelliteUpdateView(RoleRequiredMixin, FormMessageMixin, UpdateView):
|
|||||||
'id': t.id,
|
'id': t.id,
|
||||||
'name': t.name or f"TP-{t.id}",
|
'name': t.name or f"TP-{t.id}",
|
||||||
'downlink': float(t.downlink),
|
'downlink': float(t.downlink),
|
||||||
|
'uplink': float(t.uplink) if t.uplink else None,
|
||||||
'frequency_range': float(t.frequency_range),
|
'frequency_range': float(t.frequency_range),
|
||||||
'polarization': t.polarization.name if t.polarization else '-',
|
'polarization': t.polarization.name if t.polarization else '-',
|
||||||
'zone_name': t.zone_name or '-',
|
'zone_name': t.zone_name or '-',
|
||||||
|
|||||||
Reference in New Issue
Block a user