@sentropic/design-system-svelte 0.33.0 → 0.34.20

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.
Files changed (101) hide show
  1. package/dist/AppHeader.svelte +159 -1
  2. package/dist/AppHeader.svelte.d.ts +18 -1
  3. package/dist/AppHeader.svelte.d.ts.map +1 -1
  4. package/dist/ArcDiagramChart.svelte +380 -0
  5. package/dist/ArcDiagramChart.svelte.d.ts +43 -0
  6. package/dist/ArcDiagramChart.svelte.d.ts.map +1 -0
  7. package/dist/AreaRangeChart.svelte +487 -0
  8. package/dist/AreaRangeChart.svelte.d.ts +38 -0
  9. package/dist/AreaRangeChart.svelte.d.ts.map +1 -0
  10. package/dist/AreaSplineRangeChart.svelte +478 -0
  11. package/dist/AreaSplineRangeChart.svelte.d.ts +37 -0
  12. package/dist/AreaSplineRangeChart.svelte.d.ts.map +1 -0
  13. package/dist/BellCurveChart.svelte +487 -0
  14. package/dist/BellCurveChart.svelte.d.ts +40 -0
  15. package/dist/BellCurveChart.svelte.d.ts.map +1 -0
  16. package/dist/Calendar.svelte +11 -0
  17. package/dist/ChatThread.svelte +32 -1
  18. package/dist/ChatThread.svelte.d.ts +14 -0
  19. package/dist/ChatThread.svelte.d.ts.map +1 -1
  20. package/dist/ColumnPyramidChart.svelte +332 -0
  21. package/dist/ColumnPyramidChart.svelte.d.ts +35 -0
  22. package/dist/ColumnPyramidChart.svelte.d.ts.map +1 -0
  23. package/dist/ColumnRangeChart.svelte +432 -0
  24. package/dist/ColumnRangeChart.svelte.d.ts +42 -0
  25. package/dist/ColumnRangeChart.svelte.d.ts.map +1 -0
  26. package/dist/Combobox.svelte +3 -0
  27. package/dist/ContentSwitcher.svelte +1 -1
  28. package/dist/DataTable.svelte.d.ts +1 -1
  29. package/dist/DatePicker.svelte +3 -0
  30. package/dist/DependencyWheelChart.svelte +413 -0
  31. package/dist/DependencyWheelChart.svelte.d.ts +42 -0
  32. package/dist/DependencyWheelChart.svelte.d.ts.map +1 -0
  33. package/dist/DumbbellChart.svelte +403 -0
  34. package/dist/DumbbellChart.svelte.d.ts +44 -0
  35. package/dist/DumbbellChart.svelte.d.ts.map +1 -0
  36. package/dist/ErrorBarChart.svelte +428 -0
  37. package/dist/ErrorBarChart.svelte.d.ts +40 -0
  38. package/dist/ErrorBarChart.svelte.d.ts.map +1 -0
  39. package/dist/GanttChart.svelte +410 -0
  40. package/dist/GanttChart.svelte.d.ts +39 -0
  41. package/dist/GanttChart.svelte.d.ts.map +1 -0
  42. package/dist/HLCChart.svelte +330 -0
  43. package/dist/HLCChart.svelte.d.ts +32 -0
  44. package/dist/HLCChart.svelte.d.ts.map +1 -0
  45. package/dist/HeikinAshiChart.svelte +365 -0
  46. package/dist/HeikinAshiChart.svelte.d.ts +37 -0
  47. package/dist/HeikinAshiChart.svelte.d.ts.map +1 -0
  48. package/dist/HollowCandlestickChart.svelte +357 -0
  49. package/dist/HollowCandlestickChart.svelte.d.ts +34 -0
  50. package/dist/HollowCandlestickChart.svelte.d.ts.map +1 -0
  51. package/dist/Input.svelte +3 -0
  52. package/dist/ItemChart.svelte +389 -0
  53. package/dist/ItemChart.svelte.d.ts +67 -0
  54. package/dist/ItemChart.svelte.d.ts.map +1 -0
  55. package/dist/Link.svelte +12 -1
  56. package/dist/Link.svelte.d.ts +4 -0
  57. package/dist/Link.svelte.d.ts.map +1 -1
  58. package/dist/LollipopChart.svelte +1 -1
  59. package/dist/MultiSelect.svelte +3 -0
  60. package/dist/NumberInput.svelte +3 -0
  61. package/dist/OHLCChart.svelte +343 -0
  62. package/dist/OHLCChart.svelte.d.ts +33 -0
  63. package/dist/OHLCChart.svelte.d.ts.map +1 -0
  64. package/dist/OrganizationChart.svelte +284 -0
  65. package/dist/OrganizationChart.svelte.d.ts +19 -0
  66. package/dist/OrganizationChart.svelte.d.ts.map +1 -0
  67. package/dist/PasswordInput.svelte +3 -0
  68. package/dist/PolygonChart.svelte +189 -0
  69. package/dist/PolygonChart.svelte.d.ts +17 -0
  70. package/dist/PolygonChart.svelte.d.ts.map +1 -0
  71. package/dist/Search.svelte +7 -5
  72. package/dist/Select.svelte +3 -0
  73. package/dist/StreamgraphChart.svelte +283 -0
  74. package/dist/StreamgraphChart.svelte.d.ts +23 -0
  75. package/dist/StreamgraphChart.svelte.d.ts.map +1 -0
  76. package/dist/StreamingMessage.svelte +44 -2
  77. package/dist/StreamingMessage.svelte.d.ts +18 -1
  78. package/dist/StreamingMessage.svelte.d.ts.map +1 -1
  79. package/dist/TileMapChart.svelte +314 -0
  80. package/dist/TileMapChart.svelte.d.ts +45 -0
  81. package/dist/TileMapChart.svelte.d.ts.map +1 -0
  82. package/dist/TimePicker.svelte +3 -0
  83. package/dist/TimelineChart.svelte +362 -0
  84. package/dist/TimelineChart.svelte.d.ts +22 -0
  85. package/dist/TimelineChart.svelte.d.ts.map +1 -0
  86. package/dist/TreegraphChart.svelte +281 -0
  87. package/dist/TreegraphChart.svelte.d.ts +19 -0
  88. package/dist/TreegraphChart.svelte.d.ts.map +1 -0
  89. package/dist/VariablePieChart.svelte +313 -0
  90. package/dist/VariablePieChart.svelte.d.ts +52 -0
  91. package/dist/VariablePieChart.svelte.d.ts.map +1 -0
  92. package/dist/VennChart.svelte +348 -0
  93. package/dist/VennChart.svelte.d.ts +72 -0
  94. package/dist/VennChart.svelte.d.ts.map +1 -0
  95. package/dist/WordCloudChart.svelte +279 -0
  96. package/dist/WordCloudChart.svelte.d.ts +18 -0
  97. package/dist/WordCloudChart.svelte.d.ts.map +1 -0
  98. package/dist/index.d.ts +48 -0
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +24 -0
  101. package/package.json +5 -3
@@ -18,7 +18,24 @@
18
18
  * Auto-généré et stable si non fourni.
19
19
  */
20
20
  drawerId?: string;
21
- /** Logo (décision actée : logo SENT + sous-titre). */
21
+ /**
22
+ * Marque structurée (décision actée : logo SENT + sous-titre). Rend le bloc
23
+ * canonique « logo carré + nom + sous-titre produit » sans dupliquer de CSS
24
+ * côté consommateur. Si le snippet `logo` est fourni, il a priorité (contrôle
25
+ * total) ; sinon ces props produisent le bloc marque par défaut.
26
+ */
27
+ brandName?: string;
28
+ /** Sous-titre produit affiché sous le nom (ex. « Design System », « dataviz »). */
29
+ productName?: string;
30
+ /** Source de l'image du logo carré (ex. `/SENT-logo-squared.svg`). */
31
+ logoSrc?: string;
32
+ /** Texte alternatif du logo (décoratif par défaut). */
33
+ logoAlt?: string;
34
+ /** Cible du lien de la marque. Défaut : `/`. */
35
+ brandHref?: string;
36
+ /** aria-label du lien de marque (sinon dérivé de `brandName` + `productName`). */
37
+ brandLabel?: string;
38
+ /** Logo (décision actée : logo SENT + sous-titre). Prioritaire sur `brandName`/`logoSrc`. */
22
39
  logo?: Snippet;
23
40
  /** Liens de navigation (rendus dans le <nav> desktop). */
24
41
  nav?: Snippet;
@@ -47,6 +64,12 @@
47
64
  onMenuToggle,
48
65
  menuLabel = "Menu",
49
66
  drawerId,
67
+ brandName,
68
+ productName,
69
+ logoSrc,
70
+ logoAlt = "",
71
+ brandHref = "/",
72
+ brandLabel,
50
73
  logo,
51
74
  nav,
52
75
  actions,
@@ -54,6 +77,15 @@
54
77
  class: className,
55
78
  }: AppHeaderProps = $props();
56
79
 
80
+ // Marque par défaut : rendue ssi aucun snippet `logo` et qu'il y a au moins un
81
+ // nom / logo / produit. Calque le bloc marque canonique du site DS.
82
+ const hasDefaultBrand = $derived(
83
+ !logo && Boolean(brandName || productName || logoSrc),
84
+ );
85
+ const resolvedBrandLabel = $derived(
86
+ brandLabel ?? [brandName, productName].filter(Boolean).join(" "),
87
+ );
88
+
57
89
  // Id stable du tiroir : prop fournie sinon compteur module (SSR-safe, sans
58
90
  // crypto). Capturé une seule fois (untrack) : un id stable ne doit pas réagir.
59
91
  const resolvedDrawerId = untrack(
@@ -68,6 +100,18 @@
68
100
  <!-- Logo SENT à GAUCHE (+ sous-titre). -->
69
101
  {#if logo}
70
102
  <div class="st-appHeader__logo">{@render logo()}</div>
103
+ {:else if hasDefaultBrand}
104
+ <a class="st-appHeader__brand" href={brandHref} aria-label={resolvedBrandLabel || undefined}>
105
+ {#if logoSrc}
106
+ <img class="st-appHeader__brandMark" src={logoSrc} alt={logoAlt} aria-hidden={logoAlt ? undefined : "true"} />
107
+ {/if}
108
+ {#if brandName || productName}
109
+ <span class="st-appHeader__brandCopy">
110
+ {#if brandName}<span class="st-appHeader__brandName">{brandName}</span>{/if}
111
+ {#if productName}<span class="st-appHeader__brandProduct">{productName}</span>{/if}
112
+ </span>
113
+ {/if}
114
+ </a>
71
115
  {/if}
72
116
 
73
117
  <!-- Nav desktop (masquée en mode compact). -->
@@ -185,6 +229,120 @@
185
229
  flex: 0 0 auto;
186
230
  }
187
231
 
232
+ /* --- Marque canonique (logo carré + nom + sous-titre produit) --- */
233
+ .st-appHeader__brand {
234
+ align-items: center;
235
+ color: var(--st-semantic-text-primary);
236
+ display: inline-flex;
237
+ flex: 0 0 auto;
238
+ gap: var(--st-spacing-3, 0.75rem);
239
+ min-width: 0;
240
+ text-decoration: none;
241
+ }
242
+
243
+ .st-appHeader__brand:hover,
244
+ .st-appHeader__brand:focus-visible {
245
+ text-decoration: none;
246
+ }
247
+
248
+ .st-appHeader__brand:focus-visible {
249
+ border-radius: var(--st-radius-sm, 0.375rem);
250
+ box-shadow: 0 0 0 2px var(--st-semantic-border-interactive);
251
+ outline: none;
252
+ }
253
+
254
+ .st-appHeader__brandMark {
255
+ aspect-ratio: 1;
256
+ display: inline-block;
257
+ flex: 0 0 auto;
258
+ height: 2rem;
259
+ object-fit: contain;
260
+ width: 2rem;
261
+ }
262
+
263
+ .st-appHeader__brandCopy {
264
+ display: grid;
265
+ gap: 0.08rem;
266
+ line-height: 1;
267
+ min-width: 0;
268
+ }
269
+
270
+ .st-appHeader__brandName {
271
+ color: var(--st-semantic-text-primary);
272
+ font-size: 1rem;
273
+ font-weight: 760;
274
+ }
275
+
276
+ .st-appHeader__brandProduct {
277
+ color: var(--st-semantic-text-secondary);
278
+ font-size: 0.75rem;
279
+ font-weight: 650;
280
+ }
281
+
282
+ /* --- Lien de nav canonique (pill soulignée, état actif) ---
283
+ Classe utilitaire publiée : un consommateur l'applique sur ses <a> de nav
284
+ (ou via le composant Link DS) pour matcher le chrome du site DS sans
285
+ dupliquer de CSS d'application. */
286
+ :global(.st-appHeader__navLink) {
287
+ align-items: center;
288
+ border-bottom: 2px solid transparent;
289
+ border-radius: 0;
290
+ color: var(--st-semantic-text-secondary);
291
+ display: inline-flex;
292
+ font-size: 0.875rem;
293
+ gap: 0.35rem;
294
+ line-height: 1;
295
+ padding: 0.38rem 0.75rem;
296
+ text-decoration: none;
297
+ white-space: nowrap;
298
+ transition: color var(--st-motion-fast, 120ms) ease,
299
+ border-color var(--st-motion-fast, 120ms) ease;
300
+ }
301
+
302
+ :global(.st-appHeader__navLink:hover),
303
+ :global(.st-appHeader__navLink:focus-visible) {
304
+ color: var(--st-semantic-text-primary);
305
+ text-decoration: none;
306
+ }
307
+
308
+ :global(.st-appHeader__navLink[aria-current="page"]) {
309
+ border-bottom-color: var(--st-semantic-border-interactive);
310
+ color: var(--st-semantic-text-primary);
311
+ font-weight: 650;
312
+ }
313
+
314
+ /* --- Contrôle utilitaire canonique (pill : thème / langue / icône) --- */
315
+ :global(.st-appHeader__control) {
316
+ align-items: center;
317
+ background: var(--st-semantic-surface-default);
318
+ border: 1px solid var(--st-semantic-border-subtle);
319
+ border-radius: var(--st-radius-sm, 0.375rem);
320
+ color: var(--st-semantic-text-secondary);
321
+ cursor: pointer;
322
+ display: inline-flex;
323
+ font: inherit;
324
+ font-size: 0.75rem;
325
+ font-weight: 650;
326
+ gap: 0.35rem;
327
+ height: 2.25rem;
328
+ line-height: 1;
329
+ padding: 0 0.65rem;
330
+ text-decoration: none;
331
+ white-space: nowrap;
332
+ transition: background-color var(--st-motion-fast, 120ms) ease,
333
+ border-color var(--st-motion-fast, 120ms) ease,
334
+ color var(--st-motion-fast, 120ms) ease;
335
+ }
336
+
337
+ :global(.st-appHeader__control:hover),
338
+ :global(.st-appHeader__control:focus-visible),
339
+ :global(.st-appHeader__control[aria-expanded="true"]) {
340
+ background: var(--st-semantic-surface-subtle);
341
+ border-color: var(--st-semantic-border-interactive);
342
+ color: var(--st-semantic-text-primary);
343
+ outline: none;
344
+ }
345
+
188
346
  .st-appHeader__actions {
189
347
  align-items: center;
190
348
  display: flex;
@@ -16,7 +16,24 @@ export interface AppHeaderProps {
16
16
  * Auto-généré et stable si non fourni.
17
17
  */
18
18
  drawerId?: string;
19
- /** Logo (décision actée : logo SENT + sous-titre). */
19
+ /**
20
+ * Marque structurée (décision actée : logo SENT + sous-titre). Rend le bloc
21
+ * canonique « logo carré + nom + sous-titre produit » sans dupliquer de CSS
22
+ * côté consommateur. Si le snippet `logo` est fourni, il a priorité (contrôle
23
+ * total) ; sinon ces props produisent le bloc marque par défaut.
24
+ */
25
+ brandName?: string;
26
+ /** Sous-titre produit affiché sous le nom (ex. « Design System », « dataviz »). */
27
+ productName?: string;
28
+ /** Source de l'image du logo carré (ex. `/SENT-logo-squared.svg`). */
29
+ logoSrc?: string;
30
+ /** Texte alternatif du logo (décoratif par défaut). */
31
+ logoAlt?: string;
32
+ /** Cible du lien de la marque. Défaut : `/`. */
33
+ brandHref?: string;
34
+ /** aria-label du lien de marque (sinon dérivé de `brandName` + `productName`). */
35
+ brandLabel?: string;
36
+ /** Logo (décision actée : logo SENT + sous-titre). Prioritaire sur `brandName`/`logoSrc`. */
20
37
  logo?: Snippet;
21
38
  /** Liens de navigation (rendus dans le <nav> desktop). */
22
39
  nav?: Snippet;
@@ -1 +1 @@
1
- {"version":3,"file":"AppHeader.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AppHeader.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoFH,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"AppHeader.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AppHeader.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6FAA6F;IAC7F,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+GH,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,380 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * ArcDiagramChart - API canonique (référence Svelte, React/Vue doivent s'aligner)
4
+ *
5
+ * Représente des liens pondérés entre nœuds alignés sur un axe horizontal (bas).
6
+ * Chaque nœud est un point sur la ligne, dont la taille croît avec son degré
7
+ * pondéré (somme des poids de ses liens) ; chaque lien est un arc en demi-cercle
8
+ * tracé AU-DESSUS de la ligne reliant ses deux nœuds, dont l'épaisseur croît avec
9
+ * le poids et dont la couleur reprend celle du nœud source.
10
+ *
11
+ * Props obligatoires :
12
+ * data ArcDiagramChartLink[] - liste de liens {from, to, weight}
13
+ * from/to = identifiants de nœuds (string) ;
14
+ * un nœud est créé pour chaque identifiant cité,
15
+ * dans l'ordre d'apparition (union from/to).
16
+ * label string - aria-label du graphique
17
+ *
18
+ * Props optionnelles :
19
+ * labels? Record<string,string> - libellés d'affichage par identifiant de nœud
20
+ * width number (défaut 480) - largeur du viewBox en px
21
+ * height number (défaut 240) - hauteur du viewBox en px
22
+ * class string - classe CSS supplémentaire
23
+ *
24
+ * Garde : seuls les liens dont `weight` est fini et > 0 sont pris en compte.
25
+ * Les liens NaN / Infinity / négatifs / nuls sont ignorés silencieusement.
26
+ */
27
+ export type ArcDiagramChartTone =
28
+ | "category1"
29
+ | "category2"
30
+ | "category3"
31
+ | "category4"
32
+ | "category5"
33
+ | "category6"
34
+ | "category7"
35
+ | "category8";
36
+
37
+ export type ArcDiagramChartLink = {
38
+ from: string;
39
+ to: string;
40
+ weight: number;
41
+ };
42
+ </script>
43
+
44
+ <script lang="ts">
45
+ import ChartDataList from "./ChartDataList.svelte";
46
+ import GraphLegend from "./GraphLegend.svelte";
47
+
48
+ type ArcDiagramChartProps = {
49
+ data: ArcDiagramChartLink[];
50
+ label: string;
51
+ labels?: Record<string, string>;
52
+ width?: number;
53
+ height?: number;
54
+ class?: string;
55
+ };
56
+
57
+ let {
58
+ data,
59
+ label,
60
+ labels,
61
+ width = 480,
62
+ height = 240,
63
+ class: className
64
+ }: ArcDiagramChartProps = $props();
65
+
66
+ const TONES = [
67
+ "category1",
68
+ "category2",
69
+ "category3",
70
+ "category4",
71
+ "category5",
72
+ "category6",
73
+ "category7",
74
+ "category8"
75
+ ] as const;
76
+
77
+ const MARGIN_X = 24; // marge horizontale pour les premiers/derniers nœuds
78
+ const BASELINE_PAD = 28; // distance du bas réservée aux marqueurs/libellés
79
+ const MIN_NODE_R = 4;
80
+ const MAX_NODE_R = 9;
81
+
82
+ function magnitude(value: number): number {
83
+ return Number.isFinite(value) && value > 0 ? value : 0;
84
+ }
85
+
86
+ function displayLabel(id: string): string {
87
+ return labels?.[id] ?? id;
88
+ }
89
+
90
+ let hoveredLinkIndex: number | null = $state(null);
91
+
92
+ const layout = $derived.by(() => {
93
+ const baselineY = height - BASELINE_PAD;
94
+
95
+ const links = data
96
+ .map((link, index) => ({ link, index, weight: magnitude(link.weight) }))
97
+ .filter((entry) => entry.weight > 0);
98
+
99
+ const order: string[] = [];
100
+ const degree = new Map<string, number>();
101
+ for (const { link, weight } of links) {
102
+ for (const id of [link.from, link.to]) {
103
+ if (!degree.has(id)) {
104
+ degree.set(id, 0);
105
+ order.push(id);
106
+ }
107
+ }
108
+ degree.set(link.from, (degree.get(link.from) ?? 0) + weight);
109
+ degree.set(link.to, (degree.get(link.to) ?? 0) + weight);
110
+ }
111
+
112
+ if (order.length === 0) {
113
+ return { baselineY, nodes: [], arcs: [], nodeX: new Map<string, number>() };
114
+ }
115
+
116
+ const usable = Math.max(width - MARGIN_X * 2, 1);
117
+ const step = order.length > 1 ? usable / (order.length - 1) : 0;
118
+ const startX = order.length > 1 ? MARGIN_X : width / 2;
119
+
120
+ const maxDegree = Math.max(1, ...order.map((id) => degree.get(id) ?? 0));
121
+
122
+ type NodeDatum = {
123
+ id: string;
124
+ tone: ArcDiagramChartTone;
125
+ x: number;
126
+ r: number;
127
+ value: number;
128
+ };
129
+ const nodeX = new Map<string, number>();
130
+ const nodeTone = new Map<string, ArcDiagramChartTone>();
131
+ const nodes: NodeDatum[] = order.map((id, index) => {
132
+ const x = startX + step * index;
133
+ const tone = TONES[index % TONES.length];
134
+ const value = degree.get(id) ?? 0;
135
+ const r = MIN_NODE_R + (MAX_NODE_R - MIN_NODE_R) * (value / maxDegree);
136
+ nodeX.set(id, x);
137
+ nodeTone.set(id, tone);
138
+ return { id, tone, x, r, value };
139
+ });
140
+
141
+ const maxWeight = Math.max(1, ...links.map((entry) => entry.weight));
142
+ type ArcDatum = {
143
+ index: number;
144
+ from: string;
145
+ to: string;
146
+ weight: number;
147
+ tone: ArcDiagramChartTone;
148
+ strokeWidth: number;
149
+ path: string;
150
+ midX: number;
151
+ midY: number;
152
+ };
153
+ const arcs: ArcDatum[] = links.map(({ link, weight, index }) => {
154
+ const x1 = nodeX.get(link.from)!;
155
+ const x2 = nodeX.get(link.to)!;
156
+ const left = Math.min(x1, x2);
157
+ const right = Math.max(x1, x2);
158
+ const radius = (right - left) / 2;
159
+ const sweep = x1 <= x2 ? 1 : 0;
160
+ // Demi-cercle au-dessus de la ligne (arc supérieur).
161
+ const path = `M ${x1} ${baselineY} A ${radius} ${radius} 0 0 ${sweep} ${x2} ${baselineY}`;
162
+ const tone = nodeTone.get(link.from)!;
163
+ return {
164
+ index,
165
+ from: link.from,
166
+ to: link.to,
167
+ weight,
168
+ tone,
169
+ strokeWidth: Math.max(1.5, (weight / maxWeight) * 6),
170
+ path,
171
+ midX: (left + right) / 2,
172
+ midY: baselineY - radius
173
+ };
174
+ });
175
+
176
+ return { baselineY, nodes, arcs, nodeX };
177
+ });
178
+
179
+ const legendEntries = $derived(
180
+ layout.nodes.map((node) => ({
181
+ label: displayLabel(node.id),
182
+ shape: "circle" as const,
183
+ tone: node.tone
184
+ }))
185
+ );
186
+
187
+ const dataValueItems = $derived(
188
+ data
189
+ .filter((link) => magnitude(link.weight) > 0)
190
+ .map((link) => `${displayLabel(link.from)} -> ${displayLabel(link.to)}: ${link.weight}`)
191
+ );
192
+
193
+ function handleVisualPointerMove(event: PointerEvent) {
194
+ const target = event.target;
195
+ if (!(target instanceof Element)) {
196
+ hoveredLinkIndex = null;
197
+ return;
198
+ }
199
+ const index = Number(target.getAttribute("data-link-index"));
200
+ hoveredLinkIndex = Number.isInteger(index) ? index : null;
201
+ }
202
+
203
+ const classes = () => ["st-arcDiagramChart", className].filter(Boolean).join(" ");
204
+ </script>
205
+
206
+ <div class={classes()}>
207
+ <div
208
+ class="st-arcDiagramChart__visual"
209
+ role="img"
210
+ aria-label={label}
211
+ onpointermove={handleVisualPointerMove}
212
+ onpointerleave={() => (hoveredLinkIndex = null)}
213
+ >
214
+ <svg
215
+ viewBox="0 0 {width} {height}"
216
+ preserveAspectRatio="xMidYMid meet"
217
+ width="100%"
218
+ height="100%"
219
+ focusable="false"
220
+ aria-hidden="true"
221
+ >
222
+ <line
223
+ class="st-arcDiagramChart__axis"
224
+ x1={MARGIN_X}
225
+ y1={layout.baselineY}
226
+ x2={width - MARGIN_X}
227
+ y2={layout.baselineY}
228
+ />
229
+
230
+ <g class="st-arcDiagramChart__arcs">
231
+ {#each layout.arcs as arc (arc.index)}
232
+ <path
233
+ class="st-arcDiagramChart__arc st-arcDiagramChart__arc--{arc.tone}"
234
+ class:st-arcDiagramChart__arc--dim={hoveredLinkIndex !== null && hoveredLinkIndex !== arc.index}
235
+ d={arc.path}
236
+ stroke-width={arc.strokeWidth}
237
+ data-link-index={arc.index}
238
+ />
239
+ {/each}
240
+ </g>
241
+
242
+ <g class="st-arcDiagramChart__nodes">
243
+ {#each layout.nodes as node (node.id)}
244
+ <circle
245
+ class="st-arcDiagramChart__node st-arcDiagramChart__node--{node.tone}"
246
+ cx={node.x}
247
+ cy={layout.baselineY}
248
+ r={node.r}
249
+ />
250
+ {/each}
251
+ </g>
252
+ </svg>
253
+
254
+ {#if legendEntries.length > 0}
255
+ <GraphLegend class="st-arcDiagramChart__legend" entries={legendEntries} />
256
+ {/if}
257
+ </div>
258
+
259
+ <ChartDataList {label} items={dataValueItems} />
260
+
261
+ {#if hoveredLinkIndex !== null && layout.arcs.find((a) => a.index === hoveredLinkIndex)}
262
+ {@const arc = layout.arcs.find((a) => a.index === hoveredLinkIndex)!}
263
+ <div
264
+ class="st-arcDiagramChart__tooltip"
265
+ role="presentation"
266
+ style="left: {(arc.midX / width) * 100}%; top: {(arc.midY / height) * 100}%"
267
+ >
268
+ <span class="st-arcDiagramChart__tooltipLabel">{displayLabel(arc.from)} -> {displayLabel(arc.to)}</span>
269
+ <span class="st-arcDiagramChart__tooltipValue">{arc.weight}</span>
270
+ </div>
271
+ {/if}
272
+ </div>
273
+
274
+ <style>
275
+ .st-arcDiagramChart {
276
+ color: var(--st-semantic-text-secondary);
277
+ display: block;
278
+ font-family: inherit;
279
+ max-width: 100%;
280
+ position: relative;
281
+ width: 100%;
282
+ }
283
+
284
+ .st-arcDiagramChart svg,
285
+ .st-arcDiagramChart__visual {
286
+ display: block;
287
+ overflow: visible;
288
+ }
289
+
290
+ .st-arcDiagramChart__visual {
291
+ position: relative;
292
+ }
293
+
294
+ .st-arcDiagramChart__legend {
295
+ position: absolute;
296
+ right: 0;
297
+ top: 0;
298
+ }
299
+
300
+ .st-arcDiagramChart__axis {
301
+ stroke: var(--st-semantic-border-subtle, var(--st-semantic-text-secondary));
302
+ stroke-width: 1;
303
+ }
304
+
305
+ .st-arcDiagramChart__arc {
306
+ cursor: pointer;
307
+ fill: none;
308
+ stroke-opacity: 0.6;
309
+ transition: opacity 120ms ease, stroke-opacity 120ms ease;
310
+ }
311
+
312
+ .st-arcDiagramChart__arc:hover {
313
+ stroke-opacity: 0.85;
314
+ }
315
+
316
+ .st-arcDiagramChart__arc--dim {
317
+ opacity: 0.18;
318
+ }
319
+
320
+ @media (prefers-reduced-motion: reduce) {
321
+ .st-arcDiagramChart__arc {
322
+ transition: none;
323
+ }
324
+ }
325
+
326
+ .st-arcDiagramChart__node {
327
+ stroke: var(--st-semantic-surface-default, Canvas);
328
+ stroke-width: 1.5;
329
+ }
330
+
331
+ .st-arcDiagramChart__arc--category1,
332
+ .st-arcDiagramChart__node--category1 { stroke: var(--st-semantic-data-category1); }
333
+ .st-arcDiagramChart__node--category1 { fill: var(--st-semantic-data-category1); }
334
+ .st-arcDiagramChart__arc--category2,
335
+ .st-arcDiagramChart__node--category2 { stroke: var(--st-semantic-data-category2); }
336
+ .st-arcDiagramChart__node--category2 { fill: var(--st-semantic-data-category2); }
337
+ .st-arcDiagramChart__arc--category3,
338
+ .st-arcDiagramChart__node--category3 { stroke: var(--st-semantic-data-category3); }
339
+ .st-arcDiagramChart__node--category3 { fill: var(--st-semantic-data-category3); }
340
+ .st-arcDiagramChart__arc--category4,
341
+ .st-arcDiagramChart__node--category4 { stroke: var(--st-semantic-data-category4); }
342
+ .st-arcDiagramChart__node--category4 { fill: var(--st-semantic-data-category4); }
343
+ .st-arcDiagramChart__arc--category5,
344
+ .st-arcDiagramChart__node--category5 { stroke: var(--st-semantic-data-category5); }
345
+ .st-arcDiagramChart__node--category5 { fill: var(--st-semantic-data-category5); }
346
+ .st-arcDiagramChart__arc--category6,
347
+ .st-arcDiagramChart__node--category6 { stroke: var(--st-semantic-data-category6); }
348
+ .st-arcDiagramChart__node--category6 { fill: var(--st-semantic-data-category6); }
349
+ .st-arcDiagramChart__arc--category7,
350
+ .st-arcDiagramChart__node--category7 { stroke: var(--st-semantic-data-category7); }
351
+ .st-arcDiagramChart__node--category7 { fill: var(--st-semantic-data-category7); }
352
+ .st-arcDiagramChart__arc--category8,
353
+ .st-arcDiagramChart__node--category8 { stroke: var(--st-semantic-data-category8); }
354
+ .st-arcDiagramChart__node--category8 { fill: var(--st-semantic-data-category8); }
355
+
356
+ .st-arcDiagramChart__tooltip {
357
+ background: var(--st-semantic-surface-inverse);
358
+ border-radius: var(--st-radius-sm, 0.25rem);
359
+ color: var(--st-semantic-text-inverse);
360
+ display: inline-flex;
361
+ flex-direction: column;
362
+ font-size: 0.75rem;
363
+ gap: 0.125rem;
364
+ line-height: 1.2;
365
+ padding: 0.375rem 0.5rem;
366
+ pointer-events: none;
367
+ position: absolute;
368
+ transform: translate(-50%, -115%);
369
+ white-space: nowrap;
370
+ z-index: 1;
371
+ }
372
+
373
+ .st-arcDiagramChart__tooltipLabel {
374
+ font-weight: 600;
375
+ }
376
+
377
+ .st-arcDiagramChart__tooltipValue {
378
+ opacity: 0.85;
379
+ }
380
+ </style>
@@ -0,0 +1,43 @@
1
+ /**
2
+ * ArcDiagramChart - API canonique (référence Svelte, React/Vue doivent s'aligner)
3
+ *
4
+ * Représente des liens pondérés entre nœuds alignés sur un axe horizontal (bas).
5
+ * Chaque nœud est un point sur la ligne, dont la taille croît avec son degré
6
+ * pondéré (somme des poids de ses liens) ; chaque lien est un arc en demi-cercle
7
+ * tracé AU-DESSUS de la ligne reliant ses deux nœuds, dont l'épaisseur croît avec
8
+ * le poids et dont la couleur reprend celle du nœud source.
9
+ *
10
+ * Props obligatoires :
11
+ * data ArcDiagramChartLink[] - liste de liens {from, to, weight}
12
+ * from/to = identifiants de nœuds (string) ;
13
+ * un nœud est créé pour chaque identifiant cité,
14
+ * dans l'ordre d'apparition (union from/to).
15
+ * label string - aria-label du graphique
16
+ *
17
+ * Props optionnelles :
18
+ * labels? Record<string,string> - libellés d'affichage par identifiant de nœud
19
+ * width number (défaut 480) - largeur du viewBox en px
20
+ * height number (défaut 240) - hauteur du viewBox en px
21
+ * class string - classe CSS supplémentaire
22
+ *
23
+ * Garde : seuls les liens dont `weight` est fini et > 0 sont pris en compte.
24
+ * Les liens NaN / Infinity / négatifs / nuls sont ignorés silencieusement.
25
+ */
26
+ export type ArcDiagramChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
27
+ export type ArcDiagramChartLink = {
28
+ from: string;
29
+ to: string;
30
+ weight: number;
31
+ };
32
+ type ArcDiagramChartProps = {
33
+ data: ArcDiagramChartLink[];
34
+ label: string;
35
+ labels?: Record<string, string>;
36
+ width?: number;
37
+ height?: number;
38
+ class?: string;
39
+ };
40
+ declare const ArcDiagramChart: import("svelte").Component<ArcDiagramChartProps, {}, "">;
41
+ type ArcDiagramChart = ReturnType<typeof ArcDiagramChart>;
42
+ export default ArcDiagramChart;
43
+ //# sourceMappingURL=ArcDiagramChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ArcDiagramChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ArcDiagramChart.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAOF,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAmMJ,QAAA,MAAM,eAAe,0DAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}