@smartnet360/svelte-components 0.0.21 → 0.0.23
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/utils/msi-parser.js +18 -1
- package/dist/cellular/CellularChartsView.svelte +293 -0
- package/dist/cellular/CellularChartsView.svelte.d.ts +7 -0
- package/dist/cellular/HierarchicalTree.svelte +469 -0
- package/dist/cellular/HierarchicalTree.svelte.d.ts +9 -0
- package/dist/cellular/SiteTree.svelte +286 -0
- package/dist/cellular/SiteTree.svelte.d.ts +11 -0
- package/dist/cellular/cellular-transforms.d.ts +25 -0
- package/dist/cellular/cellular-transforms.js +129 -0
- package/dist/cellular/cellular.model.d.ts +63 -0
- package/dist/cellular/cellular.model.js +6 -0
- package/dist/cellular/index.d.ts +11 -0
- package/dist/cellular/index.js +11 -0
- package/dist/cellular/mock-cellular-data.d.ts +13 -0
- package/dist/cellular/mock-cellular-data.js +241 -0
- package/dist/core/Charts/ChartCard.svelte +65 -16
- package/dist/core/Charts/ChartCard.svelte.d.ts +2 -0
- package/dist/core/Charts/ChartComponent.svelte +166 -34
- package/dist/core/Charts/ChartComponent.svelte.d.ts +1 -0
- package/dist/core/Charts/GlobalControls.svelte +188 -0
- package/dist/core/Charts/GlobalControls.svelte.d.ts +8 -0
- package/dist/core/Charts/charts.model.d.ts +7 -0
- package/dist/core/TreeChartView/TreeChartView.svelte +208 -0
- package/dist/core/TreeChartView/TreeChartView.svelte.d.ts +42 -0
- package/dist/core/TreeChartView/index.d.ts +7 -0
- package/dist/core/TreeChartView/index.js +7 -0
- package/dist/core/TreeView/TreeNode.svelte +173 -0
- package/dist/core/TreeView/TreeNode.svelte.d.ts +10 -0
- package/dist/core/TreeView/TreeView.svelte +163 -0
- package/dist/core/TreeView/TreeView.svelte.d.ts +10 -0
- package/dist/core/TreeView/index.d.ts +48 -0
- package/dist/core/TreeView/index.js +50 -0
- package/dist/core/TreeView/tree-utils.d.ts +56 -0
- package/dist/core/TreeView/tree-utils.js +194 -0
- package/dist/core/TreeView/tree.model.d.ts +104 -0
- package/dist/core/TreeView/tree.model.js +5 -0
- package/dist/core/TreeView/tree.store.d.ts +10 -0
- package/dist/core/TreeView/tree.store.js +225 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
@@ -0,0 +1,241 @@
|
|
1
|
+
/**
|
2
|
+
* Generate realistic time series data for testing
|
3
|
+
*/
|
4
|
+
function generateTimeSeries(startDate, points, baseValue, variance, intervalMinutes = 15) {
|
5
|
+
const data = [];
|
6
|
+
const current = new Date(startDate);
|
7
|
+
for (let i = 0; i < points; i++) {
|
8
|
+
const noise = (Math.random() - 0.5) * variance;
|
9
|
+
const trend = Math.sin((i / points) * Math.PI * 2) * (variance / 2); // Sinusoidal pattern
|
10
|
+
const value = Math.max(0, baseValue + noise + trend);
|
11
|
+
data.push({
|
12
|
+
timestamp: current.toISOString(),
|
13
|
+
value: Math.round(value * 100) / 100 // Round to 2 decimals
|
14
|
+
});
|
15
|
+
current.setMinutes(current.getMinutes() + intervalMinutes);
|
16
|
+
}
|
17
|
+
return data;
|
18
|
+
}
|
19
|
+
/**
|
20
|
+
* Get base throughput value for a frequency band (Mbps)
|
21
|
+
*/
|
22
|
+
function getThroughputBase(band) {
|
23
|
+
const baseValues = {
|
24
|
+
700: 30,
|
25
|
+
800: 35,
|
26
|
+
900: 40,
|
27
|
+
1800: 60,
|
28
|
+
2100: 80,
|
29
|
+
2600: 100,
|
30
|
+
3500: 150 // C-Band
|
31
|
+
};
|
32
|
+
return baseValues[band];
|
33
|
+
}
|
34
|
+
/**
|
35
|
+
* Get base timing advance value for a frequency band (microseconds)
|
36
|
+
*/
|
37
|
+
function getTimingAdvanceBase(band) {
|
38
|
+
// Lower frequency = larger cell = higher TA
|
39
|
+
const baseValues = {
|
40
|
+
700: 8.5,
|
41
|
+
800: 7.8,
|
42
|
+
900: 7.2,
|
43
|
+
1800: 5.5,
|
44
|
+
2100: 4.8,
|
45
|
+
2600: 4.2,
|
46
|
+
3500: 3.5
|
47
|
+
};
|
48
|
+
return baseValues[band];
|
49
|
+
}
|
50
|
+
/**
|
51
|
+
* Generate a single cell with KPI data
|
52
|
+
*/
|
53
|
+
function generateCell(siteId, sectorId, band, startDate, points = 96 // 24 hours at 15-min intervals
|
54
|
+
) {
|
55
|
+
const cellId = `${siteId}-${sectorId}-${band}`;
|
56
|
+
return {
|
57
|
+
cellId,
|
58
|
+
band,
|
59
|
+
kpis: {
|
60
|
+
throughput: generateTimeSeries(startDate, points, getThroughputBase(band), getThroughputBase(band) * 0.3, // 30% variance
|
61
|
+
15),
|
62
|
+
timingAdvance: generateTimeSeries(startDate, points, getTimingAdvanceBase(band), 1.5, // ±1.5 μs variance
|
63
|
+
15)
|
64
|
+
}
|
65
|
+
};
|
66
|
+
}
|
67
|
+
/**
|
68
|
+
* Generate mock cellular sites for testing
|
69
|
+
*/
|
70
|
+
export function generateMockCellularData() {
|
71
|
+
const startDate = new Date('2025-10-09T00:00:00Z');
|
72
|
+
const points = 96; // 24 hours of data
|
73
|
+
return [
|
74
|
+
// Site A - Urban, 3 sectors, multiple bands
|
75
|
+
{
|
76
|
+
siteId: 'site-a',
|
77
|
+
siteName: 'Site A (Urban)',
|
78
|
+
sectors: [
|
79
|
+
{
|
80
|
+
sectorId: 'sec-1',
|
81
|
+
sectorName: 'Sector 1',
|
82
|
+
azimuth: 0,
|
83
|
+
cells: [
|
84
|
+
generateCell('site-a', 'sec-1', 700, startDate, points),
|
85
|
+
generateCell('site-a', 'sec-1', 2100, startDate, points),
|
86
|
+
generateCell('site-a', 'sec-1', 3500, startDate, points)
|
87
|
+
]
|
88
|
+
},
|
89
|
+
{
|
90
|
+
sectorId: 'sec-2',
|
91
|
+
sectorName: 'Sector 2',
|
92
|
+
azimuth: 120,
|
93
|
+
cells: [
|
94
|
+
generateCell('site-a', 'sec-2', 700, startDate, points),
|
95
|
+
generateCell('site-a', 'sec-2', 1800, startDate, points),
|
96
|
+
generateCell('site-a', 'sec-2', 2100, startDate, points)
|
97
|
+
]
|
98
|
+
},
|
99
|
+
{
|
100
|
+
sectorId: 'sec-3',
|
101
|
+
sectorName: 'Sector 3',
|
102
|
+
azimuth: 240,
|
103
|
+
cells: [
|
104
|
+
generateCell('site-a', 'sec-3', 1800, startDate, points),
|
105
|
+
generateCell('site-a', 'sec-3', 2600, startDate, points),
|
106
|
+
generateCell('site-a', 'sec-3', 3500, startDate, points)
|
107
|
+
]
|
108
|
+
}
|
109
|
+
]
|
110
|
+
},
|
111
|
+
// Site B - Suburban, 2 sectors, fewer bands
|
112
|
+
{
|
113
|
+
siteId: 'site-b',
|
114
|
+
siteName: 'Site B (Suburban)',
|
115
|
+
sectors: [
|
116
|
+
{
|
117
|
+
sectorId: 'sec-1',
|
118
|
+
sectorName: 'Sector 1',
|
119
|
+
azimuth: 0,
|
120
|
+
cells: [
|
121
|
+
generateCell('site-b', 'sec-1', 800, startDate, points),
|
122
|
+
generateCell('site-b', 'sec-1', 2100, startDate, points)
|
123
|
+
]
|
124
|
+
},
|
125
|
+
{
|
126
|
+
sectorId: 'sec-2',
|
127
|
+
sectorName: 'Sector 2',
|
128
|
+
azimuth: 180,
|
129
|
+
cells: [
|
130
|
+
generateCell('site-b', 'sec-2', 800, startDate, points),
|
131
|
+
generateCell('site-b', 'sec-2', 1800, startDate, points),
|
132
|
+
generateCell('site-b', 'sec-2', 2600, startDate, points)
|
133
|
+
]
|
134
|
+
}
|
135
|
+
]
|
136
|
+
},
|
137
|
+
// Site C - Rural, 4 sectors, low-band focus
|
138
|
+
{
|
139
|
+
siteId: 'site-c',
|
140
|
+
siteName: 'Site C (Rural)',
|
141
|
+
sectors: [
|
142
|
+
{
|
143
|
+
sectorId: 'sec-1',
|
144
|
+
sectorName: 'Sector 1',
|
145
|
+
azimuth: 0,
|
146
|
+
cells: [
|
147
|
+
generateCell('site-c', 'sec-1', 700, startDate, points),
|
148
|
+
generateCell('site-c', 'sec-1', 900, startDate, points)
|
149
|
+
]
|
150
|
+
},
|
151
|
+
{
|
152
|
+
sectorId: 'sec-2',
|
153
|
+
sectorName: 'Sector 2',
|
154
|
+
azimuth: 90,
|
155
|
+
cells: [
|
156
|
+
generateCell('site-c', 'sec-2', 700, startDate, points),
|
157
|
+
generateCell('site-c', 'sec-2', 1800, startDate, points)
|
158
|
+
]
|
159
|
+
},
|
160
|
+
{
|
161
|
+
sectorId: 'sec-3',
|
162
|
+
sectorName: 'Sector 3',
|
163
|
+
azimuth: 180,
|
164
|
+
cells: [
|
165
|
+
generateCell('site-c', 'sec-3', 800, startDate, points),
|
166
|
+
generateCell('site-c', 'sec-3', 900, startDate, points)
|
167
|
+
]
|
168
|
+
},
|
169
|
+
{
|
170
|
+
sectorId: 'sec-4',
|
171
|
+
sectorName: 'Sector 4',
|
172
|
+
azimuth: 270,
|
173
|
+
cells: [
|
174
|
+
generateCell('site-c', 'sec-4', 700, startDate, points),
|
175
|
+
generateCell('site-c', 'sec-4', 2100, startDate, points)
|
176
|
+
]
|
177
|
+
}
|
178
|
+
]
|
179
|
+
},
|
180
|
+
// Site D - Dense Urban, 3 sectors, high-band focus
|
181
|
+
{
|
182
|
+
siteId: 'site-d',
|
183
|
+
siteName: 'Site D (Dense Urban)',
|
184
|
+
sectors: [
|
185
|
+
{
|
186
|
+
sectorId: 'sec-1',
|
187
|
+
sectorName: 'Sector 1',
|
188
|
+
azimuth: 0,
|
189
|
+
cells: [
|
190
|
+
generateCell('site-d', 'sec-1', 2100, startDate, points),
|
191
|
+
generateCell('site-d', 'sec-1', 2600, startDate, points),
|
192
|
+
generateCell('site-d', 'sec-1', 3500, startDate, points)
|
193
|
+
]
|
194
|
+
},
|
195
|
+
{
|
196
|
+
sectorId: 'sec-2',
|
197
|
+
sectorName: 'Sector 2',
|
198
|
+
azimuth: 120,
|
199
|
+
cells: [
|
200
|
+
generateCell('site-d', 'sec-2', 1800, startDate, points),
|
201
|
+
generateCell('site-d', 'sec-2', 2600, startDate, points),
|
202
|
+
generateCell('site-d', 'sec-2', 3500, startDate, points)
|
203
|
+
]
|
204
|
+
},
|
205
|
+
{
|
206
|
+
sectorId: 'sec-3',
|
207
|
+
sectorName: 'Sector 3',
|
208
|
+
azimuth: 240,
|
209
|
+
cells: [
|
210
|
+
generateCell('site-d', 'sec-3', 2100, startDate, points),
|
211
|
+
generateCell('site-d', 'sec-3', 2600, startDate, points),
|
212
|
+
generateCell('site-d', 'sec-3', 3500, startDate, points)
|
213
|
+
]
|
214
|
+
}
|
215
|
+
]
|
216
|
+
}
|
217
|
+
];
|
218
|
+
}
|
219
|
+
/**
|
220
|
+
* Get color for frequency band (consistent color scheme)
|
221
|
+
*/
|
222
|
+
export function getBandColor(band) {
|
223
|
+
const colors = {
|
224
|
+
700: '#e74c3c', // Red
|
225
|
+
800: '#e67e22', // Orange
|
226
|
+
900: '#f39c12', // Yellow-Orange
|
227
|
+
1800: '#3498db', // Blue
|
228
|
+
2100: '#9b59b6', // Purple
|
229
|
+
2600: '#1abc9c', // Teal
|
230
|
+
3500: '#2ecc71' // Green (C-Band)
|
231
|
+
};
|
232
|
+
return colors[band];
|
233
|
+
}
|
234
|
+
/**
|
235
|
+
* Get label for frequency band
|
236
|
+
*/
|
237
|
+
export function getBandLabel(band) {
|
238
|
+
if (band === 3500)
|
239
|
+
return `${band} MHz (C-Band)`;
|
240
|
+
return `${band} MHz`;
|
241
|
+
}
|
@@ -25,9 +25,11 @@
|
|
25
25
|
sectionId?: string;
|
26
26
|
sectionMovingAverage?: MovingAverageConfig; // Section-level MA config
|
27
27
|
layoutMovingAverage?: MovingAverageConfig; // Layout-level MA config
|
28
|
+
runtimeMAOverride?: MovingAverageConfig | null; // Runtime override from global controls
|
29
|
+
runtimeShowOriginal?: boolean; // Runtime control for showing original lines
|
28
30
|
}
|
29
31
|
|
30
|
-
let { chart, data, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage }: Props = $props();
|
32
|
+
let { chart, data, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, runtimeMAOverride, runtimeShowOriginal }: Props = $props();
|
31
33
|
|
32
34
|
// Chart container div and state
|
33
35
|
let chartDiv: HTMLElement;
|
@@ -71,34 +73,67 @@
|
|
71
73
|
let colorIndex = 0;
|
72
74
|
|
73
75
|
// Helper function to apply MA with full hierarchy:
|
74
|
-
// KPI > Chart > Section > Layout (higher priority wins)
|
76
|
+
// Runtime Override > KPI > Chart > Section > Layout (higher priority wins)
|
75
77
|
const applyMovingAverageHierarchy = (kpi: any) => {
|
76
|
-
//
|
78
|
+
// Step 1: Get base MA config from hierarchy (KPI > Chart > Section > Layout)
|
79
|
+
let baseMA: MovingAverageConfig | undefined;
|
80
|
+
|
77
81
|
if (kpi.movingAverage) {
|
78
|
-
|
82
|
+
baseMA = kpi.movingAverage;
|
83
|
+
} else if (chart.movingAverage) {
|
84
|
+
baseMA = chart.movingAverage;
|
85
|
+
} else if (sectionMovingAverage) {
|
86
|
+
baseMA = sectionMovingAverage;
|
87
|
+
} else if (layoutMovingAverage) {
|
88
|
+
baseMA = layoutMovingAverage;
|
79
89
|
}
|
80
90
|
|
81
|
-
//
|
82
|
-
if (
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
91
|
+
// Step 2: Apply runtime overrides (highest priority)
|
92
|
+
if (runtimeMAOverride !== undefined && runtimeMAOverride !== null) {
|
93
|
+
// Runtime override explicitly disables MA
|
94
|
+
if (runtimeMAOverride.enabled === false) {
|
95
|
+
return {
|
96
|
+
...kpi,
|
97
|
+
movingAverage: { enabled: false, window: 7, showOriginal: true }
|
98
|
+
};
|
99
|
+
}
|
100
|
+
|
101
|
+
// Runtime override provides window - merge with base or use override
|
102
|
+
if (baseMA) {
|
103
|
+
return {
|
104
|
+
...kpi,
|
105
|
+
movingAverage: {
|
106
|
+
enabled: runtimeMAOverride.enabled ?? baseMA.enabled,
|
107
|
+
window: runtimeMAOverride.window ?? baseMA.window,
|
108
|
+
showOriginal: runtimeShowOriginal ?? baseMA.showOriginal,
|
109
|
+
label: baseMA.label
|
110
|
+
}
|
111
|
+
};
|
112
|
+
} else {
|
113
|
+
// No base config, use runtime override entirely
|
114
|
+
return {
|
115
|
+
...kpi,
|
116
|
+
movingAverage: runtimeMAOverride
|
117
|
+
};
|
118
|
+
}
|
87
119
|
}
|
88
120
|
|
89
|
-
//
|
90
|
-
if (
|
121
|
+
// Step 3: Apply showOriginal runtime control if set
|
122
|
+
if (baseMA && runtimeShowOriginal !== undefined) {
|
91
123
|
return {
|
92
124
|
...kpi,
|
93
|
-
movingAverage:
|
125
|
+
movingAverage: {
|
126
|
+
...baseMA,
|
127
|
+
showOriginal: runtimeShowOriginal
|
128
|
+
}
|
94
129
|
};
|
95
130
|
}
|
96
131
|
|
97
|
-
//
|
98
|
-
if (
|
132
|
+
// Step 4: Return KPI with base MA or no MA
|
133
|
+
if (baseMA) {
|
99
134
|
return {
|
100
135
|
...kpi,
|
101
|
-
movingAverage:
|
136
|
+
movingAverage: baseMA
|
102
137
|
};
|
103
138
|
}
|
104
139
|
|
@@ -217,6 +252,20 @@
|
|
217
252
|
};
|
218
253
|
}
|
219
254
|
});
|
255
|
+
|
256
|
+
// React to prop changes - re-render chart when runtime controls change
|
257
|
+
$effect(() => {
|
258
|
+
// Watch these props and re-render when they change
|
259
|
+
runtimeMAOverride;
|
260
|
+
runtimeShowOriginal;
|
261
|
+
data;
|
262
|
+
markers;
|
263
|
+
|
264
|
+
// Only re-render if chartDiv is already initialized
|
265
|
+
if (chartDiv && chartDiv.children.length > 0) {
|
266
|
+
renderChart();
|
267
|
+
}
|
268
|
+
});
|
220
269
|
</script>
|
221
270
|
|
222
271
|
<div class="chart-card" role="group" oncontextmenu={handleContextMenu}>
|
@@ -8,6 +8,8 @@ interface Props {
|
|
8
8
|
sectionId?: string;
|
9
9
|
sectionMovingAverage?: MovingAverageConfig;
|
10
10
|
layoutMovingAverage?: MovingAverageConfig;
|
11
|
+
runtimeMAOverride?: MovingAverageConfig | null;
|
12
|
+
runtimeShowOriginal?: boolean;
|
11
13
|
}
|
12
14
|
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
13
15
|
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
<script lang="ts">
|
4
4
|
import { onMount } from 'svelte';
|
5
|
-
import type { Layout, Mode, ChartMarker, Section, ChartGrid, Chart } from './charts.model.js';
|
5
|
+
import type { Layout, Mode, ChartMarker, Section, ChartGrid, Chart, GlobalChartControls, MovingAverageConfig } from './charts.model.js';
|
6
6
|
import ChartCard from './ChartCard.svelte';
|
7
|
+
import GlobalControls from './GlobalControls.svelte';
|
7
8
|
|
8
9
|
interface Props {
|
9
10
|
layout: Layout;
|
@@ -12,6 +13,7 @@
|
|
12
13
|
markers?: ChartMarker[]; // Global markers for all charts
|
13
14
|
plotlyLayout?: any; // Optional custom Plotly layout
|
14
15
|
enableAdaptation?: boolean; // Enable size-based adaptations
|
16
|
+
showGlobalControls?: boolean; // Show/hide global controls section (default: true)
|
15
17
|
}
|
16
18
|
|
17
19
|
const GRID_DIMENSIONS: Record<ChartGrid, { rows: number; columns: number }> = {
|
@@ -50,7 +52,49 @@
|
|
50
52
|
section: Section;
|
51
53
|
}
|
52
54
|
|
53
|
-
let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true }: Props = $props();
|
55
|
+
let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true, showGlobalControls = true }: Props = $props();
|
56
|
+
|
57
|
+
// Global runtime controls state
|
58
|
+
let globalControls = $state<GlobalChartControls>({
|
59
|
+
movingAverage: {
|
60
|
+
enabled: true,
|
61
|
+
windowOverride: undefined,
|
62
|
+
showOriginal: true
|
63
|
+
}
|
64
|
+
});
|
65
|
+
|
66
|
+
// Handler for global controls updates
|
67
|
+
function handleControlsUpdate(updatedControls: GlobalChartControls) {
|
68
|
+
globalControls = updatedControls;
|
69
|
+
}
|
70
|
+
|
71
|
+
// Toggle state for showing/hiding the controls panel
|
72
|
+
let showControlsPanel = $state(true);
|
73
|
+
|
74
|
+
// Derived reactive state for MA override - automatically updates when globalControls changes
|
75
|
+
let effectiveMAOverride = $derived.by(() => {
|
76
|
+
const maControls = globalControls.movingAverage;
|
77
|
+
|
78
|
+
if (!maControls) return null;
|
79
|
+
|
80
|
+
// If MA is disabled globally, return disabled config
|
81
|
+
if (!maControls.enabled) {
|
82
|
+
return { enabled: false, window: 7, showOriginal: true };
|
83
|
+
}
|
84
|
+
|
85
|
+
// If window override is set, return override config
|
86
|
+
if (maControls.windowOverride !== undefined) {
|
87
|
+
return {
|
88
|
+
enabled: true,
|
89
|
+
window: maControls.windowOverride,
|
90
|
+
showOriginal: maControls.showOriginal ?? true
|
91
|
+
};
|
92
|
+
}
|
93
|
+
|
94
|
+
// If only showOriginal is different, we need to pass that through
|
95
|
+
// Return null to use configured values (handled in ChartCard)
|
96
|
+
return null;
|
97
|
+
});
|
54
98
|
|
55
99
|
// Internal tab state management
|
56
100
|
let activeTabId = $state(layout.sections[0]?.id || '');
|
@@ -248,6 +292,8 @@
|
|
248
292
|
sectionId={section.id}
|
249
293
|
sectionMovingAverage={section.movingAverage}
|
250
294
|
layoutMovingAverage={layout.movingAverage}
|
295
|
+
runtimeMAOverride={effectiveMAOverride}
|
296
|
+
runtimeShowOriginal={globalControls.movingAverage?.showOriginal}
|
251
297
|
on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, section)}
|
252
298
|
/>
|
253
299
|
</div>
|
@@ -256,28 +302,50 @@
|
|
256
302
|
{/snippet}
|
257
303
|
|
258
304
|
<div class="chart-component" bind:this={componentElement}>
|
305
|
+
<!-- Global Controls Section (appears above tabs/scrollspy) -->
|
306
|
+
{#if showGlobalControls && showControlsPanel}
|
307
|
+
<GlobalControls controls={globalControls} onUpdate={handleControlsUpdate} />
|
308
|
+
{/if}
|
309
|
+
|
259
310
|
<!-- Always render the main content (tabs or scrollspy) -->
|
260
311
|
{#if mode === 'tabs'}
|
261
312
|
<!-- Tab Mode with Navigation -->
|
262
313
|
<div class="tabs-container">
|
263
|
-
<!-- Tab Navigation -->
|
264
|
-
<
|
265
|
-
|
266
|
-
|
267
|
-
<
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
314
|
+
<!-- Tab Navigation with Controls Toggle -->
|
315
|
+
<div class="nav-tabs-wrapper">
|
316
|
+
<ul class="nav nav-tabs" role="tablist">
|
317
|
+
{#each layout.sections as section, index}
|
318
|
+
<li class="nav-item" role="presentation">
|
319
|
+
<button
|
320
|
+
class="nav-link {section.id === activeTabId ? 'active' : ''}"
|
321
|
+
id="{section.id}-tab"
|
322
|
+
type="button"
|
323
|
+
role="tab"
|
324
|
+
aria-controls="{section.id}"
|
325
|
+
aria-selected="{section.id === activeTabId}"
|
326
|
+
onclick={() => activeTabId = section.id}
|
327
|
+
>
|
328
|
+
{section.title}
|
329
|
+
</button>
|
330
|
+
</li>
|
331
|
+
{/each}
|
332
|
+
</ul>
|
333
|
+
|
334
|
+
<!-- Controls Toggle Button -->
|
335
|
+
{#if showGlobalControls}
|
336
|
+
<button
|
337
|
+
class="btn btn-sm btn-outline-secondary controls-toggle"
|
338
|
+
onclick={() => showControlsPanel = !showControlsPanel}
|
339
|
+
title={showControlsPanel ? "Hide Controls" : "Show Controls"}
|
340
|
+
type="button"
|
341
|
+
>
|
342
|
+
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
343
|
+
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
344
|
+
</svg>
|
345
|
+
<span class="ms-1">{showControlsPanel ? 'Hide' : 'Show'} Controls</span>
|
346
|
+
</button>
|
347
|
+
{/if}
|
348
|
+
</div>
|
281
349
|
|
282
350
|
<!-- Tab Content -->
|
283
351
|
<div class="tab-content">
|
@@ -295,21 +363,38 @@
|
|
295
363
|
{:else if mode === 'scrollspy'}
|
296
364
|
<!-- ScrollSpy Mode with Navigation -->
|
297
365
|
<div class="scrollspy-container">
|
298
|
-
<!-- ScrollSpy Navigation -->
|
366
|
+
<!-- ScrollSpy Navigation with Controls Toggle -->
|
299
367
|
<nav class="scrollspy-nav">
|
300
|
-
<
|
301
|
-
|
302
|
-
|
303
|
-
<
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
368
|
+
<div class="nav-wrapper">
|
369
|
+
<ul class="nav nav-pills">
|
370
|
+
{#each layout.sections as section}
|
371
|
+
<li class="nav-item">
|
372
|
+
<a
|
373
|
+
class="nav-link {section.id === activeSectionId ? 'active' : ''}"
|
374
|
+
href="#{section.id}"
|
375
|
+
onclick={(e) => scrollToSection(section.id, e)}
|
376
|
+
>
|
377
|
+
{section.title}
|
378
|
+
</a>
|
379
|
+
</li>
|
380
|
+
{/each}
|
381
|
+
</ul>
|
382
|
+
|
383
|
+
<!-- Controls Toggle Button -->
|
384
|
+
{#if showGlobalControls}
|
385
|
+
<button
|
386
|
+
class="btn btn-sm btn-outline-secondary controls-toggle"
|
387
|
+
onclick={() => showControlsPanel = !showControlsPanel}
|
388
|
+
title={showControlsPanel ? "Hide Controls" : "Show Controls"}
|
389
|
+
type="button"
|
390
|
+
>
|
391
|
+
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
392
|
+
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
393
|
+
</svg>
|
394
|
+
<span class="ms-1">{showControlsPanel ? 'Hide' : 'Show'} Controls</span>
|
395
|
+
</button>
|
396
|
+
{/if}
|
397
|
+
</div>
|
313
398
|
</nav>
|
314
399
|
|
315
400
|
<!-- ScrollSpy Content -->
|
@@ -347,6 +432,8 @@
|
|
347
432
|
sectionId={activeZoom.section.id}
|
348
433
|
sectionMovingAverage={activeZoom.section.movingAverage}
|
349
434
|
layoutMovingAverage={layout.movingAverage}
|
435
|
+
runtimeMAOverride={effectiveMAOverride}
|
436
|
+
runtimeShowOriginal={globalControls.movingAverage?.showOriginal}
|
350
437
|
on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, activeZoom.section)}
|
351
438
|
/>
|
352
439
|
</div>
|
@@ -593,4 +680,49 @@
|
|
593
680
|
min-height: 80px; /* Even smaller minimum for grid */
|
594
681
|
max-height: 100%; /* Constrain to slot */
|
595
682
|
}
|
683
|
+
|
684
|
+
/* Tab navigation wrapper with toggle button */
|
685
|
+
.nav-tabs-wrapper {
|
686
|
+
display: flex;
|
687
|
+
align-items: center;
|
688
|
+
gap: 1rem;
|
689
|
+
border-bottom: 1px solid #dee2e6;
|
690
|
+
}
|
691
|
+
|
692
|
+
.nav-tabs-wrapper .nav-tabs {
|
693
|
+
flex-grow: 1;
|
694
|
+
border-bottom: none;
|
695
|
+
margin-bottom: 0;
|
696
|
+
}
|
697
|
+
|
698
|
+
.controls-toggle {
|
699
|
+
display: flex;
|
700
|
+
align-items: center;
|
701
|
+
gap: 0.25rem;
|
702
|
+
white-space: nowrap;
|
703
|
+
border-radius: 0.25rem;
|
704
|
+
transition: all 0.2s ease;
|
705
|
+
}
|
706
|
+
|
707
|
+
.controls-toggle:hover {
|
708
|
+
background-color: #f8f9fa;
|
709
|
+
border-color: #6c757d;
|
710
|
+
}
|
711
|
+
|
712
|
+
.controls-toggle svg {
|
713
|
+
flex-shrink: 0;
|
714
|
+
}
|
715
|
+
|
716
|
+
/* ScrollSpy navigation wrapper with toggle button */
|
717
|
+
.scrollspy-nav .nav-wrapper {
|
718
|
+
display: flex;
|
719
|
+
align-items: center;
|
720
|
+
gap: 1rem;
|
721
|
+
}
|
722
|
+
|
723
|
+
.scrollspy-nav .nav-wrapper .nav {
|
724
|
+
flex-grow: 1;
|
725
|
+
}
|
596
726
|
</style>
|
727
|
+
|
728
|
+
|
@@ -6,6 +6,7 @@ interface Props {
|
|
6
6
|
markers?: ChartMarker[];
|
7
7
|
plotlyLayout?: any;
|
8
8
|
enableAdaptation?: boolean;
|
9
|
+
showGlobalControls?: boolean;
|
9
10
|
}
|
10
11
|
declare const ChartComponent: import("svelte").Component<Props, {}, "">;
|
11
12
|
type ChartComponent = ReturnType<typeof ChartComponent>;
|