@smartnet360/svelte-components 0.0.37 → 0.0.39
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 +7 -6
- package/dist/apps/site-check/SiteCheck.svelte.d.ts +2 -1
- package/dist/apps/site-check/default-cell-styling.d.ts +6 -0
- package/dist/apps/site-check/default-cell-styling.js +24 -0
- package/dist/apps/site-check/default-cell-styling.json +20 -0
- package/dist/apps/site-check/helper.d.ts +3 -2
- package/dist/apps/site-check/helper.js +16 -15
- package/dist/apps/site-check/index.d.ts +2 -1
- package/dist/apps/site-check/index.js +4 -2
- package/dist/apps/site-check/transforms.d.ts +32 -0
- package/dist/apps/site-check/transforms.js +101 -3
- package/dist/core/Charts/ChartCard.svelte +7 -5
- package/dist/core/Charts/ChartCard.svelte.d.ts +3 -1
- package/dist/core/Charts/ChartComponent.svelte +3 -1
- package/dist/core/Charts/adapt.js +19 -27
- package/dist/core/Charts/charts.model.d.ts +9 -0
- package/dist/core/Charts/data-utils.d.ts +4 -4
- package/dist/core/Charts/data-utils.js +69 -10
- package/dist/core/Charts/index.d.ts +1 -1
- package/dist/core/Settings/FieldRenderer.svelte +234 -0
- package/dist/core/Settings/FieldRenderer.svelte.d.ts +30 -0
- package/dist/core/Settings/Settings.svelte +199 -0
- package/dist/core/Settings/Settings.svelte.d.ts +24 -0
- package/dist/core/Settings/index.d.ts +9 -0
- package/dist/core/Settings/index.js +8 -0
- package/dist/core/Settings/store.d.ts +56 -0
- package/dist/core/Settings/store.js +184 -0
- package/dist/core/Settings/types.d.ts +162 -0
- package/dist/core/Settings/types.js +7 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +2 -0
- package/package.json +1 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import { TreeView, createTreeStore } from '../../core/TreeView';
|
|
5
|
-
import { ChartComponent, type Layout } from '../../core/Charts';
|
|
6
|
-
import { buildTreeNodes, filterChartData, transformChartData, type CellTrafficRecord } from './index';
|
|
5
|
+
import { ChartComponent, type Layout, type CellStylingConfig } from '../../core/Charts';
|
|
6
|
+
import { buildTreeNodes, filterChartData, transformChartData, type CellTrafficRecord, defaultCellStyling } from './index';
|
|
7
7
|
import { expandLayoutForCells } from './helper';
|
|
8
8
|
import { log } from '../../core/logger';
|
|
9
9
|
import { onMount } from 'svelte';
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
baseLayout: Layout;
|
|
15
15
|
baseMetrics: string[];
|
|
16
16
|
mode: Mode;
|
|
17
|
+
cellStyling?: CellStylingConfig; // Optional cell styling config (defaults to defaultCellStyling)
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
let { rawData, baseLayout, baseMetrics, mode = "scrollspy" }: Props = $props();
|
|
20
|
+
let { rawData, baseLayout, baseMetrics, mode = "scrollspy", cellStyling = defaultCellStyling }: Props = $props();
|
|
20
21
|
|
|
21
22
|
let treeStore = $state<ReturnType<typeof createTreeStore> | null>(null);
|
|
22
23
|
|
|
@@ -74,12 +75,12 @@
|
|
|
74
75
|
|
|
75
76
|
// Expand layout based on selected cells
|
|
76
77
|
let chartLayout = $derived.by(() => {
|
|
77
|
-
const expanded = expandLayoutForCells(baseLayout, filteredData);
|
|
78
|
+
const expanded = expandLayoutForCells(baseLayout, filteredData, cellStyling);
|
|
78
79
|
log('📐 Chart Layout:', {
|
|
79
|
-
grid: expanded.grid,
|
|
80
80
|
sectionsCount: expanded.sections.length,
|
|
81
81
|
totalCharts: expanded.sections.reduce((sum, s) => sum + s.charts.length, 0),
|
|
82
|
-
firstSection: expanded.sections[0]
|
|
82
|
+
firstSection: expanded.sections[0],
|
|
83
|
+
cellStylingEnabled: !!cellStyling
|
|
83
84
|
});
|
|
84
85
|
return expanded;
|
|
85
86
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Layout } from '../../core/Charts';
|
|
1
|
+
import { type Layout, type CellStylingConfig } from '../../core/Charts';
|
|
2
2
|
import { type CellTrafficRecord } from './index';
|
|
3
3
|
import type { Mode } from '../../index.js';
|
|
4
4
|
interface Props {
|
|
@@ -6,6 +6,7 @@ interface Props {
|
|
|
6
6
|
baseLayout: Layout;
|
|
7
7
|
baseMetrics: string[];
|
|
8
8
|
mode: Mode;
|
|
9
|
+
cellStyling?: CellStylingConfig;
|
|
9
10
|
}
|
|
10
11
|
declare const SiteCheck: import("svelte").Component<Props, {}, "">;
|
|
11
12
|
type SiteCheck = ReturnType<typeof SiteCheck>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default cell styling configuration for SiteCheck component
|
|
3
|
+
* Provides band colors and sector line styles for network cells
|
|
4
|
+
*/
|
|
5
|
+
export const defaultCellStyling = {
|
|
6
|
+
bandColors: {
|
|
7
|
+
"LTE700": "#DC2626",
|
|
8
|
+
"LTE800": "#EA580C",
|
|
9
|
+
"LTE900": "#D97706",
|
|
10
|
+
"LTE1800": "#2563EB",
|
|
11
|
+
"LTE2100": "#7C3AED",
|
|
12
|
+
"LTE2600": "#DB2777",
|
|
13
|
+
"NR700": "#B91C1C",
|
|
14
|
+
"NR2100": "#059669",
|
|
15
|
+
"NR3500": "#0891B2",
|
|
16
|
+
"NR26000": "#BE185D"
|
|
17
|
+
},
|
|
18
|
+
sectorLineStyles: {
|
|
19
|
+
"1": "solid",
|
|
20
|
+
"2": "dash",
|
|
21
|
+
"3": "dot",
|
|
22
|
+
"4": "dashdot"
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bandColors": {
|
|
3
|
+
"LTE700": "#DC2626",
|
|
4
|
+
"LTE800": "#EA580C",
|
|
5
|
+
"LTE900": "#D97706",
|
|
6
|
+
"LTE1800": "#2563EB",
|
|
7
|
+
"LTE2100": "#7C3AED",
|
|
8
|
+
"LTE2600": "#DB2777",
|
|
9
|
+
"NR700": "#B91C1C",
|
|
10
|
+
"NR2100": "#059669",
|
|
11
|
+
"NR3500": "#0891B2",
|
|
12
|
+
"NR26000": "#BE185D"
|
|
13
|
+
},
|
|
14
|
+
"sectorLineStyles": {
|
|
15
|
+
"1": "solid",
|
|
16
|
+
"2": "dash",
|
|
17
|
+
"3": "dot",
|
|
18
|
+
"4": "dashdot"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Layout } from '../../core/Charts';
|
|
1
|
+
import type { Layout, CellStylingConfig } from '../../core/Charts';
|
|
2
2
|
import type { CellTrafficRecord } from './';
|
|
3
3
|
/**
|
|
4
4
|
* Expand base layout configuration with dynamic KPIs based on selected cells
|
|
@@ -6,9 +6,10 @@ import type { CellTrafficRecord } from './';
|
|
|
6
6
|
*
|
|
7
7
|
* @param baseLayout - The base layout configuration from JSON
|
|
8
8
|
* @param data - Filtered cell traffic records for selected cells
|
|
9
|
+
* @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
|
|
9
10
|
* @returns Expanded layout with cell-specific KPIs
|
|
10
11
|
*/
|
|
11
|
-
export declare function expandLayoutForCells(baseLayout: Layout, data: CellTrafficRecord[]): Layout;
|
|
12
|
+
export declare function expandLayoutForCells(baseLayout: Layout, data: CellTrafficRecord[], stylingConfig?: CellStylingConfig): Layout;
|
|
12
13
|
/**
|
|
13
14
|
* Extract base metric names from a layout configuration
|
|
14
15
|
* Returns unique metric rawNames that need to be pivoted
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
+
import { createStyledKPI, sortCellsByBandFrequency } from './transforms.js';
|
|
1
2
|
/**
|
|
2
3
|
* Expand base layout configuration with dynamic KPIs based on selected cells
|
|
3
4
|
* Takes a base layout (with one KPI per metric) and expands it to include one KPI per cell
|
|
4
5
|
*
|
|
5
6
|
* @param baseLayout - The base layout configuration from JSON
|
|
6
7
|
* @param data - Filtered cell traffic records for selected cells
|
|
8
|
+
* @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
|
|
7
9
|
* @returns Expanded layout with cell-specific KPIs
|
|
8
10
|
*/
|
|
9
|
-
export function expandLayoutForCells(baseLayout, data) {
|
|
10
|
-
// Get unique cells and their metadata
|
|
11
|
+
export function expandLayoutForCells(baseLayout, data, stylingConfig) {
|
|
12
|
+
// Get unique cells and their metadata, sorted by band frequency
|
|
11
13
|
const cellMap = new Map();
|
|
12
14
|
data.forEach((record) => {
|
|
13
15
|
if (!cellMap.has(record.cellName)) {
|
|
14
16
|
cellMap.set(record.cellName, record);
|
|
15
17
|
}
|
|
16
18
|
});
|
|
17
|
-
|
|
19
|
+
// Sort cells by band frequency instead of alphabetically
|
|
20
|
+
const cells = sortCellsByBandFrequency(Array.from(cellMap.entries()));
|
|
18
21
|
// Deep clone the layout structure and expand KPIs
|
|
19
22
|
const expandedLayout = {
|
|
20
23
|
layoutName: baseLayout.layoutName,
|
|
@@ -22,8 +25,8 @@ export function expandLayoutForCells(baseLayout, data) {
|
|
|
22
25
|
...section,
|
|
23
26
|
charts: section.charts.map((chart) => ({
|
|
24
27
|
...chart,
|
|
25
|
-
yLeft: expandKPIs(chart.yLeft, cells),
|
|
26
|
-
yRight: expandKPIs(chart.yRight, cells)
|
|
28
|
+
yLeft: expandKPIs(chart.yLeft, cells, stylingConfig),
|
|
29
|
+
yRight: expandKPIs(chart.yRight, cells, stylingConfig)
|
|
27
30
|
}))
|
|
28
31
|
}))
|
|
29
32
|
};
|
|
@@ -31,22 +34,20 @@ export function expandLayoutForCells(baseLayout, data) {
|
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
36
|
* Expand a single KPI into multiple KPIs (one per cell)
|
|
37
|
+
* Now uses createStyledKPI to apply band colors and sector line styles
|
|
34
38
|
*
|
|
35
39
|
* @param baseKPIs - Array of base KPIs from layout
|
|
36
40
|
* @param cells - Array of [cellName, record] tuples
|
|
37
|
-
* @
|
|
41
|
+
* @param stylingConfig - Optional cell styling configuration
|
|
42
|
+
* @returns Expanded array of KPIs with cell-specific styling
|
|
38
43
|
*/
|
|
39
|
-
function expandKPIs(baseKPIs, cells) {
|
|
44
|
+
function expandKPIs(baseKPIs, cells, stylingConfig) {
|
|
40
45
|
const expandedKPIs = [];
|
|
41
46
|
baseKPIs.forEach((baseKPI) => {
|
|
42
|
-
cells.forEach(([cellName, record]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
scale: baseKPI.scale,
|
|
47
|
-
unit: baseKPI.unit,
|
|
48
|
-
color: getColorForIndex(index)
|
|
49
|
-
});
|
|
47
|
+
cells.forEach(([cellName, record]) => {
|
|
48
|
+
// Use createStyledKPI to apply band color and sector line style
|
|
49
|
+
const styledKPI = createStyledKPI(baseKPI.rawName, record, baseKPI.unit, stylingConfig);
|
|
50
|
+
expandedKPIs.push(styledKPI);
|
|
50
51
|
});
|
|
51
52
|
});
|
|
52
53
|
return expandedKPIs;
|
|
@@ -4,5 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { default as SiteCheck } from './SiteCheck.svelte';
|
|
6
6
|
export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell, type CellTrafficRecord } from './data-loader.js';
|
|
7
|
-
export { buildTreeNodes, filterChartData, transformChartData } from './transforms.js';
|
|
7
|
+
export { buildTreeNodes, filterChartData, transformChartData, createStyledKPI, extractBandFromCell, getBandFrequency, sortCellsByBandFrequency } from './transforms.js';
|
|
8
8
|
export { expandLayoutForCells, extractBaseMetrics } from './helper.js';
|
|
9
|
+
export { defaultCellStyling } from './default-cell-styling.js';
|
|
@@ -7,6 +7,8 @@ export { default as SiteCheck } from './SiteCheck.svelte';
|
|
|
7
7
|
// Data loading
|
|
8
8
|
export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell } from './data-loader.js';
|
|
9
9
|
// Data transforms
|
|
10
|
-
export { buildTreeNodes, filterChartData, transformChartData } from './transforms.js';
|
|
11
|
-
// Helper
|
|
10
|
+
export { buildTreeNodes, filterChartData, transformChartData, createStyledKPI, extractBandFromCell, getBandFrequency, sortCellsByBandFrequency } from './transforms.js';
|
|
11
|
+
// Helper functions
|
|
12
12
|
export { expandLayoutForCells, extractBaseMetrics } from './helper.js';
|
|
13
|
+
// Default cell styling configuration
|
|
14
|
+
export { defaultCellStyling } from './default-cell-styling.js';
|
|
@@ -3,7 +3,27 @@
|
|
|
3
3
|
* Converts raw CSV data to TreeView nodes and Chart configurations
|
|
4
4
|
*/
|
|
5
5
|
import type { TreeNode } from '../../core/TreeView';
|
|
6
|
+
import type { KPI, CellStylingConfig } from '../../core/Charts';
|
|
6
7
|
import type { CellTrafficRecord } from './data-loader';
|
|
8
|
+
/**
|
|
9
|
+
* Extract band from cell name using regex pattern matching
|
|
10
|
+
* @param cellName - Cell name like "LTE700_1", "NR3500_2", etc.
|
|
11
|
+
* @returns Band string like "LTE700", "NR3500" or null if not found
|
|
12
|
+
* @deprecated Use the band field from CellTrafficRecord instead
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractBandFromCell(cellName: string): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Get frequency order for a band (for sorting)
|
|
17
|
+
* @param band - Band string like "LTE700", "NR3500"
|
|
18
|
+
* @returns Frequency number or high value for unknown bands
|
|
19
|
+
*/
|
|
20
|
+
export declare function getBandFrequency(band: string | null): number;
|
|
21
|
+
/**
|
|
22
|
+
* Sort items by band frequency using actual band data from records
|
|
23
|
+
* @param items - Array of [cellName, record] tuples
|
|
24
|
+
* @returns Sorted array (ascending frequency order)
|
|
25
|
+
*/
|
|
26
|
+
export declare function sortCellsByBandFrequency(items: [string, CellTrafficRecord][]): [string, CellTrafficRecord][];
|
|
7
27
|
/**
|
|
8
28
|
* Build hierarchical tree structure: Site → Sector (Azimuth) → Cell (Band)
|
|
9
29
|
*/
|
|
@@ -22,3 +42,15 @@ export declare function filterChartData(data: CellTrafficRecord[], checkedPaths:
|
|
|
22
42
|
* @param baseMetrics - Array of metric names to pivot (e.g., ['dlGBytes', 'ulGBytes'])
|
|
23
43
|
*/
|
|
24
44
|
export declare function transformChartData(data: CellTrafficRecord[], baseMetrics: string[]): any[];
|
|
45
|
+
/**
|
|
46
|
+
* Apply cell styling based on band and sector
|
|
47
|
+
* Modifies KPI objects to include color (from band) and lineStyle (from sector)
|
|
48
|
+
* Updates KPI name to format: Band_Azimuth°
|
|
49
|
+
*
|
|
50
|
+
* @param metricName - Base metric name (e.g., 'dlGBytes')
|
|
51
|
+
* @param cellRecord - Cell traffic record with band, sector, azimuth metadata
|
|
52
|
+
* @param unit - Unit string for the metric
|
|
53
|
+
* @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
|
|
54
|
+
* @returns Styled KPI object
|
|
55
|
+
*/
|
|
56
|
+
export declare function createStyledKPI(metricName: string, cellRecord: CellTrafficRecord, unit: string, stylingConfig?: CellStylingConfig): KPI;
|
|
@@ -3,6 +3,64 @@
|
|
|
3
3
|
* Converts raw CSV data to TreeView nodes and Chart configurations
|
|
4
4
|
*/
|
|
5
5
|
import { log } from '../../core/logger';
|
|
6
|
+
/**
|
|
7
|
+
* Band frequency mapping for consistent ordering
|
|
8
|
+
* Maps band strings to their actual frequencies in MHz
|
|
9
|
+
*/
|
|
10
|
+
const BAND_FREQUENCY_ORDER = {
|
|
11
|
+
// LTE Bands (by frequency)
|
|
12
|
+
'LTE700': 700,
|
|
13
|
+
'LTE800': 800,
|
|
14
|
+
'LTE900': 900,
|
|
15
|
+
'LTE1800': 1800,
|
|
16
|
+
'LTE2100': 2100,
|
|
17
|
+
'LTE2600': 2600,
|
|
18
|
+
// NR/5G Bands (by frequency)
|
|
19
|
+
'NR700': 700.1, // Slightly higher to sort after LTE700
|
|
20
|
+
'NR2100': 2100.1,
|
|
21
|
+
'NR3500': 3500,
|
|
22
|
+
'NR26000': 26000 // mmWave
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Extract band from cell name using regex pattern matching
|
|
26
|
+
* @param cellName - Cell name like "LTE700_1", "NR3500_2", etc.
|
|
27
|
+
* @returns Band string like "LTE700", "NR3500" or null if not found
|
|
28
|
+
* @deprecated Use the band field from CellTrafficRecord instead
|
|
29
|
+
*/
|
|
30
|
+
export function extractBandFromCell(cellName) {
|
|
31
|
+
// Match patterns like "LTE700", "NR3500", etc.
|
|
32
|
+
const match = cellName.match(/(LTE|NR)(\d+)/i);
|
|
33
|
+
return match ? `${match[1].toUpperCase()}${match[2]}` : null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get frequency order for a band (for sorting)
|
|
37
|
+
* @param band - Band string like "LTE700", "NR3500"
|
|
38
|
+
* @returns Frequency number or high value for unknown bands
|
|
39
|
+
*/
|
|
40
|
+
export function getBandFrequency(band) {
|
|
41
|
+
if (!band)
|
|
42
|
+
return 999999; // Unknown bands go to end
|
|
43
|
+
return BAND_FREQUENCY_ORDER[band] || 999999;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sort items by band frequency using actual band data from records
|
|
47
|
+
* @param items - Array of [cellName, record] tuples
|
|
48
|
+
* @returns Sorted array (ascending frequency order)
|
|
49
|
+
*/
|
|
50
|
+
export function sortCellsByBandFrequency(items) {
|
|
51
|
+
return items.sort((a, b) => {
|
|
52
|
+
const [cellNameA, recordA] = a;
|
|
53
|
+
const [cellNameB, recordB] = b;
|
|
54
|
+
const freqA = getBandFrequency(recordA.band);
|
|
55
|
+
const freqB = getBandFrequency(recordB.band);
|
|
56
|
+
// Primary sort: by frequency
|
|
57
|
+
if (freqA !== freqB) {
|
|
58
|
+
return freqA - freqB;
|
|
59
|
+
}
|
|
60
|
+
// Secondary sort: by cell name for same frequency
|
|
61
|
+
return cellNameA.localeCompare(cellNameB);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
6
64
|
/**
|
|
7
65
|
* Build hierarchical tree structure: Site → Sector (Azimuth) → Cell (Band)
|
|
8
66
|
*/
|
|
@@ -50,9 +108,9 @@ export function buildTreeNodes(data) {
|
|
|
50
108
|
defaultChecked: false, // Don't check parent nodes
|
|
51
109
|
children: []
|
|
52
110
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
111
|
+
// Sort cells by band frequency (LTE700, LTE800, etc.)
|
|
112
|
+
const sortedCells = sortCellsByBandFrequency(Array.from(cellMap.entries()));
|
|
113
|
+
sortedCells.forEach(([cellName, record]) => {
|
|
56
114
|
const cellNode = {
|
|
57
115
|
id: cellName, // Simple ID (just cell name)
|
|
58
116
|
label: `${cellName} (${record.band})`,
|
|
@@ -173,3 +231,43 @@ export function transformChartData(data, baseMetrics) {
|
|
|
173
231
|
});
|
|
174
232
|
return pivotedData;
|
|
175
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Apply cell styling based on band and sector
|
|
236
|
+
* Modifies KPI objects to include color (from band) and lineStyle (from sector)
|
|
237
|
+
* Updates KPI name to format: Band_Azimuth°
|
|
238
|
+
*
|
|
239
|
+
* @param metricName - Base metric name (e.g., 'dlGBytes')
|
|
240
|
+
* @param cellRecord - Cell traffic record with band, sector, azimuth metadata
|
|
241
|
+
* @param unit - Unit string for the metric
|
|
242
|
+
* @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
|
|
243
|
+
* @returns Styled KPI object
|
|
244
|
+
*/
|
|
245
|
+
export function createStyledKPI(metricName, cellRecord, unit, stylingConfig) {
|
|
246
|
+
const { band, sector, azimuth, cellName } = cellRecord;
|
|
247
|
+
// Get color from band (if config provided)
|
|
248
|
+
const color = stylingConfig?.bandColors?.[band];
|
|
249
|
+
// Get line style from sector (if config provided)
|
|
250
|
+
const lineStyle = stylingConfig?.sectorLineStyles?.[sector.toString()];
|
|
251
|
+
// Format name as: Band_Azimuth°
|
|
252
|
+
const displayName = `${band}_${azimuth}°`;
|
|
253
|
+
// Build KPI with cell-specific styling
|
|
254
|
+
const kpi = {
|
|
255
|
+
rawName: `${metricName}_${cellName}`, // Column name in pivoted data
|
|
256
|
+
name: displayName,
|
|
257
|
+
scale: 'absolute',
|
|
258
|
+
unit,
|
|
259
|
+
...(color && { color }),
|
|
260
|
+
...(lineStyle && { lineStyle })
|
|
261
|
+
};
|
|
262
|
+
log('🎨 Styled KPI created', {
|
|
263
|
+
metricName,
|
|
264
|
+
cellName,
|
|
265
|
+
displayName,
|
|
266
|
+
band,
|
|
267
|
+
sector,
|
|
268
|
+
azimuth,
|
|
269
|
+
color,
|
|
270
|
+
lineStyle
|
|
271
|
+
});
|
|
272
|
+
return kpi;
|
|
273
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
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, MovingAverageConfig } from './charts.model.js';
|
|
6
|
+
import type { Chart as ChartModel, ChartMarker, MovingAverageConfig, HoverMode } 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
9
|
import { getKPIValues, type ProcessedChartData } from './data-processor.js';
|
|
@@ -27,13 +27,15 @@
|
|
|
27
27
|
sectionId?: string;
|
|
28
28
|
sectionMovingAverage?: MovingAverageConfig; // Section-level MA config
|
|
29
29
|
layoutMovingAverage?: MovingAverageConfig; // Layout-level MA config
|
|
30
|
+
layoutHoverMode?: HoverMode; // Layout-level hover mode config
|
|
31
|
+
layoutColoredHover?: boolean; // Layout-level colored hover config (default: true)
|
|
30
32
|
runtimeMAOverride?: MovingAverageConfig | null; // Runtime override from global controls
|
|
31
33
|
runtimeShowOriginal?: boolean; // Runtime control for showing original lines
|
|
32
34
|
runtimeShowMarkers?: boolean; // Runtime control for showing markers (default: true)
|
|
33
35
|
runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true }: Props = $props();
|
|
38
|
+
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true }: Props = $props();
|
|
37
39
|
|
|
38
40
|
// Chart container div and state
|
|
39
41
|
let chartDiv: HTMLElement;
|
|
@@ -162,7 +164,7 @@
|
|
|
162
164
|
// Add left Y-axis traces (with moving average support)
|
|
163
165
|
resolvedKPIs.left.forEach(kpi => {
|
|
164
166
|
const values = getKPIValues(processedData, kpi);
|
|
165
|
-
const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y1', colorIndex, chartType, stackGroup);
|
|
167
|
+
const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y1', colorIndex, chartType, stackGroup, layoutColoredHover);
|
|
166
168
|
traces.push(...kpiTraces);
|
|
167
169
|
colorIndex++;
|
|
168
170
|
});
|
|
@@ -170,13 +172,13 @@
|
|
|
170
172
|
// Add right Y-axis traces (with moving average support)
|
|
171
173
|
resolvedKPIs.right.forEach(kpi => {
|
|
172
174
|
const values = getKPIValues(processedData, kpi);
|
|
173
|
-
const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y2', colorIndex, chartType, stackGroup);
|
|
175
|
+
const kpiTraces = createTimeSeriesTraceWithMA(values, timestamps, kpi, 'y2', colorIndex, chartType, stackGroup, layoutColoredHover);
|
|
174
176
|
traces.push(...kpiTraces);
|
|
175
177
|
colorIndex++;
|
|
176
178
|
});
|
|
177
179
|
|
|
178
180
|
// Create default modern layout using the centralized function
|
|
179
|
-
const defaultLayout: any = createDefaultPlotlyLayout(chart.title);
|
|
181
|
+
const defaultLayout: any = createDefaultPlotlyLayout(chart.title, layoutHoverMode, layoutColoredHover);
|
|
180
182
|
|
|
181
183
|
// Override specific properties for this chart
|
|
182
184
|
defaultLayout.yaxis.title = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Chart as ChartModel, ChartMarker, MovingAverageConfig } from './charts.model.js';
|
|
1
|
+
import type { Chart as ChartModel, ChartMarker, MovingAverageConfig, HoverMode } from './charts.model.js';
|
|
2
2
|
import { type ProcessedChartData } from './data-processor.js';
|
|
3
3
|
interface Props {
|
|
4
4
|
chart: ChartModel;
|
|
@@ -9,6 +9,8 @@ interface Props {
|
|
|
9
9
|
sectionId?: string;
|
|
10
10
|
sectionMovingAverage?: MovingAverageConfig;
|
|
11
11
|
layoutMovingAverage?: MovingAverageConfig;
|
|
12
|
+
layoutHoverMode?: HoverMode;
|
|
13
|
+
layoutColoredHover?: boolean;
|
|
12
14
|
runtimeMAOverride?: MovingAverageConfig | null;
|
|
13
15
|
runtimeShowOriginal?: boolean;
|
|
14
16
|
runtimeShowMarkers?: boolean;
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
sections: layout.sections.length,
|
|
65
65
|
totalCharts: layout.sections.reduce((sum, s) => sum + s.charts.length, 0),
|
|
66
66
|
enableAdaptation,
|
|
67
|
-
showGlobalControls
|
|
67
|
+
showGlobalControls,
|
|
68
|
+
layoutHoverMode: layout.hoverMode
|
|
68
69
|
});
|
|
69
70
|
});
|
|
70
71
|
|
|
@@ -327,6 +328,7 @@
|
|
|
327
328
|
sectionId={section.id}
|
|
328
329
|
sectionMovingAverage={section.movingAverage}
|
|
329
330
|
layoutMovingAverage={layout.movingAverage}
|
|
331
|
+
layoutHoverMode={layout.hoverMode}
|
|
330
332
|
runtimeMAOverride={effectiveMAOverride}
|
|
331
333
|
runtimeShowOriginal={globalControls.movingAverage?.showOriginal}
|
|
332
334
|
runtimeShowMarkers={globalControls.markers?.enabled}
|
|
@@ -4,46 +4,36 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Adapts hover behavior based on container size and series count
|
|
7
|
-
*
|
|
7
|
+
* Preserves user's configured hover mode unless adaptation is critical for performance/UX
|
|
8
8
|
*/
|
|
9
|
-
function adaptHoverBehavior(layout, containerSize, chartInfo) {
|
|
9
|
+
function adaptHoverBehavior(layout, containerSize, chartInfo, originalHoverMode) {
|
|
10
10
|
const { width, height } = containerSize;
|
|
11
11
|
const isTiny = width < 250 || height < 200;
|
|
12
12
|
const isSmall = width < 400 || height < 300;
|
|
13
13
|
const isMedium = width < 600 || height < 400;
|
|
14
14
|
const totalSeries = chartInfo.leftSeriesCount + chartInfo.rightSeriesCount;
|
|
15
|
-
//
|
|
16
|
-
if (isTiny) {
|
|
17
|
-
layout.hovermode = 'closest'; // Single point instead of unified
|
|
18
|
-
if (layout.hoverlabel) {
|
|
19
|
-
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
20
|
-
layout.hoverlabel.font.size = 9; // Smaller font
|
|
21
|
-
}
|
|
22
|
-
return layout;
|
|
23
|
-
}
|
|
24
|
-
// Priority 2: Simplify hover in small charts
|
|
15
|
+
// Only override hover mode in critical cases for performance/UX
|
|
25
16
|
if (isSmall) {
|
|
26
|
-
|
|
17
|
+
// Force 'closest' in small charts for performance and readability
|
|
18
|
+
layout.hovermode = 'closest';
|
|
27
19
|
if (layout.hoverlabel) {
|
|
28
20
|
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
29
|
-
layout.hoverlabel.font.size = 9;
|
|
21
|
+
layout.hoverlabel.font.size = 9;
|
|
30
22
|
}
|
|
31
23
|
return layout;
|
|
32
24
|
}
|
|
33
|
-
//
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
layout.hovermode = 'x';
|
|
37
|
-
}
|
|
38
|
-
else if (totalSeries > 8) {
|
|
39
|
-
// Very many series - even in large charts, use x
|
|
40
|
-
layout.hovermode = 'x';
|
|
25
|
+
// For all other sizes, preserve the user's configured hover mode
|
|
26
|
+
if (originalHoverMode !== undefined) {
|
|
27
|
+
layout.hovermode = originalHoverMode;
|
|
41
28
|
}
|
|
42
|
-
//
|
|
43
|
-
//
|
|
29
|
+
// If no original hover mode provided, keep whatever is in the layout
|
|
30
|
+
// Only adapt font sizes, not hover behavior for non-tiny charts
|
|
44
31
|
if (layout.hoverlabel) {
|
|
45
32
|
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
46
|
-
if (
|
|
33
|
+
if (isSmall) {
|
|
34
|
+
layout.hoverlabel.font.size = 9;
|
|
35
|
+
}
|
|
36
|
+
else if (isMedium) {
|
|
47
37
|
layout.hoverlabel.font.size = 10;
|
|
48
38
|
}
|
|
49
39
|
else {
|
|
@@ -62,6 +52,8 @@ export function adaptPlotlyLayout(baseLayout, containerSize, chartInfo, config =
|
|
|
62
52
|
return baseLayout;
|
|
63
53
|
const { width, height } = containerSize;
|
|
64
54
|
const adaptedLayout = { ...baseLayout };
|
|
55
|
+
// Preserve the original hover mode before any adaptations
|
|
56
|
+
const originalHoverMode = baseLayout.hovermode;
|
|
65
57
|
// Size categories for adaptation rules
|
|
66
58
|
const isTiny = width < 250 || height < 200;
|
|
67
59
|
const isSmall = width < 400 || height < 300;
|
|
@@ -146,8 +138,8 @@ export function adaptPlotlyLayout(baseLayout, containerSize, chartInfo, config =
|
|
|
146
138
|
adaptedLayout.legend.font.size = 11;
|
|
147
139
|
}
|
|
148
140
|
}
|
|
149
|
-
// Apply adaptive hover behavior (
|
|
150
|
-
adaptHoverBehavior(adaptedLayout, containerSize, chartInfo);
|
|
141
|
+
// Apply adaptive hover behavior (preserve user config except in tiny charts)
|
|
142
|
+
adaptHoverBehavior(adaptedLayout, containerSize, chartInfo, originalHoverMode);
|
|
151
143
|
return adaptedLayout;
|
|
152
144
|
}
|
|
153
145
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type Scale = "percent" | "absolute";
|
|
2
|
+
export type LineStyle = 'solid' | 'dash' | 'dot' | 'dashdot' | 'longdash' | 'longdashdot';
|
|
2
3
|
export interface MovingAverageConfig {
|
|
3
4
|
enabled: boolean;
|
|
4
5
|
window: number;
|
|
@@ -11,6 +12,7 @@ export interface KPI {
|
|
|
11
12
|
scale: Scale;
|
|
12
13
|
unit: string;
|
|
13
14
|
color?: string;
|
|
15
|
+
lineStyle?: LineStyle;
|
|
14
16
|
movingAverage?: MovingAverageConfig;
|
|
15
17
|
}
|
|
16
18
|
export type ChartPosition = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
@@ -33,10 +35,13 @@ export interface Section {
|
|
|
33
35
|
movingAverage?: MovingAverageConfig;
|
|
34
36
|
}
|
|
35
37
|
export type Mode = "tabs" | "scrollspy";
|
|
38
|
+
export type HoverMode = 'x' | 'y' | 'closest' | 'x unified' | 'y unified' | false;
|
|
36
39
|
export interface Layout {
|
|
37
40
|
layoutName: string;
|
|
38
41
|
sections: Section[];
|
|
39
42
|
movingAverage?: MovingAverageConfig;
|
|
43
|
+
hoverMode?: HoverMode;
|
|
44
|
+
coloredHover?: boolean;
|
|
40
45
|
}
|
|
41
46
|
export interface ChartMarker {
|
|
42
47
|
date: string | Date;
|
|
@@ -59,3 +64,7 @@ export interface GlobalChartControls {
|
|
|
59
64
|
enabled: boolean;
|
|
60
65
|
};
|
|
61
66
|
}
|
|
67
|
+
export interface CellStylingConfig {
|
|
68
|
+
bandColors: Record<string, string>;
|
|
69
|
+
sectorLineStyles: Record<string, LineStyle>;
|
|
70
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { KPI } from './charts.model.js';
|
|
1
|
+
import type { KPI, HoverMode } 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, chartType?: string, stackGroup?: string): any;
|
|
4
|
+
export declare function createTimeSeriesTrace(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number, chartType?: string, stackGroup?: string, coloredHover?: boolean): 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
|
|
@@ -13,7 +13,7 @@ export declare function createTimeSeriesTrace(values: number[], timestamps: any[
|
|
|
13
13
|
* @param stackGroup - Optional stack group identifier
|
|
14
14
|
* @returns Array of traces (original + MA if configured)
|
|
15
15
|
*/
|
|
16
|
-
export declare function createTimeSeriesTraceWithMA(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number, chartType?: string, stackGroup?: string): any[];
|
|
16
|
+
export declare function createTimeSeriesTraceWithMA(values: number[], timestamps: any[], kpi: KPI, yaxis?: 'y1' | 'y2', colorIndex?: number, chartType?: string, stackGroup?: string, coloredHover?: boolean): any[];
|
|
17
17
|
export declare function getYAxisTitle(kpis: KPI[]): string;
|
|
18
18
|
export declare function formatValue(value: number, scale: 'percent' | 'absolute', unit: string): string;
|
|
19
|
-
export declare function createDefaultPlotlyLayout(title?: string): any;
|
|
19
|
+
export declare function createDefaultPlotlyLayout(title?: string, hoverMode?: HoverMode, coloredHover?: boolean): any;
|