@smartnet360/svelte-components 0.0.11 → 0.0.13

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.
Files changed (88) hide show
  1. package/dist/apps/antenna-pattern/components/AntennaControls.svelte +506 -0
  2. package/dist/apps/antenna-pattern/components/AntennaControls.svelte.d.ts +16 -0
  3. package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte +62 -0
  4. package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte.d.ts +18 -0
  5. package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +339 -0
  6. package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte.d.ts +3 -0
  7. package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +299 -0
  8. package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte.d.ts +24 -0
  9. package/dist/apps/antenna-pattern/components/DbNotification.svelte +67 -0
  10. package/dist/apps/antenna-pattern/components/DbNotification.svelte.d.ts +18 -0
  11. package/dist/apps/antenna-pattern/components/JsonImporter.svelte +116 -0
  12. package/dist/apps/antenna-pattern/components/JsonImporter.svelte.d.ts +18 -0
  13. package/dist/apps/antenna-pattern/components/MSIConverter.svelte +207 -0
  14. package/dist/apps/antenna-pattern/components/MSIConverter.svelte.d.ts +18 -0
  15. package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +252 -0
  16. package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte.d.ts +22 -0
  17. package/dist/apps/antenna-pattern/db.d.ts +24 -0
  18. package/dist/apps/antenna-pattern/db.js +15 -0
  19. package/dist/apps/antenna-pattern/helpers/plotly-utils.d.ts +54 -0
  20. package/dist/apps/antenna-pattern/helpers/plotly-utils.js +324 -0
  21. package/dist/apps/antenna-pattern/index.d.ts +15 -0
  22. package/dist/apps/antenna-pattern/index.js +19 -0
  23. package/dist/apps/antenna-pattern/stores/antennas.d.ts +5 -0
  24. package/dist/apps/antenna-pattern/stores/antennas.js +14 -0
  25. package/dist/apps/antenna-pattern/stores/db-status.d.ts +28 -0
  26. package/dist/apps/antenna-pattern/stores/db-status.js +34 -0
  27. package/dist/apps/antenna-pattern/utils/db-utils.d.ts +9 -0
  28. package/dist/apps/antenna-pattern/utils/db-utils.js +180 -0
  29. package/dist/apps/antenna-pattern/utils/init-db.d.ts +2 -0
  30. package/dist/apps/antenna-pattern/utils/init-db.js +95 -0
  31. package/dist/apps/antenna-pattern/utils/msi-parser.d.ts +3 -0
  32. package/dist/apps/antenna-pattern/utils/msi-parser.js +197 -0
  33. package/dist/apps/antenna-pattern/utils/plotly-chart-utils.d.ts +101 -0
  34. package/dist/apps/antenna-pattern/utils/plotly-chart-utils.js +152 -0
  35. package/dist/apps/index.d.ts +1 -0
  36. package/dist/apps/index.js +6 -0
  37. package/dist/{Charts → core/Charts}/ChartComponent.svelte +131 -39
  38. package/dist/{Charts → core/Charts}/charts.model.d.ts +1 -1
  39. package/dist/{Desktop → core/Desktop}/GridRenderer.svelte +1 -1
  40. package/dist/core/index.d.ts +2 -0
  41. package/dist/core/index.js +6 -0
  42. package/dist/index.d.ts +2 -2
  43. package/dist/index.js +6 -2
  44. package/package.json +6 -2
  45. /package/dist/{Charts → core/Charts}/ChartCard.svelte +0 -0
  46. /package/dist/{Charts → core/Charts}/ChartCard.svelte.d.ts +0 -0
  47. /package/dist/{Charts → core/Charts}/ChartComponent.svelte.d.ts +0 -0
  48. /package/dist/{Charts → core/Charts}/adapt.d.ts +0 -0
  49. /package/dist/{Charts → core/Charts}/adapt.js +0 -0
  50. /package/dist/{Charts → core/Charts}/charts.model.js +0 -0
  51. /package/dist/{Charts → core/Charts}/data-utils.d.ts +0 -0
  52. /package/dist/{Charts → core/Charts}/data-utils.js +0 -0
  53. /package/dist/{Charts → core/Charts}/index.d.ts +0 -0
  54. /package/dist/{Charts → core/Charts}/index.js +0 -0
  55. /package/dist/{Charts → core/Charts}/plotly.d.ts +0 -0
  56. /package/dist/{Desktop → core/Desktop}/Desktop.svelte +0 -0
  57. /package/dist/{Desktop → core/Desktop}/Desktop.svelte.d.ts +0 -0
  58. /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte +0 -0
  59. /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte.d.ts +0 -0
  60. /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte +0 -0
  61. /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte.d.ts +0 -0
  62. /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte +0 -0
  63. /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte.d.ts +0 -0
  64. /package/dist/{Desktop → core/Desktop}/Grid/index.d.ts +0 -0
  65. /package/dist/{Desktop → core/Desktop}/Grid/index.js +0 -0
  66. /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.d.ts +0 -0
  67. /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.js +0 -0
  68. /package/dist/{Desktop → core/Desktop}/GridRenderer.svelte.d.ts +0 -0
  69. /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte +0 -0
  70. /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte.d.ts +0 -0
  71. /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte +0 -0
  72. /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte.d.ts +0 -0
  73. /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte +0 -0
  74. /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte.d.ts +0 -0
  75. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte +0 -0
  76. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte.d.ts +0 -0
  77. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte +0 -0
  78. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte.d.ts +0 -0
  79. /package/dist/{Desktop → core/Desktop}/GridSelector/index.d.ts +0 -0
  80. /package/dist/{Desktop → core/Desktop}/GridSelector/index.js +0 -0
  81. /package/dist/{Desktop → core/Desktop}/GridViewer.svelte +0 -0
  82. /package/dist/{Desktop → core/Desktop}/GridViewer.svelte.d.ts +0 -0
  83. /package/dist/{Desktop → core/Desktop}/gridLayouts.d.ts +0 -0
  84. /package/dist/{Desktop → core/Desktop}/gridLayouts.js +0 -0
  85. /package/dist/{Desktop → core/Desktop}/index.d.ts +0 -0
  86. /package/dist/{Desktop → core/Desktop}/index.js +0 -0
  87. /package/dist/{Desktop → core/Desktop}/launchHelpers.d.ts +0 -0
  88. /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
+ }