@smartnet360/svelte-components 0.0.44 → 0.0.46
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/site-check/SiteCheck.svelte +130 -29
- package/dist/apps/site-check/SiteCheck.svelte.d.ts +2 -0
- package/dist/apps/site-check/helper.js +17 -8
- package/dist/core/Charts/ChartCard.svelte +15 -4
- package/dist/core/Charts/ChartCard.svelte.d.ts +1 -0
- package/dist/core/Charts/ChartComponent.svelte +6 -1
- package/dist/core/Charts/GlobalControls.svelte +97 -19
- package/dist/core/Charts/charts.model.d.ts +5 -0
- package/package.json +1 -1
|
@@ -20,9 +20,33 @@
|
|
|
20
20
|
cellStyling?: CellStylingConfig; // Optional cell styling config (defaults to defaultCellStyling)
|
|
21
21
|
initialGrouping?: TreeGroupingConfig; // Optional initial tree grouping (defaults to Site → Azimuth → Cell)
|
|
22
22
|
showGroupingSelector?: boolean; // Show/hide the grouping dropdown (default: true)
|
|
23
|
+
onSearch?: (searchTerm: string) => void; // Optional: Search callback (if provided, shows search box)
|
|
24
|
+
searchPlaceholder?: string; // Optional: Search box placeholder text (default: "Search...")
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
let { rawData, multiCellLayout, singleLteLayout, singleNrLayout, baseMetrics, mode = "scrollspy", markers = [], cellStyling = defaultCellStyling, initialGrouping = defaultTreeGrouping, showGroupingSelector = true }: Props = $props();
|
|
27
|
+
let { rawData, multiCellLayout, singleLteLayout, singleNrLayout, baseMetrics, mode = "scrollspy", markers = [], cellStyling = defaultCellStyling, initialGrouping = defaultTreeGrouping, showGroupingSelector = true, onSearch, searchPlaceholder = "Search..." }: Props = $props();
|
|
28
|
+
|
|
29
|
+
// Search state
|
|
30
|
+
let searchTerm = $state('');
|
|
31
|
+
|
|
32
|
+
// Controls visibility state (starts expanded)
|
|
33
|
+
let controlsExpanded = $state(true);
|
|
34
|
+
|
|
35
|
+
// Handlers
|
|
36
|
+
function handleSearch() {
|
|
37
|
+
if (onSearch) {
|
|
38
|
+
onSearch(searchTerm);
|
|
39
|
+
log('🔍 Search triggered:', searchTerm);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function handleClearSearch() {
|
|
44
|
+
searchTerm = '';
|
|
45
|
+
if (onSearch) {
|
|
46
|
+
onSearch('');
|
|
47
|
+
log('🧹 Search cleared');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
26
50
|
|
|
27
51
|
// Check feature health
|
|
28
52
|
let isHealthy = $state(checkHealth('sitecheck'));
|
|
@@ -63,8 +87,7 @@
|
|
|
63
87
|
firstNode: treeNodes[0],
|
|
64
88
|
grouping: treeGrouping
|
|
65
89
|
});
|
|
66
|
-
if(isHealthy
|
|
67
|
-
console.log('Configuration Required');
|
|
90
|
+
if(!isHealthy) {
|
|
68
91
|
return;
|
|
69
92
|
}
|
|
70
93
|
// Create tree store
|
|
@@ -110,13 +133,14 @@
|
|
|
110
133
|
|
|
111
134
|
// Expand layout based on selected cells and chosen base layout
|
|
112
135
|
let chartLayout = $derived.by(() => {
|
|
136
|
+
// Pass cellStyling - helper will decide per-section whether to use it
|
|
113
137
|
const expanded = expandLayoutForCells(selectedBaseLayout, filteredData, cellStyling);
|
|
114
138
|
log('📐 Chart Layout:', {
|
|
115
139
|
layoutName: selectedBaseLayout.layoutName,
|
|
140
|
+
layoutDefaultColors: selectedBaseLayout.useDefaultChartColors ?? false,
|
|
116
141
|
sectionsCount: expanded.sections.length,
|
|
117
142
|
totalCharts: expanded.sections.reduce((sum, s) => sum + s.charts.length, 0),
|
|
118
|
-
firstSection: expanded.sections[0]
|
|
119
|
-
cellStylingEnabled: !!cellStyling
|
|
143
|
+
firstSection: expanded.sections[0]
|
|
120
144
|
});
|
|
121
145
|
return expanded;
|
|
122
146
|
});
|
|
@@ -191,30 +215,90 @@
|
|
|
191
215
|
<div class="row flex-grow-1" style="min-height: 0;">
|
|
192
216
|
<!-- Left: Tree View -->
|
|
193
217
|
<div class="col-lg-3 col-md-4 border-end bg-white d-flex flex-column" style="min-height: 0; height: 100%;">
|
|
194
|
-
<!--
|
|
195
|
-
{#if showGroupingSelector}
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
<!-- Collapsible Controls Toggle -->
|
|
219
|
+
{#if onSearch || showGroupingSelector}
|
|
220
|
+
<button
|
|
221
|
+
class="controls-toggle w-100 text-start p-2 bg-light border-bottom d-flex align-items-center flex-shrink-0"
|
|
222
|
+
onclick={() => controlsExpanded = !controlsExpanded}
|
|
223
|
+
aria-expanded={controlsExpanded}
|
|
224
|
+
aria-label="Toggle controls"
|
|
225
|
+
>
|
|
226
|
+
<i class="bi bi-sliders me-2"></i>
|
|
227
|
+
<span class="fw-semibold small">Controls</span>
|
|
228
|
+
<i class="bi ms-auto"
|
|
229
|
+
class:bi-chevron-down={controlsExpanded}
|
|
230
|
+
class:bi-chevron-right={!controlsExpanded}>
|
|
231
|
+
</i>
|
|
232
|
+
</button>
|
|
233
|
+
{/if}
|
|
234
|
+
|
|
235
|
+
<!-- Collapsible Controls Content -->
|
|
236
|
+
{#if controlsExpanded}
|
|
237
|
+
<!-- Search Box -->
|
|
238
|
+
{#if onSearch}
|
|
239
|
+
<div class="p-3 border-bottom flex-shrink-0">
|
|
240
|
+
<label for="searchInput" class="form-label small fw-semibold mb-2">
|
|
241
|
+
Search
|
|
242
|
+
</label>
|
|
243
|
+
<div class="input-group input-group-sm">
|
|
244
|
+
<input
|
|
245
|
+
type="text"
|
|
246
|
+
id="searchInput"
|
|
247
|
+
class="form-control"
|
|
248
|
+
placeholder={searchPlaceholder}
|
|
249
|
+
bind:value={searchTerm}
|
|
250
|
+
onkeydown={(e) => {
|
|
251
|
+
if (e.key === 'Enter') {
|
|
252
|
+
handleSearch();
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
{#if searchTerm}
|
|
257
|
+
<button
|
|
258
|
+
class="btn btn-outline-secondary"
|
|
259
|
+
type="button"
|
|
260
|
+
onclick={handleClearSearch}
|
|
261
|
+
title="Clear search"
|
|
262
|
+
aria-label="Clear search"
|
|
263
|
+
>
|
|
264
|
+
<i class="bi bi-x-lg"></i>
|
|
265
|
+
</button>
|
|
266
|
+
{/if}
|
|
267
|
+
<button
|
|
268
|
+
class="btn btn-primary"
|
|
269
|
+
type="button"
|
|
270
|
+
onclick={handleSearch}
|
|
271
|
+
title="Search"
|
|
272
|
+
aria-label="Search"
|
|
273
|
+
>
|
|
274
|
+
<i class="bi bi-search"></i>
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
{/if}
|
|
279
|
+
|
|
280
|
+
<!-- Grouping Selector -->
|
|
281
|
+
{#if showGroupingSelector}
|
|
282
|
+
<div class="p-3 border-bottom flex-shrink-0">
|
|
283
|
+
<label for="groupingSelect" class="form-label small fw-semibold mb-2">
|
|
284
|
+
Tree Grouping
|
|
285
|
+
</label>
|
|
286
|
+
<select
|
|
287
|
+
id="groupingSelect"
|
|
288
|
+
class="form-select form-select-sm"
|
|
289
|
+
onchange={(e) => {
|
|
290
|
+
const index = parseInt(e.currentTarget.value);
|
|
291
|
+
treeGrouping = groupingPresets[index].value;
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
{#each groupingPresets as preset, i}
|
|
295
|
+
<option value={i} selected={JSON.stringify(preset.value) === JSON.stringify(treeGrouping)}>
|
|
296
|
+
{preset.label}
|
|
297
|
+
</option>
|
|
298
|
+
{/each}
|
|
299
|
+
</select>
|
|
300
|
+
</div>
|
|
301
|
+
{/if}
|
|
218
302
|
{/if}
|
|
219
303
|
|
|
220
304
|
<!-- Tree View -->
|
|
@@ -247,3 +331,20 @@
|
|
|
247
331
|
</div>
|
|
248
332
|
</div>
|
|
249
333
|
|
|
334
|
+
<style>
|
|
335
|
+
.controls-toggle {
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
border: none;
|
|
338
|
+
transition: background-color 0.2s;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.controls-toggle:hover {
|
|
342
|
+
background-color: #e9ecef !important;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.controls-toggle:focus {
|
|
346
|
+
outline: 2px solid #0d6efd;
|
|
347
|
+
outline-offset: -2px;
|
|
348
|
+
}
|
|
349
|
+
</style>
|
|
350
|
+
|
|
@@ -12,6 +12,8 @@ interface Props {
|
|
|
12
12
|
cellStyling?: CellStylingConfig;
|
|
13
13
|
initialGrouping?: TreeGroupingConfig;
|
|
14
14
|
showGroupingSelector?: boolean;
|
|
15
|
+
onSearch?: (searchTerm: string) => void;
|
|
16
|
+
searchPlaceholder?: string;
|
|
15
17
|
}
|
|
16
18
|
declare const SiteCheck: import("svelte").Component<Props, {}, "">;
|
|
17
19
|
type SiteCheck = ReturnType<typeof SiteCheck>;
|
|
@@ -24,14 +24,23 @@ export function expandLayoutForCells(baseLayout, data, stylingConfig) {
|
|
|
24
24
|
hoverMode: baseLayout.hoverMode, // Preserve hover mode from base layout
|
|
25
25
|
coloredHover: baseLayout.coloredHover, // Preserve colored hover setting
|
|
26
26
|
movingAverage: baseLayout.movingAverage, // Preserve moving average config
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
useDefaultChartColors: baseLayout.useDefaultChartColors, // Preserve layout-level default
|
|
28
|
+
sections: baseLayout.sections.map((section) => {
|
|
29
|
+
// Check per-section flag with fallback to layout-level, defaulting to false
|
|
30
|
+
const useDefaults = section.useDefaultChartColors ??
|
|
31
|
+
baseLayout.useDefaultChartColors ??
|
|
32
|
+
false;
|
|
33
|
+
// Decide styling for THIS section
|
|
34
|
+
const effectiveStyling = useDefaults ? undefined : stylingConfig;
|
|
35
|
+
return {
|
|
36
|
+
...section,
|
|
37
|
+
charts: section.charts.map((chart) => ({
|
|
38
|
+
...chart,
|
|
39
|
+
yLeft: expandKPIs(chart.yLeft, cells, effectiveStyling),
|
|
40
|
+
yRight: expandKPIs(chart.yRight, cells, effectiveStyling)
|
|
41
|
+
}))
|
|
42
|
+
};
|
|
43
|
+
})
|
|
35
44
|
};
|
|
36
45
|
return expandedLayout;
|
|
37
46
|
}
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
runtimeShowOriginal?: boolean; // Runtime control for showing original lines
|
|
25
25
|
runtimeShowMarkers?: boolean; // Runtime control for showing markers (default: true)
|
|
26
26
|
runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
|
|
27
|
+
runtimeHoverMode?: HoverMode; // Runtime override for hover mode from global controls
|
|
27
28
|
onchartcontextmenu?: (detail: {
|
|
28
29
|
chart: ChartModel;
|
|
29
30
|
sectionId?: string;
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
}) => void;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true, onchartcontextmenu }: Props = $props();
|
|
36
|
+
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true, runtimeHoverMode, onchartcontextmenu }: Props = $props();
|
|
36
37
|
|
|
37
38
|
// Chart container div and state
|
|
38
39
|
let chartDiv: HTMLElement;
|
|
@@ -174,8 +175,11 @@
|
|
|
174
175
|
colorIndex++;
|
|
175
176
|
});
|
|
176
177
|
|
|
178
|
+
// Determine effective hover mode: runtime override takes precedence over layout config
|
|
179
|
+
const effectiveHoverMode = runtimeHoverMode !== undefined ? runtimeHoverMode : layoutHoverMode;
|
|
180
|
+
|
|
177
181
|
// Create default modern layout using the centralized function
|
|
178
|
-
const defaultLayout: any = createDefaultPlotlyLayout(chart.title,
|
|
182
|
+
const defaultLayout: any = createDefaultPlotlyLayout(chart.title, effectiveHoverMode, layoutColoredHover);
|
|
179
183
|
|
|
180
184
|
// Override specific properties for this chart
|
|
181
185
|
defaultLayout.yaxis.title = {
|
|
@@ -235,7 +239,11 @@
|
|
|
235
239
|
// Use Plotly.react() for updates (preserves zoom/pan) or newPlot for initial render
|
|
236
240
|
if (chartInitialized) {
|
|
237
241
|
// Update existing chart - much faster, preserves user interactions
|
|
238
|
-
log('🔄 Updating chart with Plotly.react', {
|
|
242
|
+
log('🔄 Updating chart with Plotly.react', {
|
|
243
|
+
chartTitle: chart.title,
|
|
244
|
+
hoverMode: effectiveHoverMode,
|
|
245
|
+
layoutHoverMode: finalLayout.hovermode
|
|
246
|
+
});
|
|
239
247
|
Plotly.react(chartDiv, traces, finalLayout, config);
|
|
240
248
|
} else {
|
|
241
249
|
// Initial chart creation
|
|
@@ -243,7 +251,9 @@
|
|
|
243
251
|
chartTitle: chart.title,
|
|
244
252
|
traces: traces.length,
|
|
245
253
|
leftKPIs: chart.yLeft.length,
|
|
246
|
-
rightKPIs: chart.yRight.length
|
|
254
|
+
rightKPIs: chart.yRight.length,
|
|
255
|
+
hoverMode: effectiveHoverMode,
|
|
256
|
+
layoutHoverMode: finalLayout.hovermode
|
|
247
257
|
});
|
|
248
258
|
Plotly.newPlot(chartDiv, traces, finalLayout, config);
|
|
249
259
|
chartInitialized = true;
|
|
@@ -340,6 +350,7 @@
|
|
|
340
350
|
const currentShowOriginal = runtimeShowOriginal;
|
|
341
351
|
const currentShowMarkers = runtimeShowMarkers;
|
|
342
352
|
const currentShowLegend = runtimeShowLegend;
|
|
353
|
+
const currentHoverMode = runtimeHoverMode;
|
|
343
354
|
|
|
344
355
|
// Only re-render if chartDiv is already initialized
|
|
345
356
|
if (chartDiv && chartDiv.children.length > 0) {
|
|
@@ -85,6 +85,9 @@
|
|
|
85
85
|
},
|
|
86
86
|
legend: {
|
|
87
87
|
enabled: true // Default to showing legend
|
|
88
|
+
},
|
|
89
|
+
hoverMode: {
|
|
90
|
+
mode: layout.hoverMode ?? 'x' // Default to 'x' if not specified
|
|
88
91
|
}
|
|
89
92
|
});
|
|
90
93
|
|
|
@@ -94,7 +97,8 @@
|
|
|
94
97
|
movingAverageEnabled: updatedControls.movingAverage?.enabled,
|
|
95
98
|
windowOverride: updatedControls.movingAverage?.windowOverride,
|
|
96
99
|
markersEnabled: updatedControls.markers?.enabled,
|
|
97
|
-
legendEnabled: updatedControls.legend?.enabled
|
|
100
|
+
legendEnabled: updatedControls.legend?.enabled,
|
|
101
|
+
hoverMode: updatedControls.hoverMode?.mode
|
|
98
102
|
});
|
|
99
103
|
globalControls = updatedControls;
|
|
100
104
|
}
|
|
@@ -333,6 +337,7 @@
|
|
|
333
337
|
runtimeShowOriginal={globalControls.movingAverage?.showOriginal}
|
|
334
338
|
runtimeShowMarkers={globalControls.markers?.enabled}
|
|
335
339
|
runtimeShowLegend={globalControls.legend?.enabled}
|
|
340
|
+
runtimeHoverMode={globalControls.hoverMode?.mode}
|
|
336
341
|
onchartcontextmenu={(detail) => handleChartContextMenu(detail, section)}
|
|
337
342
|
/>
|
|
338
343
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
|
-
import type { GlobalChartControls } from './charts.model.js';
|
|
4
|
+
import type { GlobalChartControls, HoverMode } from './charts.model.js';
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
controls: GlobalChartControls;
|
|
@@ -48,6 +48,16 @@
|
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
function updateHoverMode(updates: Partial<NonNullable<GlobalChartControls['hoverMode']>>) {
|
|
53
|
+
onUpdate({
|
|
54
|
+
...controls,
|
|
55
|
+
hoverMode: {
|
|
56
|
+
...controls.hoverMode!,
|
|
57
|
+
...updates
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
51
61
|
</script>
|
|
52
62
|
|
|
53
63
|
<!-- Floating Controls Container -->
|
|
@@ -92,7 +102,7 @@
|
|
|
92
102
|
checked={controls.markers.enabled}
|
|
93
103
|
onchange={() => updateMarkers({ enabled: !controls.markers!.enabled })}
|
|
94
104
|
/>
|
|
95
|
-
<label class="btn btn-outline-
|
|
105
|
+
<label class="btn btn-outline-primary btn-sm" for="markersToggle">
|
|
96
106
|
Markers
|
|
97
107
|
</label>
|
|
98
108
|
</div>
|
|
@@ -108,13 +118,81 @@
|
|
|
108
118
|
checked={controls.legend.enabled}
|
|
109
119
|
onchange={() => updateLegend({ enabled: !controls.legend!.enabled })}
|
|
110
120
|
/>
|
|
111
|
-
<label class="btn btn-outline-
|
|
121
|
+
<label class="btn btn-outline-primary btn-sm" for="legendToggle">
|
|
112
122
|
Legend
|
|
113
123
|
</label>
|
|
114
124
|
</div>
|
|
115
125
|
{/if}
|
|
116
126
|
</div>
|
|
117
127
|
|
|
128
|
+
<!-- Hover Mode Controls -->
|
|
129
|
+
{#if controls.hoverMode}
|
|
130
|
+
<div class="control-group">
|
|
131
|
+
<div class="control-label">Hover Mode</div>
|
|
132
|
+
<div class="btn-group btn-group-sm" role="group" aria-label="Hover Mode">
|
|
133
|
+
<input
|
|
134
|
+
type="radio"
|
|
135
|
+
class="btn-check"
|
|
136
|
+
name="hoverMode"
|
|
137
|
+
id="hoverModeX"
|
|
138
|
+
checked={controls.hoverMode.mode === 'x'}
|
|
139
|
+
onchange={() => updateHoverMode({ mode: 'x' })}
|
|
140
|
+
/>
|
|
141
|
+
<label class="btn btn-outline-primary" for="hoverModeX">X-Axis</label>
|
|
142
|
+
|
|
143
|
+
<!-- <input
|
|
144
|
+
type="radio"
|
|
145
|
+
class="btn-check"
|
|
146
|
+
name="hoverMode"
|
|
147
|
+
id="hoverModeY"
|
|
148
|
+
checked={controls.hoverMode.mode === 'y'}
|
|
149
|
+
onchange={() => updateHoverMode({ mode: 'y' })}
|
|
150
|
+
/>
|
|
151
|
+
<label class="btn btn-outline-primary" for="hoverModeY">Y-Axis</label> -->
|
|
152
|
+
|
|
153
|
+
<input
|
|
154
|
+
type="radio"
|
|
155
|
+
class="btn-check"
|
|
156
|
+
name="hoverMode"
|
|
157
|
+
id="hoverModeClosest"
|
|
158
|
+
checked={controls.hoverMode.mode === 'closest'}
|
|
159
|
+
onchange={() => updateHoverMode({ mode: 'closest' })}
|
|
160
|
+
/>
|
|
161
|
+
<label class="btn btn-outline-primary" for="hoverModeClosest">Closest</label>
|
|
162
|
+
|
|
163
|
+
<input
|
|
164
|
+
type="radio"
|
|
165
|
+
class="btn-check"
|
|
166
|
+
name="hoverMode"
|
|
167
|
+
id="hoverModeXUnified"
|
|
168
|
+
checked={controls.hoverMode.mode === 'x unified'}
|
|
169
|
+
onchange={() => updateHoverMode({ mode: 'x unified' })}
|
|
170
|
+
/>
|
|
171
|
+
<label class="btn btn-outline-primary" for="hoverModeXUnified">X-Unified</label>
|
|
172
|
+
|
|
173
|
+
<!-- <input
|
|
174
|
+
type="radio"
|
|
175
|
+
class="btn-check"
|
|
176
|
+
name="hoverMode"
|
|
177
|
+
id="hoverModeYUnified"
|
|
178
|
+
checked={controls.hoverMode.mode === 'y unified'}
|
|
179
|
+
onchange={() => updateHoverMode({ mode: 'y unified' })}
|
|
180
|
+
/>
|
|
181
|
+
<label class="btn btn-outline-primary" for="hoverModeYUnified">Y-Unified</label> -->
|
|
182
|
+
|
|
183
|
+
<input
|
|
184
|
+
type="radio"
|
|
185
|
+
class="btn-check"
|
|
186
|
+
name="hoverMode"
|
|
187
|
+
id="hoverModeFalse"
|
|
188
|
+
checked={controls.hoverMode.mode === false}
|
|
189
|
+
onchange={() => updateHoverMode({ mode: false })}
|
|
190
|
+
/>
|
|
191
|
+
<label class="btn btn-outline-primary" for="hoverModeFalse">Off</label>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
{/if}
|
|
195
|
+
|
|
118
196
|
<!-- Bottom: Moving Average Controls -->
|
|
119
197
|
{#if controls.movingAverage}
|
|
120
198
|
<div class="control-group">
|
|
@@ -209,8 +287,8 @@
|
|
|
209
287
|
/* Floating Controls Wrapper - positioned in top-right */
|
|
210
288
|
.floating-controls-wrapper {
|
|
211
289
|
position: fixed;
|
|
212
|
-
top: 0.
|
|
213
|
-
right: 0.
|
|
290
|
+
top: 0.3rem;
|
|
291
|
+
right: 0.3rem;
|
|
214
292
|
z-index: 1000;
|
|
215
293
|
display: flex;
|
|
216
294
|
flex-direction: column;
|
|
@@ -330,6 +408,13 @@
|
|
|
330
408
|
font-weight: 500;
|
|
331
409
|
}
|
|
332
410
|
|
|
411
|
+
.control-label {
|
|
412
|
+
font-size: 0.8125rem;
|
|
413
|
+
font-weight: 600;
|
|
414
|
+
color: #495057;
|
|
415
|
+
margin-bottom: 0.25rem;
|
|
416
|
+
}
|
|
417
|
+
|
|
333
418
|
.control-subgroup {
|
|
334
419
|
display: flex;
|
|
335
420
|
flex-wrap: wrap;
|
|
@@ -363,26 +448,19 @@
|
|
|
363
448
|
border-color: #0d6efd;
|
|
364
449
|
}
|
|
365
450
|
|
|
366
|
-
.floating-controls-panel :global(.btn-outline-secondary) {
|
|
367
|
-
border-color: rgba(108, 117, 125, 0.3);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
.floating-controls-panel :global(.btn-outline-secondary:hover) {
|
|
371
|
-
background-color: rgba(108, 117, 125, 0.1);
|
|
372
|
-
border-color: rgba(108, 117, 125, 0.5);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
.floating-controls-panel :global(.btn-check:checked + .btn-outline-secondary) {
|
|
376
|
-
background-color: #6c757d;
|
|
377
|
-
border-color: #6c757d;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
451
|
.floating-controls-panel :global(.btn-group) {
|
|
381
452
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
382
453
|
border-radius: 6px;
|
|
383
454
|
overflow: hidden;
|
|
384
455
|
}
|
|
385
456
|
|
|
457
|
+
.floating-controls-panel :global(.form-label) {
|
|
458
|
+
font-size: 0.8125rem;
|
|
459
|
+
font-weight: 600;
|
|
460
|
+
color: #495057;
|
|
461
|
+
margin-bottom: 0.5rem;
|
|
462
|
+
}
|
|
463
|
+
|
|
386
464
|
/* Responsive adjustments */
|
|
387
465
|
@media (max-width: 768px) {
|
|
388
466
|
.floating-controls-wrapper {
|
|
@@ -33,6 +33,7 @@ export interface Section {
|
|
|
33
33
|
charts: Chart[];
|
|
34
34
|
grid?: ChartGrid;
|
|
35
35
|
movingAverage?: MovingAverageConfig;
|
|
36
|
+
useDefaultChartColors?: boolean;
|
|
36
37
|
}
|
|
37
38
|
export type Mode = "tabs" | "scrollspy";
|
|
38
39
|
export type HoverMode = 'x' | 'y' | 'closest' | 'x unified' | 'y unified' | false;
|
|
@@ -42,6 +43,7 @@ export interface Layout {
|
|
|
42
43
|
movingAverage?: MovingAverageConfig;
|
|
43
44
|
hoverMode?: HoverMode;
|
|
44
45
|
coloredHover?: boolean;
|
|
46
|
+
useDefaultChartColors?: boolean;
|
|
45
47
|
}
|
|
46
48
|
export interface ChartMarker {
|
|
47
49
|
date: string | Date;
|
|
@@ -63,6 +65,9 @@ export interface GlobalChartControls {
|
|
|
63
65
|
legend?: {
|
|
64
66
|
enabled: boolean;
|
|
65
67
|
};
|
|
68
|
+
hoverMode?: {
|
|
69
|
+
mode: HoverMode;
|
|
70
|
+
};
|
|
66
71
|
}
|
|
67
72
|
export interface CellStylingConfig {
|
|
68
73
|
bandColors: Record<string, string>;
|