@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.
- package/dist/apps/site-check/SiteCheck.svelte +60 -80
- package/dist/apps/site-check/data-loader.d.ts +9 -6
- package/dist/apps/site-check/data-loader.js +2 -3
- package/dist/apps/site-check/helper.d.ts +3 -2
- package/dist/apps/site-check/helper.js +7 -5
- package/dist/apps/site-check/index.d.ts +1 -1
- package/dist/apps/site-check/transforms.d.ts +4 -2
- package/dist/apps/site-check/transforms.js +49 -10
- package/package.json +1 -1
|
@@ -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: '
|
|
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
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
<
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
<
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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' | '
|
|
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
|
-
*
|
|
22
|
-
* - For 2-level tree: level0 →
|
|
23
|
-
* - For
|
|
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 →
|
|
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 →
|
|
6
|
+
* Default tree grouping: Site → Band → Cell (3-level)
|
|
7
7
|
*/
|
|
8
8
|
export const defaultTreeGrouping = {
|
|
9
9
|
level0: 'site',
|
|
10
|
-
level1: '
|
|
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'
|
|
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
|
-
//
|
|
457
|
-
|
|
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
|
|
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
|
-
//
|
|
536
|
-
|
|
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
|
}
|