@smartnet360/svelte-components 0.0.31 → 0.0.33
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 +24 -6
- package/dist/core/Charts/ChartCard.svelte.d.ts +2 -1
- package/dist/core/Charts/ChartComponent.svelte +2 -0
- package/dist/core/Charts/adapt.d.ts +2 -1
- package/dist/core/Charts/adapt.js +76 -1
- package/dist/core/Charts/charts.model.d.ts +2 -0
- package/dist/core/Charts/data-utils.js +2 -2
- package/dist/core/Charts/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import { onMount, createEventDispatcher } from 'svelte';
|
|
5
5
|
import Plotly from 'plotly.js-dist-min';
|
|
6
|
-
import type { Chart as ChartModel, ChartMarker, MovingAverageConfig } from './charts.model.js';
|
|
6
|
+
import type { Chart as ChartModel, ChartMarker, MovingAverageConfig, HoverMode } from './charts.model.js';
|
|
7
7
|
import { createTimeSeriesTraceWithMA, getYAxisTitle, createDefaultPlotlyLayout } from './data-utils.js';
|
|
8
8
|
import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
|
|
9
9
|
import { getKPIValues, type ProcessedChartData } from './data-processor.js';
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
markers?: ChartMarker[]; // Global markers for all charts
|
|
24
24
|
plotlyLayout?: any; // Optional custom Plotly layout for styling/theming
|
|
25
25
|
enableAdaptation?: boolean; // Enable size-based adaptations (default: true)
|
|
26
|
+
configuredHoverMode?: HoverMode; // Global hover mode from layout config
|
|
26
27
|
sectionId?: string;
|
|
27
28
|
sectionMovingAverage?: MovingAverageConfig; // Section-level MA config
|
|
28
29
|
layoutMovingAverage?: MovingAverageConfig; // Layout-level MA config
|
|
@@ -32,11 +33,12 @@
|
|
|
32
33
|
runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true }: Props = $props();
|
|
36
|
+
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, configuredHoverMode, sectionId, sectionMovingAverage, layoutMovingAverage, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true }: Props = $props();
|
|
36
37
|
|
|
37
38
|
// Chart container div and state
|
|
38
39
|
let chartDiv: HTMLElement;
|
|
39
40
|
let containerSize = $state<ContainerSize>({ width: 0, height: 0 });
|
|
41
|
+
let chartInitialized = $state(false); // Track if chart has been created
|
|
40
42
|
|
|
41
43
|
function handleContextMenu(event: MouseEvent) {
|
|
42
44
|
event.preventDefault();
|
|
@@ -209,7 +211,7 @@
|
|
|
209
211
|
leftSeriesCount: chart.yLeft.length,
|
|
210
212
|
rightSeriesCount: chart.yRight.length
|
|
211
213
|
},
|
|
212
|
-
{ enableAdaptation }
|
|
214
|
+
{ enableAdaptation, configuredHoverMode }
|
|
213
215
|
);
|
|
214
216
|
|
|
215
217
|
// Add markers to the layout only if runtime control allows
|
|
@@ -229,7 +231,15 @@
|
|
|
229
231
|
displaylogo: false
|
|
230
232
|
};
|
|
231
233
|
|
|
232
|
-
Plotly.
|
|
234
|
+
// Use Plotly.react() for updates (preserves zoom/pan) or newPlot for initial render
|
|
235
|
+
if (chartInitialized) {
|
|
236
|
+
// Update existing chart - much faster, preserves user interactions
|
|
237
|
+
Plotly.react(chartDiv, traces, finalLayout, config);
|
|
238
|
+
} else {
|
|
239
|
+
// Initial chart creation
|
|
240
|
+
Plotly.newPlot(chartDiv, traces, finalLayout, config);
|
|
241
|
+
chartInitialized = true;
|
|
242
|
+
}
|
|
233
243
|
|
|
234
244
|
// Resize immediately after creation to ensure proper sizing
|
|
235
245
|
setTimeout(() => {
|
|
@@ -274,8 +284,9 @@
|
|
|
274
284
|
containerSize.height = height;
|
|
275
285
|
|
|
276
286
|
if (chartDiv && chartDiv.children.length > 0) {
|
|
277
|
-
//
|
|
278
|
-
|
|
287
|
+
// Re-render chart to apply size-based adaptations
|
|
288
|
+
// This recalculates layout with new containerSize for proper adaptation
|
|
289
|
+
renderChart();
|
|
279
290
|
}
|
|
280
291
|
}
|
|
281
292
|
}
|
|
@@ -290,6 +301,12 @@
|
|
|
290
301
|
clearTimeout(resizeTimeout);
|
|
291
302
|
}
|
|
292
303
|
resizeObserver.disconnect();
|
|
304
|
+
|
|
305
|
+
// Clean up Plotly chart
|
|
306
|
+
if (chartDiv && chartInitialized) {
|
|
307
|
+
Plotly.purge(chartDiv);
|
|
308
|
+
chartInitialized = false;
|
|
309
|
+
}
|
|
293
310
|
};
|
|
294
311
|
}
|
|
295
312
|
});
|
|
@@ -333,6 +350,7 @@
|
|
|
333
350
|
transition: box-shadow 0.2s ease;
|
|
334
351
|
overflow: hidden; /* Prevent content overflow */
|
|
335
352
|
box-sizing: border-box; /* Include padding in size calculations */
|
|
353
|
+
contain: layout style paint; /* Browser optimization: isolate rendering from rest of page */
|
|
336
354
|
}
|
|
337
355
|
|
|
338
356
|
.chart-card:hover {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Chart as ChartModel, ChartMarker, MovingAverageConfig } from './charts.model.js';
|
|
1
|
+
import type { Chart as ChartModel, ChartMarker, MovingAverageConfig, HoverMode } from './charts.model.js';
|
|
2
2
|
import { type ProcessedChartData } from './data-processor.js';
|
|
3
3
|
interface Props {
|
|
4
4
|
chart: ChartModel;
|
|
@@ -6,6 +6,7 @@ interface Props {
|
|
|
6
6
|
markers?: ChartMarker[];
|
|
7
7
|
plotlyLayout?: any;
|
|
8
8
|
enableAdaptation?: boolean;
|
|
9
|
+
configuredHoverMode?: HoverMode;
|
|
9
10
|
sectionId?: string;
|
|
10
11
|
sectionMovingAverage?: MovingAverageConfig;
|
|
11
12
|
layoutMovingAverage?: MovingAverageConfig;
|
|
@@ -300,6 +300,7 @@
|
|
|
300
300
|
{markers}
|
|
301
301
|
{plotlyLayout}
|
|
302
302
|
{enableAdaptation}
|
|
303
|
+
configuredHoverMode={layout.hoverMode}
|
|
303
304
|
sectionId={section.id}
|
|
304
305
|
sectionMovingAverage={section.movingAverage}
|
|
305
306
|
layoutMovingAverage={layout.movingAverage}
|
|
@@ -442,6 +443,7 @@
|
|
|
442
443
|
{markers}
|
|
443
444
|
{plotlyLayout}
|
|
444
445
|
{enableAdaptation}
|
|
446
|
+
configuredHoverMode={layout.hoverMode}
|
|
445
447
|
sectionId={activeZoom.section.id}
|
|
446
448
|
sectionMovingAverage={activeZoom.section.movingAverage}
|
|
447
449
|
layoutMovingAverage={layout.movingAverage}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Chart-specific adaptation utilities for Plotly layouts
|
|
3
3
|
* Handles size-based adaptations while preserving external styling/theming
|
|
4
4
|
*/
|
|
5
|
-
import type { ChartMarker } from './charts.model.js';
|
|
5
|
+
import type { ChartMarker, HoverMode } from './charts.model.js';
|
|
6
6
|
export interface ContainerSize {
|
|
7
7
|
width: number;
|
|
8
8
|
height: number;
|
|
@@ -13,6 +13,7 @@ export interface ChartInfo {
|
|
|
13
13
|
}
|
|
14
14
|
export interface AdaptationConfig {
|
|
15
15
|
enableAdaptation?: boolean;
|
|
16
|
+
configuredHoverMode?: HoverMode;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Adapts a Plotly layout based on container size
|
|
@@ -2,12 +2,85 @@
|
|
|
2
2
|
* Chart-specific adaptation utilities for Plotly layouts
|
|
3
3
|
* Handles size-based adaptations while preserving external styling/theming
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Adapts hover behavior based on container size and series count
|
|
7
|
+
* Optimizes tooltip display and performance for different chart sizes
|
|
8
|
+
* Respects configured hover mode, with exception for tiny charts (always 'closest' for performance)
|
|
9
|
+
*/
|
|
10
|
+
function adaptHoverBehavior(layout, containerSize, chartInfo, configuredHoverMode) {
|
|
11
|
+
const { width, height } = containerSize;
|
|
12
|
+
const isTiny = width < 250 || height < 200;
|
|
13
|
+
const isSmall = width < 400 || height < 300;
|
|
14
|
+
const isMedium = width < 600 || height < 400;
|
|
15
|
+
const totalSeries = chartInfo.leftSeriesCount + chartInfo.rightSeriesCount;
|
|
16
|
+
// EXCEPTION: Always use 'closest' in tiny charts for performance (override user setting)
|
|
17
|
+
if (isTiny) {
|
|
18
|
+
layout.hovermode = 'closest';
|
|
19
|
+
if (layout.hoverlabel) {
|
|
20
|
+
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
21
|
+
layout.hoverlabel.font.size = 9; // Smaller font
|
|
22
|
+
}
|
|
23
|
+
return layout;
|
|
24
|
+
}
|
|
25
|
+
// If user configured a specific hover mode, respect it (except tiny override above)
|
|
26
|
+
if (configuredHoverMode !== undefined) {
|
|
27
|
+
layout.hovermode = configuredHoverMode;
|
|
28
|
+
// Still apply adaptive font sizing based on container size
|
|
29
|
+
if (layout.hoverlabel) {
|
|
30
|
+
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
31
|
+
if (isSmall) {
|
|
32
|
+
layout.hoverlabel.font.size = 9;
|
|
33
|
+
}
|
|
34
|
+
else if (isMedium) {
|
|
35
|
+
layout.hoverlabel.font.size = 10;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
layout.hoverlabel.font.size = 11;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return layout;
|
|
42
|
+
}
|
|
43
|
+
// No configured mode - apply full adaptive logic (default: 'x')
|
|
44
|
+
// Priority 2: Simplify hover in small charts
|
|
45
|
+
if (isSmall) {
|
|
46
|
+
layout.hovermode = 'x'; // Single point instead of unified
|
|
47
|
+
if (layout.hoverlabel) {
|
|
48
|
+
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
49
|
+
layout.hoverlabel.font.size = 9; // Smaller font
|
|
50
|
+
}
|
|
51
|
+
return layout;
|
|
52
|
+
}
|
|
53
|
+
// Priority 3: Adaptive hover mode based on series count
|
|
54
|
+
if (totalSeries > 4 && isMedium) {
|
|
55
|
+
// Too many series in medium chart - switch to x
|
|
56
|
+
layout.hovermode = 'x';
|
|
57
|
+
}
|
|
58
|
+
else if (totalSeries > 8) {
|
|
59
|
+
// Very many series - even in large charts, use x
|
|
60
|
+
layout.hovermode = 'x';
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Default for large charts with reasonable series count
|
|
64
|
+
layout.hovermode = 'x';
|
|
65
|
+
}
|
|
66
|
+
// Priority 4: Adaptive hover label font size
|
|
67
|
+
if (layout.hoverlabel) {
|
|
68
|
+
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
69
|
+
if (isMedium) {
|
|
70
|
+
layout.hoverlabel.font.size = 10;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
layout.hoverlabel.font.size = 11; // Default for large
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return layout;
|
|
77
|
+
}
|
|
5
78
|
/**
|
|
6
79
|
* Adapts a Plotly layout based on container size
|
|
7
80
|
* Preserves external styling while optimizing functional properties
|
|
8
81
|
*/
|
|
9
82
|
export function adaptPlotlyLayout(baseLayout, containerSize, chartInfo, config = {}) {
|
|
10
|
-
const { enableAdaptation = true } = config;
|
|
83
|
+
const { enableAdaptation = true, configuredHoverMode } = config;
|
|
11
84
|
if (!enableAdaptation)
|
|
12
85
|
return baseLayout;
|
|
13
86
|
const { width, height } = containerSize;
|
|
@@ -96,6 +169,8 @@ export function adaptPlotlyLayout(baseLayout, containerSize, chartInfo, config =
|
|
|
96
169
|
adaptedLayout.legend.font.size = 11;
|
|
97
170
|
}
|
|
98
171
|
}
|
|
172
|
+
// Apply adaptive hover behavior (disable in tiny, simplify in small, optimize for series count)
|
|
173
|
+
adaptHoverBehavior(adaptedLayout, containerSize, chartInfo, configuredHoverMode);
|
|
99
174
|
return adaptedLayout;
|
|
100
175
|
}
|
|
101
176
|
/**
|
|
@@ -30,10 +30,12 @@ export interface Section {
|
|
|
30
30
|
movingAverage?: MovingAverageConfig;
|
|
31
31
|
}
|
|
32
32
|
export type Mode = "tabs" | "scrollspy";
|
|
33
|
+
export type HoverMode = 'x' | 'y' | 'closest' | 'x unified' | 'y unified' | false;
|
|
33
34
|
export interface Layout {
|
|
34
35
|
layoutName: string;
|
|
35
36
|
sections: Section[];
|
|
36
37
|
movingAverage?: MovingAverageConfig;
|
|
38
|
+
hoverMode?: HoverMode;
|
|
37
39
|
}
|
|
38
40
|
export interface ChartMarker {
|
|
39
41
|
date: string | Date;
|
|
@@ -90,7 +90,7 @@ export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', col
|
|
|
90
90
|
dash: yaxis === 'y1' ? 'solid' : 'dot' // Y1 = solid, Y2 = dotted
|
|
91
91
|
},
|
|
92
92
|
hovertemplate: `<b>${kpi.name}</b><br>` +
|
|
93
|
-
`Value: %{y
|
|
93
|
+
`Value: %{y:} ${kpi.unit}<br>` +
|
|
94
94
|
'<extra></extra>'
|
|
95
95
|
};
|
|
96
96
|
}
|
|
@@ -201,7 +201,7 @@ export function createDefaultPlotlyLayout(title) {
|
|
|
201
201
|
paper_bgcolor: 'rgba(0,0,0,0)',
|
|
202
202
|
plot_bgcolor: 'rgba(0,0,0,0)',
|
|
203
203
|
font: { family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif' },
|
|
204
|
-
hovermode: 'x
|
|
204
|
+
hovermode: 'x', // Default hover mode (can be overridden by layout.hoverMode or adaptive behavior)
|
|
205
205
|
hoverlabel: {
|
|
206
206
|
font: {
|
|
207
207
|
family: 'Inter, Segoe UI, Tahoma, Geneva, Verdana, sans-serif',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default as ChartComponent } from './ChartComponent.svelte';
|
|
2
2
|
export { default as ChartCard } from './ChartCard.svelte';
|
|
3
|
-
export type { Layout, Section, Chart, KPI, Mode, Scale, ChartMarker, ChartGrid, ChartPosition } from './charts.model.js';
|
|
3
|
+
export type { Layout, Section, Chart, KPI, Mode, Scale, ChartMarker, ChartGrid, ChartPosition, HoverMode } from './charts.model.js';
|
|
4
4
|
export { createTimeSeriesTrace, getYAxisTitle, formatValue, processKPIData, createDefaultPlotlyLayout } from './data-utils.js';
|
|
5
5
|
export { adaptPlotlyLayout, getSizeCategory, createMarkerShapes, createMarkerAnnotations, addMarkersToLayout } from './adapt.js';
|
|
6
6
|
export type { ContainerSize, ChartInfo, AdaptationConfig } from './adapt.js';
|