@smartnet360/svelte-components 0.0.21 → 0.0.22
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/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/package.json +1 -1
@@ -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>;
|
@@ -0,0 +1,188 @@
|
|
1
|
+
<svelte:options runes={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import type { GlobalChartControls } from './charts.model.js';
|
5
|
+
|
6
|
+
interface Props {
|
7
|
+
controls: GlobalChartControls;
|
8
|
+
onUpdate: (controls: GlobalChartControls) => void;
|
9
|
+
}
|
10
|
+
|
11
|
+
let { controls, onUpdate }: Props = $props();
|
12
|
+
|
13
|
+
function updateControls(updates: Partial<GlobalChartControls>) {
|
14
|
+
onUpdate({
|
15
|
+
...controls,
|
16
|
+
...updates
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
function updateMovingAverage(updates: Partial<NonNullable<GlobalChartControls['movingAverage']>>) {
|
21
|
+
onUpdate({
|
22
|
+
...controls,
|
23
|
+
movingAverage: {
|
24
|
+
...controls.movingAverage!,
|
25
|
+
...updates
|
26
|
+
}
|
27
|
+
});
|
28
|
+
}
|
29
|
+
</script>
|
30
|
+
|
31
|
+
<div class="global-controls">
|
32
|
+
<div class="controls-section">
|
33
|
+
<!-- <span class="controls-label">Display Controls:</span> -->
|
34
|
+
|
35
|
+
<!-- Moving Average Controls -->
|
36
|
+
{#if controls.movingAverage}
|
37
|
+
<div class="control-group">
|
38
|
+
<!-- MA Enable Toggle Button -->
|
39
|
+
<input
|
40
|
+
type="checkbox"
|
41
|
+
class="btn-check"
|
42
|
+
id="maToggle"
|
43
|
+
checked={controls.movingAverage.enabled}
|
44
|
+
onchange={() => updateMovingAverage({ enabled: !controls.movingAverage!.enabled })}
|
45
|
+
/>
|
46
|
+
<label class="btn btn-outline-primary btn-sm" for="maToggle">
|
47
|
+
Moving Average
|
48
|
+
</label>
|
49
|
+
|
50
|
+
{#if controls.movingAverage.enabled}
|
51
|
+
<div class="control-subgroup">
|
52
|
+
<!-- MA Window Size -->
|
53
|
+
<div class="btn-group btn-group-sm" role="group" aria-label="MA Window">
|
54
|
+
<input
|
55
|
+
type="radio"
|
56
|
+
class="btn-check"
|
57
|
+
name="maWindow"
|
58
|
+
id="maWindowAuto"
|
59
|
+
checked={controls.movingAverage.windowOverride === undefined}
|
60
|
+
onchange={() => updateMovingAverage({ windowOverride: undefined })}
|
61
|
+
/>
|
62
|
+
<label class="btn btn-outline-primary" for="maWindowAuto">Auto</label>
|
63
|
+
|
64
|
+
<input
|
65
|
+
type="radio"
|
66
|
+
class="btn-check"
|
67
|
+
name="maWindow"
|
68
|
+
id="maWindow7"
|
69
|
+
checked={controls.movingAverage.windowOverride === 7}
|
70
|
+
onchange={() => updateMovingAverage({ windowOverride: 7 })}
|
71
|
+
/>
|
72
|
+
<label class="btn btn-outline-primary" for="maWindow7">7</label>
|
73
|
+
|
74
|
+
<input
|
75
|
+
type="radio"
|
76
|
+
class="btn-check"
|
77
|
+
name="maWindow"
|
78
|
+
id="maWindow14"
|
79
|
+
checked={controls.movingAverage.windowOverride === 14}
|
80
|
+
onchange={() => updateMovingAverage({ windowOverride: 14 })}
|
81
|
+
/>
|
82
|
+
<label class="btn btn-outline-primary" for="maWindow14">14</label>
|
83
|
+
|
84
|
+
<input
|
85
|
+
type="radio"
|
86
|
+
class="btn-check"
|
87
|
+
name="maWindow"
|
88
|
+
id="maWindow24"
|
89
|
+
checked={controls.movingAverage.windowOverride === 24}
|
90
|
+
onchange={() => updateMovingAverage({ windowOverride: 24 })}
|
91
|
+
/>
|
92
|
+
<label class="btn btn-outline-primary" for="maWindow24">24</label>
|
93
|
+
|
94
|
+
<input
|
95
|
+
type="radio"
|
96
|
+
class="btn-check"
|
97
|
+
name="maWindow"
|
98
|
+
id="maWindow30"
|
99
|
+
checked={controls.movingAverage.windowOverride === 30}
|
100
|
+
onchange={() => updateMovingAverage({ windowOverride: 30 })}
|
101
|
+
/>
|
102
|
+
<label class="btn btn-outline-primary" for="maWindow30">30</label>
|
103
|
+
</div>
|
104
|
+
|
105
|
+
<!-- Show Original Toggle Button -->
|
106
|
+
<input
|
107
|
+
type="checkbox"
|
108
|
+
class="btn-check"
|
109
|
+
id="showOriginal"
|
110
|
+
checked={controls.movingAverage.showOriginal}
|
111
|
+
onchange={() => updateMovingAverage({ showOriginal: !controls.movingAverage!.showOriginal })}
|
112
|
+
/>
|
113
|
+
<label class="btn btn-outline-primary btn-sm ms-2" for="showOriginal">
|
114
|
+
Show Original
|
115
|
+
</label>
|
116
|
+
</div>
|
117
|
+
{/if}
|
118
|
+
</div>
|
119
|
+
{/if}
|
120
|
+
|
121
|
+
<!-- Future controls can be added here -->
|
122
|
+
<!-- Example:
|
123
|
+
<div class="control-group">
|
124
|
+
<div class="form-check form-check-inline">
|
125
|
+
<input type="checkbox" id="markersToggle" />
|
126
|
+
<label for="markersToggle">Markers</label>
|
127
|
+
</div>
|
128
|
+
</div>
|
129
|
+
-->
|
130
|
+
</div>
|
131
|
+
</div>
|
132
|
+
|
133
|
+
<style>
|
134
|
+
/* Global Controls Section */
|
135
|
+
.global-controls {
|
136
|
+
flex-shrink: 0;
|
137
|
+
background-color: #f8f9fa;
|
138
|
+
border-bottom: 1px solid #dee2e6;
|
139
|
+
padding: 0.5rem 1rem;
|
140
|
+
font-size: 0.875rem;
|
141
|
+
}
|
142
|
+
|
143
|
+
.controls-section {
|
144
|
+
display: flex;
|
145
|
+
align-items: center;
|
146
|
+
gap: 1.5rem;
|
147
|
+
flex-wrap: wrap;
|
148
|
+
}
|
149
|
+
|
150
|
+
.controls-label {
|
151
|
+
font-weight: 600;
|
152
|
+
color: #495057;
|
153
|
+
margin-right: 0.5rem;
|
154
|
+
}
|
155
|
+
|
156
|
+
.control-group {
|
157
|
+
display: flex;
|
158
|
+
align-items: center;
|
159
|
+
gap: 0.75rem;
|
160
|
+
}
|
161
|
+
|
162
|
+
.control-subgroup {
|
163
|
+
display: flex;
|
164
|
+
align-items: center;
|
165
|
+
gap: 0.5rem;
|
166
|
+
padding-left: 0.5rem;
|
167
|
+
border-left: 2px solid #dee2e6;
|
168
|
+
}
|
169
|
+
|
170
|
+
.global-controls :global(.btn-group-sm .btn) {
|
171
|
+
padding: 0.25rem 0.75rem;
|
172
|
+
font-size: 0.75rem;
|
173
|
+
font-weight: 500;
|
174
|
+
}
|
175
|
+
|
176
|
+
/* Responsive adjustments */
|
177
|
+
@media (max-width: 768px) {
|
178
|
+
.controls-section {
|
179
|
+
flex-direction: column;
|
180
|
+
align-items: flex-start;
|
181
|
+
gap: 0.75rem;
|
182
|
+
}
|
183
|
+
|
184
|
+
.control-subgroup {
|
185
|
+
flex-wrap: wrap;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
</style>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import type { GlobalChartControls } from './charts.model.js';
|
2
|
+
interface Props {
|
3
|
+
controls: GlobalChartControls;
|
4
|
+
onUpdate: (controls: GlobalChartControls) => void;
|
5
|
+
}
|
6
|
+
declare const GlobalControls: import("svelte").Component<Props, {}, "">;
|
7
|
+
type GlobalControls = ReturnType<typeof GlobalControls>;
|
8
|
+
export default GlobalControls;
|
@@ -43,3 +43,10 @@ export interface ChartMarker {
|
|
43
43
|
showLabel?: boolean;
|
44
44
|
category?: 'release' | 'incident' | 'maintenance' | 'other';
|
45
45
|
}
|
46
|
+
export interface GlobalChartControls {
|
47
|
+
movingAverage?: {
|
48
|
+
enabled: boolean;
|
49
|
+
windowOverride?: number;
|
50
|
+
showOriginal?: boolean;
|
51
|
+
};
|
52
|
+
}
|