@sentropic/design-system-svelte 0.34.35 → 0.34.37

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.
@@ -0,0 +1,291 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * FlamegraphChart - pile d'appels (call stacks) « icicle » empilée (façon
4
+ * Grafana flamegraph / Brendan Gregg). La LARGEUR d'un nœud est ∝ `value` ; la
5
+ * racine occupe toute la largeur, chaque enfant occupe `value/sum(siblings)` de
6
+ * la largeur de son parent. La PROFONDEUR (niveau) est l'axe vertical : les
7
+ * enfants sont posés sous leur parent, rangée par rangée.
8
+ * API canonique (référence Svelte, React/Vue doivent s'aligner).
9
+ *
10
+ * Modèle : UN nœud racine récursif. La couleur suit la profondeur (cycle sur
11
+ * category1..8). Étiquette tronquée par nœud (ellipsis). Survol d'un nœud →
12
+ * infobulle nom + valeur.
13
+ *
14
+ * Props obligatoires :
15
+ * data FlamegraphNode - { name, value, children? }
16
+ *
17
+ * Props optionnelles :
18
+ * label string
19
+ * width number (défaut 640)
20
+ * height number (défaut 320)
21
+ * size number (alias de width)
22
+ * class string
23
+ */
24
+ export type FlamegraphNode = {
25
+ name: string;
26
+ value: number;
27
+ children?: FlamegraphNode[];
28
+ };
29
+ </script>
30
+
31
+ <script lang="ts">
32
+ import ChartDataList from "./ChartDataList.svelte";
33
+
34
+ type FlamegraphChartProps = {
35
+ data: FlamegraphNode;
36
+ label?: string;
37
+ width?: number;
38
+ height?: number;
39
+ size?: number;
40
+ class?: string;
41
+ };
42
+
43
+ let {
44
+ data,
45
+ label,
46
+ width,
47
+ height = 320,
48
+ size,
49
+ class: className
50
+ }: FlamegraphChartProps = $props();
51
+
52
+ const resolvedWidth = $derived(width ?? size ?? 640);
53
+
54
+ const MARGIN = { top: 16, right: 16, bottom: 16, left: 16 };
55
+ const ROW_H = 26;
56
+ const ROW_GAP = 2;
57
+
58
+ let hoveredKey: string | null = $state(null);
59
+
60
+ const plotWidth = $derived(Math.max(resolvedWidth - MARGIN.left - MARGIN.right, 1));
61
+
62
+ // Couleur par profondeur (1..8, cyclé).
63
+ const toneForDepth = (depth: number): string => `category${(depth % 8) + 1}`;
64
+
65
+ // Tronque une étiquette à la largeur du rectangle (approx. par char).
66
+ function ellipsize(text: string, maxChars: number): string {
67
+ if (text.length <= maxChars) return text;
68
+ if (maxChars <= 1) return "…";
69
+ return `${text.slice(0, maxChars - 1)}…`;
70
+ }
71
+
72
+ type Cell = {
73
+ key: string;
74
+ name: string;
75
+ value: number;
76
+ depth: number;
77
+ x: number;
78
+ y: number;
79
+ width: number;
80
+ tone: string;
81
+ cx: number;
82
+ cy: number;
83
+ };
84
+
85
+ // Layout récursif « icicle » : la racine occupe `plotWidth` ; chaque enfant
86
+ // occupe une fraction `value/sum(siblings)` de la largeur de son parent. y =
87
+ // profondeur × (hauteur de rangée + gap).
88
+ const cells = $derived.by(() => {
89
+ const out: Cell[] = [];
90
+ if (!data || typeof data.name !== "string" || !Number.isFinite(data.value)) return out;
91
+ let maxDepth = 0;
92
+
93
+ const walk = (node: FlamegraphNode, depth: number, x0: number, w: number, path: string) => {
94
+ if (w <= 0) return;
95
+ if (depth > maxDepth) maxDepth = depth;
96
+ const y = MARGIN.top + depth * (ROW_H + ROW_GAP);
97
+ out.push({
98
+ key: path,
99
+ name: node.name,
100
+ value: node.value,
101
+ depth,
102
+ x: x0,
103
+ y,
104
+ width: w,
105
+ tone: toneForDepth(depth),
106
+ cx: x0 + w / 2,
107
+ cy: y + ROW_H / 2
108
+ });
109
+ const kids = (node.children ?? []).filter(
110
+ (c) => c && typeof c.name === "string" && Number.isFinite(c.value) && c.value > 0
111
+ );
112
+ if (kids.length === 0) return;
113
+ const total = kids.reduce((s, c) => s + Math.max(c.value, 0), 0);
114
+ if (total <= 0) return;
115
+ let cursor = x0;
116
+ kids.forEach((child, ci) => {
117
+ const cw = (Math.max(child.value, 0) / total) * w;
118
+ walk(child, depth + 1, cursor, cw, `${path}.${ci}`);
119
+ cursor += cw;
120
+ });
121
+ };
122
+
123
+ walk(data, 0, MARGIN.left, plotWidth, "0");
124
+ return out;
125
+ });
126
+
127
+ const computedHeight = $derived.by(() => {
128
+ if (cells.length === 0) return height;
129
+ const maxDepth = cells.reduce((m, c) => Math.max(m, c.depth), 0);
130
+ const needed = MARGIN.top + (maxDepth + 1) * (ROW_H + ROW_GAP) - ROW_GAP + MARGIN.bottom;
131
+ return Math.max(height, needed);
132
+ });
133
+
134
+ // Nombre de caractères affichables avant ellipsis (approx. largeur de glyphe).
135
+ const charsFor = (w: number) => Math.max(0, Math.floor((w - 8) / 6.6));
136
+
137
+ const dataValueItems = $derived(cells.map((c) => `${"·".repeat(c.depth)}${c.name}: ${c.value}`));
138
+
139
+ function handlePointerMove(event: PointerEvent) {
140
+ const target = event.target;
141
+ if (!(target instanceof Element)) {
142
+ hoveredKey = null;
143
+ return;
144
+ }
145
+ const key = target.getAttribute("data-chart-key");
146
+ hoveredKey = key ?? null;
147
+ }
148
+
149
+ const hoveredCell = $derived.by(() => {
150
+ if (hoveredKey === null) return null;
151
+ return cells.find((c) => c.key === hoveredKey) ?? null;
152
+ });
153
+
154
+ const classes = () => ["st-flamegraphChart", className].filter(Boolean).join(" ");
155
+ </script>
156
+
157
+ <div class={classes()}>
158
+ <div
159
+ class="st-flamegraphChart__visual"
160
+ role="img"
161
+ aria-label={label}
162
+ onpointermove={handlePointerMove}
163
+ onpointerleave={() => (hoveredKey = null)}
164
+ >
165
+ <svg
166
+ viewBox="0 0 {resolvedWidth} {computedHeight}"
167
+ preserveAspectRatio="xMidYMid meet"
168
+ width="100%"
169
+ height="100%"
170
+ focusable="false"
171
+ aria-hidden="true"
172
+ >
173
+ <!-- une rangée par profondeur : rectangle dont la largeur ∝ value -->
174
+ {#each cells as cell (cell.key)}
175
+ {@const chars = charsFor(cell.width)}
176
+ <g class="st-flamegraphChart__node">
177
+ <rect
178
+ class="st-flamegraphChart__frame st-flamegraphChart__frame--{cell.tone}"
179
+ class:st-flamegraphChart__frame--dim={hoveredKey !== null && hoveredKey !== cell.key}
180
+ x={cell.x}
181
+ y={cell.y}
182
+ width={Math.max(cell.width, 1)}
183
+ height={ROW_H}
184
+ rx="2"
185
+ data-chart-key={cell.key}
186
+ />
187
+ {#if chars >= 2}
188
+ <text
189
+ class="st-flamegraphChart__label"
190
+ x={cell.x + 4}
191
+ y={cell.y + ROW_H / 2}
192
+ dominant-baseline="central"
193
+ >
194
+ {ellipsize(cell.name, chars)}
195
+ </text>
196
+ {/if}
197
+ </g>
198
+ {/each}
199
+ </svg>
200
+ </div>
201
+
202
+ <ChartDataList label={label ?? "flamegraph"} items={dataValueItems} />
203
+
204
+ {#if hoveredCell}
205
+ {@const cell = hoveredCell}
206
+ <div
207
+ class="st-flamegraphChart__tooltip"
208
+ role="presentation"
209
+ style="left: {(cell.cx / resolvedWidth) * 100}%; top: {(cell.cy / computedHeight) * 100}%"
210
+ >
211
+ <span class="st-flamegraphChart__tooltipLabel">{cell.name}</span>
212
+ <span class="st-flamegraphChart__tooltipValue">{cell.value}</span>
213
+ </div>
214
+ {/if}
215
+ </div>
216
+
217
+ <style>
218
+ .st-flamegraphChart {
219
+ color: var(--st-semantic-text-secondary);
220
+ display: block;
221
+ font-family: inherit;
222
+ position: relative;
223
+ width: 100%;
224
+ }
225
+
226
+ .st-flamegraphChart svg {
227
+ display: block;
228
+ overflow: visible;
229
+ }
230
+
231
+ .st-flamegraphChart__visual {
232
+ display: block;
233
+ }
234
+
235
+ .st-flamegraphChart__frame {
236
+ cursor: pointer;
237
+ stroke: var(--st-semantic-surface-default, Canvas);
238
+ stroke-width: 1;
239
+ transition: opacity 120ms ease;
240
+ }
241
+
242
+ .st-flamegraphChart__frame--dim {
243
+ opacity: 0.4;
244
+ }
245
+
246
+ .st-flamegraphChart__frame--category1 { fill: var(--st-semantic-data-category1); }
247
+ .st-flamegraphChart__frame--category2 { fill: var(--st-semantic-data-category2); }
248
+ .st-flamegraphChart__frame--category3 { fill: var(--st-semantic-data-category3); }
249
+ .st-flamegraphChart__frame--category4 { fill: var(--st-semantic-data-category4); }
250
+ .st-flamegraphChart__frame--category5 { fill: var(--st-semantic-data-category5); }
251
+ .st-flamegraphChart__frame--category6 { fill: var(--st-semantic-data-category6); }
252
+ .st-flamegraphChart__frame--category7 { fill: var(--st-semantic-data-category7); }
253
+ .st-flamegraphChart__frame--category8 { fill: var(--st-semantic-data-category8); }
254
+
255
+ .st-flamegraphChart__label {
256
+ fill: var(--st-semantic-text-inverse, #fff);
257
+ font-size: 0.6875rem;
258
+ pointer-events: none;
259
+ }
260
+
261
+ .st-flamegraphChart__tooltip {
262
+ background: var(--st-semantic-surface-inverse);
263
+ border-radius: var(--st-radius-sm, 0.25rem);
264
+ color: var(--st-semantic-text-inverse);
265
+ display: inline-flex;
266
+ flex-direction: column;
267
+ font-size: 0.75rem;
268
+ gap: 0.125rem;
269
+ line-height: 1.2;
270
+ padding: 0.375rem 0.5rem;
271
+ pointer-events: none;
272
+ position: absolute;
273
+ transform: translate(-50%, calc(-100% - 8px));
274
+ white-space: nowrap;
275
+ z-index: 1;
276
+ }
277
+
278
+ .st-flamegraphChart__tooltipLabel {
279
+ font-weight: 600;
280
+ }
281
+
282
+ .st-flamegraphChart__tooltipValue {
283
+ opacity: 0.85;
284
+ }
285
+
286
+ @media (prefers-reduced-motion: reduce) {
287
+ .st-flamegraphChart__frame {
288
+ transition: none;
289
+ }
290
+ }
291
+ </style>
@@ -0,0 +1,39 @@
1
+ /**
2
+ * FlamegraphChart - pile d'appels (call stacks) « icicle » empilée (façon
3
+ * Grafana flamegraph / Brendan Gregg). La LARGEUR d'un nœud est ∝ `value` ; la
4
+ * racine occupe toute la largeur, chaque enfant occupe `value/sum(siblings)` de
5
+ * la largeur de son parent. La PROFONDEUR (niveau) est l'axe vertical : les
6
+ * enfants sont posés sous leur parent, rangée par rangée.
7
+ * API canonique (référence Svelte, React/Vue doivent s'aligner).
8
+ *
9
+ * Modèle : UN nœud racine récursif. La couleur suit la profondeur (cycle sur
10
+ * category1..8). Étiquette tronquée par nœud (ellipsis). Survol d'un nœud →
11
+ * infobulle nom + valeur.
12
+ *
13
+ * Props obligatoires :
14
+ * data FlamegraphNode - { name, value, children? }
15
+ *
16
+ * Props optionnelles :
17
+ * label string
18
+ * width number (défaut 640)
19
+ * height number (défaut 320)
20
+ * size number (alias de width)
21
+ * class string
22
+ */
23
+ export type FlamegraphNode = {
24
+ name: string;
25
+ value: number;
26
+ children?: FlamegraphNode[];
27
+ };
28
+ type FlamegraphChartProps = {
29
+ data: FlamegraphNode;
30
+ label?: string;
31
+ width?: number;
32
+ height?: number;
33
+ size?: number;
34
+ class?: string;
35
+ };
36
+ declare const FlamegraphChart: import("svelte").Component<FlamegraphChartProps, {}, "">;
37
+ type FlamegraphChart = ReturnType<typeof FlamegraphChart>;
38
+ export default FlamegraphChart;
39
+ //# sourceMappingURL=FlamegraphChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlamegraphChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/FlamegraphChart.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B,CAAC;AAMF,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA2JJ,QAAA,MAAM,eAAe,0DAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}