@smartnet360/svelte-components 0.0.1 → 0.0.2

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.
@@ -0,0 +1,204 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { onMount } from 'svelte';
5
+ import Plotly from 'plotly.js-dist-min';
6
+ import type { Chart as ChartModel, ChartMarker } from './charts.model.js';
7
+ import { createTimeSeriesTrace, getYAxisTitle } from './data-utils.js';
8
+ import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
9
+
10
+ interface Props {
11
+ chart: ChartModel;
12
+ data: any[];
13
+ markers?: ChartMarker[]; // Global markers for all charts
14
+ plotlyLayout?: any; // Optional custom Plotly layout for styling/theming
15
+ enableAdaptation?: boolean; // Enable size-based adaptations (default: true)
16
+ }
17
+
18
+ let { chart, data, markers, plotlyLayout, enableAdaptation = true }: Props = $props();
19
+
20
+ // Chart container div and state
21
+ let chartDiv: HTMLElement;
22
+ let containerSize = $state<ContainerSize>({ width: 0, height: 0 });
23
+
24
+ function renderChart() {
25
+ if (!chartDiv || !data?.length) return;
26
+
27
+ const traces: any[] = [];
28
+
29
+ // Add left Y-axis traces
30
+ chart.yLeft.forEach(kpi => {
31
+ const trace = createTimeSeriesTrace(data, kpi, 'TIMESTAMP', 'y1');
32
+ traces.push(trace);
33
+ });
34
+
35
+ // Add right Y-axis traces
36
+ chart.yRight.forEach(kpi => {
37
+ const trace = createTimeSeriesTrace(data, kpi, 'TIMESTAMP', 'y2');
38
+ traces.push(trace);
39
+ });
40
+
41
+ // Create default modern layout
42
+ const defaultLayout: any = {
43
+ title: {
44
+ text: chart.title,
45
+ font: {
46
+ size: 16,
47
+ color: '#2c3e50',
48
+ weight: 600
49
+ },
50
+ x: 0.5,
51
+ xanchor: 'center'
52
+ },
53
+ showlegend: true,
54
+ legend: {
55
+ x: 1,
56
+ y: 1,
57
+ xanchor: 'right',
58
+ yanchor: 'top',
59
+ font: { size: 12 }
60
+ },
61
+ xaxis: {
62
+ showgrid: true,
63
+ gridcolor: '#ecf0f1',
64
+ linecolor: '#bdc3c7',
65
+ tickfont: { size: 11 }
66
+ },
67
+ yaxis: {
68
+ title: {
69
+ text: getYAxisTitle(chart.yLeft),
70
+ font: { size: 12, color: '#7f8c8d' }
71
+ },
72
+ showgrid: true,
73
+ gridcolor: '#ecf0f1',
74
+ linecolor: '#bdc3c7',
75
+ tickfont: { size: 11 }
76
+ },
77
+ margin: { l: 60, r: 60, t: 60, b: 50 },
78
+ paper_bgcolor: 'rgba(0,0,0,0)',
79
+ plot_bgcolor: 'rgba(0,0,0,0)',
80
+ font: { family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif' }
81
+ };
82
+
83
+ // Add second Y-axis if we have right-side KPIs
84
+ if (chart.yRight.length > 0) {
85
+ defaultLayout.yaxis2 = {
86
+ title: {
87
+ text: getYAxisTitle(chart.yRight),
88
+ font: { size: 12, color: '#7f8c8d' }
89
+ },
90
+ overlaying: 'y',
91
+ side: 'right',
92
+ showgrid: false,
93
+ linecolor: '#bdc3c7',
94
+ tickfont: { size: 11 }
95
+ };
96
+ }
97
+
98
+ // Merge external layout with defaults, then apply size adaptations
99
+ let finalLayout = plotlyLayout ?
100
+ { ...defaultLayout, ...plotlyLayout } :
101
+ defaultLayout;
102
+
103
+ // Apply size-based adaptations using helper
104
+ finalLayout = adaptPlotlyLayout(
105
+ finalLayout,
106
+ containerSize,
107
+ {
108
+ leftSeriesCount: chart.yLeft.length,
109
+ rightSeriesCount: chart.yRight.length
110
+ },
111
+ { enableAdaptation }
112
+ );
113
+
114
+ // Add markers to the layout
115
+ finalLayout = addMarkersToLayout(finalLayout, markers || [], containerSize, enableAdaptation);
116
+
117
+ const config = {
118
+ responsive: true,
119
+ displayModeBar: false,
120
+ displaylogo: false
121
+ };
122
+
123
+ Plotly.newPlot(chartDiv, traces, finalLayout, config);
124
+
125
+ // Resize immediately after creation to ensure proper sizing
126
+ setTimeout(() => {
127
+ if (chartDiv) {
128
+ Plotly.Plots.resize(chartDiv);
129
+ }
130
+ }, 0);
131
+ }
132
+
133
+ onMount(() => {
134
+ // Initial container size measurement
135
+ if (chartDiv) {
136
+ const rect = chartDiv.getBoundingClientRect();
137
+ containerSize.width = rect.width;
138
+ containerSize.height = rect.height;
139
+ }
140
+
141
+ renderChart();
142
+
143
+ // Set up ResizeObserver to handle container size changes
144
+ if (chartDiv && window.ResizeObserver) {
145
+ const resizeObserver = new ResizeObserver((entries) => {
146
+ for (const entry of entries) {
147
+ const { width, height } = entry.contentRect;
148
+
149
+ // Update container size state
150
+ containerSize.width = width;
151
+ containerSize.height = height;
152
+
153
+ if (chartDiv && chartDiv.children.length > 0) {
154
+ // Re-render chart with new adaptive layout
155
+ renderChart();
156
+ }
157
+ }
158
+ });
159
+
160
+ resizeObserver.observe(chartDiv);
161
+
162
+ // Clean up observer on component destroy
163
+ return () => {
164
+ resizeObserver.disconnect();
165
+ };
166
+ }
167
+ });
168
+ </script>
169
+
170
+ <div class="chart-card">
171
+ <div
172
+ bind:this={chartDiv}
173
+ class="chart-container"
174
+ ></div>
175
+ </div>
176
+
177
+ <style>
178
+ .chart-card {
179
+ width: 100%;
180
+ height: 100%;
181
+ min-height: 100px; /* Much smaller minimum for grid usage */
182
+ max-height: 100%; /* Prevent exceeding parent */
183
+ background: white;
184
+ border-radius: 8px;
185
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
186
+ transition: box-shadow 0.2s ease;
187
+ overflow: hidden; /* Prevent content overflow */
188
+ box-sizing: border-box; /* Include padding in size calculations */
189
+ }
190
+
191
+ .chart-card:hover {
192
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
193
+ }
194
+
195
+ .chart-container {
196
+ width: 100%;
197
+ height: 100%;
198
+ min-height: 100px; /* Much smaller minimum for grid usage */
199
+ max-height: 100%; /* Prevent exceeding parent */
200
+ padding: 4px; /* Even smaller padding for grid constraints */
201
+ box-sizing: border-box; /* Include padding in size calculations */
202
+ overflow: hidden; /* Prevent Plotly overflow */
203
+ }
204
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Chart as ChartModel, ChartMarker } from './charts.model.js';
2
+ interface Props {
3
+ chart: ChartModel;
4
+ data: any[];
5
+ markers?: ChartMarker[];
6
+ plotlyLayout?: any;
7
+ enableAdaptation?: boolean;
8
+ }
9
+ declare const ChartCard: import("svelte").Component<Props, {}, "">;
10
+ type ChartCard = ReturnType<typeof ChartCard>;
11
+ export default ChartCard;
@@ -0,0 +1,226 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import type { Layout, Mode, ChartMarker } from './charts.model.js';
5
+ import ChartCard from './ChartCard.svelte';
6
+
7
+ interface Props {
8
+ layout: Layout;
9
+ data: any[];
10
+ mode: Mode;
11
+ markers?: ChartMarker[]; // Global markers for all charts
12
+ plotlyLayout?: any; // Optional custom Plotly layout
13
+ enableAdaptation?: boolean; // Enable size-based adaptations
14
+ }
15
+
16
+ let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true }: Props = $props();
17
+
18
+ // Internal tab state management
19
+ let activeTabId = $state(layout.sections[0]?.id || '');
20
+ </script>
21
+
22
+ <div class="chart-component">
23
+ {#if mode === 'tabs'}
24
+ <!-- Tab Mode with Navigation -->
25
+ <div class="tabs-container">
26
+ <!-- Tab Navigation -->
27
+ <ul class="nav nav-tabs" role="tablist">
28
+ {#each layout.sections as section, index}
29
+ <li class="nav-item" role="presentation">
30
+ <button
31
+ class="nav-link {section.id === activeTabId ? 'active' : ''}"
32
+ id="{section.id}-tab"
33
+ type="button"
34
+ role="tab"
35
+ aria-controls="{section.id}"
36
+ aria-selected="{section.id === activeTabId}"
37
+ onclick={() => activeTabId = section.id}
38
+ >
39
+ {section.title}
40
+ </button>
41
+ </li>
42
+ {/each}
43
+ </ul>
44
+
45
+ <!-- Tab Content -->
46
+ <div class="tab-content">
47
+ {#each layout.sections as section, index}
48
+ <div
49
+ class="tab-section {section.id === activeTabId ? 'active' : 'hidden'}"
50
+ data-section-id="{section.id}"
51
+ >
52
+ <!-- 2x2 Grid -->
53
+ <div class="chart-grid">
54
+ {#each section.charts as chart}
55
+ <div class="chart-slot">
56
+ <ChartCard {chart} {data} {plotlyLayout} />
57
+ </div>
58
+ {/each}
59
+ </div>
60
+ </div>
61
+ {/each}
62
+ </div>
63
+ </div>
64
+ {:else if mode === 'scrollspy'}
65
+ <!-- ScrollSpy Mode with Navigation -->
66
+ <div class="scrollspy-container">
67
+ <!-- ScrollSpy Navigation -->
68
+ <nav class="scrollspy-nav">
69
+ <ul class="nav nav-pills">
70
+ {#each layout.sections as section}
71
+ <li class="nav-item">
72
+ <a class="nav-link" href="#{section.id}">{section.title}</a>
73
+ </li>
74
+ {/each}
75
+ </ul>
76
+ </nav>
77
+
78
+ <!-- ScrollSpy Content -->
79
+ <div class="scrollspy-content">
80
+ {#each layout.sections as section}
81
+ <div class="section-content" id="{section.id}">
82
+ <!-- 2x2 Grid -->
83
+ <div class="chart-grid">
84
+ {#each section.charts as chart}
85
+ <div class="chart-slot">
86
+ <ChartCard {chart} {data} {markers} {plotlyLayout} {enableAdaptation} />
87
+ </div>
88
+ {/each}
89
+ </div>
90
+ </div>
91
+ {/each}
92
+ </div>
93
+ </div>
94
+ {/if}
95
+ </div>
96
+
97
+ <style>
98
+ /* Child-first component - adapts to parent container */
99
+ .chart-component {
100
+ width: 100%;
101
+ height: 100%;
102
+ display: flex;
103
+ flex-direction: column;
104
+ }
105
+
106
+ /* Tab Mode */
107
+ .tabs-container {
108
+ width: 100%;
109
+ height: 100%;
110
+ display: flex;
111
+ flex-direction: column;
112
+ }
113
+
114
+ .nav-tabs {
115
+ flex-shrink: 0;
116
+ margin-bottom: 0.125rem; /* Minimal margin */
117
+ padding: 0; /* Remove any padding */
118
+ }
119
+
120
+ /* Compact tabs for grid usage */
121
+ .nav-tabs .nav-link {
122
+ padding: 0.25rem 0.5rem; /* Much smaller padding */
123
+ font-size: 0.75rem; /* Smaller font */
124
+ border: none;
125
+ border-bottom: 2px solid transparent;
126
+ }
127
+
128
+ .nav-tabs .nav-link.active {
129
+ border-bottom-color: #007bff;
130
+ background: none;
131
+ }
132
+
133
+ .tab-content {
134
+ flex-grow: 1;
135
+ min-height: 0;
136
+ position: relative;
137
+ overflow: hidden; /* Prevent overflow */
138
+ }
139
+
140
+ .tab-section {
141
+ position: absolute;
142
+ top: 0;
143
+ left: 0;
144
+ width: 100%;
145
+ height: 100%;
146
+ transition: opacity 0.2s ease;
147
+ }
148
+
149
+ .tab-section.active {
150
+ opacity: 1;
151
+ z-index: 1;
152
+ }
153
+
154
+ .tab-section.hidden {
155
+ opacity: 0;
156
+ z-index: 0;
157
+ pointer-events: none;
158
+ }
159
+
160
+ /* ScrollSpy Mode */
161
+ .scrollspy-container {
162
+ width: 100%;
163
+ height: 100%;
164
+ display: flex;
165
+ flex-direction: column;
166
+ }
167
+
168
+ .scrollspy-nav {
169
+ flex-shrink: 0;
170
+ padding: 0.125rem; /* Minimal padding */
171
+ background-color: #f8f9fa;
172
+ margin-bottom: 0.125rem; /* Minimal margin */
173
+ }
174
+
175
+ /* Compact scrollspy nav for grid usage */
176
+ .scrollspy-nav .nav-pills .nav-link {
177
+ padding: 0.25rem 0.5rem; /* Much smaller padding */
178
+ font-size: 0.75rem; /* Smaller font */
179
+ margin-right: 0.25rem;
180
+ }
181
+
182
+ .scrollspy-content {
183
+ flex-grow: 1;
184
+ overflow-y: auto;
185
+ min-height: 0;
186
+ }
187
+
188
+ .section-content {
189
+ width: 100%;
190
+ height: 100%;
191
+ margin-bottom: 0.5rem; /* Reduce margin */
192
+ }
193
+
194
+ /* 2x2 Chart Grid - responsive to container */
195
+ .chart-grid {
196
+ display: grid;
197
+ grid-template-columns: 1fr 1fr;
198
+ grid-template-rows: 1fr 1fr;
199
+ gap: 0.25rem; /* Reduce gap */
200
+ width: 100%;
201
+ height: 100%;
202
+ min-height: 0; /* Remove fixed minimum to allow full flexibility */
203
+ max-height: 100%; /* Ensure it doesn't exceed container */
204
+ }
205
+
206
+ .chart-slot {
207
+ display: flex;
208
+ min-height: 0; /* Allow grid to shrink */
209
+ min-width: 0;
210
+ max-height: 100%; /* Prevent exceeding grid cell */
211
+ max-width: 100%;
212
+ overflow: hidden; /* Prevent slot overflow */
213
+ }
214
+
215
+ /* Ensure ChartCard adapts to slot size */
216
+ .chart-slot :global(.chart-card) {
217
+ flex-grow: 1;
218
+ min-height: 80px; /* Even smaller minimum for grid */
219
+ max-height: 100%; /* Constrain to slot */
220
+ }
221
+
222
+ .chart-slot :global(.chart-container) {
223
+ min-height: 80px; /* Even smaller minimum for grid */
224
+ max-height: 100%; /* Constrain to slot */
225
+ }
226
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { Layout, Mode, ChartMarker } from './charts.model.js';
2
+ interface Props {
3
+ layout: Layout;
4
+ data: any[];
5
+ mode: Mode;
6
+ markers?: ChartMarker[];
7
+ plotlyLayout?: any;
8
+ enableAdaptation?: boolean;
9
+ }
10
+ declare const ChartComponent: import("svelte").Component<Props, {}, "">;
11
+ type ChartComponent = ReturnType<typeof ChartComponent>;
12
+ export default ChartComponent;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Chart-specific adaptation utilities for Plotly layouts
3
+ * Handles size-based adaptations while preserving external styling/theming
4
+ */
5
+ import type { ChartMarker } from './charts.model.js';
6
+ export interface ContainerSize {
7
+ width: number;
8
+ height: number;
9
+ }
10
+ export interface ChartInfo {
11
+ leftSeriesCount: number;
12
+ rightSeriesCount: number;
13
+ }
14
+ export interface AdaptationConfig {
15
+ enableAdaptation?: boolean;
16
+ }
17
+ /**
18
+ * Adapts a Plotly layout based on container size
19
+ * Preserves external styling while optimizing functional properties
20
+ */
21
+ export declare function adaptPlotlyLayout(baseLayout: any, containerSize: ContainerSize, chartInfo: ChartInfo, config?: AdaptationConfig): any;
22
+ /**
23
+ * Helper to get size category for debugging/logging
24
+ */
25
+ export declare function getSizeCategory(containerSize: ContainerSize): 'tiny' | 'small' | 'medium' | 'large';
26
+ /**
27
+ * Create Plotly shapes for chart markers (vertical lines)
28
+ */
29
+ export declare function createMarkerShapes(markers: ChartMarker[], yAxisRange?: [number, number]): any[];
30
+ /**
31
+ * Create Plotly annotations for chart marker labels
32
+ */
33
+ export declare function createMarkerAnnotations(markers: ChartMarker[], containerSize: ContainerSize, enableAdaptation?: boolean): any[];
34
+ /**
35
+ * Add markers to a Plotly layout
36
+ */
37
+ export declare function addMarkersToLayout(layout: any, markers: ChartMarker[], containerSize: ContainerSize, enableAdaptation?: boolean): any;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Chart-specific adaptation utilities for Plotly layouts
3
+ * Handles size-based adaptations while preserving external styling/theming
4
+ */
5
+ /**
6
+ * Adapts a Plotly layout based on container size
7
+ * Preserves external styling while optimizing functional properties
8
+ */
9
+ export function adaptPlotlyLayout(baseLayout, containerSize, chartInfo, config = {}) {
10
+ const { enableAdaptation = true } = config;
11
+ if (!enableAdaptation)
12
+ return baseLayout;
13
+ const { width, height } = containerSize;
14
+ const adaptedLayout = { ...baseLayout };
15
+ // Size categories for adaptation rules
16
+ const isTiny = width < 250 || height < 200;
17
+ const isSmall = width < 400 || height < 300;
18
+ const isMedium = width < 600 || height < 400;
19
+ // Adaptive font scaling for title
20
+ if (adaptedLayout.title?.font) {
21
+ if (isTiny) {
22
+ adaptedLayout.title.font.size = Math.max(10, adaptedLayout.title.font.size * 0.7);
23
+ }
24
+ else if (isSmall) {
25
+ adaptedLayout.title.font.size = Math.max(12, adaptedLayout.title.font.size * 0.8);
26
+ }
27
+ }
28
+ // Adaptive legend behavior
29
+ if (isTiny) {
30
+ adaptedLayout.showlegend = false;
31
+ }
32
+ else if (isSmall && chartInfo.leftSeriesCount + chartInfo.rightSeriesCount > 2) {
33
+ // Hide legend for small containers with many series
34
+ adaptedLayout.showlegend = false;
35
+ }
36
+ // Adaptive margins
37
+ if (isTiny) {
38
+ adaptedLayout.margin = { l: 35, r: 25, t: 30, b: 25 };
39
+ }
40
+ else if (isSmall) {
41
+ adaptedLayout.margin = { l: 45, r: 35, t: 40, b: 35 };
42
+ }
43
+ else if (isMedium) {
44
+ adaptedLayout.margin = { l: 50, r: 45, t: 50, b: 40 };
45
+ }
46
+ // Adaptive axis font sizes
47
+ if (adaptedLayout.xaxis?.tickfont) {
48
+ if (isTiny) {
49
+ adaptedLayout.xaxis.tickfont.size = 8;
50
+ }
51
+ else if (isSmall) {
52
+ adaptedLayout.xaxis.tickfont.size = 9;
53
+ }
54
+ }
55
+ if (adaptedLayout.yaxis?.tickfont) {
56
+ if (isTiny) {
57
+ adaptedLayout.yaxis.tickfont.size = 8;
58
+ }
59
+ else if (isSmall) {
60
+ adaptedLayout.yaxis.tickfont.size = 9;
61
+ }
62
+ }
63
+ if (adaptedLayout.yaxis?.title?.font) {
64
+ if (isTiny) {
65
+ adaptedLayout.yaxis.title.font.size = 9;
66
+ }
67
+ else if (isSmall) {
68
+ adaptedLayout.yaxis.title.font.size = 10;
69
+ }
70
+ }
71
+ // Apply same adaptations to second Y-axis
72
+ if (adaptedLayout.yaxis2) {
73
+ if (adaptedLayout.yaxis2.tickfont) {
74
+ if (isTiny) {
75
+ adaptedLayout.yaxis2.tickfont.size = 8;
76
+ }
77
+ else if (isSmall) {
78
+ adaptedLayout.yaxis2.tickfont.size = 9;
79
+ }
80
+ }
81
+ if (adaptedLayout.yaxis2.title?.font) {
82
+ if (isTiny) {
83
+ adaptedLayout.yaxis2.title.font.size = 9;
84
+ }
85
+ else if (isSmall) {
86
+ adaptedLayout.yaxis2.title.font.size = 10;
87
+ }
88
+ }
89
+ }
90
+ // Adaptive legend font size
91
+ if (adaptedLayout.legend?.font && adaptedLayout.showlegend) {
92
+ if (isSmall) {
93
+ adaptedLayout.legend.font.size = 10;
94
+ }
95
+ }
96
+ return adaptedLayout;
97
+ }
98
+ /**
99
+ * Helper to get size category for debugging/logging
100
+ */
101
+ export function getSizeCategory(containerSize) {
102
+ const { width, height } = containerSize;
103
+ if (width < 250 || height < 200)
104
+ return 'tiny';
105
+ if (width < 400 || height < 300)
106
+ return 'small';
107
+ if (width < 600 || height < 400)
108
+ return 'medium';
109
+ return 'large';
110
+ }
111
+ /**
112
+ * Create Plotly shapes for chart markers (vertical lines)
113
+ */
114
+ export function createMarkerShapes(markers, yAxisRange) {
115
+ if (!markers || markers.length === 0)
116
+ return [];
117
+ return markers.map(marker => ({
118
+ type: 'line',
119
+ x0: marker.date,
120
+ x1: marker.date,
121
+ y0: yAxisRange ? yAxisRange[0] : 0,
122
+ y1: yAxisRange ? yAxisRange[1] : 1,
123
+ yref: yAxisRange ? 'y' : 'paper',
124
+ line: {
125
+ color: marker.color || '#ff0000',
126
+ width: 2,
127
+ dash: marker.style === 'dashed' ? 'dash' : marker.style === 'dotted' ? 'dot' : 'solid'
128
+ }
129
+ }));
130
+ }
131
+ /**
132
+ * Create Plotly annotations for chart marker labels
133
+ */
134
+ export function createMarkerAnnotations(markers, containerSize, enableAdaptation = true) {
135
+ if (!markers || markers.length === 0)
136
+ return [];
137
+ const sizeCategory = getSizeCategory(containerSize);
138
+ return markers
139
+ .filter(marker => {
140
+ // Hide labels in tiny containers if adaptation is enabled
141
+ if (enableAdaptation && sizeCategory === 'tiny')
142
+ return false;
143
+ // Respect individual marker showLabel setting
144
+ return marker.showLabel !== false;
145
+ })
146
+ .map(marker => {
147
+ // Adaptive font sizing
148
+ let fontSize = 10;
149
+ if (enableAdaptation) {
150
+ if (sizeCategory === 'small')
151
+ fontSize = 8;
152
+ else if (sizeCategory === 'medium')
153
+ fontSize = 9;
154
+ else if (sizeCategory === 'large')
155
+ fontSize = 10;
156
+ }
157
+ // Adaptive label positioning
158
+ const yPosition = sizeCategory === 'small' ? 0.95 : 0.9;
159
+ return {
160
+ x: marker.date,
161
+ y: yPosition,
162
+ yref: 'paper',
163
+ text: marker.label,
164
+ showarrow: true,
165
+ arrowhead: 2,
166
+ arrowcolor: marker.color || '#ff0000',
167
+ arrowsize: sizeCategory === 'small' ? 0.8 : 1,
168
+ font: {
169
+ size: fontSize,
170
+ color: marker.color || '#ff0000'
171
+ },
172
+ bgcolor: 'rgba(255,255,255,0.8)',
173
+ bordercolor: marker.color || '#ff0000',
174
+ borderwidth: 1
175
+ };
176
+ });
177
+ }
178
+ /**
179
+ * Add markers to a Plotly layout
180
+ */
181
+ export function addMarkersToLayout(layout, markers, containerSize, enableAdaptation = true) {
182
+ if (!markers || markers.length === 0)
183
+ return layout;
184
+ const updatedLayout = { ...layout };
185
+ // Add marker shapes (vertical lines)
186
+ const shapes = createMarkerShapes(markers);
187
+ updatedLayout.shapes = updatedLayout.shapes ? [...updatedLayout.shapes, ...shapes] : shapes;
188
+ // Add marker annotations (labels)
189
+ const annotations = createMarkerAnnotations(markers, containerSize, enableAdaptation);
190
+ updatedLayout.annotations = updatedLayout.annotations ? [...updatedLayout.annotations, ...annotations] : annotations;
191
+ return updatedLayout;
192
+ }