@smartnet360/svelte-components 0.0.84 → 0.0.85

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 { TreeView, createTreeStore } from '../../core/TreeView';
5
5
  import { ChartComponent, type Layout, type CellStylingConfig } from '../../core/Charts';
6
- import { buildTreeNodes, filterChartData, transformChartData, type CellTrafficRecord, defaultCellStyling, type TreeGroupingConfig, type TreeGroupField, defaultTreeGrouping } from './index';
6
+ import { buildTreeNodes, filterChartData, transformChartData, type CellTrafficRecord, defaultCellStyling, type TreeGroupingConfig, type TreeGroupField, type ColorDimension, defaultTreeGrouping } from './index';
7
7
  import { expandLayoutForCells } from './helper';
8
8
  import { log } from '../../core/logger';
9
9
  import type {ChartMarker, Mode } from '../../index.js';
@@ -58,55 +58,38 @@
58
58
  // Internal state for current grouping
59
59
  let treeGrouping = $state<TreeGroupingConfig>(initialGrouping);
60
60
 
61
+ // Color dimension state (defaults to 'band' for semantic RF characteristics)
62
+ let colorDimension = $state<ColorDimension>('band');
63
+
61
64
  // Available field options for grouping levels
62
65
  const fieldOptions: { value: TreeGroupField; label: string }[] = [
63
66
  { value: 'site', label: 'Site' },
64
67
  { value: 'band', label: 'Band' },
65
68
  { value: 'azimuth', label: 'Azimuth' },
66
- { value: 'cell', label: 'Cell' }
69
+ { value: 'sector', label: 'Sector' }
67
70
  ];
68
71
 
69
72
  // Handlers for level changes
70
73
  function handleLevel0Change(value: TreeGroupField) {
71
74
  // Clear level1 if it conflicts with new level0
72
75
  const newLevel1 = treeGrouping.level1 === value ? null : treeGrouping.level1;
73
- // Clear level2 if it conflicts with new level0
74
- let newLevel2 = treeGrouping.level2;
75
- if (newLevel2 !== null && newLevel2 !== 'cell' && newLevel2 === value) {
76
- newLevel2 = null;
77
- }
78
76
  treeGrouping = {
79
77
  level0: value,
80
- level1: newLevel1,
81
- level2: newLevel2
78
+ level1: newLevel1
82
79
  };
83
80
  }
84
81
 
85
82
  function handleLevel1Change(value: TreeGroupField | 'none') {
86
83
  const newLevel1 = value === 'none' ? null : value;
87
- // Clear level2 if it conflicts with new level1
88
- let newLevel2 = treeGrouping.level2;
89
- if (newLevel2 !== null && newLevel2 !== 'cell' && newLevel2 === newLevel1) {
90
- newLevel2 = null;
91
- }
92
84
  treeGrouping = {
93
85
  level0: treeGrouping.level0,
94
- level1: newLevel1,
95
- level2: newLevel2
86
+ level1: newLevel1
96
87
  };
97
88
  }
98
89
 
99
90
  // Get available options for level1 (exclude level0)
100
91
  let availableLevel1Options = $derived.by(() => {
101
92
  return fieldOptions.filter(opt => opt.value !== treeGrouping.level0);
102
- });
103
-
104
- // Get available options for level2 (exclude level0 and level1)
105
- let availableLevel2Options = $derived.by(() => {
106
- return fieldOptions.filter(opt =>
107
- opt.value !== treeGrouping.level0 &&
108
- opt.value !== treeGrouping.level1
109
- );
110
93
  }); let treeStore = $state<ReturnType<typeof createTreeStore> | null>(null);
111
94
 
112
95
  // Rebuild tree whenever treeGrouping changes
@@ -174,16 +157,17 @@
174
157
 
175
158
  // Expand layout based on selected cells and chosen base layout
176
159
  let chartLayout = $derived.by(() => {
177
- // Pass cellStyling and treeGrouping - helper will decide per-section whether to use styling,
178
- // and generate appropriate labels based on grouping
179
- const expanded = expandLayoutForCells(selectedBaseLayout, filteredData, treeGrouping, cellStyling);
160
+ // Pass cellStyling, treeGrouping, and colorDimension - helper will decide per-section whether to use styling,
161
+ // and generate appropriate labels based on grouping and colors based on colorDimension
162
+ const expanded = expandLayoutForCells(selectedBaseLayout, filteredData, treeGrouping, colorDimension, cellStyling);
180
163
  log('📐 Chart Layout:', {
181
164
  layoutName: selectedBaseLayout.layoutName,
182
165
  layoutDefaultColors: selectedBaseLayout.useDefaultChartColors ?? false,
183
166
  sectionsCount: expanded.sections.length,
184
167
  totalCharts: expanded.sections.reduce((sum, s) => sum + s.charts.length, 0),
185
168
  firstSection: expanded.sections[0],
186
- grouping: treeGrouping
169
+ grouping: treeGrouping,
170
+ colorDimension
187
171
  });
188
172
  return expanded;
189
173
  });
@@ -325,60 +309,56 @@
325
309
  <div class="p-3 border-bottom flex-shrink-0">
326
310
  <div class="small fw-semibold mb-2">Tree Grouping</div>
327
311
 
328
- <!-- Level 0 (Mandatory) -->
329
- <div class="mb-2">
330
- <label for="level0Select" class="form-label small mb-1">Level 0 (Root)</label>
331
- <select
332
- id="level0Select"
333
- class="form-select form-select-sm"
334
- value={treeGrouping.level0}
335
- onchange={(e) => handleLevel0Change(e.currentTarget.value as TreeGroupField)}
336
- >
337
- {#each fieldOptions as option}
338
- <option value={option.value}>{option.label}</option>
339
- {/each}
340
- </select>
341
- </div>
342
-
343
- <!-- Level 1 (Optional) -->
344
- <div class="mb-2">
345
- <label for="level1Select" class="form-label small mb-1">Level 1 (Optional)</label>
346
- <select
347
- id="level1Select"
348
- class="form-select form-select-sm"
349
- value={treeGrouping.level1 ?? 'none'}
350
- onchange={(e) => handleLevel1Change(e.currentTarget.value as TreeGroupField | 'none')}
351
- >
352
- <option value="none">None</option>
353
- {#each availableLevel1Options as option}
354
- <option value={option.value}>{option.label}</option>
355
- {/each}
356
- </select>
312
+ <div class="row g-2 mb-2">
313
+ <!-- Level 0 (Mandatory) -->
314
+ <div class="col-4">
315
+ <label for="level0Select" class="form-label small mb-1">Level 0</label>
316
+ <select
317
+ id="level0Select"
318
+ class="form-select form-select-sm"
319
+ value={treeGrouping.level0}
320
+ onchange={(e) => handleLevel0Change(e.currentTarget.value as TreeGroupField)}
321
+ >
322
+ {#each fieldOptions as option}
323
+ <option value={option.value}>{option.label}</option>
324
+ {/each}
325
+ </select>
326
+ </div>
327
+
328
+ <!-- Level 1 (Optional) -->
329
+ <div class="col-4">
330
+ <label for="level1Select" class="form-label small mb-1">Level 1</label>
331
+ <select
332
+ id="level1Select"
333
+ class="form-select form-select-sm"
334
+ value={treeGrouping.level1 ?? 'none'}
335
+ onchange={(e) => handleLevel1Change(e.currentTarget.value as TreeGroupField | 'none')}
336
+ >
337
+ <option value="none">None</option>
338
+ {#each availableLevel1Options as option}
339
+ <option value={option.value}>{option.label}</option>
340
+ {/each}
341
+ </select>
342
+ </div>
343
+
344
+ <!-- Color By -->
345
+ <div class="col-4">
346
+ <label for="colorDimensionSelect" class="form-label small mb-1">Color By</label>
347
+ <select
348
+ id="colorDimensionSelect"
349
+ class="form-select form-select-sm"
350
+ value={colorDimension}
351
+ onchange={(e) => {
352
+ colorDimension = e.currentTarget.value as ColorDimension;
353
+ log('🎨 Color dimension changed:', colorDimension);
354
+ }}
355
+ >
356
+ <option value="band">Band</option>
357
+ <option value="site">Site</option>
358
+ </select>
359
+ </div>
357
360
  </div>
358
361
 
359
- <!-- Level 2 (Optional) -->
360
- <div class="mb-0">
361
- <label for="level2Select" class="form-label small mb-1">Level 2 (Optional)</label>
362
- <select
363
- id="level2Select"
364
- class="form-select form-select-sm"
365
- value={treeGrouping.level2 ?? 'none'}
366
- onchange={(e) => {
367
- const value = e.currentTarget.value;
368
- treeGrouping = {
369
- level0: treeGrouping.level0,
370
- level1: treeGrouping.level1,
371
- level2: value === 'none' ? null : (value as 'cell')
372
- };
373
- }}
374
- >
375
- <option value="none">None</option>
376
- <option value="cell">Cell</option>
377
- {#each availableLevel2Options as option}
378
- <option value={option.value}>{option.label}</option>
379
- {/each}
380
- </select>
381
- </div>
382
362
  </div>
383
363
  {/if}
384
364
  {/if} <!-- Tree View -->
@@ -14,21 +14,24 @@ export interface CellTrafficRecord {
14
14
  /**
15
15
  * Tree grouping field types
16
16
  */
17
- export type TreeGroupField = 'site' | 'azimuth' | 'band' | 'cell';
17
+ export type TreeGroupField = 'site' | 'azimuth' | 'band' | 'sector';
18
+ /**
19
+ * Color dimension types - determines which field is used for chart coloring
20
+ */
21
+ export type ColorDimension = 'site' | 'band';
18
22
  /**
19
23
  * Configuration for tree hierarchy grouping
20
24
  * Defines which fields appear at each level of the tree
21
- * - For 3-level tree: level0 level1 level2
22
- * - For 2-level tree: level0 → level1 (set level2 to null)
23
- * - For 1-level tree: level0 only (set level1 and level2 to null)
25
+ * Cells are always the leaf nodes (implicit, not configured)
26
+ * - For 2-level tree: level0 → cell (set level1 to null)
27
+ * - For 3-level tree: level0 level1 cell
24
28
  */
25
29
  export interface TreeGroupingConfig {
26
30
  level0: TreeGroupField;
27
31
  level1: TreeGroupField | null;
28
- level2: TreeGroupField | 'cell' | null;
29
32
  }
30
33
  /**
31
- * Default tree grouping: Site → Azimuth → Cell (3-level)
34
+ * Default tree grouping: Site → Band → Cell (3-level)
32
35
  */
33
36
  export declare const defaultTreeGrouping: TreeGroupingConfig;
34
37
  /**
@@ -3,12 +3,11 @@
3
3
  * Loads and parses cell_traffic_with_band.csv
4
4
  */
5
5
  /**
6
- * Default tree grouping: Site → Azimuth → Cell (3-level)
6
+ * Default tree grouping: Site → Band → Cell (3-level)
7
7
  */
8
8
  export const defaultTreeGrouping = {
9
9
  level0: 'site',
10
- level1: 'azimuth',
11
- level2: 'cell'
10
+ level1: 'band'
12
11
  };
13
12
  /**
14
13
  * Load cell traffic data from CSV file
@@ -1,5 +1,5 @@
1
1
  import type { Layout, CellStylingConfig } from '../../core/Charts';
2
- import type { CellTrafficRecord, TreeGroupingConfig } from './';
2
+ import type { CellTrafficRecord, TreeGroupingConfig, ColorDimension } from './';
3
3
  import { type StackGroupMode } from './transforms.js';
4
4
  /**
5
5
  * Expand base layout configuration with dynamic KPIs based on selected cells
@@ -8,11 +8,12 @@ import { type StackGroupMode } from './transforms.js';
8
8
  * @param baseLayout - The base layout configuration from JSON
9
9
  * @param data - Filtered cell traffic records for selected cells
10
10
  * @param grouping - Current tree grouping configuration (determines label format)
11
+ * @param colorDimension - Which field to use for coloring (site or band)
11
12
  * @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
12
13
  * @param stackGroupMode - Optional stackgroup strategy for stacked charts (default: 'none' = single stack)
13
14
  * @returns Expanded layout with cell-specific KPIs
14
15
  */
15
- export declare function expandLayoutForCells(baseLayout: Layout, data: CellTrafficRecord[], grouping: TreeGroupingConfig, stylingConfig?: CellStylingConfig, stackGroupMode?: StackGroupMode): Layout;
16
+ export declare function expandLayoutForCells(baseLayout: Layout, data: CellTrafficRecord[], grouping: TreeGroupingConfig, colorDimension: ColorDimension, stylingConfig?: CellStylingConfig, stackGroupMode?: StackGroupMode): Layout;
16
17
  /**
17
18
  * Extract base metric names from a layout configuration
18
19
  * Returns unique metric rawNames that need to be pivoted
@@ -6,11 +6,12 @@ import { createStyledKPI, sortCellsByBandFrequency, assignStackGroups } from './
6
6
  * @param baseLayout - The base layout configuration from JSON
7
7
  * @param data - Filtered cell traffic records for selected cells
8
8
  * @param grouping - Current tree grouping configuration (determines label format)
9
+ * @param colorDimension - Which field to use for coloring (site or band)
9
10
  * @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
10
11
  * @param stackGroupMode - Optional stackgroup strategy for stacked charts (default: 'none' = single stack)
11
12
  * @returns Expanded layout with cell-specific KPIs
12
13
  */
13
- export function expandLayoutForCells(baseLayout, data, grouping, stylingConfig, stackGroupMode = 'none') {
14
+ export function expandLayoutForCells(baseLayout, data, grouping, colorDimension, stylingConfig, stackGroupMode = 'none') {
14
15
  // Get unique cells and their metadata, sorted by band frequency
15
16
  const cellMap = new Map();
16
17
  data.forEach((record) => {
@@ -38,8 +39,8 @@ export function expandLayoutForCells(baseLayout, data, grouping, stylingConfig,
38
39
  ...section,
39
40
  charts: section.charts.map((chart) => ({
40
41
  ...chart,
41
- yLeft: expandKPIs(chart.yLeft, cells, grouping, effectiveStyling, stackGroupMode),
42
- yRight: expandKPIs(chart.yRight, cells, grouping, effectiveStyling, stackGroupMode)
42
+ yLeft: expandKPIs(chart.yLeft, cells, grouping, colorDimension, effectiveStyling, stackGroupMode),
43
+ yRight: expandKPIs(chart.yRight, cells, grouping, colorDimension, effectiveStyling, stackGroupMode)
43
44
  }))
44
45
  };
45
46
  })
@@ -54,17 +55,18 @@ export function expandLayoutForCells(baseLayout, data, grouping, stylingConfig,
54
55
  * @param baseKPIs - Array of base KPIs from layout
55
56
  * @param cells - Array of [cellName, record] tuples
56
57
  * @param grouping - Current tree grouping configuration (determines label format)
58
+ * @param colorDimension - Which field to use for coloring (site or band)
57
59
  * @param stylingConfig - Optional cell styling configuration
58
60
  * @param stackGroupMode - Stackgroup strategy for this set of KPIs
59
61
  * @returns Expanded array of KPIs (styled or default, with stackgroups assigned)
60
62
  */
61
- function expandKPIs(baseKPIs, cells, grouping, stylingConfig, stackGroupMode = 'none') {
63
+ function expandKPIs(baseKPIs, cells, grouping, colorDimension, stylingConfig, stackGroupMode = 'none') {
62
64
  let expandedKPIs = [];
63
65
  baseKPIs.forEach((baseKPI) => {
64
66
  cells.forEach(([cellName, record]) => {
65
67
  if (stylingConfig) {
66
68
  // Apply custom styling (band colors, sector line styles)
67
- const styledKPI = createStyledKPI(baseKPI.rawName, record, baseKPI.unit, grouping, stylingConfig);
69
+ const styledKPI = createStyledKPI(baseKPI.rawName, record, baseKPI.unit, grouping, colorDimension, stylingConfig);
68
70
  expandedKPIs.push({
69
71
  ...styledKPI,
70
72
  stackGroup: undefined // Initialize for treeshake-safe property assignment
@@ -3,7 +3,7 @@
3
3
  * Public API exports for cell traffic KPI visualization
4
4
  */
5
5
  export { default as SiteCheck } from './SiteCheck.svelte';
6
- export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell, defaultTreeGrouping, type CellTrafficRecord, type TreeGroupingConfig, type TreeGroupField } from './data-loader.js';
6
+ export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell, defaultTreeGrouping, type CellTrafficRecord, type TreeGroupingConfig, type TreeGroupField, type ColorDimension } from './data-loader.js';
7
7
  export { buildTreeNodes, filterChartData, transformChartData, createStyledKPI, extractBandFromCell, getBandFrequency, sortCellsByBandFrequency, assignStackGroups, createStackGroupId, type StackGroupMode } from './transforms.js';
8
8
  export { expandLayoutForCells, extractBaseMetrics } from './helper.js';
9
9
  export { defaultCellStyling } from './default-cell-styling.js';
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { TreeNode } from '../../core/TreeView';
6
6
  import type { KPI, CellStylingConfig } from '../../core/Charts';
7
- import type { CellTrafficRecord, TreeGroupingConfig } from './data-loader';
7
+ import type { CellTrafficRecord, TreeGroupingConfig, ColorDimension } from './data-loader';
8
8
  /**
9
9
  * Stackgroup mode types for dynamic stacking strategies
10
10
  */
@@ -75,11 +75,13 @@ export declare function transformChartData(data: CellTrafficRecord[], baseMetric
75
75
  /**
76
76
  * Create a styled KPI with band colors and sector line styles
77
77
  * Label format adapts to tree grouping configuration
78
+ * Color assignment adapts to colorDimension selection
78
79
  * @param metricName - Base metric name (e.g., 'DL_GBYTES')
79
80
  * @param cellRecord - Cell traffic record with metadata
80
81
  * @param unit - Unit string (e.g., 'GB', '%')
81
82
  * @param grouping - Current tree grouping configuration (determines label format)
83
+ * @param colorDimension - Which field to use for coloring (site or band)
82
84
  * @param stylingConfig - Optional styling configuration
83
85
  * @returns KPI with cell-specific styling applied
84
86
  */
85
- export declare function createStyledKPI(metricName: string, cellRecord: CellTrafficRecord, unit: string, grouping: TreeGroupingConfig, stylingConfig?: CellStylingConfig): KPI;
87
+ export declare function createStyledKPI(metricName: string, cellRecord: CellTrafficRecord, unit: string, grouping: TreeGroupingConfig, colorDimension: ColorDimension, stylingConfig?: CellStylingConfig): KPI;
@@ -154,7 +154,7 @@ export function sortCellsByBandFrequency(items) {
154
154
  * @param data - Cell traffic records
155
155
  * @param grouping - Tree grouping configuration (defaults to Site → Azimuth → Cell)
156
156
  */
157
- export function buildTreeNodes(data, grouping = { level0: 'site', level1: 'azimuth', level2: 'cell' }) {
157
+ export function buildTreeNodes(data, grouping = { level0: 'site', level1: 'azimuth' }) {
158
158
  log('🔄 Building tree nodes', {
159
159
  recordCount: data.length,
160
160
  grouping,
@@ -441,20 +441,60 @@ export function transformChartData(data, baseMetrics) {
441
441
  });
442
442
  return pivotedData;
443
443
  }
444
+ /**
445
+ * Generate a consistent color for a site name using a hash function
446
+ * Same site will always get the same color
447
+ * @param siteName - Site name to generate color for
448
+ * @returns Hex color string
449
+ */
450
+ function generateSiteColor(siteName) {
451
+ // Modern color palette for sites
452
+ const siteColors = [
453
+ '#3B82F6', // Blue
454
+ '#EF4444', // Red
455
+ '#10B981', // Emerald
456
+ '#F59E0B', // Amber
457
+ '#8B5CF6', // Violet
458
+ '#06B6D4', // Cyan
459
+ '#F97316', // Orange
460
+ '#84CC16', // Lime
461
+ '#EC4899', // Pink
462
+ '#14B8A6', // Teal
463
+ '#F43F5E', // Rose
464
+ '#A855F7' // Purple
465
+ ];
466
+ // Simple hash function for consistent color assignment
467
+ let hash = 0;
468
+ for (let i = 0; i < siteName.length; i++) {
469
+ hash = siteName.charCodeAt(i) + ((hash << 5) - hash);
470
+ }
471
+ hash = Math.abs(hash);
472
+ return siteColors[hash % siteColors.length];
473
+ }
444
474
  /**
445
475
  * Create a styled KPI with band colors and sector line styles
446
476
  * Label format adapts to tree grouping configuration
477
+ * Color assignment adapts to colorDimension selection
447
478
  * @param metricName - Base metric name (e.g., 'DL_GBYTES')
448
479
  * @param cellRecord - Cell traffic record with metadata
449
480
  * @param unit - Unit string (e.g., 'GB', '%')
450
481
  * @param grouping - Current tree grouping configuration (determines label format)
482
+ * @param colorDimension - Which field to use for coloring (site or band)
451
483
  * @param stylingConfig - Optional styling configuration
452
484
  * @returns KPI with cell-specific styling applied
453
485
  */
454
- export function createStyledKPI(metricName, cellRecord, unit, grouping, stylingConfig) {
486
+ export function createStyledKPI(metricName, cellRecord, unit, grouping, colorDimension, stylingConfig) {
455
487
  const { band, sector, azimuth, cellName, siteName } = cellRecord;
456
- // Get color from band (if config provided)
457
- const color = stylingConfig?.bandColors?.[band];
488
+ // Determine color based on colorDimension
489
+ let color;
490
+ if (colorDimension === 'band') {
491
+ // Get color from band colors config
492
+ color = stylingConfig?.bandColors?.[band];
493
+ }
494
+ else if (colorDimension === 'site') {
495
+ // Generate consistent color for site
496
+ color = generateSiteColor(siteName);
497
+ }
458
498
  // Get line style from sector (if config provided)
459
499
  const lineStyle = stylingConfig?.sectorLineStyles?.[sector.toString()];
460
500
  // Generate label based on tree grouping configuration
@@ -475,6 +515,7 @@ export function createStyledKPI(metricName, cellRecord, unit, grouping, stylingC
475
515
  band,
476
516
  sector,
477
517
  azimuth,
518
+ colorDimension,
478
519
  color,
479
520
  lineStyle
480
521
  });
@@ -489,7 +530,7 @@ export function createStyledKPI(metricName, cellRecord, unit, grouping, stylingC
489
530
  * @returns Formatted label string matching complete tree hierarchy (only non-null levels)
490
531
  */
491
532
  function generateAdaptiveLabel(record, grouping) {
492
- const { level0, level1, level2 } = grouping;
533
+ const { level0, level1 } = grouping;
493
534
  // Helper to format field values exactly as shown in tree
494
535
  const formatField = (field, value) => {
495
536
  if (field === 'cell') {
@@ -532,10 +573,8 @@ function generateAdaptiveLabel(record, grouping) {
532
573
  if (level1 !== null) {
533
574
  parts.push(formatField(level1, getFieldValue(level1)));
534
575
  }
535
- // Level 2 (optional)
536
- if (level2 !== null) {
537
- parts.push(formatField(level2, getFieldValue(level2)));
538
- }
576
+ // Cell is always the final leaf node
577
+ parts.push(record.cellName);
539
578
  // Join with arrow separator
540
- return parts.join('_');
579
+ return parts.join('');
541
580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.84",
3
+ "version": "0.0.85",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",