@smartnet360/svelte-components 0.0.19 → 0.0.21

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,8 +3,8 @@
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 } from './charts.model.js';
7
- import { createTimeSeriesTrace, getYAxisTitle, createDefaultPlotlyLayout } from './data-utils.js';
6
+ import type { Chart as ChartModel, ChartMarker, MovingAverageConfig } from './charts.model.js';
7
+ import { createTimeSeriesTraceWithMA, getYAxisTitle, createDefaultPlotlyLayout } from './data-utils.js';
8
8
  import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
9
9
 
10
10
  const dispatch = createEventDispatcher<{
@@ -23,9 +23,11 @@
23
23
  plotlyLayout?: any; // Optional custom Plotly layout for styling/theming
24
24
  enableAdaptation?: boolean; // Enable size-based adaptations (default: true)
25
25
  sectionId?: string;
26
+ sectionMovingAverage?: MovingAverageConfig; // Section-level MA config
27
+ layoutMovingAverage?: MovingAverageConfig; // Layout-level MA config
26
28
  }
27
29
 
28
- let { chart, data, markers, plotlyLayout, enableAdaptation = true, sectionId }: Props = $props();
30
+ let { chart, data, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage }: Props = $props();
29
31
 
30
32
  // Chart container div and state
31
33
  let chartDiv: HTMLElement;
@@ -68,17 +70,55 @@
68
70
  const traces: any[] = [];
69
71
  let colorIndex = 0;
70
72
 
71
- // Add left Y-axis traces
73
+ // Helper function to apply MA with full hierarchy:
74
+ // KPI > Chart > Section > Layout (higher priority wins)
75
+ const applyMovingAverageHierarchy = (kpi: any) => {
76
+ // If KPI has its own MA config, use it (highest priority)
77
+ if (kpi.movingAverage) {
78
+ return kpi;
79
+ }
80
+
81
+ // Check chart-level MA
82
+ if (chart.movingAverage) {
83
+ return {
84
+ ...kpi,
85
+ movingAverage: chart.movingAverage
86
+ };
87
+ }
88
+
89
+ // Check section-level MA
90
+ if (sectionMovingAverage) {
91
+ return {
92
+ ...kpi,
93
+ movingAverage: sectionMovingAverage
94
+ };
95
+ }
96
+
97
+ // Check layout-level MA (lowest priority, global default)
98
+ if (layoutMovingAverage) {
99
+ return {
100
+ ...kpi,
101
+ movingAverage: layoutMovingAverage
102
+ };
103
+ }
104
+
105
+ // No MA configuration at any level
106
+ return kpi;
107
+ };
108
+
109
+ // Add left Y-axis traces (with moving average support)
72
110
  chart.yLeft.forEach(kpi => {
73
- const trace = createTimeSeriesTrace(data, kpi, 'TIMESTAMP', 'y1', colorIndex);
74
- traces.push(trace);
111
+ const kpiWithMA = applyMovingAverageHierarchy(kpi);
112
+ const kpiTraces = createTimeSeriesTraceWithMA(data, kpiWithMA, 'TIMESTAMP', 'y1', colorIndex);
113
+ traces.push(...kpiTraces);
75
114
  colorIndex++;
76
115
  });
77
116
 
78
- // Add right Y-axis traces
117
+ // Add right Y-axis traces (with moving average support)
79
118
  chart.yRight.forEach(kpi => {
80
- const trace = createTimeSeriesTrace(data, kpi, 'TIMESTAMP', 'y2', colorIndex);
81
- traces.push(trace);
119
+ const kpiWithMA = applyMovingAverageHierarchy(kpi);
120
+ const kpiTraces = createTimeSeriesTraceWithMA(data, kpiWithMA, 'TIMESTAMP', 'y2', colorIndex);
121
+ traces.push(...kpiTraces);
82
122
  colorIndex++;
83
123
  });
84
124
 
@@ -1,4 +1,4 @@
1
- import type { Chart as ChartModel, ChartMarker } from './charts.model.js';
1
+ import type { Chart as ChartModel, ChartMarker, MovingAverageConfig } from './charts.model.js';
2
2
  interface Props {
3
3
  chart: ChartModel;
4
4
  data: any[];
@@ -6,6 +6,8 @@ interface Props {
6
6
  plotlyLayout?: any;
7
7
  enableAdaptation?: boolean;
8
8
  sectionId?: string;
9
+ sectionMovingAverage?: MovingAverageConfig;
10
+ layoutMovingAverage?: MovingAverageConfig;
9
11
  }
10
12
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
11
13
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
@@ -246,6 +246,8 @@
246
246
  {plotlyLayout}
247
247
  {enableAdaptation}
248
248
  sectionId={section.id}
249
+ sectionMovingAverage={section.movingAverage}
250
+ layoutMovingAverage={layout.movingAverage}
249
251
  on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, section)}
250
252
  />
251
253
  </div>
@@ -343,6 +345,8 @@
343
345
  {plotlyLayout}
344
346
  {enableAdaptation}
345
347
  sectionId={activeZoom.section.id}
348
+ sectionMovingAverage={activeZoom.section.movingAverage}
349
+ layoutMovingAverage={layout.movingAverage}
346
350
  on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, activeZoom.section)}
347
351
  />
348
352
  </div>
@@ -1,10 +1,17 @@
1
1
  export type Scale = "percent" | "absolute";
2
+ export interface MovingAverageConfig {
3
+ enabled: boolean;
4
+ window: number;
5
+ showOriginal?: boolean;
6
+ label?: string;
7
+ }
2
8
  export interface KPI {
3
9
  rawName: string;
4
10
  name: string;
5
11
  scale: Scale;
6
12
  unit: string;
7
13
  color?: string;
14
+ movingAverage?: MovingAverageConfig;
8
15
  }
9
16
  export type ChartPosition = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
10
17
  export type ChartGrid = "2x2" | "3x3" | "4x4" | "1x2" | "1x4" | "1x8";
@@ -13,17 +20,20 @@ export interface Chart {
13
20
  title: string;
14
21
  yLeft: KPI[];
15
22
  yRight: KPI[];
23
+ movingAverage?: MovingAverageConfig;
16
24
  }
17
25
  export interface Section {
18
26
  id: string;
19
27
  title: string;
20
28
  charts: Chart[];
21
29
  grid?: ChartGrid;
30
+ movingAverage?: MovingAverageConfig;
22
31
  }
23
32
  export type Mode = "tabs" | "scrollspy";
24
33
  export interface Layout {
25
34
  layoutName: string;
26
35
  sections: Section[];
36
+ movingAverage?: MovingAverageConfig;
27
37
  }
28
38
  export interface ChartMarker {
29
39
  date: string | Date;
@@ -1,6 +1,23 @@
1
1
  import type { KPI } from './charts.model.js';
2
2
  export declare function processKPIData(data: any[], kpi: KPI): number[];
3
+ /**
4
+ * Calculate moving average for a series of values
5
+ * @param values - Array of numeric values
6
+ * @param window - Number of periods for MA calculation
7
+ * @returns Array of moving average values (NaN for insufficient data points)
8
+ */
9
+ export declare function calculateMovingAverage(values: number[], window: number): number[];
3
10
  export declare function createTimeSeriesTrace(data: any[], kpi: KPI, timestampField?: string, yaxis?: 'y1' | 'y2', colorIndex?: number): any;
11
+ /**
12
+ * Create time series trace(s) with optional moving average
13
+ * @param data - Raw data array
14
+ * @param kpi - KPI configuration (may include movingAverage config)
15
+ * @param timestampField - Field name for timestamp column
16
+ * @param yaxis - Which Y-axis to use ('y1' or 'y2')
17
+ * @param colorIndex - Index for color selection
18
+ * @returns Array of traces (original + MA if configured)
19
+ */
20
+ export declare function createTimeSeriesTraceWithMA(data: any[], kpi: KPI, timestampField?: string, yaxis?: 'y1' | 'y2', colorIndex?: number): any[];
4
21
  export declare function getYAxisTitle(kpis: KPI[]): string;
5
22
  export declare function formatValue(value: number, scale: 'percent' | 'absolute', unit: string): string;
6
23
  export declare function createDefaultPlotlyLayout(title?: string): any;
@@ -19,6 +19,28 @@ export function processKPIData(data, kpi) {
19
19
  })
20
20
  .filter(val => !isNaN(val));
21
21
  }
22
+ /**
23
+ * Calculate moving average for a series of values
24
+ * @param values - Array of numeric values
25
+ * @param window - Number of periods for MA calculation
26
+ * @returns Array of moving average values (NaN for insufficient data points)
27
+ */
28
+ export function calculateMovingAverage(values, window) {
29
+ const result = [];
30
+ for (let i = 0; i < values.length; i++) {
31
+ if (i < window - 1) {
32
+ // Not enough data points yet - use NaN to skip these points
33
+ result.push(NaN);
34
+ }
35
+ else {
36
+ // Calculate average of the window
37
+ const windowValues = values.slice(i - window + 1, i + 1);
38
+ const sum = windowValues.reduce((a, b) => a + b, 0);
39
+ result.push(sum / window);
40
+ }
41
+ }
42
+ return result;
43
+ }
22
44
  export function createTimeSeriesTrace(data, kpi, timestampField = 'TIMESTAMP', yaxis = 'y1', colorIndex = 0) {
23
45
  const values = processKPIData(data, kpi);
24
46
  const timestamps = data.map(row => row[timestampField]);
@@ -43,6 +65,57 @@ export function createTimeSeriesTrace(data, kpi, timestampField = 'TIMESTAMP', y
43
65
  '<extra></extra>'
44
66
  };
45
67
  }
68
+ /**
69
+ * Create time series trace(s) with optional moving average
70
+ * @param data - Raw data array
71
+ * @param kpi - KPI configuration (may include movingAverage config)
72
+ * @param timestampField - Field name for timestamp column
73
+ * @param yaxis - Which Y-axis to use ('y1' or 'y2')
74
+ * @param colorIndex - Index for color selection
75
+ * @returns Array of traces (original + MA if configured)
76
+ */
77
+ export function createTimeSeriesTraceWithMA(data, kpi, timestampField = 'TIMESTAMP', yaxis = 'y1', colorIndex = 0) {
78
+ const traces = [];
79
+ const values = processKPIData(data, kpi);
80
+ const timestamps = data.map(row => row[timestampField]);
81
+ const traceColor = kpi.color || modernColors[colorIndex % modernColors.length];
82
+ // Add original trace (unless explicitly disabled)
83
+ if (!kpi.movingAverage || kpi.movingAverage.showOriginal !== false) {
84
+ const originalTrace = createTimeSeriesTrace(data, kpi, timestampField, yaxis, colorIndex);
85
+ // If MA is enabled, make the original line slightly transparent
86
+ if (kpi.movingAverage?.enabled) {
87
+ originalTrace.opacity = 0.4;
88
+ originalTrace.line.width = 2;
89
+ }
90
+ traces.push(originalTrace);
91
+ }
92
+ // Add moving average trace if configured
93
+ if (kpi.movingAverage?.enabled) {
94
+ const maValues = calculateMovingAverage(values, kpi.movingAverage.window);
95
+ const maLabel = kpi.movingAverage.label ||
96
+ `${kpi.name} (MA${kpi.movingAverage.window})`;
97
+ const maTrace = {
98
+ x: timestamps,
99
+ y: maValues,
100
+ type: 'scatter',
101
+ mode: 'lines',
102
+ name: maLabel,
103
+ yaxis: yaxis,
104
+ line: {
105
+ color: traceColor,
106
+ width: 3,
107
+ shape: 'spline',
108
+ smoothing: 0.3,
109
+ dash: yaxis === 'y1' ? 'solid' : 'dot'
110
+ },
111
+ hovertemplate: `<b>${maLabel}</b><br>` +
112
+ `Value: %{y:,.2f} ${kpi.unit}<br>` +
113
+ '<extra></extra>'
114
+ };
115
+ traces.push(maTrace);
116
+ }
117
+ return traces;
118
+ }
46
119
  export function getYAxisTitle(kpis) {
47
120
  if (kpis.length === 0)
48
121
  return '';
@@ -6,6 +6,13 @@
6
6
  import GridPreview from './GridPreview.svelte';
7
7
  import PropertiesPanel from './PropertiesPanel.svelte';
8
8
  import KPIPicker from './KPIPicker.svelte';
9
+ import type { KPI } from '../charts.model.js';
10
+
11
+ interface Props {
12
+ availableKPIs: KPI[];
13
+ }
14
+
15
+ let { availableKPIs }: Props = $props();
9
16
 
10
17
  let showKPIPicker = $state(false);
11
18
  let showLibrary = $state(false);
@@ -180,6 +187,7 @@
180
187
  {#if showKPIPicker}
181
188
  <KPIPicker
182
189
  show={showKPIPicker}
190
+ {availableKPIs}
183
191
  on:select={handleKPISelected}
184
192
  on:close={() => { showKPIPicker = false; kpiPickerContext = null; }}
185
193
  />
@@ -1,3 +1,7 @@
1
- declare const ChartLayoutEditor: import("svelte").Component<Record<string, never>, {}, "">;
1
+ import type { KPI } from '../charts.model.js';
2
+ interface Props {
3
+ availableKPIs: KPI[];
4
+ }
5
+ declare const ChartLayoutEditor: import("svelte").Component<Props, {}, "">;
2
6
  type ChartLayoutEditor = ReturnType<typeof ChartLayoutEditor>;
3
7
  export default ChartLayoutEditor;
@@ -2,22 +2,32 @@
2
2
 
3
3
  <script lang="ts">
4
4
  import { createEventDispatcher } from 'svelte';
5
- import { availableKPIs, searchKPIs } from '../../../../routes/charts/schemas/available-kpis.js';
6
5
  import type { KPI } from '../charts.model.js';
7
6
 
8
7
  interface Props {
9
8
  show: boolean;
9
+ availableKPIs: KPI[];
10
10
  }
11
11
 
12
- let { show }: Props = $props();
12
+ let { show, availableKPIs }: Props = $props();
13
13
 
14
14
  const dispatch = createEventDispatcher();
15
15
 
16
16
  let searchQuery = $state('');
17
17
  let selectedKPI = $state<KPI | null>(null);
18
18
 
19
+ // Local search function
20
+ function searchKPIs(query: string, kpis: KPI[]): KPI[] {
21
+ const lowerQuery = query.toLowerCase();
22
+ return kpis.filter(
23
+ kpi =>
24
+ kpi.name.toLowerCase().includes(lowerQuery) ||
25
+ kpi.rawName.toLowerCase().includes(lowerQuery)
26
+ );
27
+ }
28
+
19
29
  let filteredKPIs = $derived(
20
- searchQuery ? searchKPIs(searchQuery) : availableKPIs
30
+ searchQuery ? searchKPIs(searchQuery, availableKPIs) : availableKPIs
21
31
  );
22
32
 
23
33
  function handleSelect(kpi: KPI) {
@@ -1,5 +1,7 @@
1
+ import type { KPI } from '../charts.model.js';
1
2
  interface Props {
2
3
  show: boolean;
4
+ availableKPIs: KPI[];
3
5
  }
4
6
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
7
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Example KPI Configuration
3
+ *
4
+ * This is a reference example showing the structure for availableKPIs.
5
+ * In your application, you should:
6
+ * 1. Load KPIs from your backend API
7
+ * 2. Store in a JSON file specific to your domain
8
+ * 3. Generate dynamically based on your data schema
9
+ */
10
+ import type { KPI } from '../charts.model.js';
11
+ /**
12
+ * Example KPI list structure
13
+ * Each KPI must have: rawName, name, scale, unit, and optional color
14
+ */
15
+ export declare const exampleKPIs: KPI[];
16
+ /**
17
+ * KPI Interface Reference:
18
+ *
19
+ * interface KPI {
20
+ * rawName: string; // Column name in your data source
21
+ * name: string; // Display name shown in UI
22
+ * scale: 'percent' | 'absolute'; // Data type
23
+ * unit: string; // Unit of measurement (%, GB, users, etc.)
24
+ * color?: string; // Optional hex color for chart line
25
+ * }
26
+ */
27
+ /**
28
+ * Usage Example:
29
+ *
30
+ * <script>
31
+ * import { ChartLayoutEditor } from '..';
32
+ * import { exampleKPIs } from './exampleKPIs';
33
+ * // OR load from API:
34
+ * // const myKPIs = await fetch('/api/kpis').then(r => r.json());
35
+ * </script>
36
+ *
37
+ * <ChartLayoutEditor availableKPIs={exampleKPIs} />
38
+ */
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Example KPI Configuration
3
+ *
4
+ * This is a reference example showing the structure for availableKPIs.
5
+ * In your application, you should:
6
+ * 1. Load KPIs from your backend API
7
+ * 2. Store in a JSON file specific to your domain
8
+ * 3. Generate dynamically based on your data schema
9
+ */
10
+ /**
11
+ * Example KPI list structure
12
+ * Each KPI must have: rawName, name, scale, unit, and optional color
13
+ */
14
+ export const exampleKPIs = [
15
+ {
16
+ rawName: 'CCSR_PC',
17
+ name: 'CCSR Success Rate',
18
+ scale: 'percent',
19
+ unit: '%',
20
+ color: '#2ca02c' // Optional: hex color for the trace
21
+ },
22
+ {
23
+ rawName: 'DL_GBYTES',
24
+ name: 'Downlink Traffic',
25
+ scale: 'absolute',
26
+ unit: 'GB',
27
+ color: '#06B6D4'
28
+ },
29
+ {
30
+ rawName: 'UL_GBYTES',
31
+ name: 'Uplink Traffic',
32
+ scale: 'absolute',
33
+ unit: 'GB',
34
+ color: '#84CC16'
35
+ },
36
+ {
37
+ rawName: 'AVG_USERS',
38
+ name: 'Average Connected Users',
39
+ scale: 'absolute',
40
+ unit: 'users',
41
+ color: '#EF4444'
42
+ }
43
+ ];
44
+ /**
45
+ * KPI Interface Reference:
46
+ *
47
+ * interface KPI {
48
+ * rawName: string; // Column name in your data source
49
+ * name: string; // Display name shown in UI
50
+ * scale: 'percent' | 'absolute'; // Data type
51
+ * unit: string; // Unit of measurement (%, GB, users, etc.)
52
+ * color?: string; // Optional hex color for chart line
53
+ * }
54
+ */
55
+ /**
56
+ * Usage Example:
57
+ *
58
+ * <script>
59
+ * import { ChartLayoutEditor } from '..';
60
+ * import { exampleKPIs } from './exampleKPIs';
61
+ * // OR load from API:
62
+ * // const myKPIs = await fetch('/api/kpis').then(r => r.json());
63
+ * </script>
64
+ *
65
+ * <ChartLayoutEditor availableKPIs={exampleKPIs} />
66
+ */
@@ -7,3 +7,4 @@ export type { ContainerSize, ChartInfo, AdaptationConfig } from './adapt.js';
7
7
  export { default as ChartLayoutEditor } from './editor/ChartLayoutEditor.svelte';
8
8
  export { editorStore, currentLayout, savedLayouts, selection, isDirty, selectedItem } from './editor/editorState.js';
9
9
  export type { Selection, SelectionType, EditorState } from './editor/editorState.js';
10
+ export { exampleKPIs } from './editor/exampleKPIs.js';
@@ -5,3 +5,4 @@ export { adaptPlotlyLayout, getSizeCategory, createMarkerShapes, createMarkerAnn
5
5
  // Editor exports
6
6
  export { default as ChartLayoutEditor } from './editor/ChartLayoutEditor.svelte';
7
7
  export { editorStore, currentLayout, savedLayouts, selection, isDirty, selectedItem } from './editor/editorState.js';
8
+ export { exampleKPIs } from './editor/exampleKPIs.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",