@smartnet360/svelte-components 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/antenna-pattern/components/AntennaControls.svelte +424 -0
- package/dist/apps/antenna-pattern/components/AntennaControls.svelte.d.ts +16 -0
- package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte +62 -0
- package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +339 -0
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte.d.ts +3 -0
- package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +299 -0
- package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte.d.ts +24 -0
- package/dist/apps/antenna-pattern/components/DbNotification.svelte +67 -0
- package/dist/apps/antenna-pattern/components/DbNotification.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/JsonImporter.svelte +116 -0
- package/dist/apps/antenna-pattern/components/JsonImporter.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/MSIConverter.svelte +209 -0
- package/dist/apps/antenna-pattern/components/MSIConverter.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +252 -0
- package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte.d.ts +22 -0
- package/dist/apps/antenna-pattern/db.d.ts +24 -0
- package/dist/apps/antenna-pattern/db.js +15 -0
- package/dist/apps/antenna-pattern/helpers/plotly-utils.d.ts +54 -0
- package/dist/apps/antenna-pattern/helpers/plotly-utils.js +324 -0
- package/dist/apps/antenna-pattern/index.d.ts +15 -0
- package/dist/apps/antenna-pattern/index.js +19 -0
- package/dist/apps/antenna-pattern/stores/antennas.d.ts +5 -0
- package/dist/apps/antenna-pattern/stores/antennas.js +14 -0
- package/dist/apps/antenna-pattern/stores/db-status.d.ts +28 -0
- package/dist/apps/antenna-pattern/stores/db-status.js +34 -0
- package/dist/apps/antenna-pattern/utils/db-utils.d.ts +9 -0
- package/dist/apps/antenna-pattern/utils/db-utils.js +180 -0
- package/dist/apps/antenna-pattern/utils/init-db.d.ts +2 -0
- package/dist/apps/antenna-pattern/utils/init-db.js +95 -0
- package/dist/apps/antenna-pattern/utils/msi-parser.d.ts +3 -0
- package/dist/apps/antenna-pattern/utils/msi-parser.js +197 -0
- package/dist/apps/antenna-pattern/utils/plotly-chart-utils.d.ts +101 -0
- package/dist/apps/antenna-pattern/utils/plotly-chart-utils.js +152 -0
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.js +6 -0
- package/dist/{Charts → core/Charts}/ChartComponent.svelte +131 -39
- package/dist/{Charts → core/Charts}/charts.model.d.ts +1 -1
- package/dist/{Charts → core/Charts}/data-utils.js +0 -4
- package/dist/{Desktop → core/Desktop}/GridRenderer.svelte +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -2
- package/package.json +6 -2
- /package/dist/{Charts → core/Charts}/ChartCard.svelte +0 -0
- /package/dist/{Charts → core/Charts}/ChartCard.svelte.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/ChartComponent.svelte.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/adapt.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/adapt.js +0 -0
- /package/dist/{Charts → core/Charts}/charts.model.js +0 -0
- /package/dist/{Charts → core/Charts}/data-utils.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/index.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/index.js +0 -0
- /package/dist/{Charts → core/Charts}/plotly.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Desktop.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Desktop.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/index.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/index.js +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.js +0 -0
- /package/dist/{Desktop → core/Desktop}/GridRenderer.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/index.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/index.js +0 -0
- /package/dist/{Desktop → core/Desktop}/GridViewer.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridViewer.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/gridLayouts.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/gridLayouts.js +0 -0
- /package/dist/{Desktop → core/Desktop}/index.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/index.js +0 -0
- /package/dist/{Desktop → core/Desktop}/launchHelpers.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/launchHelpers.js +0 -0
@@ -0,0 +1,252 @@
|
|
1
|
+
<svelte:options runes={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import { onMount } from 'svelte';
|
5
|
+
import { browser } from '$app/environment';
|
6
|
+
import type { PlotlyRadarData } from '../helpers/plotly-utils';
|
7
|
+
import { createPlotlyRadarConfig, createTiltSliders } from '../helpers/plotly-utils';
|
8
|
+
|
9
|
+
interface Props {
|
10
|
+
data: PlotlyRadarData[];
|
11
|
+
title?: string;
|
12
|
+
minValue?: number;
|
13
|
+
maxValue?: number;
|
14
|
+
height?: string;
|
15
|
+
showSliders?: boolean;
|
16
|
+
showPatternToggle?: boolean;
|
17
|
+
availableTilts1?: string[];
|
18
|
+
availableTilts2?: string[];
|
19
|
+
activeTiltIndex1?: number;
|
20
|
+
activeTiltIndex2?: number;
|
21
|
+
onTiltChange?: (antennaIndex: 1 | 2, tiltIndex: number) => void;
|
22
|
+
onDataUpdate?: (newData: PlotlyRadarData[]) => void;
|
23
|
+
}
|
24
|
+
|
25
|
+
let {
|
26
|
+
data = [],
|
27
|
+
title = '',
|
28
|
+
minValue = -70,
|
29
|
+
maxValue = 0,
|
30
|
+
height = '400px',
|
31
|
+
showSliders = false,
|
32
|
+
showPatternToggle = false,
|
33
|
+
availableTilts1 = [],
|
34
|
+
availableTilts2 = [],
|
35
|
+
activeTiltIndex1 = 0,
|
36
|
+
activeTiltIndex2 = 0,
|
37
|
+
onTiltChange,
|
38
|
+
onDataUpdate
|
39
|
+
}: Props = $props();
|
40
|
+
|
41
|
+
let chartDiv = $state<HTMLDivElement>();
|
42
|
+
let Plotly: any;
|
43
|
+
let chartInitialized = $state(false);
|
44
|
+
|
45
|
+
// Watch for data changes and update chart
|
46
|
+
$effect(() => {
|
47
|
+
if (chartInitialized && Plotly && data.length > 0) {
|
48
|
+
updateChart();
|
49
|
+
}
|
50
|
+
});
|
51
|
+
|
52
|
+
onMount(async () => {
|
53
|
+
if (browser) {
|
54
|
+
try {
|
55
|
+
// Import Plotly dynamically for SSR compatibility
|
56
|
+
Plotly = await import('plotly.js-dist-min');
|
57
|
+
await initializeChart();
|
58
|
+
} catch (error) {
|
59
|
+
console.error('Failed to load Plotly:', error);
|
60
|
+
}
|
61
|
+
}
|
62
|
+
});
|
63
|
+
|
64
|
+
async function initializeChart() {
|
65
|
+
if (!Plotly || !chartDiv || !data.length) {
|
66
|
+
console.log('PlotlyRadarChart - Cannot initialize:', {
|
67
|
+
hasPlotly: !!Plotly,
|
68
|
+
hasChartDiv: !!chartDiv,
|
69
|
+
dataLength: data.length
|
70
|
+
});
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
|
74
|
+
console.log('PlotlyRadarChart - Initializing with data:', {
|
75
|
+
dataCount: data.length,
|
76
|
+
sampleData: data[0],
|
77
|
+
title,
|
78
|
+
showSliders
|
79
|
+
});
|
80
|
+
|
81
|
+
const { data: plotData, layout, config } = createPlotlyRadarConfig(
|
82
|
+
data,
|
83
|
+
title,
|
84
|
+
minValue,
|
85
|
+
maxValue
|
86
|
+
);
|
87
|
+
|
88
|
+
// Add sliders if requested and available
|
89
|
+
const sliders = createSliders();
|
90
|
+
if (sliders.length > 0) {
|
91
|
+
layout.sliders = sliders;
|
92
|
+
console.log('Added sliders:', sliders.length);
|
93
|
+
}
|
94
|
+
|
95
|
+
// Add pattern toggle if requested
|
96
|
+
if (showPatternToggle) {
|
97
|
+
layout.updatemenus = [createPatternToggleMenu()];
|
98
|
+
}
|
99
|
+
|
100
|
+
console.log('PlotlyRadarChart - Final layout:', layout);
|
101
|
+
console.log('PlotlyRadarChart - Plot data:', plotData);
|
102
|
+
|
103
|
+
try {
|
104
|
+
await Plotly.newPlot(chartDiv, plotData, layout, config);
|
105
|
+
chartInitialized = true;
|
106
|
+
console.log('PlotlyRadarChart - Successfully initialized');
|
107
|
+
|
108
|
+
// Set up event listeners using Plotly's event system
|
109
|
+
if (chartDiv) {
|
110
|
+
chartDiv.addEventListener('plotly_sliderchange', handleSliderChange);
|
111
|
+
chartDiv.addEventListener('plotly_sliderend', handleSliderChange);
|
112
|
+
chartDiv.addEventListener('plotly_buttonclicked', handleButtonClick);
|
113
|
+
}
|
114
|
+
} catch (error) {
|
115
|
+
console.error('Failed to create Plotly chart:', error);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
async function updateChart() {
|
120
|
+
if (!Plotly || !chartDiv || !chartInitialized) return;
|
121
|
+
|
122
|
+
try {
|
123
|
+
const { data: plotData, layout } = createPlotlyRadarConfig(
|
124
|
+
data,
|
125
|
+
title,
|
126
|
+
minValue,
|
127
|
+
maxValue
|
128
|
+
);
|
129
|
+
|
130
|
+
await Plotly.react(chartDiv, plotData, layout);
|
131
|
+
} catch (error) {
|
132
|
+
console.error('Failed to update Plotly chart:', error);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
function createSliders() {
|
137
|
+
if (!showSliders) return [];
|
138
|
+
|
139
|
+
// Only create sliders if we have tilts available
|
140
|
+
const sliders = createTiltSliders(
|
141
|
+
availableTilts1,
|
142
|
+
availableTilts2,
|
143
|
+
activeTiltIndex1,
|
144
|
+
activeTiltIndex2,
|
145
|
+
onTiltChange
|
146
|
+
);
|
147
|
+
|
148
|
+
// Filter out empty sliders
|
149
|
+
return sliders.filter(slider => slider.steps && slider.steps.length > 1);
|
150
|
+
}
|
151
|
+
|
152
|
+
function createPatternToggleMenu() {
|
153
|
+
return {
|
154
|
+
type: 'buttons',
|
155
|
+
direction: 'left',
|
156
|
+
showactive: true,
|
157
|
+
x: 0.1,
|
158
|
+
y: 1.15,
|
159
|
+
buttons: [
|
160
|
+
{
|
161
|
+
label: 'Horizontal',
|
162
|
+
method: 'restyle',
|
163
|
+
args: [{ visible: true }, [0, 1]]
|
164
|
+
},
|
165
|
+
{
|
166
|
+
label: 'Vertical',
|
167
|
+
method: 'restyle',
|
168
|
+
args: [{ visible: true }, [0, 1]]
|
169
|
+
}
|
170
|
+
]
|
171
|
+
};
|
172
|
+
}
|
173
|
+
|
174
|
+
function handleSliderChange(eventData: any) {
|
175
|
+
console.log('Slider change event:', eventData);
|
176
|
+
|
177
|
+
// Handle slider changes and notify parent
|
178
|
+
if (onTiltChange && eventData && eventData.detail) {
|
179
|
+
const step = eventData.detail.step;
|
180
|
+
const sliderIndex = eventData.detail.slider.active;
|
181
|
+
const slider = eventData.detail.slider;
|
182
|
+
|
183
|
+
console.log('Slider details:', { step, sliderIndex, slider });
|
184
|
+
|
185
|
+
// Determine which antenna slider was changed based on position or other identifier
|
186
|
+
const antennaIndex = slider.x < 0.3 ? 1 : 2;
|
187
|
+
onTiltChange(antennaIndex, sliderIndex);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
function handleButtonClick(eventData: any) {
|
192
|
+
// Handle button clicks (pattern toggle)
|
193
|
+
console.log('Button clicked:', eventData);
|
194
|
+
}
|
195
|
+
|
196
|
+
// Export methods for parent components
|
197
|
+
export function resizeChart() {
|
198
|
+
if (Plotly && chartDiv && chartInitialized) {
|
199
|
+
Plotly.Plots.resize(chartDiv);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
export function downloadChart(filename: string = 'antenna_pattern') {
|
204
|
+
if (Plotly && chartDiv && chartInitialized) {
|
205
|
+
Plotly.downloadImage(chartDiv, {
|
206
|
+
format: 'png',
|
207
|
+
width: 800,
|
208
|
+
height: 800,
|
209
|
+
filename
|
210
|
+
});
|
211
|
+
}
|
212
|
+
}
|
213
|
+
</script>
|
214
|
+
|
215
|
+
<div class="plotly-chart-container" style:height>
|
216
|
+
<div bind:this={chartDiv} class="plotly-chart"></div>
|
217
|
+
</div>
|
218
|
+
|
219
|
+
<style>
|
220
|
+
.plotly-chart-container {
|
221
|
+
width: 100%;
|
222
|
+
position: relative;
|
223
|
+
}
|
224
|
+
|
225
|
+
.plotly-chart {
|
226
|
+
width: 100%;
|
227
|
+
height: 100%;
|
228
|
+
}
|
229
|
+
|
230
|
+
/* Override Plotly's default styles to match Bootstrap theme */
|
231
|
+
:global(.plotly .modebar) {
|
232
|
+
background: rgba(255, 255, 255, 0.9) !important;
|
233
|
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
234
|
+
border-radius: 4px !important;
|
235
|
+
}
|
236
|
+
|
237
|
+
:global(.plotly .modebar-btn) {
|
238
|
+
background: transparent !important;
|
239
|
+
border: none !important;
|
240
|
+
color: #6c757d !important;
|
241
|
+
}
|
242
|
+
|
243
|
+
:global(.plotly .modebar-btn:hover) {
|
244
|
+
background: rgba(0, 0, 0, 0.05) !important;
|
245
|
+
color: #495057 !important;
|
246
|
+
}
|
247
|
+
|
248
|
+
:global(.plotly .modebar-btn.active) {
|
249
|
+
background: #007bff !important;
|
250
|
+
color: white !important;
|
251
|
+
}
|
252
|
+
</style>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import type { PlotlyRadarData } from '../helpers/plotly-utils';
|
2
|
+
interface Props {
|
3
|
+
data: PlotlyRadarData[];
|
4
|
+
title?: string;
|
5
|
+
minValue?: number;
|
6
|
+
maxValue?: number;
|
7
|
+
height?: string;
|
8
|
+
showSliders?: boolean;
|
9
|
+
showPatternToggle?: boolean;
|
10
|
+
availableTilts1?: string[];
|
11
|
+
availableTilts2?: string[];
|
12
|
+
activeTiltIndex1?: number;
|
13
|
+
activeTiltIndex2?: number;
|
14
|
+
onTiltChange?: (antennaIndex: 1 | 2, tiltIndex: number) => void;
|
15
|
+
onDataUpdate?: (newData: PlotlyRadarData[]) => void;
|
16
|
+
}
|
17
|
+
declare const PlotlyRadarChart: import("svelte").Component<Props, {
|
18
|
+
resizeChart: () => void;
|
19
|
+
downloadChart: (filename?: string) => void;
|
20
|
+
}, "">;
|
21
|
+
type PlotlyRadarChart = ReturnType<typeof PlotlyRadarChart>;
|
22
|
+
export default PlotlyRadarChart;
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import Dexie, { type Table } from 'dexie';
|
2
|
+
export interface Antenna {
|
3
|
+
id?: number;
|
4
|
+
name: string;
|
5
|
+
frequency: number;
|
6
|
+
gain_dBd: number;
|
7
|
+
tilt: string;
|
8
|
+
comment?: string;
|
9
|
+
horizontal: number;
|
10
|
+
pattern: number[];
|
11
|
+
vertical_pattern: number[];
|
12
|
+
polarization?: string;
|
13
|
+
x_pol?: string;
|
14
|
+
co?: string;
|
15
|
+
tilt_from_filename?: string;
|
16
|
+
sourcePath?: string;
|
17
|
+
}
|
18
|
+
declare class AntennaDatabase extends Dexie {
|
19
|
+
antennas: Table<Antenna, number>;
|
20
|
+
constructor();
|
21
|
+
hasData(): Promise<boolean>;
|
22
|
+
}
|
23
|
+
export declare const db: AntennaDatabase;
|
24
|
+
export {};
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import Dexie, {} from 'dexie';
|
2
|
+
class AntennaDatabase extends Dexie {
|
3
|
+
antennas;
|
4
|
+
constructor() {
|
5
|
+
super('antennaDB');
|
6
|
+
this.version(1).stores({
|
7
|
+
antennas: '++id, name, frequency, tilt'
|
8
|
+
});
|
9
|
+
}
|
10
|
+
async hasData() {
|
11
|
+
const count = await this.antennas.count();
|
12
|
+
return count > 0;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
export const db = new AntennaDatabase();
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import type { Antenna } from '../db';
|
2
|
+
export interface PlotlyRadarData {
|
3
|
+
r: (number | null)[];
|
4
|
+
theta: number[];
|
5
|
+
type: 'scatterpolar';
|
6
|
+
mode: 'lines';
|
7
|
+
name: string;
|
8
|
+
line: {
|
9
|
+
color: string;
|
10
|
+
width: number;
|
11
|
+
};
|
12
|
+
fill?: 'tonext' | 'toself' | 'none';
|
13
|
+
fillcolor?: string;
|
14
|
+
}
|
15
|
+
export interface AntennaPattern {
|
16
|
+
horizontal: number[];
|
17
|
+
vertical: number[];
|
18
|
+
}
|
19
|
+
/**
|
20
|
+
* Creates a Plotly polar/radar chart configuration for antenna patterns
|
21
|
+
*/
|
22
|
+
export declare function createPlotlyRadarConfig(data: PlotlyRadarData[], title?: string, minValue?: number, maxValue?: number): {
|
23
|
+
data: any[];
|
24
|
+
layout: any;
|
25
|
+
config: any;
|
26
|
+
};
|
27
|
+
export declare function convertAntennaToPlotlyData(antenna: Antenna, mechanicalTilt: number, patternType: 'horizontal' | 'vertical', name: string, color: string, fillColor?: string): PlotlyRadarData;
|
28
|
+
/**
|
29
|
+
* Applies mechanical tilt to vertical pattern data
|
30
|
+
/**
|
31
|
+
* Creates Plotly slider configuration for electrical tilt control
|
32
|
+
*/
|
33
|
+
export declare function createTiltSliders(availableTilts1: string[], availableTilts2: string[], activeTiltIndex1: number, activeTiltIndex2: number, onTiltChange?: (antennaIndex: 1 | 2, tiltIndex: number) => void): any[];
|
34
|
+
/**
|
35
|
+
* Creates a simple mechanical tilt slider for single antenna view
|
36
|
+
*/
|
37
|
+
export declare function createMechanicalTiltSlider(currentTilt?: number, minTilt?: number, maxTilt?: number): any;
|
38
|
+
/**
|
39
|
+
* Creates update menu for pattern type switching
|
40
|
+
*/
|
41
|
+
export declare function createPatternToggle(): any;
|
42
|
+
/**
|
43
|
+
* Calculates pattern comparison error between two antennas
|
44
|
+
*/
|
45
|
+
export declare function calculatePatternError(pattern1: number[], pattern2: number[], threshold?: number): number;
|
46
|
+
/**
|
47
|
+
* Gets CSS color values for consistent theming
|
48
|
+
*/
|
49
|
+
export declare function getThemeColors(): {
|
50
|
+
primary: string;
|
51
|
+
secondary: string;
|
52
|
+
primaryRgb: string;
|
53
|
+
secondaryRgb: string;
|
54
|
+
};
|
@@ -0,0 +1,324 @@
|
|
1
|
+
/**
|
2
|
+
* Creates a Plotly polar/radar chart configuration for antenna patterns
|
3
|
+
*/
|
4
|
+
export function createPlotlyRadarConfig(data, title = '', minValue = -70, maxValue = 0) {
|
5
|
+
const layout = {
|
6
|
+
title: {
|
7
|
+
text: title,
|
8
|
+
font: { size: 16 },
|
9
|
+
x: 0.5,
|
10
|
+
y: 0.95
|
11
|
+
},
|
12
|
+
polar: {
|
13
|
+
radialaxis: {
|
14
|
+
visible: true,
|
15
|
+
range: [minValue, maxValue],
|
16
|
+
tickfont: { size: 10 },
|
17
|
+
gridcolor: 'rgba(0, 0, 0, 0.1)',
|
18
|
+
linecolor: 'rgba(0, 0, 0, 0.2)',
|
19
|
+
tickmode: 'linear',
|
20
|
+
tick0: minValue,
|
21
|
+
dtick: 10
|
22
|
+
},
|
23
|
+
angularaxis: {
|
24
|
+
visible: true,
|
25
|
+
tickfont: { size: 10 },
|
26
|
+
rotation: 90, // Start at top (like compass)
|
27
|
+
direction: 'clockwise',
|
28
|
+
gridcolor: 'rgba(0, 0, 0, 0.1)',
|
29
|
+
linecolor: 'rgba(0, 0, 0, 0.2)',
|
30
|
+
tickmode: 'array',
|
31
|
+
tickvals: [0, 45, 90, 135, 180, 225, 270, 315],
|
32
|
+
ticktext: ['0°', '45°', '90°', '135°', '180°', '225°', '270°', '315°']
|
33
|
+
},
|
34
|
+
bgcolor: 'rgba(255, 255, 255, 0)'
|
35
|
+
},
|
36
|
+
showlegend: true,
|
37
|
+
legend: {
|
38
|
+
x: 0.02,
|
39
|
+
y: 0.98,
|
40
|
+
bgcolor: 'rgba(255, 255, 255, 0.9)',
|
41
|
+
bordercolor: 'rgba(0, 0, 0, 0.2)',
|
42
|
+
borderwidth: 1,
|
43
|
+
font: { size: 12 }
|
44
|
+
},
|
45
|
+
margin: { t: 100, r: 80, b: 120, l: 80 }, // Increased bottom margin for sliders
|
46
|
+
autosize: true
|
47
|
+
};
|
48
|
+
const config = {
|
49
|
+
responsive: true,
|
50
|
+
displayModeBar: true,
|
51
|
+
displaylogo: false,
|
52
|
+
modeBarButtonsToRemove: [
|
53
|
+
'zoom2d',
|
54
|
+
'pan2d',
|
55
|
+
'select2d',
|
56
|
+
'lasso2d',
|
57
|
+
'zoomIn2d',
|
58
|
+
'zoomOut2d',
|
59
|
+
'autoScale2d',
|
60
|
+
'resetScale2d',
|
61
|
+
'toggleSpikelines',
|
62
|
+
'toImage'
|
63
|
+
]
|
64
|
+
};
|
65
|
+
return { data, layout, config };
|
66
|
+
}
|
67
|
+
/**
|
68
|
+
* Converts antenna pattern data to Plotly format
|
69
|
+
*/
|
70
|
+
/**
|
71
|
+
* Converts attenuation values to dB signal strength
|
72
|
+
* Raw antenna data represents attenuation where:
|
73
|
+
* - 0 = maximum signal (no attenuation)
|
74
|
+
* - Larger values = more attenuation (weaker signal)
|
75
|
+
* This function converts to dB by negating the attenuation values
|
76
|
+
*/
|
77
|
+
function attenuationToDb(attenuationValues) {
|
78
|
+
return attenuationValues.map(value => {
|
79
|
+
// For attenuation values, 0 is max signal, so we negate the dB value
|
80
|
+
// to represent the reduction from the max signal
|
81
|
+
return -value;
|
82
|
+
});
|
83
|
+
}
|
84
|
+
export function convertAntennaToPlotlyData(antenna, mechanicalTilt, patternType, name, color, fillColor) {
|
85
|
+
// Get the correct pattern array based on type
|
86
|
+
// For horizontal: use 'pattern' array
|
87
|
+
// For vertical: use 'vertical_pattern' array
|
88
|
+
const pattern = patternType === 'horizontal' ? antenna.pattern : antenna.vertical_pattern;
|
89
|
+
console.log(`convertAntennaToPlotlyData - ${patternType} for ${antenna.name}:`, {
|
90
|
+
antennaName: antenna.name,
|
91
|
+
patternType,
|
92
|
+
hasPattern: !!pattern,
|
93
|
+
patternLength: pattern?.length,
|
94
|
+
patternSample: pattern?.slice(0, 10), // First 10 values
|
95
|
+
mechanicalTilt
|
96
|
+
});
|
97
|
+
// Ensure we have valid pattern data
|
98
|
+
if (!pattern || !Array.isArray(pattern) || pattern.length === 0) {
|
99
|
+
console.warn(`No ${patternType} pattern data available for antenna:`, antenna.name);
|
100
|
+
return {
|
101
|
+
r: [],
|
102
|
+
theta: [],
|
103
|
+
type: 'scatterpolar',
|
104
|
+
mode: 'lines',
|
105
|
+
name,
|
106
|
+
line: { color, width: 2 }
|
107
|
+
};
|
108
|
+
}
|
109
|
+
// Process the pattern data - ensure we have 360 data points
|
110
|
+
let processedPattern = Array.from({ length: 360 }, (_, i) => {
|
111
|
+
if (patternType === 'vertical' && mechanicalTilt !== 0) {
|
112
|
+
// Apply mechanical tilt by shifting the index
|
113
|
+
const shiftedIndex = (i - Math.round(mechanicalTilt) + 360) % 360;
|
114
|
+
return pattern[shiftedIndex] || 0;
|
115
|
+
}
|
116
|
+
else {
|
117
|
+
// No mechanical tilt for horizontal pattern
|
118
|
+
return pattern[i] || 0;
|
119
|
+
}
|
120
|
+
});
|
121
|
+
// Convert attenuation values to dB (always use dB scale like the Chart.js version)
|
122
|
+
processedPattern = attenuationToDb(processedPattern);
|
123
|
+
console.log(`Processed ${patternType} pattern:`, {
|
124
|
+
originalSample: pattern.slice(0, 5),
|
125
|
+
processedSample: processedPattern.slice(0, 5),
|
126
|
+
dataRange: {
|
127
|
+
min: Math.min(...processedPattern),
|
128
|
+
max: Math.max(...processedPattern)
|
129
|
+
}
|
130
|
+
});
|
131
|
+
// Generate theta values (0-359 degrees)
|
132
|
+
// For vertical patterns, rotate clockwise by 90 degrees
|
133
|
+
const theta = Array.from({ length: 360 }, (_, i) => {
|
134
|
+
if (patternType === 'vertical') {
|
135
|
+
// Rotate vertical pattern clockwise by 90 degrees
|
136
|
+
return (i + 90) % 360;
|
137
|
+
}
|
138
|
+
else {
|
139
|
+
// Keep horizontal pattern as-is
|
140
|
+
return i;
|
141
|
+
}
|
142
|
+
});
|
143
|
+
const plotlyData = {
|
144
|
+
r: processedPattern,
|
145
|
+
theta,
|
146
|
+
type: 'scatterpolar',
|
147
|
+
mode: 'lines',
|
148
|
+
name,
|
149
|
+
line: {
|
150
|
+
color,
|
151
|
+
width: 2
|
152
|
+
}
|
153
|
+
};
|
154
|
+
if (fillColor) {
|
155
|
+
plotlyData.fill = 'toself';
|
156
|
+
plotlyData.fillcolor = fillColor;
|
157
|
+
}
|
158
|
+
return plotlyData;
|
159
|
+
}
|
160
|
+
/**
|
161
|
+
* Applies mechanical tilt to vertical pattern data
|
162
|
+
/**
|
163
|
+
* Creates Plotly slider configuration for electrical tilt control
|
164
|
+
*/
|
165
|
+
export function createTiltSliders(availableTilts1, availableTilts2, activeTiltIndex1, activeTiltIndex2, onTiltChange) {
|
166
|
+
const sliders = [];
|
167
|
+
// Antenna 1 electrical tilt slider (positioned at bottom left)
|
168
|
+
if (availableTilts1.length > 1) {
|
169
|
+
sliders.push({
|
170
|
+
active: activeTiltIndex1,
|
171
|
+
currentvalue: {
|
172
|
+
visible: true,
|
173
|
+
prefix: 'Antenna 1 E-Tilt: ',
|
174
|
+
suffix: '°',
|
175
|
+
xanchor: 'left',
|
176
|
+
font: { size: 12, color: '#0d6efd' }
|
177
|
+
},
|
178
|
+
pad: { t: 20, b: 10 },
|
179
|
+
len: 0.35,
|
180
|
+
x: 0.05,
|
181
|
+
y: 0,
|
182
|
+
steps: availableTilts1.map((tilt, i) => ({
|
183
|
+
label: `${tilt}°`,
|
184
|
+
value: tilt,
|
185
|
+
method: 'animate',
|
186
|
+
args: [
|
187
|
+
[`frame_${i}`],
|
188
|
+
{
|
189
|
+
mode: 'immediate',
|
190
|
+
transition: { duration: 300 },
|
191
|
+
frame: { duration: 300 }
|
192
|
+
}
|
193
|
+
]
|
194
|
+
}))
|
195
|
+
});
|
196
|
+
}
|
197
|
+
// Antenna 2 electrical tilt slider (positioned at bottom right)
|
198
|
+
if (availableTilts2.length > 1) {
|
199
|
+
sliders.push({
|
200
|
+
active: activeTiltIndex2,
|
201
|
+
currentvalue: {
|
202
|
+
visible: true,
|
203
|
+
prefix: 'Antenna 2 E-Tilt: ',
|
204
|
+
suffix: '°',
|
205
|
+
xanchor: 'right',
|
206
|
+
font: { size: 12, color: '#fd7e14' }
|
207
|
+
},
|
208
|
+
pad: { t: 20, b: 10 },
|
209
|
+
len: 0.35,
|
210
|
+
x: 0.60,
|
211
|
+
y: 0,
|
212
|
+
steps: availableTilts2.map((tilt, i) => ({
|
213
|
+
label: `${tilt}°`,
|
214
|
+
value: tilt,
|
215
|
+
method: 'animate',
|
216
|
+
args: [
|
217
|
+
[`frame_${i}`],
|
218
|
+
{
|
219
|
+
mode: 'immediate',
|
220
|
+
transition: { duration: 300 },
|
221
|
+
frame: { duration: 300 }
|
222
|
+
}
|
223
|
+
]
|
224
|
+
}))
|
225
|
+
});
|
226
|
+
}
|
227
|
+
return sliders;
|
228
|
+
}
|
229
|
+
/**
|
230
|
+
* Creates a simple mechanical tilt slider for single antenna view
|
231
|
+
*/
|
232
|
+
export function createMechanicalTiltSlider(currentTilt = 0, minTilt = -10, maxTilt = 10) {
|
233
|
+
const tiltRange = maxTilt - minTilt;
|
234
|
+
const activeIndex = currentTilt - minTilt;
|
235
|
+
return {
|
236
|
+
active: activeIndex,
|
237
|
+
currentvalue: {
|
238
|
+
visible: true,
|
239
|
+
prefix: 'Mechanical Tilt: ',
|
240
|
+
suffix: '°',
|
241
|
+
xanchor: 'center',
|
242
|
+
font: { size: 12, color: '#198754' }
|
243
|
+
},
|
244
|
+
pad: { t: 20, b: 10 },
|
245
|
+
len: 0.6,
|
246
|
+
x: 0.2,
|
247
|
+
y: 0,
|
248
|
+
steps: Array.from({ length: tiltRange + 1 }, (_, i) => {
|
249
|
+
const tiltValue = minTilt + i;
|
250
|
+
return {
|
251
|
+
label: `${tiltValue}°`,
|
252
|
+
value: tiltValue.toString(),
|
253
|
+
method: 'restyle',
|
254
|
+
args: [
|
255
|
+
{
|
256
|
+
// This will trigger a custom event that we can listen for
|
257
|
+
'meta': [tiltValue]
|
258
|
+
},
|
259
|
+
[1] // Apply to vertical pattern trace (index 1)
|
260
|
+
]
|
261
|
+
};
|
262
|
+
})
|
263
|
+
};
|
264
|
+
}
|
265
|
+
/**
|
266
|
+
* Creates update menu for pattern type switching
|
267
|
+
*/
|
268
|
+
export function createPatternToggle() {
|
269
|
+
return {
|
270
|
+
type: 'buttons',
|
271
|
+
direction: 'left',
|
272
|
+
showactive: true,
|
273
|
+
x: 0.1,
|
274
|
+
y: 1.15,
|
275
|
+
buttons: [
|
276
|
+
{
|
277
|
+
label: 'Horizontal',
|
278
|
+
method: 'restyle',
|
279
|
+
args: [{ visible: [true, true] }, [0, 1]]
|
280
|
+
},
|
281
|
+
{
|
282
|
+
label: 'Vertical',
|
283
|
+
method: 'restyle',
|
284
|
+
args: [{ visible: [true, true] }, [2, 3]]
|
285
|
+
}
|
286
|
+
]
|
287
|
+
};
|
288
|
+
}
|
289
|
+
/**
|
290
|
+
* Calculates pattern comparison error between two antennas
|
291
|
+
*/
|
292
|
+
export function calculatePatternError(pattern1, pattern2, threshold = -3) {
|
293
|
+
let totalError = 0;
|
294
|
+
let validPoints = 0;
|
295
|
+
for (let i = 0; i < Math.min(pattern1.length, pattern2.length); i++) {
|
296
|
+
const val1 = pattern1[i];
|
297
|
+
const val2 = pattern2[i];
|
298
|
+
if (val1 !== null && val2 !== null && val1 >= threshold && val2 >= threshold) {
|
299
|
+
totalError += Math.abs(val1 - val2);
|
300
|
+
validPoints++;
|
301
|
+
}
|
302
|
+
}
|
303
|
+
return validPoints > 0 ? totalError / validPoints : Infinity;
|
304
|
+
}
|
305
|
+
/**
|
306
|
+
* Gets CSS color values for consistent theming
|
307
|
+
*/
|
308
|
+
export function getThemeColors() {
|
309
|
+
if (typeof document === 'undefined') {
|
310
|
+
return {
|
311
|
+
primary: '#0d6efd',
|
312
|
+
secondary: '#fd7e14',
|
313
|
+
primaryRgb: '13, 110, 253',
|
314
|
+
secondaryRgb: '253, 126, 20'
|
315
|
+
};
|
316
|
+
}
|
317
|
+
const styles = getComputedStyle(document.documentElement);
|
318
|
+
return {
|
319
|
+
primary: styles.getPropertyValue('--bs-primary').trim() || '#0d6efd',
|
320
|
+
secondary: styles.getPropertyValue('--bs-warning').trim() || '#fd7e14',
|
321
|
+
primaryRgb: styles.getPropertyValue('--bs-primary-rgb').trim() || '13, 110, 253',
|
322
|
+
secondaryRgb: styles.getPropertyValue('--bs-warning-rgb').trim() || '253, 126, 20'
|
323
|
+
};
|
324
|
+
}
|