@smartnet360/svelte-components 0.0.34 → 0.0.35

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.
Files changed (38) hide show
  1. package/dist/apps/site-check/SiteCheck.svelte +96 -0
  2. package/dist/apps/site-check/SiteCheck.svelte.d.ts +12 -0
  3. package/dist/apps/site-check/data-loader.d.ts +29 -0
  4. package/dist/apps/site-check/data-loader.js +105 -0
  5. package/dist/apps/site-check/helper.d.ts +19 -0
  6. package/dist/apps/site-check/helper.js +103 -0
  7. package/dist/apps/site-check/index.d.ts +8 -0
  8. package/dist/apps/site-check/index.js +12 -0
  9. package/dist/apps/site-check/transforms.d.ts +24 -0
  10. package/dist/apps/site-check/transforms.js +142 -0
  11. package/dist/core/Charts/ChartCard.svelte +4 -2
  12. package/dist/core/Charts/charts.model.d.ts +3 -0
  13. package/dist/core/Charts/data-utils.d.ts +4 -2
  14. package/dist/core/Charts/data-utils.js +69 -16
  15. package/dist/core/TreeView/TreeView.svelte +2 -2
  16. package/dist/core/index.d.ts +0 -1
  17. package/dist/core/index.js +0 -2
  18. package/dist/index.d.ts +0 -1
  19. package/dist/index.js +0 -2
  20. package/package.json +1 -1
  21. package/dist/cellular/CellularChartsView.svelte +0 -293
  22. package/dist/cellular/CellularChartsView.svelte.d.ts +0 -7
  23. package/dist/cellular/HierarchicalTree.svelte +0 -469
  24. package/dist/cellular/HierarchicalTree.svelte.d.ts +0 -9
  25. package/dist/cellular/SiteTree.svelte +0 -286
  26. package/dist/cellular/SiteTree.svelte.d.ts +0 -11
  27. package/dist/cellular/cellular-transforms.d.ts +0 -25
  28. package/dist/cellular/cellular-transforms.js +0 -129
  29. package/dist/cellular/cellular.model.d.ts +0 -63
  30. package/dist/cellular/cellular.model.js +0 -6
  31. package/dist/cellular/index.d.ts +0 -11
  32. package/dist/cellular/index.js +0 -11
  33. package/dist/cellular/mock-cellular-data.d.ts +0 -13
  34. package/dist/cellular/mock-cellular-data.js +0 -241
  35. package/dist/core/TreeChartView/TreeChartView.svelte +0 -208
  36. package/dist/core/TreeChartView/TreeChartView.svelte.d.ts +0 -42
  37. package/dist/core/TreeChartView/index.d.ts +0 -7
  38. package/dist/core/TreeChartView/index.js +0 -7
@@ -0,0 +1,96 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { TreeView, createTreeStore } from '../../core/TreeView';
5
+ import { ChartComponent, type Layout } from '../../core/Charts';
6
+ import { buildTreeNodes, filterChartData, transformChartData, type CellTrafficRecord } from './index';
7
+ import { expandLayoutForCells } from './helper';
8
+ import { onMount } from 'svelte';
9
+ import type { Mode } from '../../index.js';
10
+
11
+ interface Props {
12
+ rawData: CellTrafficRecord[];
13
+ baseLayout: Layout;
14
+ baseMetrics: string[];
15
+ mode: Mode;
16
+ }
17
+
18
+ let { rawData, baseLayout, baseMetrics, mode = "scrollspy" }: Props = $props();
19
+
20
+ let treeStore = $state<ReturnType<typeof createTreeStore> | null>(null);
21
+
22
+ onMount(() => {
23
+ // Build tree nodes from raw data
24
+ const treeNodes = buildTreeNodes(rawData);
25
+
26
+ // Create tree store
27
+ treeStore = createTreeStore({
28
+ nodes: treeNodes,
29
+ namespace: 'site-check',
30
+ persistState: true,
31
+ defaultExpandAll: false
32
+ });
33
+ });
34
+
35
+ // Derive chart data from tree selection
36
+ let filteredData = $derived.by(() => {
37
+ if (!treeStore) return [];
38
+ const storeValue = $treeStore;
39
+ if (!storeValue) return [];
40
+ return filterChartData(rawData, storeValue.state.checkedPaths);
41
+ });
42
+
43
+ // Transform data using base metrics from layout
44
+ let chartData = $derived(transformChartData(filteredData, baseMetrics));
45
+
46
+ // Expand layout based on selected cells
47
+ let chartLayout = $derived(expandLayoutForCells(baseLayout, filteredData));
48
+ console.log('chartLayout', chartLayout);
49
+
50
+ let totalRecords = $derived(rawData.length);
51
+ let visibleRecords = $derived(filteredData.length);
52
+
53
+ // Compute simple stats
54
+ let totalCells = $derived(new Set(filteredData.map((r) => r.cellName)).size);
55
+ let totalSites = $derived(new Set(filteredData.map((r) => r.siteName)).size);
56
+ </script>
57
+
58
+ <div class="container-fluid vh-100 d-flex flex-column">
59
+ <!-- Main Content -->
60
+ <div class="row flex-grow-1 ">
61
+ <!-- Left: Tree View -->
62
+ <div class="col-lg-3 col-md-4 border-end bg-white overflow-auto">
63
+ <div class="p-3">
64
+ <!-- <h5 class="mb-3">
65
+ <span class="me-2">📡</span>
66
+ Site Selection
67
+ </h5> -->
68
+
69
+ {#if treeStore}
70
+ <TreeView store={$treeStore!} showControls={false} showIndeterminate={true} height="100%" />
71
+ {/if}
72
+ </div>
73
+ </div>
74
+
75
+ <!-- Right: Charts -->
76
+ <div class="col-lg-9 col-md-8 bg-light overflow-auto">
77
+ {#if chartData.length > 0}
78
+ <ChartComponent
79
+ layout={chartLayout}
80
+ data={chartData}
81
+ mode={mode}
82
+ showGlobalControls={true}
83
+ enableAdaptation={true}
84
+ />
85
+ {:else}
86
+ <div class="d-flex align-items-center justify-content-center h-100">
87
+ <div class="text-center text-muted">
88
+ <div class="mb-3" style="font-size: 4rem;">📊</div>
89
+ <h5>No Data Selected</h5>
90
+ <p>Select one or more cells from the tree to display KPI charts.</p>
91
+ </div>
92
+ </div>
93
+ {/if}
94
+ </div>
95
+ </div>
96
+ </div>
@@ -0,0 +1,12 @@
1
+ import { type Layout } from '../../core/Charts';
2
+ import { type CellTrafficRecord } from './index';
3
+ import type { Mode } from '../../index.js';
4
+ interface Props {
5
+ rawData: CellTrafficRecord[];
6
+ baseLayout: Layout;
7
+ baseMetrics: string[];
8
+ mode: Mode;
9
+ }
10
+ declare const SiteCheck: import("svelte").Component<Props, {}, "">;
11
+ type SiteCheck = ReturnType<typeof SiteCheck>;
12
+ export default SiteCheck;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Data Loader for Site Check Component
3
+ * Loads and parses cell_traffic_with_band.csv
4
+ */
5
+ export interface CellTrafficRecord {
6
+ date: string;
7
+ cellName: string;
8
+ siteName: string;
9
+ sector: number;
10
+ azimuth: number;
11
+ band: string;
12
+ metrics: Record<string, number>;
13
+ }
14
+ /**
15
+ * Load cell traffic data from CSV file
16
+ */
17
+ export declare function loadCellTrafficData(): Promise<CellTrafficRecord[]>;
18
+ /**
19
+ * Get unique sites from data
20
+ */
21
+ export declare function getUniqueSites(data: CellTrafficRecord[]): string[];
22
+ /**
23
+ * Get unique cells from data
24
+ */
25
+ export declare function getUniqueCells(data: CellTrafficRecord[]): string[];
26
+ /**
27
+ * Group data by cell name for efficient lookup
28
+ */
29
+ export declare function groupDataByCell(data: CellTrafficRecord[]): Map<string, CellTrafficRecord[]>;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Data Loader for Site Check Component
3
+ * Loads and parses cell_traffic_with_band.csv
4
+ */
5
+ /**
6
+ * Load cell traffic data from CSV file
7
+ */
8
+ export async function loadCellTrafficData() {
9
+ const response = await fetch('/cell_traffic_with_band.csv');
10
+ if (!response.ok) {
11
+ throw new Error(`Failed to load CSV: ${response.statusText}`);
12
+ }
13
+ const text = await response.text();
14
+ return parseCsvData(text);
15
+ }
16
+ /**
17
+ * Parse CSV text into structured records
18
+ */
19
+ function parseCsvData(csv) {
20
+ const lines = csv.trim().split('\n');
21
+ // Parse header to identify metric columns
22
+ const header = lines[0].split(',').map(h => h.trim());
23
+ const dataLines = lines.slice(1);
24
+ return dataLines
25
+ .map((line) => {
26
+ const parts = line.split(',');
27
+ // Handle potential issues with CSV parsing
28
+ if (parts.length < 8) {
29
+ console.warn('Skipping malformed CSV line:', line);
30
+ return null;
31
+ }
32
+ // Fixed columns (metadata) - based on known CSV structure
33
+ const date = parts[0].trim();
34
+ const cellName = parts[1].trim();
35
+ const siteName = parts[4].trim();
36
+ const sector = parseInt(parts[5]);
37
+ const azimuth = parseInt(parts[6]);
38
+ const band = parts[7].trim();
39
+ // Dynamic metrics - automatically map ALL numeric columns from CSV
40
+ const metrics = {};
41
+ // Start from column 2 (skip PERIOD_START_TIME, CELL_NAME)
42
+ // Stop before metadata columns (SITE_NAME, SECTOR, AZIMUTH, band)
43
+ for (let i = 2; i < Math.min(parts.length, 4); i++) {
44
+ const columnName = header[i]; // Use exact column name from CSV header
45
+ const value = parseFloat(parts[i]);
46
+ if (columnName && !isNaN(value)) {
47
+ metrics[columnName] = value; // e.g., metrics['DL_GBYTES'] = 123.45
48
+ }
49
+ }
50
+ // Add any additional metric columns after band column
51
+ for (let i = 8; i < parts.length; i++) {
52
+ const columnName = header[i];
53
+ const value = parseFloat(parts[i]);
54
+ if (columnName && !isNaN(value)) {
55
+ metrics[columnName] = value;
56
+ }
57
+ }
58
+ return {
59
+ date,
60
+ cellName,
61
+ siteName,
62
+ sector,
63
+ azimuth,
64
+ band,
65
+ metrics
66
+ };
67
+ })
68
+ .filter((record) => record !== null);
69
+ }
70
+ /**
71
+ * Get unique sites from data
72
+ */
73
+ export function getUniqueSites(data) {
74
+ const sites = new Set();
75
+ data.forEach((record) => sites.add(record.siteName));
76
+ return Array.from(sites).sort();
77
+ }
78
+ /**
79
+ * Get unique cells from data
80
+ */
81
+ export function getUniqueCells(data) {
82
+ const cells = new Set();
83
+ data.forEach((record) => cells.add(record.cellName));
84
+ return Array.from(cells).sort();
85
+ }
86
+ /**
87
+ * Group data by cell name for efficient lookup
88
+ */
89
+ export function groupDataByCell(data) {
90
+ const grouped = new Map();
91
+ data.forEach((record) => {
92
+ if (!grouped.has(record.cellName)) {
93
+ grouped.set(record.cellName, []);
94
+ }
95
+ grouped.get(record.cellName).push(record);
96
+ });
97
+ // Sort each cell's data by date
98
+ grouped.forEach((records) => {
99
+ records.sort((a, b) => {
100
+ // Simple date string comparison (works for DD-MMM-YY format)
101
+ return a.date.localeCompare(b.date);
102
+ });
103
+ });
104
+ return grouped;
105
+ }
@@ -0,0 +1,19 @@
1
+ import type { Layout } from '../../core/Charts';
2
+ import type { CellTrafficRecord } from './';
3
+ /**
4
+ * Expand base layout configuration with dynamic KPIs based on selected cells
5
+ * Takes a base layout (with one KPI per metric) and expands it to include one KPI per cell
6
+ *
7
+ * @param baseLayout - The base layout configuration from JSON
8
+ * @param data - Filtered cell traffic records for selected cells
9
+ * @returns Expanded layout with cell-specific KPIs
10
+ */
11
+ export declare function expandLayoutForCells(baseLayout: Layout, data: CellTrafficRecord[]): Layout;
12
+ /**
13
+ * Extract base metric names from a layout configuration
14
+ * Returns unique metric rawNames that need to be pivoted
15
+ *
16
+ * @param layout - The layout configuration
17
+ * @returns Array of unique metric names (e.g., ['dlGBytes', 'ulGBytes'])
18
+ */
19
+ export declare function extractBaseMetrics(layout: Layout): string[];
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Expand base layout configuration with dynamic KPIs based on selected cells
3
+ * Takes a base layout (with one KPI per metric) and expands it to include one KPI per cell
4
+ *
5
+ * @param baseLayout - The base layout configuration from JSON
6
+ * @param data - Filtered cell traffic records for selected cells
7
+ * @returns Expanded layout with cell-specific KPIs
8
+ */
9
+ export function expandLayoutForCells(baseLayout, data) {
10
+ // Get unique cells and their metadata
11
+ const cellMap = new Map();
12
+ data.forEach((record) => {
13
+ if (!cellMap.has(record.cellName)) {
14
+ cellMap.set(record.cellName, record);
15
+ }
16
+ });
17
+ const cells = Array.from(cellMap.entries()).sort(([a], [b]) => a.localeCompare(b));
18
+ // Deep clone the layout structure and expand KPIs
19
+ const expandedLayout = {
20
+ layoutName: baseLayout.layoutName,
21
+ sections: baseLayout.sections.map((section) => ({
22
+ ...section,
23
+ charts: section.charts.map((chart) => ({
24
+ ...chart,
25
+ yLeft: expandKPIs(chart.yLeft, cells),
26
+ yRight: expandKPIs(chart.yRight, cells)
27
+ }))
28
+ }))
29
+ };
30
+ return expandedLayout;
31
+ }
32
+ /**
33
+ * Expand a single KPI into multiple KPIs (one per cell)
34
+ *
35
+ * @param baseKPIs - Array of base KPIs from layout
36
+ * @param cells - Array of [cellName, record] tuples
37
+ * @returns Expanded array of KPIs with cell-specific rawNames and colors
38
+ */
39
+ function expandKPIs(baseKPIs, cells) {
40
+ const expandedKPIs = [];
41
+ baseKPIs.forEach((baseKPI) => {
42
+ cells.forEach(([cellName, record], index) => {
43
+ expandedKPIs.push({
44
+ rawName: `${baseKPI.rawName}_${cellName}`,
45
+ name: `${cellName} (${record.band}) - ${baseKPI.name}`,
46
+ scale: baseKPI.scale,
47
+ unit: baseKPI.unit,
48
+ color: getColorForIndex(index)
49
+ });
50
+ });
51
+ });
52
+ return expandedKPIs;
53
+ }
54
+ /**
55
+ * Extract base metric names from a layout configuration
56
+ * Returns unique metric rawNames that need to be pivoted
57
+ *
58
+ * @param layout - The layout configuration
59
+ * @returns Array of unique metric names (e.g., ['dlGBytes', 'ulGBytes'])
60
+ */
61
+ export function extractBaseMetrics(layout) {
62
+ const metrics = new Set();
63
+ layout.sections.forEach((section) => {
64
+ section.charts.forEach((chart) => {
65
+ chart.yLeft.forEach((kpi) => metrics.add(kpi.rawName));
66
+ chart.yRight.forEach((kpi) => metrics.add(kpi.rawName));
67
+ });
68
+ });
69
+ return Array.from(metrics);
70
+ }
71
+ /**
72
+ * Get a distinct color for each cell line
73
+ * Uses a predefined color palette with good contrast
74
+ */
75
+ function getColorForIndex(index) {
76
+ const colors = [
77
+ '#0d6efd', // Blue
78
+ '#198754', // Green
79
+ '#dc3545', // Red
80
+ '#ffc107', // Yellow
81
+ '#0dcaf0', // Cyan
82
+ '#6f42c1', // Purple
83
+ '#fd7e14', // Orange
84
+ '#20c997', // Teal
85
+ '#d63384', // Pink
86
+ '#6610f2', // Indigo
87
+ '#17a2b8', // Info
88
+ '#28a745', // Success
89
+ '#e83e8c', // Magenta
90
+ '#6c757d', // Gray
91
+ '#007bff', // Primary
92
+ '#28a745', // Green variant
93
+ '#17a2b8', // Cyan variant
94
+ '#ffc107', // Amber
95
+ '#dc3545', // Danger
96
+ '#343a40', // Dark
97
+ '#6c757d', // Secondary
98
+ '#fd7e14', // Orange variant
99
+ '#20c997', // Teal variant
100
+ '#6f42c1' // Violet
101
+ ];
102
+ return colors[index % colors.length];
103
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Site Check Component
3
+ * Public API exports for cell traffic KPI visualization
4
+ */
5
+ export { default as SiteCheck } from './SiteCheck.svelte';
6
+ export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell, type CellTrafficRecord } from './data-loader.js';
7
+ export { buildTreeNodes, filterChartData, transformChartData } from './transforms.js';
8
+ export { expandLayoutForCells, extractBaseMetrics } from './helper.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Site Check Component
3
+ * Public API exports for cell traffic KPI visualization
4
+ */
5
+ // Components
6
+ export { default as SiteCheck } from './SiteCheck.svelte';
7
+ // Data loading
8
+ export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell } from './data-loader.js';
9
+ // Data transforms
10
+ export { buildTreeNodes, filterChartData, transformChartData } from './transforms.js';
11
+ // Helper utilities (for external configuration)
12
+ export { expandLayoutForCells, extractBaseMetrics } from './helper.js';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Data Transforms for Site Check Component
3
+ * Converts raw CSV data to TreeView nodes and Chart configurations
4
+ */
5
+ import type { TreeNode } from '../../core/TreeView';
6
+ import type { CellTrafficRecord } from './data-loader';
7
+ /**
8
+ * Build hierarchical tree structure: Site → Sector (Azimuth) → Cell (Band)
9
+ */
10
+ export declare function buildTreeNodes(data: CellTrafficRecord[]): TreeNode[];
11
+ /**
12
+ * Filter chart data based on selected tree paths
13
+ * Only include cells that are checked in the tree
14
+ */
15
+ export declare function filterChartData(data: CellTrafficRecord[], checkedPaths: Set<string>): CellTrafficRecord[];
16
+ /**
17
+ * Transform data for chart component consumption
18
+ * Pivots data so each cell becomes its own KPI column
19
+ * Transforms from long format (many rows per cell) to wide format (one column per cell)
20
+ *
21
+ * @param data - Filtered cell traffic records
22
+ * @param baseMetrics - Array of metric names to pivot (e.g., ['dlGBytes', 'ulGBytes'])
23
+ */
24
+ export declare function transformChartData(data: CellTrafficRecord[], baseMetrics: string[]): any[];
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Data Transforms for Site Check Component
3
+ * Converts raw CSV data to TreeView nodes and Chart configurations
4
+ */
5
+ /**
6
+ * Build hierarchical tree structure: Site → Sector (Azimuth) → Cell (Band)
7
+ */
8
+ export function buildTreeNodes(data) {
9
+ // Group by site → azimuth → cell
10
+ const siteMap = new Map();
11
+ data.forEach((record) => {
12
+ if (!siteMap.has(record.siteName)) {
13
+ siteMap.set(record.siteName, new Map());
14
+ }
15
+ const azimuthMap = siteMap.get(record.siteName);
16
+ if (!azimuthMap.has(record.azimuth)) {
17
+ azimuthMap.set(record.azimuth, new Map());
18
+ }
19
+ const cellMap = azimuthMap.get(record.azimuth);
20
+ // Store one record per cell (we just need metadata, not all time series)
21
+ if (!cellMap.has(record.cellName)) {
22
+ cellMap.set(record.cellName, record);
23
+ }
24
+ });
25
+ // Build tree structure
26
+ const treeNodes = [];
27
+ Array.from(siteMap.entries())
28
+ .sort(([a], [b]) => a.localeCompare(b))
29
+ .forEach(([siteName, azimuthMap]) => {
30
+ const siteNode = {
31
+ id: siteName, // Simple ID
32
+ label: `Site ${siteName}`,
33
+ // icon: '📡',
34
+ metadata: { type: 'site', siteName },
35
+ defaultExpanded: false,
36
+ children: []
37
+ };
38
+ Array.from(azimuthMap.entries())
39
+ .sort(([a], [b]) => a - b)
40
+ .forEach(([azimuth, cellMap]) => {
41
+ const sectorNode = {
42
+ id: `${azimuth}`, // Simple ID (just azimuth)
43
+ label: `${azimuth}° Sector`,
44
+ // icon: '📍',
45
+ metadata: { type: 'sector', azimuth, siteName },
46
+ defaultExpanded: false,
47
+ children: []
48
+ };
49
+ Array.from(cellMap.entries())
50
+ .sort(([a], [b]) => a.localeCompare(b))
51
+ .forEach(([cellName, record]) => {
52
+ const cellNode = {
53
+ id: cellName, // Simple ID (just cell name)
54
+ label: `${cellName} (${record.band})`,
55
+ icon: getBandIcon(record.band),
56
+ metadata: {
57
+ type: 'cell',
58
+ cellName,
59
+ band: record.band,
60
+ siteName: record.siteName,
61
+ sector: record.sector,
62
+ azimuth: record.azimuth
63
+ },
64
+ defaultChecked: true
65
+ };
66
+ sectorNode.children.push(cellNode);
67
+ });
68
+ siteNode.children.push(sectorNode);
69
+ });
70
+ treeNodes.push(siteNode);
71
+ });
72
+ return treeNodes;
73
+ }
74
+ /**
75
+ * Get icon emoji based on band technology
76
+ */
77
+ function getBandIcon(band) {
78
+ return '';
79
+ if (band.startsWith('NR'))
80
+ return '📶'; // 5G
81
+ if (band.startsWith('LTE'))
82
+ return '📱'; // 4G
83
+ return '📡'; // Fallback
84
+ }
85
+ /**
86
+ * Filter chart data based on selected tree paths
87
+ * Only include cells that are checked in the tree
88
+ */
89
+ export function filterChartData(data, checkedPaths) {
90
+ // Extract cell names from checked leaf paths (format: "site:azimuth:cellName")
91
+ const selectedCells = new Set();
92
+ checkedPaths.forEach((path) => {
93
+ const parts = path.split(':');
94
+ if (parts.length === 3) {
95
+ // This is a cell-level path (site:azimuth:cellName)
96
+ selectedCells.add(parts[2]);
97
+ }
98
+ });
99
+ // Filter data to only include selected cells
100
+ return data.filter((record) => selectedCells.has(record.cellName));
101
+ }
102
+ /**
103
+ * Transform data for chart component consumption
104
+ * Pivots data so each cell becomes its own KPI column
105
+ * Transforms from long format (many rows per cell) to wide format (one column per cell)
106
+ *
107
+ * @param data - Filtered cell traffic records
108
+ * @param baseMetrics - Array of metric names to pivot (e.g., ['dlGBytes', 'ulGBytes'])
109
+ */
110
+ export function transformChartData(data, baseMetrics) {
111
+ // Group data by date
112
+ const dateMap = new Map();
113
+ data.forEach((record) => {
114
+ if (!dateMap.has(record.date)) {
115
+ dateMap.set(record.date, new Map());
116
+ }
117
+ dateMap.get(record.date).set(record.cellName, record);
118
+ });
119
+ // Build pivoted data: one row per date, one column per cell per metric
120
+ const pivotedData = [];
121
+ dateMap.forEach((cellsOnDate, date) => {
122
+ const row = {
123
+ TIMESTAMP: date
124
+ };
125
+ cellsOnDate.forEach((record, cellName) => {
126
+ // Pivot each base metric into cell-specific columns
127
+ baseMetrics.forEach((metricName) => {
128
+ const value = record.metrics[metricName];
129
+ if (value !== undefined) {
130
+ row[`${metricName}_${cellName}`] = value;
131
+ }
132
+ });
133
+ // Store metadata for reference (band, azimuth, etc.)
134
+ row[`BAND_${cellName}`] = record.band;
135
+ row[`AZIMUTH_${cellName}`] = record.azimuth;
136
+ });
137
+ pivotedData.push(row);
138
+ });
139
+ // Sort by date
140
+ pivotedData.sort((a, b) => a.TIMESTAMP.localeCompare(b.TIMESTAMP));
141
+ return pivotedData;
142
+ }
@@ -155,11 +155,13 @@
155
155
 
156
156
  const traces: any[] = [];
157
157
  let colorIndex = 0;
158
+ const chartType = chart.type || 'line'; // Default to 'line' if not specified
159
+ const stackGroup = chart.stackGroup;
158
160
 
159
161
  // Add left Y-axis traces (with moving average support)
160
162
  resolvedKPIs.left.forEach(kpi => {
161
163
  const values = getKPIValues(processedData, kpi);
162
- const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y1', colorIndex);
164
+ const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y1', colorIndex, chartType, stackGroup);
163
165
  traces.push(...kpiTraces);
164
166
  colorIndex++;
165
167
  });
@@ -167,7 +169,7 @@
167
169
  // Add right Y-axis traces (with moving average support)
168
170
  resolvedKPIs.right.forEach(kpi => {
169
171
  const values = getKPIValues(processedData, kpi);
170
- const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y2', colorIndex);
172
+ const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y2', colorIndex, chartType, stackGroup);
171
173
  traces.push(...kpiTraces);
172
174
  colorIndex++;
173
175
  });
@@ -15,12 +15,15 @@ export interface KPI {
15
15
  }
16
16
  export type ChartPosition = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
17
17
  export type ChartGrid = "2x2" | "3x3" | "4x4" | "1x2" | "1x4" | "1x8";
18
+ export type ChartType = "line" | "stacked-area" | "stacked-percentage" | "bar" | "scatter";
18
19
  export interface Chart {
19
20
  pos?: ChartPosition;
20
21
  title: string;
22
+ type?: ChartType;
21
23
  yLeft: KPI[];
22
24
  yRight: KPI[];
23
25
  movingAverage?: MovingAverageConfig;
26
+ stackGroup?: string;
24
27
  }
25
28
  export interface Section {
26
29
  id: string;
@@ -1,7 +1,7 @@
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(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number): any;
4
+ export declare function createTimeSeriesTrace(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number, chartType?: string, stackGroup?: string): any;
5
5
  /**
6
6
  * Create time series trace(s) with optional moving average
7
7
  * @param values - Pre-processed numeric values array for the KPI
@@ -9,9 +9,11 @@ export declare function createTimeSeriesTrace(values: number[], timestamps: any[
9
9
  * @param kpi - KPI configuration (may include movingAverage config)
10
10
  * @param yaxis - Which Y-axis to use ('y1' or 'y2')
11
11
  * @param colorIndex - Index for color selection
12
+ * @param chartType - Type of chart (line, stacked-area, etc.)
13
+ * @param stackGroup - Optional stack group identifier
12
14
  * @returns Array of traces (original + MA if configured)
13
15
  */
14
- export declare function createTimeSeriesTraceWithMA(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number): any[];
16
+ export declare function createTimeSeriesTraceWithMA(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number, chartType?: string, stackGroup?: string): any[];
15
17
  export declare function getYAxisTitle(kpis: KPI[]): string;
16
18
  export declare function formatValue(value: number, scale: 'percent' | 'absolute', unit: string): string;
17
19
  export declare function createDefaultPlotlyLayout(title?: string): any;