@smartnet360/svelte-components 0.0.4 → 0.0.11
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/Charts/ChartCard.svelte +31 -61
- package/dist/Charts/ChartCard.svelte.d.ts +25 -2
- package/dist/Charts/ChartComponent.svelte +286 -11
- package/dist/Charts/charts.model.d.ts +5 -2
- package/dist/Charts/data-utils.js +4 -6
- package/dist/Charts/index.d.ts +1 -1
- package/dist/Desktop/launchHelpers.js +21 -30
- package/package.json +6 -6
@@ -1,26 +1,46 @@
|
|
1
1
|
<svelte:options runes={true} />
|
2
2
|
|
3
3
|
<script lang="ts">
|
4
|
-
import { onMount } from 'svelte';
|
4
|
+
import { onMount, createEventDispatcher } from 'svelte';
|
5
5
|
import Plotly from 'plotly.js-dist-min';
|
6
6
|
import type { Chart as ChartModel, ChartMarker } from './charts.model.js';
|
7
|
-
import { createTimeSeriesTrace, getYAxisTitle } from './data-utils.js';
|
7
|
+
import { createTimeSeriesTrace, getYAxisTitle, createDefaultPlotlyLayout } from './data-utils.js';
|
8
8
|
import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
|
9
9
|
|
10
|
+
const dispatch = createEventDispatcher<{
|
11
|
+
chartcontextmenu: {
|
12
|
+
chart: ChartModel;
|
13
|
+
sectionId?: string;
|
14
|
+
clientX: number;
|
15
|
+
clientY: number;
|
16
|
+
};
|
17
|
+
}>();
|
18
|
+
|
10
19
|
interface Props {
|
11
20
|
chart: ChartModel;
|
12
21
|
data: any[];
|
13
22
|
markers?: ChartMarker[]; // Global markers for all charts
|
14
23
|
plotlyLayout?: any; // Optional custom Plotly layout for styling/theming
|
15
24
|
enableAdaptation?: boolean; // Enable size-based adaptations (default: true)
|
25
|
+
sectionId?: string;
|
16
26
|
}
|
17
27
|
|
18
|
-
let { chart, data, markers, plotlyLayout, enableAdaptation = true }: Props = $props();
|
28
|
+
let { chart, data, markers, plotlyLayout, enableAdaptation = true, sectionId }: Props = $props();
|
19
29
|
|
20
30
|
// Chart container div and state
|
21
31
|
let chartDiv: HTMLElement;
|
22
32
|
let containerSize = $state<ContainerSize>({ width: 0, height: 0 });
|
23
33
|
|
34
|
+
function handleContextMenu(event: MouseEvent) {
|
35
|
+
event.preventDefault();
|
36
|
+
dispatch('chartcontextmenu', {
|
37
|
+
chart,
|
38
|
+
sectionId,
|
39
|
+
clientX: event.clientX,
|
40
|
+
clientY: event.clientY
|
41
|
+
});
|
42
|
+
}
|
43
|
+
|
24
44
|
function renderChart() {
|
25
45
|
if (!chartDiv || !data?.length) return;
|
26
46
|
|
@@ -41,63 +61,13 @@
|
|
41
61
|
colorIndex++;
|
42
62
|
});
|
43
63
|
|
44
|
-
// Create default modern layout
|
45
|
-
const defaultLayout: any =
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
weight: 600
|
52
|
-
},
|
53
|
-
x: 0.5,
|
54
|
-
xanchor: 'center'
|
55
|
-
},
|
56
|
-
showlegend: true,
|
57
|
-
legend: {
|
58
|
-
x: 1,
|
59
|
-
y: 1,
|
60
|
-
xanchor: 'right',
|
61
|
-
yanchor: 'top',
|
62
|
-
font: {
|
63
|
-
family: 'Inter, Segoe UI, Tahoma, Geneva, Verdana, sans-serif',
|
64
|
-
size: 11,
|
65
|
-
color: '#6B7280'
|
66
|
-
},
|
67
|
-
bgcolor: 'rgba(255,255,255,0.9)',
|
68
|
-
bordercolor: '#6B7280',
|
69
|
-
borderwidth: 1
|
70
|
-
},
|
71
|
-
xaxis: {
|
72
|
-
showgrid: true,
|
73
|
-
gridcolor: '#ecf0f1',
|
74
|
-
linecolor: '#bdc3c7',
|
75
|
-
tickfont: { size: 11 }
|
76
|
-
},
|
77
|
-
yaxis: {
|
78
|
-
title: {
|
79
|
-
text: getYAxisTitle(chart.yLeft),
|
80
|
-
font: { size: 12, color: '#7f8c8d' }
|
81
|
-
},
|
82
|
-
showgrid: true,
|
83
|
-
gridcolor: '#ecf0f1',
|
84
|
-
linecolor: '#bdc3c7',
|
85
|
-
tickfont: { size: 11 }
|
86
|
-
},
|
87
|
-
margin: { l: 60, r: 60, t: 60, b: 50 },
|
88
|
-
paper_bgcolor: 'rgba(0,0,0,0)',
|
89
|
-
plot_bgcolor: 'rgba(0,0,0,0)',
|
90
|
-
font: { family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif' },
|
91
|
-
hovermode: 'x unified',
|
92
|
-
hoverlabel: {
|
93
|
-
font: {
|
94
|
-
family: 'Inter, Segoe UI, Tahoma, Geneva, Verdana, sans-serif',
|
95
|
-
size: 11,
|
96
|
-
color: '#6B7280'
|
97
|
-
},
|
98
|
-
bgcolor: 'rgba(255,255,255,0.9)',
|
99
|
-
bordercolor: '#6B7280'
|
100
|
-
}
|
64
|
+
// Create default modern layout using the centralized function
|
65
|
+
const defaultLayout: any = createDefaultPlotlyLayout(chart.title);
|
66
|
+
|
67
|
+
// Override specific properties for this chart
|
68
|
+
defaultLayout.yaxis.title = {
|
69
|
+
text: getYAxisTitle(chart.yLeft),
|
70
|
+
font: { size: 12, color: '#7f8c8d' }
|
101
71
|
};
|
102
72
|
|
103
73
|
// Add second Y-axis if we have right-side KPIs
|
@@ -187,7 +157,7 @@
|
|
187
157
|
});
|
188
158
|
</script>
|
189
159
|
|
190
|
-
<div class="chart-card">
|
160
|
+
<div class="chart-card" role="group" oncontextmenu={handleContextMenu}>
|
191
161
|
<div
|
192
162
|
bind:this={chartDiv}
|
193
163
|
class="chart-container"
|
@@ -5,7 +5,30 @@ interface Props {
|
|
5
5
|
markers?: ChartMarker[];
|
6
6
|
plotlyLayout?: any;
|
7
7
|
enableAdaptation?: boolean;
|
8
|
+
sectionId?: string;
|
8
9
|
}
|
9
|
-
|
10
|
-
|
10
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
11
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
12
|
+
$$bindings?: Bindings;
|
13
|
+
} & Exports;
|
14
|
+
(internal: unknown, props: Props & {
|
15
|
+
$$events?: Events;
|
16
|
+
$$slots?: Slots;
|
17
|
+
}): Exports & {
|
18
|
+
$set?: any;
|
19
|
+
$on?: any;
|
20
|
+
};
|
21
|
+
z_$$bindings?: Bindings;
|
22
|
+
}
|
23
|
+
declare const ChartCard: $$__sveltets_2_IsomorphicComponent<Props, {
|
24
|
+
chartcontextmenu: CustomEvent<{
|
25
|
+
chart: ChartModel;
|
26
|
+
sectionId?: string;
|
27
|
+
clientX: number;
|
28
|
+
clientY: number;
|
29
|
+
}>;
|
30
|
+
} & {
|
31
|
+
[evt: string]: CustomEvent<any>;
|
32
|
+
}, {}, {}, "">;
|
33
|
+
type ChartCard = InstanceType<typeof ChartCard>;
|
11
34
|
export default ChartCard;
|
@@ -1,7 +1,8 @@
|
|
1
1
|
<svelte:options runes={true} />
|
2
2
|
|
3
3
|
<script lang="ts">
|
4
|
-
import
|
4
|
+
import { onMount } from 'svelte';
|
5
|
+
import type { Layout, Mode, ChartMarker, Section, ChartGrid, Chart } from './charts.model.js';
|
5
6
|
import ChartCard from './ChartCard.svelte';
|
6
7
|
|
7
8
|
interface Props {
|
@@ -13,25 +14,197 @@
|
|
13
14
|
enableAdaptation?: boolean; // Enable size-based adaptations
|
14
15
|
}
|
15
16
|
|
17
|
+
const GRID_DIMENSIONS: Record<ChartGrid, { rows: number; columns: number }> = {
|
18
|
+
'2x2': { rows: 2, columns: 2 },
|
19
|
+
'3x3': { rows: 3, columns: 3 },
|
20
|
+
'1x2': { rows: 1, columns: 2 },
|
21
|
+
'1x4': { rows: 1, columns: 4 },
|
22
|
+
'1x8': { rows: 1, columns: 8 }
|
23
|
+
};
|
24
|
+
|
25
|
+
const DEFAULT_GRID: ChartGrid = '2x2';
|
26
|
+
|
27
|
+
function getGridConfig(grid: ChartGrid | undefined) {
|
28
|
+
const key = grid && GRID_DIMENSIONS[grid] ? grid : DEFAULT_GRID;
|
29
|
+
return { key, ...GRID_DIMENSIONS[key] };
|
30
|
+
}
|
31
|
+
|
32
|
+
interface ContextMenuDetail {
|
33
|
+
chart: Chart;
|
34
|
+
sectionId?: string;
|
35
|
+
clientX: number;
|
36
|
+
clientY: number;
|
37
|
+
}
|
38
|
+
|
39
|
+
interface ContextMenuState {
|
40
|
+
visible: boolean;
|
41
|
+
x: number;
|
42
|
+
y: number;
|
43
|
+
chart: Chart | null;
|
44
|
+
section: Section | null;
|
45
|
+
}
|
46
|
+
|
47
|
+
interface ZoomState {
|
48
|
+
chart: Chart;
|
49
|
+
section: Section;
|
50
|
+
}
|
51
|
+
|
16
52
|
let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true }: Props = $props();
|
17
53
|
|
18
54
|
// Internal tab state management
|
19
55
|
let activeTabId = $state(layout.sections[0]?.id || '');
|
56
|
+
|
57
|
+
let componentElement: HTMLDivElement;
|
58
|
+
let contextMenuElement = $state<HTMLDivElement | null>(null);
|
59
|
+
|
60
|
+
const contextMenu = $state<ContextMenuState>({
|
61
|
+
visible: false,
|
62
|
+
x: 0,
|
63
|
+
y: 0,
|
64
|
+
chart: null,
|
65
|
+
section: null
|
66
|
+
});
|
67
|
+
|
68
|
+
let zoomedChart = $state<ZoomState | null>(null);
|
69
|
+
|
70
|
+
const MENU_WIDTH = 168;
|
71
|
+
const MENU_HEIGHT = 96;
|
72
|
+
|
73
|
+
function computeMenuPosition(clientX: number, clientY: number) {
|
74
|
+
if (!componentElement) {
|
75
|
+
return { x: clientX, y: clientY };
|
76
|
+
}
|
77
|
+
|
78
|
+
const rect = componentElement.getBoundingClientRect();
|
79
|
+
let x = clientX - rect.left;
|
80
|
+
let y = clientY - rect.top;
|
81
|
+
|
82
|
+
const maxX = Math.max(0, rect.width - MENU_WIDTH);
|
83
|
+
const maxY = Math.max(0, rect.height - MENU_HEIGHT);
|
84
|
+
|
85
|
+
x = Math.min(Math.max(0, x), maxX);
|
86
|
+
y = Math.min(Math.max(0, y), maxY);
|
87
|
+
|
88
|
+
return { x, y };
|
89
|
+
}
|
90
|
+
|
91
|
+
function closeContextMenu() {
|
92
|
+
contextMenu.visible = false;
|
93
|
+
contextMenu.chart = null;
|
94
|
+
contextMenu.section = null;
|
95
|
+
}
|
96
|
+
|
97
|
+
function handleChartContextMenu(detail: ContextMenuDetail, section: Section) {
|
98
|
+
const { x, y } = computeMenuPosition(detail.clientX, detail.clientY);
|
99
|
+
|
100
|
+
contextMenu.visible = true;
|
101
|
+
contextMenu.x = x;
|
102
|
+
contextMenu.y = y;
|
103
|
+
contextMenu.chart = detail.chart;
|
104
|
+
contextMenu.section = section;
|
105
|
+
}
|
106
|
+
|
107
|
+
function zoomSelectedChart() {
|
108
|
+
if (contextMenu.chart && contextMenu.section) {
|
109
|
+
zoomedChart = { chart: contextMenu.chart, section: contextMenu.section };
|
110
|
+
}
|
111
|
+
closeContextMenu();
|
112
|
+
}
|
113
|
+
|
114
|
+
function exitZoom() {
|
115
|
+
zoomedChart = null;
|
116
|
+
closeContextMenu();
|
117
|
+
}
|
118
|
+
|
119
|
+
function handleOverlayKeydown(event: KeyboardEvent) {
|
120
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
121
|
+
event.preventDefault();
|
122
|
+
exitZoom();
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
function handleOverlayClick(event: MouseEvent) {
|
127
|
+
if (event.target === event.currentTarget) {
|
128
|
+
exitZoom();
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
onMount(() => {
|
133
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
134
|
+
if (event.key === 'Escape') {
|
135
|
+
if (zoomedChart) {
|
136
|
+
exitZoom();
|
137
|
+
}
|
138
|
+
closeContextMenu();
|
139
|
+
}
|
140
|
+
};
|
141
|
+
|
142
|
+
const handleGlobalClick = (event: MouseEvent) => {
|
143
|
+
if (!contextMenu.visible) return;
|
144
|
+
const target = event.target as Node;
|
145
|
+
if (contextMenuElement && contextMenuElement.contains(target)) return;
|
146
|
+
closeContextMenu();
|
147
|
+
};
|
148
|
+
|
149
|
+
window.addEventListener('keydown', handleKeydown);
|
150
|
+
window.addEventListener('click', handleGlobalClick);
|
151
|
+
|
152
|
+
return () => {
|
153
|
+
window.removeEventListener('keydown', handleKeydown);
|
154
|
+
window.removeEventListener('click', handleGlobalClick);
|
155
|
+
};
|
156
|
+
});
|
20
157
|
</script>
|
21
158
|
|
22
159
|
<!-- Reusable chart grid snippet -->
|
23
160
|
{#snippet chartGrid(section: Section)}
|
24
|
-
|
161
|
+
{@const gridConfig = getGridConfig(section.grid)}
|
162
|
+
<div
|
163
|
+
class="chart-grid"
|
164
|
+
data-grid={gridConfig.key}
|
165
|
+
style:grid-template-columns={`repeat(${gridConfig.columns}, minmax(0, 1fr))`}
|
166
|
+
style:grid-template-rows={`repeat(${gridConfig.rows}, minmax(0, 1fr))`}
|
167
|
+
>
|
25
168
|
{#each section.charts as chart}
|
26
169
|
<div class="chart-slot">
|
27
|
-
<ChartCard
|
170
|
+
<ChartCard
|
171
|
+
{chart}
|
172
|
+
{data}
|
173
|
+
{markers}
|
174
|
+
{plotlyLayout}
|
175
|
+
{enableAdaptation}
|
176
|
+
sectionId={section.id}
|
177
|
+
on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, section)}
|
178
|
+
/>
|
28
179
|
</div>
|
29
180
|
{/each}
|
30
181
|
</div>
|
31
182
|
{/snippet}
|
32
183
|
|
33
|
-
<div class="chart-component">
|
34
|
-
{#if
|
184
|
+
<div class="chart-component" bind:this={componentElement}>
|
185
|
+
{#if zoomedChart}
|
186
|
+
{@const activeZoom = zoomedChart as ZoomState}
|
187
|
+
<div
|
188
|
+
class="zoom-overlay"
|
189
|
+
role="button"
|
190
|
+
tabindex="0"
|
191
|
+
onclick={handleOverlayClick}
|
192
|
+
onkeydown={handleOverlayKeydown}
|
193
|
+
>
|
194
|
+
<div class="zoom-container">
|
195
|
+
<button type="button" class="zoom-close" onclick={exitZoom} aria-label="Exit zoom">×</button>
|
196
|
+
<ChartCard
|
197
|
+
chart={activeZoom.chart}
|
198
|
+
{data}
|
199
|
+
{markers}
|
200
|
+
{plotlyLayout}
|
201
|
+
{enableAdaptation}
|
202
|
+
sectionId={activeZoom.section.id}
|
203
|
+
on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, activeZoom.section)}
|
204
|
+
/>
|
205
|
+
</div>
|
206
|
+
</div>
|
207
|
+
{:else if mode === 'tabs'}
|
35
208
|
<!-- Tab Mode with Navigation -->
|
36
209
|
<div class="tabs-container">
|
37
210
|
<!-- Tab Navigation -->
|
@@ -60,7 +233,7 @@
|
|
60
233
|
class="tab-section {section.id === activeTabId ? 'active' : 'hidden'}"
|
61
234
|
data-section-id="{section.id}"
|
62
235
|
>
|
63
|
-
<!--
|
236
|
+
<!-- Chart Grid -->
|
64
237
|
{@render chartGrid(section)}
|
65
238
|
</div>
|
66
239
|
{/each}
|
@@ -84,13 +257,33 @@
|
|
84
257
|
<div class="scrollspy-content">
|
85
258
|
{#each layout.sections as section}
|
86
259
|
<div class="section-content" id="{section.id}">
|
87
|
-
|
260
|
+
<!-- Chart Grid -->
|
88
261
|
{@render chartGrid(section)}
|
89
262
|
</div>
|
90
263
|
{/each}
|
91
264
|
</div>
|
92
265
|
</div>
|
93
266
|
{/if}
|
267
|
+
|
268
|
+
{#if contextMenu.visible}
|
269
|
+
<div
|
270
|
+
class="chart-context-menu"
|
271
|
+
style:top={`${contextMenu.y}px`}
|
272
|
+
style:left={`${contextMenu.x}px`}
|
273
|
+
bind:this={contextMenuElement}
|
274
|
+
>
|
275
|
+
{#if (!zoomedChart || zoomedChart.chart !== contextMenu.chart) && contextMenu.chart && contextMenu.section}
|
276
|
+
<button type="button" class="menu-item" onclick={zoomSelectedChart}>
|
277
|
+
Zoom to chart
|
278
|
+
</button>
|
279
|
+
{/if}
|
280
|
+
{#if zoomedChart}
|
281
|
+
<button type="button" class="menu-item" onclick={exitZoom}>
|
282
|
+
Exit zoom
|
283
|
+
</button>
|
284
|
+
{/if}
|
285
|
+
</div>
|
286
|
+
{/if}
|
94
287
|
</div>
|
95
288
|
|
96
289
|
<style>
|
@@ -100,6 +293,7 @@
|
|
100
293
|
height: 100%;
|
101
294
|
display: flex;
|
102
295
|
flex-direction: column;
|
296
|
+
position: relative;
|
103
297
|
}
|
104
298
|
|
105
299
|
/* Tab Mode */
|
@@ -190,16 +384,97 @@
|
|
190
384
|
margin-bottom: 0.5rem; /* Reduce margin */
|
191
385
|
}
|
192
386
|
|
193
|
-
|
387
|
+
.chart-context-menu {
|
388
|
+
position: absolute;
|
389
|
+
display: flex;
|
390
|
+
flex-direction: column;
|
391
|
+
gap: 0.25rem;
|
392
|
+
padding: 0.5rem;
|
393
|
+
background: #ffffff;
|
394
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
395
|
+
border-radius: 6px;
|
396
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
397
|
+
z-index: 20;
|
398
|
+
min-width: 140px;
|
399
|
+
}
|
400
|
+
|
401
|
+
.chart-context-menu .menu-item {
|
402
|
+
background: none;
|
403
|
+
border: none;
|
404
|
+
text-align: left;
|
405
|
+
padding: 0.375rem 0.5rem;
|
406
|
+
border-radius: 4px;
|
407
|
+
font-size: 0.875rem;
|
408
|
+
cursor: pointer;
|
409
|
+
}
|
410
|
+
|
411
|
+
.chart-context-menu .menu-item:hover {
|
412
|
+
background-color: #f1f3f5;
|
413
|
+
}
|
414
|
+
|
415
|
+
.zoom-overlay {
|
416
|
+
position: absolute;
|
417
|
+
top: 0;
|
418
|
+
left: 0;
|
419
|
+
right: 0;
|
420
|
+
bottom: 0;
|
421
|
+
display: flex;
|
422
|
+
align-items: center;
|
423
|
+
justify-content: center;
|
424
|
+
background: rgba(0, 0, 0, 0.4);
|
425
|
+
z-index: 15;
|
426
|
+
padding: 1.5rem;
|
427
|
+
box-sizing: border-box;
|
428
|
+
}
|
429
|
+
|
430
|
+
.zoom-container {
|
431
|
+
position: relative;
|
432
|
+
width: 100%;
|
433
|
+
height: 100%;
|
434
|
+
max-width: 1200px;
|
435
|
+
max-height: 100%;
|
436
|
+
display: flex;
|
437
|
+
flex-direction: column;
|
438
|
+
}
|
439
|
+
|
440
|
+
.zoom-container :global(.chart-card) {
|
441
|
+
flex: 1;
|
442
|
+
}
|
443
|
+
|
444
|
+
.zoom-close {
|
445
|
+
position: absolute;
|
446
|
+
top: 0.5rem;
|
447
|
+
right: 0.5rem;
|
448
|
+
background: rgba(0, 0, 0, 0.6);
|
449
|
+
color: #ffffff;
|
450
|
+
border: none;
|
451
|
+
width: 2rem;
|
452
|
+
height: 2rem;
|
453
|
+
border-radius: 50%;
|
454
|
+
cursor: pointer;
|
455
|
+
font-size: 1.25rem;
|
456
|
+
line-height: 1;
|
457
|
+
display: flex;
|
458
|
+
align-items: center;
|
459
|
+
justify-content: center;
|
460
|
+
transition: background 0.2s ease;
|
461
|
+
z-index: 1;
|
462
|
+
}
|
463
|
+
|
464
|
+
.zoom-close:hover {
|
465
|
+
background: rgba(0, 0, 0, 0.75);
|
466
|
+
}
|
467
|
+
|
468
|
+
/* Chart grid adapts to layout configuration */
|
194
469
|
.chart-grid {
|
195
470
|
display: grid;
|
196
|
-
grid-template-columns: 1fr 1fr;
|
197
|
-
grid-template-rows: 1fr 1fr;
|
198
471
|
gap: 0.25rem; /* Reduce gap */
|
199
472
|
width: 100%;
|
200
473
|
height: 100%;
|
201
474
|
min-height: 0; /* Remove fixed minimum to allow full flexibility */
|
202
475
|
max-height: 100%; /* Ensure it doesn't exceed container */
|
476
|
+
grid-auto-rows: minmax(0, 1fr);
|
477
|
+
grid-auto-columns: minmax(0, 1fr);
|
203
478
|
}
|
204
479
|
|
205
480
|
.chart-slot {
|
@@ -222,4 +497,4 @@
|
|
222
497
|
min-height: 80px; /* Even smaller minimum for grid */
|
223
498
|
max-height: 100%; /* Constrain to slot */
|
224
499
|
}
|
225
|
-
</style>
|
500
|
+
</style>
|
@@ -6,8 +6,10 @@ export interface KPI {
|
|
6
6
|
unit: string;
|
7
7
|
color?: string;
|
8
8
|
}
|
9
|
+
export type ChartPosition = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
10
|
+
export type ChartGrid = "2x2" | "3x3" | "1x2" | "1x4" | "1x8";
|
9
11
|
export interface Chart {
|
10
|
-
pos
|
12
|
+
pos?: ChartPosition;
|
11
13
|
title: string;
|
12
14
|
yLeft: KPI[];
|
13
15
|
yRight: KPI[];
|
@@ -15,7 +17,8 @@ export interface Chart {
|
|
15
17
|
export interface Section {
|
16
18
|
id: string;
|
17
19
|
title: string;
|
18
|
-
charts: [
|
20
|
+
charts: Chart[];
|
21
|
+
grid?: ChartGrid;
|
19
22
|
}
|
20
23
|
export type Mode = "tabs" | "scrollspy";
|
21
24
|
export interface Layout {
|
@@ -38,7 +38,7 @@ export function createTimeSeriesTrace(data, kpi, timestampField = 'TIMESTAMP', y
|
|
38
38
|
smoothing: 0.3,
|
39
39
|
dash: yaxis === 'y1' ? 'solid' : 'dot' // Y1 = solid, Y2 = dotted
|
40
40
|
},
|
41
|
-
hovertemplate: `<b
|
41
|
+
hovertemplate: `<b>${kpi.name}</b><br>` +
|
42
42
|
`Value: %{y:,.2f} ${kpi.unit}<br>` +
|
43
43
|
'<extra></extra>'
|
44
44
|
};
|
@@ -84,14 +84,12 @@ export function createDefaultPlotlyLayout(title) {
|
|
84
84
|
borderwidth: 1
|
85
85
|
},
|
86
86
|
xaxis: {
|
87
|
-
title: {
|
88
|
-
text: 'Time',
|
89
|
-
font: { size: 12, color: '#7f8c8d' }
|
90
|
-
},
|
91
87
|
showgrid: true,
|
92
88
|
gridcolor: '#ecf0f1',
|
93
89
|
linecolor: '#bdc3c7',
|
94
|
-
tickfont: { size: 11 }
|
90
|
+
tickfont: { size: 11 },
|
91
|
+
tickformat: '%m-%d',
|
92
|
+
hoverformat: '%Y-%m-%d'
|
95
93
|
},
|
96
94
|
yaxis: {
|
97
95
|
showgrid: true,
|
package/dist/Charts/index.d.ts
CHANGED
@@ -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 } from './charts.model.js';
|
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';
|
@@ -4,28 +4,31 @@
|
|
4
4
|
* Utility functions to help parent applications implement grid launching functionality.
|
5
5
|
* These are optional helpers - parents can implement their own launch logic.
|
6
6
|
*/
|
7
|
+
const createLaunchKey = () => `grid-viewer-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
8
|
+
const snapshotComponents = (components) => components.map(({ id, name, icon, description, category }) => ({
|
9
|
+
id,
|
10
|
+
name,
|
11
|
+
icon,
|
12
|
+
description,
|
13
|
+
category
|
14
|
+
}));
|
15
|
+
const persistGridConfiguration = (grid, componentConfigs) => {
|
16
|
+
const key = createLaunchKey();
|
17
|
+
const payload = {
|
18
|
+
grid,
|
19
|
+
componentConfigs
|
20
|
+
};
|
21
|
+
localStorage.setItem(key, JSON.stringify(payload));
|
22
|
+
return key;
|
23
|
+
};
|
7
24
|
/**
|
8
25
|
* Create a launch handler that opens grids in new tabs using localStorage
|
9
26
|
* This recreates the previous built-in behavior as a reusable helper
|
10
27
|
*/
|
11
28
|
export function createWindowLauncher(gridViewerUrl, availableComponents) {
|
29
|
+
const componentSnapshot = snapshotComponents(availableComponents);
|
12
30
|
return (grid) => {
|
13
|
-
|
14
|
-
const key = `grid-viewer-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
15
|
-
// Store only the grid configuration and component IDs - not the actual components
|
16
|
-
const data = {
|
17
|
-
grid,
|
18
|
-
// Store component configs without the actual component objects
|
19
|
-
componentConfigs: availableComponents.map(comp => ({
|
20
|
-
id: comp.id,
|
21
|
-
name: comp.name,
|
22
|
-
icon: comp.icon,
|
23
|
-
description: comp.description,
|
24
|
-
category: comp.category
|
25
|
-
}))
|
26
|
-
};
|
27
|
-
localStorage.setItem(key, JSON.stringify(data));
|
28
|
-
// Open new tab with the key parameter
|
31
|
+
const key = persistGridConfiguration(grid, componentSnapshot);
|
29
32
|
const url = `${gridViewerUrl}?key=${key}`;
|
30
33
|
const newTab = window.open(url, '_blank');
|
31
34
|
// Focus the new tab if it opened successfully
|
@@ -49,21 +52,9 @@ export function createTabLauncher(gridViewerUrl, availableComponents) {
|
|
49
52
|
* Create a launch handler that uses direct URL navigation (same tab)
|
50
53
|
*/
|
51
54
|
export function createNavigationLauncher(gridViewerUrl, availableComponents) {
|
55
|
+
const componentSnapshot = snapshotComponents(availableComponents);
|
52
56
|
return (grid) => {
|
53
|
-
|
54
|
-
const key = `grid-viewer-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
55
|
-
const data = {
|
56
|
-
grid,
|
57
|
-
componentConfigs: availableComponents.map(comp => ({
|
58
|
-
id: comp.id,
|
59
|
-
name: comp.name,
|
60
|
-
icon: comp.icon,
|
61
|
-
description: comp.description,
|
62
|
-
category: comp.category
|
63
|
-
}))
|
64
|
-
};
|
65
|
-
localStorage.setItem(key, JSON.stringify(data));
|
66
|
-
// Navigate to the grid viewer in the same tab
|
57
|
+
const key = persistGridConfiguration(grid, componentSnapshot);
|
67
58
|
window.location.href = `${gridViewerUrl}?key=${key}`;
|
68
59
|
};
|
69
60
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@smartnet360/svelte-components",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.11",
|
4
4
|
"scripts": {
|
5
5
|
"dev": "vite dev",
|
6
6
|
"build": "vite build && npm run prepack",
|
@@ -11,9 +11,9 @@
|
|
11
11
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
12
12
|
"format": "prettier --write .",
|
13
13
|
"lint": "prettier --check . && eslint .",
|
14
|
-
"release:patch": "npm version patch && npm publish --access public",
|
15
|
-
"release:minor": "npm version minor && npm publish --access public",
|
16
|
-
"release:major": "npm version major && npm publish --access public"
|
14
|
+
"release:patch": "npm version patch && npm publish --access public --no-browser",
|
15
|
+
"release:minor": "npm version minor && npm publish --access public --no-browser",
|
16
|
+
"release:major": "npm version major && npm publish --access public --no-browser"
|
17
17
|
},
|
18
18
|
"files": [
|
19
19
|
"dist",
|
@@ -32,8 +32,8 @@
|
|
32
32
|
}
|
33
33
|
},
|
34
34
|
"peerDependencies": {
|
35
|
-
"
|
36
|
-
"
|
35
|
+
"plotly.js-dist-min": "^3.1.0",
|
36
|
+
"svelte": "^5.0.0"
|
37
37
|
},
|
38
38
|
"devDependencies": {
|
39
39
|
"@eslint/compat": "^1.2.5",
|