@smartnet360/svelte-components 0.0.33 → 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.
- package/dist/apps/site-check/SiteCheck.svelte +96 -0
- package/dist/apps/site-check/SiteCheck.svelte.d.ts +12 -0
- package/dist/apps/site-check/data-loader.d.ts +29 -0
- package/dist/apps/site-check/data-loader.js +105 -0
- package/dist/apps/site-check/helper.d.ts +19 -0
- package/dist/apps/site-check/helper.js +103 -0
- package/dist/apps/site-check/index.d.ts +8 -0
- package/dist/apps/site-check/index.js +12 -0
- package/dist/apps/site-check/transforms.d.ts +24 -0
- package/dist/apps/site-check/transforms.js +142 -0
- package/dist/core/Charts/ChartCard.svelte +7 -6
- package/dist/core/Charts/ChartCard.svelte.d.ts +1 -2
- package/dist/core/Charts/ChartComponent.svelte +0 -2
- package/dist/core/Charts/adapt.d.ts +1 -2
- package/dist/core/Charts/adapt.js +7 -30
- package/dist/core/Charts/charts.model.d.ts +3 -2
- package/dist/core/Charts/data-utils.d.ts +4 -2
- package/dist/core/Charts/data-utils.js +71 -18
- package/dist/core/Charts/index.d.ts +1 -1
- package/dist/core/TreeView/TreeView.svelte +2 -2
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/package.json +1 -1
- package/dist/cellular/CellularChartsView.svelte +0 -293
- package/dist/cellular/CellularChartsView.svelte.d.ts +0 -7
- package/dist/cellular/HierarchicalTree.svelte +0 -469
- package/dist/cellular/HierarchicalTree.svelte.d.ts +0 -9
- package/dist/cellular/SiteTree.svelte +0 -286
- package/dist/cellular/SiteTree.svelte.d.ts +0 -11
- package/dist/cellular/cellular-transforms.d.ts +0 -25
- package/dist/cellular/cellular-transforms.js +0 -129
- package/dist/cellular/cellular.model.d.ts +0 -63
- package/dist/cellular/cellular.model.js +0 -6
- package/dist/cellular/index.d.ts +0 -11
- package/dist/cellular/index.js +0 -11
- package/dist/cellular/mock-cellular-data.d.ts +0 -13
- package/dist/cellular/mock-cellular-data.js +0 -241
- package/dist/core/TreeChartView/TreeChartView.svelte +0 -208
- package/dist/core/TreeChartView/TreeChartView.svelte.d.ts +0 -42
- package/dist/core/TreeChartView/index.d.ts +0 -7
- package/dist/core/TreeChartView/index.js +0 -7
|
@@ -5,42 +5,22 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Adapts hover behavior based on container size and series count
|
|
7
7
|
* Optimizes tooltip display and performance for different chart sizes
|
|
8
|
-
* Respects configured hover mode, with exception for tiny charts (always 'closest' for performance)
|
|
9
8
|
*/
|
|
10
|
-
function adaptHoverBehavior(layout, containerSize, chartInfo
|
|
9
|
+
function adaptHoverBehavior(layout, containerSize, chartInfo) {
|
|
11
10
|
const { width, height } = containerSize;
|
|
12
11
|
const isTiny = width < 250 || height < 200;
|
|
13
12
|
const isSmall = width < 400 || height < 300;
|
|
14
13
|
const isMedium = width < 600 || height < 400;
|
|
15
14
|
const totalSeries = chartInfo.leftSeriesCount + chartInfo.rightSeriesCount;
|
|
16
|
-
//
|
|
15
|
+
// Priority 1: Disable hover in tiny charts (performance + UX)
|
|
17
16
|
if (isTiny) {
|
|
18
|
-
layout.hovermode = 'closest';
|
|
17
|
+
layout.hovermode = 'closest'; // Single point instead of unified
|
|
19
18
|
if (layout.hoverlabel) {
|
|
20
19
|
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
21
20
|
layout.hoverlabel.font.size = 9; // Smaller font
|
|
22
21
|
}
|
|
23
22
|
return layout;
|
|
24
23
|
}
|
|
25
|
-
// If user configured a specific hover mode, respect it (except tiny override above)
|
|
26
|
-
if (configuredHoverMode !== undefined) {
|
|
27
|
-
layout.hovermode = configuredHoverMode;
|
|
28
|
-
// Still apply adaptive font sizing based on container size
|
|
29
|
-
if (layout.hoverlabel) {
|
|
30
|
-
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
31
|
-
if (isSmall) {
|
|
32
|
-
layout.hoverlabel.font.size = 9;
|
|
33
|
-
}
|
|
34
|
-
else if (isMedium) {
|
|
35
|
-
layout.hoverlabel.font.size = 10;
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
layout.hoverlabel.font.size = 11;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return layout;
|
|
42
|
-
}
|
|
43
|
-
// No configured mode - apply full adaptive logic (default: 'x')
|
|
44
24
|
// Priority 2: Simplify hover in small charts
|
|
45
25
|
if (isSmall) {
|
|
46
26
|
layout.hovermode = 'x'; // Single point instead of unified
|
|
@@ -52,17 +32,14 @@ function adaptHoverBehavior(layout, containerSize, chartInfo, configuredHoverMod
|
|
|
52
32
|
}
|
|
53
33
|
// Priority 3: Adaptive hover mode based on series count
|
|
54
34
|
if (totalSeries > 4 && isMedium) {
|
|
55
|
-
// Too many series in medium chart - switch to
|
|
35
|
+
// Too many series in medium chart - switch to closest
|
|
56
36
|
layout.hovermode = 'x';
|
|
57
37
|
}
|
|
58
38
|
else if (totalSeries > 8) {
|
|
59
39
|
// Very many series - even in large charts, use x
|
|
60
40
|
layout.hovermode = 'x';
|
|
61
41
|
}
|
|
62
|
-
|
|
63
|
-
// Default for large charts with reasonable series count
|
|
64
|
-
layout.hovermode = 'x';
|
|
65
|
-
}
|
|
42
|
+
// Otherwise keep default 'x unified' from base layout
|
|
66
43
|
// Priority 4: Adaptive hover label font size
|
|
67
44
|
if (layout.hoverlabel) {
|
|
68
45
|
layout.hoverlabel.font = layout.hoverlabel.font || {};
|
|
@@ -80,7 +57,7 @@ function adaptHoverBehavior(layout, containerSize, chartInfo, configuredHoverMod
|
|
|
80
57
|
* Preserves external styling while optimizing functional properties
|
|
81
58
|
*/
|
|
82
59
|
export function adaptPlotlyLayout(baseLayout, containerSize, chartInfo, config = {}) {
|
|
83
|
-
const { enableAdaptation = true
|
|
60
|
+
const { enableAdaptation = true } = config;
|
|
84
61
|
if (!enableAdaptation)
|
|
85
62
|
return baseLayout;
|
|
86
63
|
const { width, height } = containerSize;
|
|
@@ -170,7 +147,7 @@ export function adaptPlotlyLayout(baseLayout, containerSize, chartInfo, config =
|
|
|
170
147
|
}
|
|
171
148
|
}
|
|
172
149
|
// Apply adaptive hover behavior (disable in tiny, simplify in small, optimize for series count)
|
|
173
|
-
adaptHoverBehavior(adaptedLayout, containerSize, chartInfo
|
|
150
|
+
adaptHoverBehavior(adaptedLayout, containerSize, chartInfo);
|
|
174
151
|
return adaptedLayout;
|
|
175
152
|
}
|
|
176
153
|
/**
|
|
@@ -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;
|
|
@@ -30,12 +33,10 @@ export interface Section {
|
|
|
30
33
|
movingAverage?: MovingAverageConfig;
|
|
31
34
|
}
|
|
32
35
|
export type Mode = "tabs" | "scrollspy";
|
|
33
|
-
export type HoverMode = 'x' | 'y' | 'closest' | 'x unified' | 'y unified' | false;
|
|
34
36
|
export interface Layout {
|
|
35
37
|
layoutName: string;
|
|
36
38
|
sections: Section[];
|
|
37
39
|
movingAverage?: MovingAverageConfig;
|
|
38
|
-
hoverMode?: HoverMode;
|
|
39
40
|
}
|
|
40
41
|
export interface ChartMarker {
|
|
41
42
|
date: string | Date;
|
|
@@ -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;
|
|
@@ -72,27 +72,76 @@ export function calculateMovingAverage(values, window) {
|
|
|
72
72
|
maCache.set(cacheKey, result);
|
|
73
73
|
return result;
|
|
74
74
|
}
|
|
75
|
-
export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', colorIndex = 0) {
|
|
75
|
+
export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', colorIndex = 0, chartType = 'line', stackGroup) {
|
|
76
76
|
// Use KPI color if provided, otherwise cycle through modern colors
|
|
77
77
|
const traceColor = kpi.color || modernColors[colorIndex % modernColors.length];
|
|
78
|
-
|
|
78
|
+
// Base trace configuration
|
|
79
|
+
const baseTrace = {
|
|
79
80
|
x: timestamps,
|
|
80
81
|
y: values,
|
|
81
|
-
type: 'scatter',
|
|
82
|
-
mode: 'lines', // Only lines, no markers
|
|
83
82
|
name: kpi.name,
|
|
84
83
|
yaxis: yaxis,
|
|
85
|
-
line: {
|
|
86
|
-
color: traceColor,
|
|
87
|
-
width: 3,
|
|
88
|
-
shape: 'spline',
|
|
89
|
-
smoothing: 0.3,
|
|
90
|
-
dash: yaxis === 'y1' ? 'solid' : 'dot' // Y1 = solid, Y2 = dotted
|
|
91
|
-
},
|
|
92
84
|
hovertemplate: `<b>${kpi.name}</b><br>` +
|
|
93
|
-
`Value: %{y
|
|
85
|
+
`Value: %{y:,.2f} ${kpi.unit}<br>` +
|
|
94
86
|
'<extra></extra>'
|
|
95
87
|
};
|
|
88
|
+
// Configure based on chart type
|
|
89
|
+
switch (chartType) {
|
|
90
|
+
case 'stacked-area':
|
|
91
|
+
return {
|
|
92
|
+
...baseTrace,
|
|
93
|
+
type: 'scatter',
|
|
94
|
+
mode: 'lines',
|
|
95
|
+
fill: 'tonexty',
|
|
96
|
+
stackgroup: stackGroup || 'one',
|
|
97
|
+
line: { width: 0.5, color: traceColor },
|
|
98
|
+
fillcolor: traceColor
|
|
99
|
+
};
|
|
100
|
+
case 'stacked-percentage':
|
|
101
|
+
return {
|
|
102
|
+
...baseTrace,
|
|
103
|
+
type: 'scatter',
|
|
104
|
+
mode: 'lines',
|
|
105
|
+
fill: 'tonexty',
|
|
106
|
+
stackgroup: stackGroup || 'one',
|
|
107
|
+
groupnorm: 'percent',
|
|
108
|
+
line: { width: 0.5, color: traceColor },
|
|
109
|
+
fillcolor: traceColor,
|
|
110
|
+
hovertemplate: `<b>${kpi.name}</b><br>` +
|
|
111
|
+
`Percentage: %{y:.1f}%<br>` +
|
|
112
|
+
'<extra></extra>'
|
|
113
|
+
};
|
|
114
|
+
case 'bar':
|
|
115
|
+
return {
|
|
116
|
+
...baseTrace,
|
|
117
|
+
type: 'bar',
|
|
118
|
+
marker: { color: traceColor }
|
|
119
|
+
};
|
|
120
|
+
case 'scatter':
|
|
121
|
+
return {
|
|
122
|
+
...baseTrace,
|
|
123
|
+
type: 'scatter',
|
|
124
|
+
mode: 'markers',
|
|
125
|
+
marker: {
|
|
126
|
+
color: traceColor,
|
|
127
|
+
size: 8
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
case 'line':
|
|
131
|
+
default:
|
|
132
|
+
return {
|
|
133
|
+
...baseTrace,
|
|
134
|
+
type: 'scatter',
|
|
135
|
+
mode: 'lines',
|
|
136
|
+
line: {
|
|
137
|
+
color: traceColor,
|
|
138
|
+
width: 3,
|
|
139
|
+
shape: 'spline',
|
|
140
|
+
smoothing: 0.3,
|
|
141
|
+
dash: yaxis === 'y1' ? 'solid' : 'dot'
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
96
145
|
}
|
|
97
146
|
/**
|
|
98
147
|
* Create time series trace(s) with optional moving average
|
|
@@ -101,23 +150,27 @@ export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', col
|
|
|
101
150
|
* @param kpi - KPI configuration (may include movingAverage config)
|
|
102
151
|
* @param yaxis - Which Y-axis to use ('y1' or 'y2')
|
|
103
152
|
* @param colorIndex - Index for color selection
|
|
153
|
+
* @param chartType - Type of chart (line, stacked-area, etc.)
|
|
154
|
+
* @param stackGroup - Optional stack group identifier
|
|
104
155
|
* @returns Array of traces (original + MA if configured)
|
|
105
156
|
*/
|
|
106
|
-
export function createTimeSeriesTraceWithMA(values, timestamps, kpi, yaxis = 'y1', colorIndex = 0) {
|
|
157
|
+
export function createTimeSeriesTraceWithMA(values, timestamps, kpi, yaxis = 'y1', colorIndex = 0, chartType = 'line', stackGroup) {
|
|
107
158
|
const traces = [];
|
|
108
159
|
const traceColor = kpi.color || modernColors[colorIndex % modernColors.length];
|
|
109
160
|
// Add original trace (unless explicitly disabled)
|
|
110
161
|
if (!kpi.movingAverage || kpi.movingAverage.showOriginal !== false) {
|
|
111
|
-
const originalTrace = createTimeSeriesTrace(values, timestamps, kpi, yaxis, colorIndex);
|
|
162
|
+
const originalTrace = createTimeSeriesTrace(values, timestamps, kpi, yaxis, colorIndex, chartType, stackGroup);
|
|
112
163
|
// If MA is enabled, make the original line slightly transparent
|
|
113
164
|
if (kpi.movingAverage?.enabled) {
|
|
114
165
|
originalTrace.opacity = 0.4;
|
|
115
|
-
originalTrace.line
|
|
166
|
+
if (originalTrace.line) {
|
|
167
|
+
originalTrace.line.width = 2;
|
|
168
|
+
}
|
|
116
169
|
}
|
|
117
170
|
traces.push(originalTrace);
|
|
118
171
|
}
|
|
119
|
-
// Add moving average trace if configured
|
|
120
|
-
if (kpi.movingAverage?.enabled) {
|
|
172
|
+
// Add moving average trace if configured (only for line charts)
|
|
173
|
+
if (kpi.movingAverage?.enabled && (chartType === 'line' || !chartType)) {
|
|
121
174
|
const maValues = calculateMovingAverage(values, kpi.movingAverage.window);
|
|
122
175
|
const maLabel = kpi.movingAverage.label ||
|
|
123
176
|
`${kpi.name} (MA${kpi.movingAverage.window})`;
|
|
@@ -201,7 +254,7 @@ export function createDefaultPlotlyLayout(title) {
|
|
|
201
254
|
paper_bgcolor: 'rgba(0,0,0,0)',
|
|
202
255
|
plot_bgcolor: 'rgba(0,0,0,0)',
|
|
203
256
|
font: { family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif' },
|
|
204
|
-
hovermode: 'x',
|
|
257
|
+
hovermode: 'x',
|
|
205
258
|
hoverlabel: {
|
|
206
259
|
font: {
|
|
207
260
|
family: 'Inter, Segoe UI, Tahoma, Geneva, Verdana, sans-serif',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default as ChartComponent } from './ChartComponent.svelte';
|
|
2
2
|
export { default as ChartCard } from './ChartCard.svelte';
|
|
3
|
-
export type { Layout, Section, Chart, KPI, Mode, Scale, ChartMarker, ChartGrid, ChartPosition
|
|
3
|
+
export type { Layout, Section, Chart, KPI, Mode, Scale, ChartMarker, ChartGrid, ChartPosition } from './charts.model.js';
|
|
4
4
|
export { createTimeSeriesTrace, getYAxisTitle, formatValue, processKPIData, createDefaultPlotlyLayout } from './data-utils.js';
|
|
5
5
|
export { adaptPlotlyLayout, getSizeCategory, createMarkerShapes, createMarkerAnnotations, addMarkersToLayout } from './adapt.js';
|
|
6
6
|
export type { ContainerSize, ChartInfo, AdaptationConfig } from './adapt.js';
|
|
@@ -81,12 +81,12 @@
|
|
|
81
81
|
{/if}
|
|
82
82
|
|
|
83
83
|
<div class="tree-content">
|
|
84
|
-
<div class="tree-help-text">
|
|
84
|
+
<!-- <div class="tree-help-text">
|
|
85
85
|
<small class="text-muted">
|
|
86
86
|
<i class="bi bi-info-circle"></i>
|
|
87
87
|
Click checkboxes to select/deselect. Click arrows to expand/collapse.
|
|
88
88
|
</small>
|
|
89
|
-
</div>
|
|
89
|
+
</div> -->
|
|
90
90
|
|
|
91
91
|
<div class="tree-nodes">
|
|
92
92
|
{#each rootNodes as rootNode (rootNode.path)}
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
<svelte:options runes={true} />
|
|
2
|
-
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import type { CellularSite, CellLine } from './cellular.model.js';
|
|
5
|
-
import type { Layout } from '../core/Charts/charts.model.js';
|
|
6
|
-
import type { TreeNode } from '../core/TreeView/tree.model.js';
|
|
7
|
-
import ChartComponent from '../core/Charts/ChartComponent.svelte';
|
|
8
|
-
import { TreeView, createTreeStore } from '../core/TreeView';
|
|
9
|
-
import { getBandColor, getBandLabel } from './mock-cellular-data.js';
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
sites: CellularSite[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let { sites }: Props = $props();
|
|
16
|
-
|
|
17
|
-
// Transform cellular sites to TreeNode structure
|
|
18
|
-
let treeNodes = $derived.by((): TreeNode[] => {
|
|
19
|
-
return sites.map(site => ({
|
|
20
|
-
id: site.siteId,
|
|
21
|
-
label: site.siteName,
|
|
22
|
-
icon: '📡',
|
|
23
|
-
defaultExpanded: false,
|
|
24
|
-
defaultChecked: true, // Explicitly set
|
|
25
|
-
children: site.sectors.map(sector => ({
|
|
26
|
-
id: sector.sectorId, // Just the sector ID, not full path
|
|
27
|
-
label: sector.sectorName,
|
|
28
|
-
icon: '📶',
|
|
29
|
-
defaultExpanded: false,
|
|
30
|
-
defaultChecked: true, // Explicitly set
|
|
31
|
-
children: sector.cells.map(cell => ({
|
|
32
|
-
id: String(cell.band), // Just the band number
|
|
33
|
-
label: getBandLabel(cell.band),
|
|
34
|
-
icon: '📻',
|
|
35
|
-
defaultChecked: true, // Explicitly set
|
|
36
|
-
metadata: {
|
|
37
|
-
band: cell.band,
|
|
38
|
-
color: getBandColor(cell.band)
|
|
39
|
-
}
|
|
40
|
-
}))
|
|
41
|
-
}))
|
|
42
|
-
}));
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Create tree store with persistence
|
|
46
|
-
let treeStore = $derived(
|
|
47
|
-
createTreeStore({
|
|
48
|
-
nodes: treeNodes,
|
|
49
|
-
namespace: 'cellular-sites',
|
|
50
|
-
persistState: true, // Re-enabled - state will be saved/restored
|
|
51
|
-
defaultExpandAll: false,
|
|
52
|
-
showIndeterminate: true
|
|
53
|
-
})
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Flatten sites into cell lines
|
|
57
|
-
let cellLines = $derived.by((): CellLine[] => {
|
|
58
|
-
const lines: CellLine[] = [];
|
|
59
|
-
|
|
60
|
-
sites.forEach(site => {
|
|
61
|
-
site.sectors.forEach(sector => {
|
|
62
|
-
sector.cells.forEach(cell => {
|
|
63
|
-
// Path construction matches tree-utils.ts flattenTree logic
|
|
64
|
-
// Root: "siteId"
|
|
65
|
-
// Level 1: "siteId:sectorId"
|
|
66
|
-
// Level 2: "siteId:sectorId:band"
|
|
67
|
-
const cellKey = `${site.siteId}:${sector.sectorId}:${cell.band}`;
|
|
68
|
-
|
|
69
|
-
// Check visibility from tree store
|
|
70
|
-
const isVisible = $treeStore.state.checkedPaths.has(cellKey);
|
|
71
|
-
|
|
72
|
-
lines.push({
|
|
73
|
-
siteId: site.siteId,
|
|
74
|
-
siteName: site.siteName,
|
|
75
|
-
sectorId: sector.sectorId,
|
|
76
|
-
sectorName: sector.sectorName,
|
|
77
|
-
cellId: cell.cellId,
|
|
78
|
-
band: cell.band,
|
|
79
|
-
label: `${site.siteName} - ${sector.sectorName} - ${getBandLabel(cell.band)}`,
|
|
80
|
-
color: getBandColor(cell.band),
|
|
81
|
-
kpis: cell.kpis,
|
|
82
|
-
visible: isVisible
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
return lines;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Filter visible cell lines for chart display
|
|
92
|
-
let visibleCellLines = $derived(cellLines.filter(line => line.visible));
|
|
93
|
-
|
|
94
|
-
// Transform cell lines into chart data format
|
|
95
|
-
let chartData = $derived.by(() => {
|
|
96
|
-
const data: any[] = [];
|
|
97
|
-
|
|
98
|
-
visibleCellLines.forEach(line => {
|
|
99
|
-
// Create one row per timestamp with all KPI values
|
|
100
|
-
const timestamps = new Set(line.kpis.throughput.map(kpi => kpi.timestamp));
|
|
101
|
-
|
|
102
|
-
timestamps.forEach(timestamp => {
|
|
103
|
-
const throughputPoint = line.kpis.throughput.find(kpi => kpi.timestamp === timestamp);
|
|
104
|
-
const taPoint = line.kpis.timingAdvance.find(kpi => kpi.timestamp === timestamp);
|
|
105
|
-
|
|
106
|
-
if (throughputPoint && taPoint) {
|
|
107
|
-
data.push({
|
|
108
|
-
TIMESTAMP: timestamp,
|
|
109
|
-
[`DL_THROUGHPUT_${line.cellId}`]: throughputPoint.value,
|
|
110
|
-
[`AVG_TA_${line.cellId}`]: taPoint.value,
|
|
111
|
-
_cellId: line.cellId,
|
|
112
|
-
_label: line.label,
|
|
113
|
-
_color: line.color
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
return data;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Chart layout configuration - dynamically build KPIs from visible cells
|
|
123
|
-
let chartLayout = $derived.by((): Layout => {
|
|
124
|
-
const throughputKPIs = visibleCellLines.map(line => ({
|
|
125
|
-
rawName: `DL_THROUGHPUT_${line.cellId}`,
|
|
126
|
-
name: line.label,
|
|
127
|
-
scale: 'absolute' as const,
|
|
128
|
-
unit: 'Mbps',
|
|
129
|
-
color: line.color
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
|
-
const taKPIs = visibleCellLines.map(line => ({
|
|
133
|
-
rawName: `AVG_TA_${line.cellId}`,
|
|
134
|
-
name: line.label,
|
|
135
|
-
scale: 'absolute' as const,
|
|
136
|
-
unit: 'μs',
|
|
137
|
-
color: line.color
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
layoutName: 'Cellular KPIs',
|
|
142
|
-
sections: [
|
|
143
|
-
{
|
|
144
|
-
id: 'kpis',
|
|
145
|
-
title: 'KPIs',
|
|
146
|
-
grid: '2x2',
|
|
147
|
-
charts: [
|
|
148
|
-
{
|
|
149
|
-
title: 'Downlink Throughput',
|
|
150
|
-
yLeft: throughputKPIs,
|
|
151
|
-
yRight: []
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
title: 'Average Timing Advance',
|
|
155
|
-
yLeft: taKPIs,
|
|
156
|
-
yRight: []
|
|
157
|
-
}
|
|
158
|
-
]
|
|
159
|
-
}
|
|
160
|
-
]
|
|
161
|
-
};
|
|
162
|
-
});
|
|
163
|
-
</script>
|
|
164
|
-
|
|
165
|
-
<div class="cellular-charts-view">
|
|
166
|
-
<!-- Left: Site Tree -->
|
|
167
|
-
<aside class="site-selector">
|
|
168
|
-
<TreeView store={$treeStore} showControls={true} height="100%" />
|
|
169
|
-
</aside>
|
|
170
|
-
|
|
171
|
-
<!-- Right: Charts -->
|
|
172
|
-
<main class="charts-area">
|
|
173
|
-
<div class="charts-header">
|
|
174
|
-
<h5 class="mb-0">Cellular Network KPIs</h5>
|
|
175
|
-
<div class="stats">
|
|
176
|
-
<span class="badge bg-primary">
|
|
177
|
-
{visibleCellLines.length} of {cellLines.length} cells visible
|
|
178
|
-
</span>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
<div class="charts-container">
|
|
182
|
-
{#if visibleCellLines.length === 0}
|
|
183
|
-
<div class="empty-state">
|
|
184
|
-
<div class="empty-state-content">
|
|
185
|
-
<i class="bi bi-info-circle" style="font-size: 3rem; color: #6c757d;"></i>
|
|
186
|
-
<h4 class="mt-3">No Cells Selected</h4>
|
|
187
|
-
<p class="text-muted">
|
|
188
|
-
Select one or more cells from the tree on the left to display KPI charts.
|
|
189
|
-
</p>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
{:else}
|
|
193
|
-
<ChartComponent
|
|
194
|
-
layout={chartLayout}
|
|
195
|
-
data={chartData}
|
|
196
|
-
mode="tabs"
|
|
197
|
-
showGlobalControls={true}
|
|
198
|
-
enableAdaptation={true}
|
|
199
|
-
/>
|
|
200
|
-
{/if}
|
|
201
|
-
</div>
|
|
202
|
-
</main>
|
|
203
|
-
</div>
|
|
204
|
-
|
|
205
|
-
<style>
|
|
206
|
-
.cellular-charts-view {
|
|
207
|
-
width: 100%;
|
|
208
|
-
height: 100%;
|
|
209
|
-
display: flex;
|
|
210
|
-
gap: 0;
|
|
211
|
-
background-color: #fff;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.site-selector {
|
|
215
|
-
width: 300px;
|
|
216
|
-
flex-shrink: 0;
|
|
217
|
-
height: 100%;
|
|
218
|
-
overflow: hidden;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.charts-area {
|
|
222
|
-
flex: 1;
|
|
223
|
-
display: flex;
|
|
224
|
-
flex-direction: column;
|
|
225
|
-
min-width: 0;
|
|
226
|
-
height: 100%;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.charts-header {
|
|
230
|
-
padding: 1rem;
|
|
231
|
-
border-bottom: 1px solid #dee2e6;
|
|
232
|
-
background-color: #f8f9fa;
|
|
233
|
-
display: flex;
|
|
234
|
-
justify-content: space-between;
|
|
235
|
-
align-items: center;
|
|
236
|
-
flex-shrink: 0;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.charts-header h5 {
|
|
240
|
-
margin: 0;
|
|
241
|
-
color: #495057;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.stats {
|
|
245
|
-
display: flex;
|
|
246
|
-
gap: 0.5rem;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.charts-container {
|
|
250
|
-
flex: 1;
|
|
251
|
-
min-height: 0;
|
|
252
|
-
overflow: hidden;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.empty-state {
|
|
256
|
-
display: flex;
|
|
257
|
-
align-items: center;
|
|
258
|
-
justify-content: center;
|
|
259
|
-
height: 100%;
|
|
260
|
-
width: 100%;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.empty-state-content {
|
|
264
|
-
text-align: center;
|
|
265
|
-
max-width: 400px;
|
|
266
|
-
padding: 2rem;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.empty-state-content h4 {
|
|
270
|
-
color: #495057;
|
|
271
|
-
margin-bottom: 0.5rem;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/* Responsive layout */
|
|
275
|
-
@media (max-width: 992px) {
|
|
276
|
-
.site-selector {
|
|
277
|
-
width: 250px;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
@media (max-width: 768px) {
|
|
282
|
-
.cellular-charts-view {
|
|
283
|
-
flex-direction: column;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.site-selector {
|
|
287
|
-
width: 100%;
|
|
288
|
-
height: 300px;
|
|
289
|
-
border-right: none;
|
|
290
|
-
border-bottom: 1px solid #dee2e6;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
</style>
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { CellularSite } from './cellular.model.js';
|
|
2
|
-
interface Props {
|
|
3
|
-
sites: CellularSite[];
|
|
4
|
-
}
|
|
5
|
-
declare const CellularChartsView: import("svelte").Component<Props, {}, "">;
|
|
6
|
-
type CellularChartsView = ReturnType<typeof CellularChartsView>;
|
|
7
|
-
export default CellularChartsView;
|