@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.
@@ -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.newPlot(chartDiv, traces, finalLayout, config);
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
- // Use Plotly.Plots.resize for better performance than full re-render
278
- Plotly.Plots.resize(chartDiv);
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:,.2f} ${kpi.unit}<br>` +
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 unified',
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",