@smartnet360/svelte-components 0.0.30 → 0.0.32

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.
@@ -6,6 +6,7 @@
6
6
  import type { Chart as ChartModel, ChartMarker, MovingAverageConfig } 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
+ import { getKPIValues, type ProcessedChartData } from './data-processor.js';
9
10
 
10
11
  const dispatch = createEventDispatcher<{
11
12
  chartcontextmenu: {
@@ -18,8 +19,7 @@
18
19
 
19
20
  interface Props {
20
21
  chart: ChartModel;
21
- data: any[];
22
- timestamps: any[]; // Pre-extracted timestamps from ChartComponent
22
+ processedData: ProcessedChartData; // Pre-processed KPI values and timestamps
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)
@@ -32,7 +32,7 @@
32
32
  runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
33
33
  }
34
34
 
35
- let { chart, data, timestamps, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true }: Props = $props();
35
+ let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true }: Props = $props();
36
36
 
37
37
  // Chart container div and state
38
38
  let chartDiv: HTMLElement;
@@ -146,22 +146,27 @@
146
146
  };
147
147
  });
148
148
 
149
+ // Timestamps are already extracted in processedData
150
+ let timestamps = $derived(processedData.timestamps);
151
+
149
152
  function renderChart() {
150
- if (!chartDiv || !data?.length) return;
153
+ if (!chartDiv || !processedData?.kpiValues.size) return;
151
154
 
152
155
  const traces: any[] = [];
153
156
  let colorIndex = 0;
154
157
 
155
158
  // Add left Y-axis traces (with moving average support)
156
159
  resolvedKPIs.left.forEach(kpi => {
157
- const kpiTraces = createTimeSeriesTraceWithMA(data, kpi, 'TIMESTAMP', 'y1', colorIndex, timestamps);
160
+ const values = getKPIValues(processedData, kpi);
161
+ const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y1', colorIndex);
158
162
  traces.push(...kpiTraces);
159
163
  colorIndex++;
160
164
  });
161
165
 
162
166
  // Add right Y-axis traces (with moving average support)
163
167
  resolvedKPIs.right.forEach(kpi => {
164
- const kpiTraces = createTimeSeriesTraceWithMA(data, kpi, 'TIMESTAMP', 'y2', colorIndex, timestamps);
168
+ const values = getKPIValues(processedData, kpi);
169
+ const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y2', colorIndex);
165
170
  traces.push(...kpiTraces);
166
171
  colorIndex++;
167
172
  });
@@ -269,8 +274,9 @@
269
274
  containerSize.height = height;
270
275
 
271
276
  if (chartDiv && chartDiv.children.length > 0) {
272
- // Use Plotly.Plots.resize for better performance than full re-render
273
- Plotly.Plots.resize(chartDiv);
277
+ // Re-render chart to apply size-based adaptations
278
+ // This recalculates layout with new containerSize for proper adaptation
279
+ renderChart();
274
280
  }
275
281
  }
276
282
  }
@@ -292,7 +298,7 @@
292
298
  // React to prop changes - debounce re-renders for better performance
293
299
  $effect(() => {
294
300
  // Watch these props and re-render when they change
295
- const currentData = data;
301
+ const currentProcessedData = processedData;
296
302
  const currentMarkers = markers;
297
303
  const currentMAOverride = runtimeMAOverride;
298
304
  const currentShowOriginal = runtimeShowOriginal;
@@ -1,8 +1,8 @@
1
1
  import type { Chart as ChartModel, ChartMarker, MovingAverageConfig } from './charts.model.js';
2
+ import { type ProcessedChartData } from './data-processor.js';
2
3
  interface Props {
3
4
  chart: ChartModel;
4
- data: any[];
5
- timestamps: any[];
5
+ processedData: ProcessedChartData;
6
6
  markers?: ChartMarker[];
7
7
  plotlyLayout?: any;
8
8
  enableAdaptation?: boolean;
@@ -5,6 +5,7 @@
5
5
  import type { Layout, Mode, ChartMarker, Section, ChartGrid, Chart, GlobalChartControls, MovingAverageConfig } from './charts.model.js';
6
6
  import ChartCard from './ChartCard.svelte';
7
7
  import GlobalControls from './GlobalControls.svelte';
8
+ import { getPreprocessedData, type ProcessedChartData } from './data-processor.js';
8
9
 
9
10
  interface Props {
10
11
  layout: Layout;
@@ -54,8 +55,9 @@
54
55
 
55
56
  let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true, showGlobalControls = true }: Props = $props();
56
57
 
57
- // Extract timestamps once at component level - all charts share the same time series
58
- let timestamps = $derived(data.map(row => row['TIMESTAMP']));
58
+ // Preprocess raw data once - automatically memoized by Svelte's $derived
59
+ // This extracts all KPI values and timestamps, cached until data or layout changes
60
+ let processedData = $derived(getPreprocessedData(data, layout));
59
61
 
60
62
  // Global runtime controls state - initialize from layout config
61
63
  let globalControls = $state<GlobalChartControls>({
@@ -294,8 +296,7 @@
294
296
  <div class="chart-slot">
295
297
  <ChartCard
296
298
  {chart}
297
- {data}
298
- {timestamps}
299
+ {processedData}
299
300
  {markers}
300
301
  {plotlyLayout}
301
302
  {enableAdaptation}
@@ -437,8 +438,7 @@
437
438
  </button>
438
439
  <ChartCard
439
440
  chart={activeZoom.chart}
440
- {data}
441
- {timestamps}
441
+ {processedData}
442
442
  {markers}
443
443
  {plotlyLayout}
444
444
  {enableAdaptation}
@@ -0,0 +1,43 @@
1
+ import type { Layout, KPI } from './charts.model.js';
2
+ /**
3
+ * Preprocessed chart data structure
4
+ * Contains extracted numeric values and timestamps, ready for chart rendering
5
+ */
6
+ export interface ProcessedChartData {
7
+ /** Map of KPI rawName to numeric values array */
8
+ kpiValues: Map<string, number[]>;
9
+ /** Extracted timestamps array */
10
+ timestamps: any[];
11
+ /** Original raw data reference (for cache invalidation) */
12
+ _rawDataRef: any[];
13
+ }
14
+ /**
15
+ * Extract all unique KPI rawNames from a layout configuration
16
+ * This determines which columns we need to extract from raw data
17
+ */
18
+ export declare function extractKPINames(layout: Layout): Set<string>;
19
+ /**
20
+ * Process raw data into numeric arrays for each KPI
21
+ * Handles type conversion and filtering of invalid values
22
+ *
23
+ * @param data - Raw data array from API/database
24
+ * @param layout - Chart layout configuration containing all KPIs
25
+ * @param timestampField - Name of timestamp field in raw data (default: 'TIMESTAMP')
26
+ * @returns Preprocessed data ready for chart rendering
27
+ */
28
+ export declare function preprocessChartData(data: any[], layout: Layout, timestampField?: string): ProcessedChartData;
29
+ /**
30
+ * Get preprocessed chart data with automatic caching
31
+ * Returns cached result if raw data hasn't changed
32
+ *
33
+ * @param data - Raw data array
34
+ * @param layout - Chart layout configuration
35
+ * @param timestampField - Name of timestamp field (default: 'TIMESTAMP')
36
+ * @returns Preprocessed data (cached or freshly computed)
37
+ */
38
+ export declare function getPreprocessedData(data: any[], layout: Layout, timestampField?: string): ProcessedChartData;
39
+ /**
40
+ * Helper to get values for a specific KPI from preprocessed data
41
+ * Returns empty array if KPI not found
42
+ */
43
+ export declare function getKPIValues(processedData: ProcessedChartData, kpi: KPI): number[];
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Extract all unique KPI rawNames from a layout configuration
3
+ * This determines which columns we need to extract from raw data
4
+ */
5
+ export function extractKPINames(layout) {
6
+ const kpiNames = new Set();
7
+ for (const section of layout.sections) {
8
+ for (const chart of section.charts) {
9
+ // Collect from left Y-axis KPIs
10
+ for (const kpi of chart.yLeft) {
11
+ kpiNames.add(kpi.rawName);
12
+ }
13
+ // Collect from right Y-axis KPIs
14
+ for (const kpi of chart.yRight) {
15
+ kpiNames.add(kpi.rawName);
16
+ }
17
+ }
18
+ }
19
+ return kpiNames;
20
+ }
21
+ /**
22
+ * Process raw data into numeric arrays for each KPI
23
+ * Handles type conversion and filtering of invalid values
24
+ *
25
+ * @param data - Raw data array from API/database
26
+ * @param layout - Chart layout configuration containing all KPIs
27
+ * @param timestampField - Name of timestamp field in raw data (default: 'TIMESTAMP')
28
+ * @returns Preprocessed data ready for chart rendering
29
+ */
30
+ export function preprocessChartData(data, layout, timestampField = 'TIMESTAMP') {
31
+ // Extract all unique KPI names we need to process
32
+ const kpiNames = extractKPINames(layout);
33
+ // Initialize the result map
34
+ const kpiValues = new Map();
35
+ // Process each KPI column
36
+ for (const kpiName of kpiNames) {
37
+ const values = data
38
+ .map(row => {
39
+ const val = row[kpiName];
40
+ // Convert to number if it's a string, return as-is if already a number
41
+ return typeof val === 'number' ? val : parseFloat(val);
42
+ })
43
+ .filter(val => !isNaN(val)); // Remove invalid values
44
+ kpiValues.set(kpiName, values);
45
+ }
46
+ // Extract timestamps once
47
+ const timestamps = data.map(row => row[timestampField]);
48
+ return {
49
+ kpiValues,
50
+ timestamps,
51
+ _rawDataRef: data // Keep reference for cache invalidation
52
+ };
53
+ }
54
+ /**
55
+ * Memoized wrapper for preprocessChartData
56
+ * Uses WeakMap to cache results by raw data reference
57
+ * Cache is automatically garbage collected when raw data is no longer referenced
58
+ */
59
+ const preprocessCache = new WeakMap();
60
+ /**
61
+ * Get preprocessed chart data with automatic caching
62
+ * Returns cached result if raw data hasn't changed
63
+ *
64
+ * @param data - Raw data array
65
+ * @param layout - Chart layout configuration
66
+ * @param timestampField - Name of timestamp field (default: 'TIMESTAMP')
67
+ * @returns Preprocessed data (cached or freshly computed)
68
+ */
69
+ export function getPreprocessedData(data, layout, timestampField = 'TIMESTAMP') {
70
+ // Check cache first
71
+ const cached = preprocessCache.get(data);
72
+ if (cached) {
73
+ // Verify cache is still valid (data reference matches)
74
+ if (cached._rawDataRef === data) {
75
+ return cached;
76
+ }
77
+ }
78
+ // Cache miss or invalid - compute and cache
79
+ const processed = preprocessChartData(data, layout, timestampField);
80
+ preprocessCache.set(data, processed);
81
+ return processed;
82
+ }
83
+ /**
84
+ * Helper to get values for a specific KPI from preprocessed data
85
+ * Returns empty array if KPI not found
86
+ */
87
+ export function getKPIValues(processedData, kpi) {
88
+ return processedData.kpiValues.get(kpi.rawName) || [];
89
+ }
@@ -1,18 +1,17 @@
1
1
  import type { KPI } from './charts.model.js';
2
2
  export declare function processKPIData(data: any[], kpi: KPI): number[];
3
3
  export declare function calculateMovingAverage(values: number[], window: number): number[];
4
- export declare function createTimeSeriesTrace(data: any[], kpi: KPI, timestampField?: string, yaxis?: 'y1' | 'y2', colorIndex?: number): any;
4
+ export declare function createTimeSeriesTrace(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number): any;
5
5
  /**
6
6
  * Create time series trace(s) with optional moving average
7
- * @param data - Raw data array
7
+ * @param values - Pre-processed numeric values array for the KPI
8
+ * @param timestamps - Pre-extracted timestamps array
8
9
  * @param kpi - KPI configuration (may include movingAverage config)
9
- * @param timestampField - Field name for timestamp column
10
10
  * @param yaxis - Which Y-axis to use ('y1' or 'y2')
11
11
  * @param colorIndex - Index for color selection
12
- * @param precomputedTimestamps - Optional pre-extracted timestamps array (performance optimization)
13
12
  * @returns Array of traces (original + MA if configured)
14
13
  */
15
- export declare function createTimeSeriesTraceWithMA(data: any[], kpi: KPI, timestampField?: string, yaxis?: 'y1' | 'y2', colorIndex?: number, precomputedTimestamps?: any[]): any[];
14
+ export declare function createTimeSeriesTraceWithMA(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number): any[];
16
15
  export declare function getYAxisTitle(kpis: KPI[]): string;
17
16
  export declare function formatValue(value: number, scale: 'percent' | 'absolute', unit: string): string;
18
17
  export declare function createDefaultPlotlyLayout(title?: string): any;
@@ -72,9 +72,7 @@ export function calculateMovingAverage(values, window) {
72
72
  maCache.set(cacheKey, result);
73
73
  return result;
74
74
  }
75
- export function createTimeSeriesTrace(data, kpi, timestampField = 'TIMESTAMP', yaxis = 'y1', colorIndex = 0) {
76
- const values = processKPIData(data, kpi);
77
- const timestamps = data.map(row => row[timestampField]);
75
+ export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', colorIndex = 0) {
78
76
  // Use KPI color if provided, otherwise cycle through modern colors
79
77
  const traceColor = kpi.color || modernColors[colorIndex % modernColors.length];
80
78
  return {
@@ -98,22 +96,19 @@ export function createTimeSeriesTrace(data, kpi, timestampField = 'TIMESTAMP', y
98
96
  }
99
97
  /**
100
98
  * Create time series trace(s) with optional moving average
101
- * @param data - Raw data array
99
+ * @param values - Pre-processed numeric values array for the KPI
100
+ * @param timestamps - Pre-extracted timestamps array
102
101
  * @param kpi - KPI configuration (may include movingAverage config)
103
- * @param timestampField - Field name for timestamp column
104
102
  * @param yaxis - Which Y-axis to use ('y1' or 'y2')
105
103
  * @param colorIndex - Index for color selection
106
- * @param precomputedTimestamps - Optional pre-extracted timestamps array (performance optimization)
107
104
  * @returns Array of traces (original + MA if configured)
108
105
  */
109
- export function createTimeSeriesTraceWithMA(data, kpi, timestampField = 'TIMESTAMP', yaxis = 'y1', colorIndex = 0, precomputedTimestamps) {
106
+ export function createTimeSeriesTraceWithMA(values, timestamps, kpi, yaxis = 'y1', colorIndex = 0) {
110
107
  const traces = [];
111
- const values = processKPIData(data, kpi);
112
- const timestamps = precomputedTimestamps || data.map(row => row[timestampField]);
113
108
  const traceColor = kpi.color || modernColors[colorIndex % modernColors.length];
114
109
  // Add original trace (unless explicitly disabled)
115
110
  if (!kpi.movingAverage || kpi.movingAverage.showOriginal !== false) {
116
- const originalTrace = createTimeSeriesTrace(data, kpi, timestampField, yaxis, colorIndex);
111
+ const originalTrace = createTimeSeriesTrace(values, timestamps, kpi, yaxis, colorIndex);
117
112
  // If MA is enabled, make the original line slightly transparent
118
113
  if (kpi.movingAverage?.enabled) {
119
114
  originalTrace.opacity = 0.4;
@@ -4,6 +4,8 @@ export type { Layout, Section, Chart, KPI, Mode, Scale, ChartMarker, ChartGrid,
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';
7
+ export { getPreprocessedData, extractKPINames, getKPIValues, preprocessChartData } from './data-processor.js';
8
+ export type { ProcessedChartData } from './data-processor.js';
7
9
  export { default as ChartLayoutEditor } from './editor/ChartLayoutEditor.svelte';
8
10
  export { editorStore, currentLayout, savedLayouts, selection, isDirty, selectedItem } from './editor/editorState.js';
9
11
  export type { Selection, SelectionType, EditorState } from './editor/editorState.js';
@@ -2,6 +2,7 @@ export { default as ChartComponent } from './ChartComponent.svelte';
2
2
  export { default as ChartCard } from './ChartCard.svelte';
3
3
  export { createTimeSeriesTrace, getYAxisTitle, formatValue, processKPIData, createDefaultPlotlyLayout } from './data-utils.js';
4
4
  export { adaptPlotlyLayout, getSizeCategory, createMarkerShapes, createMarkerAnnotations, addMarkersToLayout } from './adapt.js';
5
+ export { getPreprocessedData, extractKPINames, getKPIValues, preprocessChartData } from './data-processor.js';
5
6
  // Editor exports
6
7
  export { default as ChartLayoutEditor } from './editor/ChartLayoutEditor.svelte';
7
8
  export { editorStore, currentLayout, savedLayouts, selection, isDirty, selectedItem } from './editor/editorState.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",