@smartnet360/svelte-components 0.0.29 → 0.0.31

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,7 +19,7 @@
18
19
 
19
20
  interface Props {
20
21
  chart: ChartModel;
21
- data: any[];
22
+ processedData: ProcessedChartData; // Pre-processed KPI values and timestamps
22
23
  markers?: ChartMarker[]; // Global markers for all charts
23
24
  plotlyLayout?: any; // Optional custom Plotly layout for styling/theming
24
25
  enableAdaptation?: boolean; // Enable size-based adaptations (default: true)
@@ -31,7 +32,7 @@
31
32
  runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
32
33
  }
33
34
 
34
- let { chart, data, 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();
35
36
 
36
37
  // Chart container div and state
37
38
  let chartDiv: HTMLElement;
@@ -145,25 +146,27 @@
145
146
  };
146
147
  });
147
148
 
148
- // Extract timestamps once, not for every KPI
149
- let timestamps = $derived(data.map(row => row['TIMESTAMP']));
149
+ // Timestamps are already extracted in processedData
150
+ let timestamps = $derived(processedData.timestamps);
150
151
 
151
152
  function renderChart() {
152
- if (!chartDiv || !data?.length) return;
153
+ if (!chartDiv || !processedData?.kpiValues.size) return;
153
154
 
154
155
  const traces: any[] = [];
155
156
  let colorIndex = 0;
156
157
 
157
158
  // Add left Y-axis traces (with moving average support)
158
159
  resolvedKPIs.left.forEach(kpi => {
159
- const kpiTraces = createTimeSeriesTraceWithMA(data, kpi, 'TIMESTAMP', 'y1', colorIndex, timestamps);
160
+ const values = getKPIValues(processedData, kpi);
161
+ const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y1', colorIndex);
160
162
  traces.push(...kpiTraces);
161
163
  colorIndex++;
162
164
  });
163
165
 
164
166
  // Add right Y-axis traces (with moving average support)
165
167
  resolvedKPIs.right.forEach(kpi => {
166
- const kpiTraces = createTimeSeriesTraceWithMA(data, kpi, 'TIMESTAMP', 'y2', colorIndex, timestamps);
168
+ const values = getKPIValues(processedData, kpi);
169
+ const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y2', colorIndex);
167
170
  traces.push(...kpiTraces);
168
171
  colorIndex++;
169
172
  });
@@ -294,7 +297,7 @@
294
297
  // React to prop changes - debounce re-renders for better performance
295
298
  $effect(() => {
296
299
  // Watch these props and re-render when they change
297
- const currentData = data;
300
+ const currentProcessedData = processedData;
298
301
  const currentMarkers = markers;
299
302
  const currentMAOverride = runtimeMAOverride;
300
303
  const currentShowOriginal = runtimeShowOriginal;
@@ -1,7 +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
+ processedData: ProcessedChartData;
5
6
  markers?: ChartMarker[];
6
7
  plotlyLayout?: any;
7
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,6 +55,10 @@
54
55
 
55
56
  let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true, showGlobalControls = true }: Props = $props();
56
57
 
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));
61
+
57
62
  // Global runtime controls state - initialize from layout config
58
63
  let globalControls = $state<GlobalChartControls>({
59
64
  movingAverage: {
@@ -291,7 +296,7 @@
291
296
  <div class="chart-slot">
292
297
  <ChartCard
293
298
  {chart}
294
- {data}
299
+ {processedData}
295
300
  {markers}
296
301
  {plotlyLayout}
297
302
  {enableAdaptation}
@@ -433,7 +438,7 @@
433
438
  </button>
434
439
  <ChartCard
435
440
  chart={activeZoom.chart}
436
- {data}
441
+ {processedData}
437
442
  {markers}
438
443
  {plotlyLayout}
439
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.29",
3
+ "version": "0.0.31",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",