@sentropic/design-system-svelte 0.34.0 → 0.34.21

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 (112) hide show
  1. package/dist/AppChrome.svelte +660 -0
  2. package/dist/AppChrome.svelte.d.ts +74 -0
  3. package/dist/AppChrome.svelte.d.ts.map +1 -0
  4. package/dist/AppChrome.test.d.ts +2 -0
  5. package/dist/AppChrome.test.d.ts.map +1 -0
  6. package/dist/AppChrome.test.js +122 -0
  7. package/dist/AppHeader.svelte +159 -1
  8. package/dist/AppHeader.svelte.d.ts +18 -1
  9. package/dist/AppHeader.svelte.d.ts.map +1 -1
  10. package/dist/ArcDiagramChart.svelte +380 -0
  11. package/dist/ArcDiagramChart.svelte.d.ts +43 -0
  12. package/dist/ArcDiagramChart.svelte.d.ts.map +1 -0
  13. package/dist/AreaRangeChart.svelte +487 -0
  14. package/dist/AreaRangeChart.svelte.d.ts +38 -0
  15. package/dist/AreaRangeChart.svelte.d.ts.map +1 -0
  16. package/dist/AreaSplineRangeChart.svelte +478 -0
  17. package/dist/AreaSplineRangeChart.svelte.d.ts +37 -0
  18. package/dist/AreaSplineRangeChart.svelte.d.ts.map +1 -0
  19. package/dist/BellCurveChart.svelte +487 -0
  20. package/dist/BellCurveChart.svelte.d.ts +40 -0
  21. package/dist/BellCurveChart.svelte.d.ts.map +1 -0
  22. package/dist/Calendar.svelte +11 -0
  23. package/dist/ChatThread.svelte +32 -1
  24. package/dist/ChatThread.svelte.d.ts +14 -0
  25. package/dist/ChatThread.svelte.d.ts.map +1 -1
  26. package/dist/ColumnPyramidChart.svelte +332 -0
  27. package/dist/ColumnPyramidChart.svelte.d.ts +35 -0
  28. package/dist/ColumnPyramidChart.svelte.d.ts.map +1 -0
  29. package/dist/ColumnRangeChart.svelte +432 -0
  30. package/dist/ColumnRangeChart.svelte.d.ts +42 -0
  31. package/dist/ColumnRangeChart.svelte.d.ts.map +1 -0
  32. package/dist/Combobox.svelte +3 -0
  33. package/dist/ConfigItemCard.svelte +303 -0
  34. package/dist/ConfigItemCard.svelte.d.ts +37 -0
  35. package/dist/ConfigItemCard.svelte.d.ts.map +1 -0
  36. package/dist/ContentSwitcher.svelte +1 -1
  37. package/dist/DatePicker.svelte +3 -0
  38. package/dist/DependencyWheelChart.svelte +413 -0
  39. package/dist/DependencyWheelChart.svelte.d.ts +42 -0
  40. package/dist/DependencyWheelChart.svelte.d.ts.map +1 -0
  41. package/dist/DumbbellChart.svelte +403 -0
  42. package/dist/DumbbellChart.svelte.d.ts +44 -0
  43. package/dist/DumbbellChart.svelte.d.ts.map +1 -0
  44. package/dist/ErrorBarChart.svelte +428 -0
  45. package/dist/ErrorBarChart.svelte.d.ts +40 -0
  46. package/dist/ErrorBarChart.svelte.d.ts.map +1 -0
  47. package/dist/FieldCard.svelte +220 -0
  48. package/dist/FieldCard.svelte.d.ts +28 -0
  49. package/dist/FieldCard.svelte.d.ts.map +1 -0
  50. package/dist/GanttChart.svelte +410 -0
  51. package/dist/GanttChart.svelte.d.ts +39 -0
  52. package/dist/GanttChart.svelte.d.ts.map +1 -0
  53. package/dist/HLCChart.svelte +330 -0
  54. package/dist/HLCChart.svelte.d.ts +32 -0
  55. package/dist/HLCChart.svelte.d.ts.map +1 -0
  56. package/dist/HeikinAshiChart.svelte +365 -0
  57. package/dist/HeikinAshiChart.svelte.d.ts +37 -0
  58. package/dist/HeikinAshiChart.svelte.d.ts.map +1 -0
  59. package/dist/HollowCandlestickChart.svelte +357 -0
  60. package/dist/HollowCandlestickChart.svelte.d.ts +34 -0
  61. package/dist/HollowCandlestickChart.svelte.d.ts.map +1 -0
  62. package/dist/Input.svelte +3 -0
  63. package/dist/ItemChart.svelte +389 -0
  64. package/dist/ItemChart.svelte.d.ts +67 -0
  65. package/dist/ItemChart.svelte.d.ts.map +1 -0
  66. package/dist/LollipopChart.svelte +1 -1
  67. package/dist/MultiSelect.svelte +3 -0
  68. package/dist/NumberInput.svelte +3 -0
  69. package/dist/OHLCChart.svelte +343 -0
  70. package/dist/OHLCChart.svelte.d.ts +33 -0
  71. package/dist/OHLCChart.svelte.d.ts.map +1 -0
  72. package/dist/OrganizationChart.svelte +284 -0
  73. package/dist/OrganizationChart.svelte.d.ts +19 -0
  74. package/dist/OrganizationChart.svelte.d.ts.map +1 -0
  75. package/dist/PasswordInput.svelte +3 -0
  76. package/dist/PolygonChart.svelte +189 -0
  77. package/dist/PolygonChart.svelte.d.ts +17 -0
  78. package/dist/PolygonChart.svelte.d.ts.map +1 -0
  79. package/dist/ScoreCard.svelte +172 -0
  80. package/dist/ScoreCard.svelte.d.ts +27 -0
  81. package/dist/ScoreCard.svelte.d.ts.map +1 -0
  82. package/dist/Search.svelte +7 -5
  83. package/dist/Select.svelte +3 -0
  84. package/dist/StreamgraphChart.svelte +283 -0
  85. package/dist/StreamgraphChart.svelte.d.ts +23 -0
  86. package/dist/StreamgraphChart.svelte.d.ts.map +1 -0
  87. package/dist/StreamingMessage.svelte +44 -2
  88. package/dist/StreamingMessage.svelte.d.ts +18 -1
  89. package/dist/StreamingMessage.svelte.d.ts.map +1 -1
  90. package/dist/TileMapChart.svelte +314 -0
  91. package/dist/TileMapChart.svelte.d.ts +45 -0
  92. package/dist/TileMapChart.svelte.d.ts.map +1 -0
  93. package/dist/TimePicker.svelte +3 -0
  94. package/dist/TimelineChart.svelte +362 -0
  95. package/dist/TimelineChart.svelte.d.ts +22 -0
  96. package/dist/TimelineChart.svelte.d.ts.map +1 -0
  97. package/dist/TreegraphChart.svelte +281 -0
  98. package/dist/TreegraphChart.svelte.d.ts +19 -0
  99. package/dist/TreegraphChart.svelte.d.ts.map +1 -0
  100. package/dist/VariablePieChart.svelte +313 -0
  101. package/dist/VariablePieChart.svelte.d.ts +52 -0
  102. package/dist/VariablePieChart.svelte.d.ts.map +1 -0
  103. package/dist/VennChart.svelte +348 -0
  104. package/dist/VennChart.svelte.d.ts +72 -0
  105. package/dist/VennChart.svelte.d.ts.map +1 -0
  106. package/dist/WordCloudChart.svelte +279 -0
  107. package/dist/WordCloudChart.svelte.d.ts +18 -0
  108. package/dist/WordCloudChart.svelte.d.ts.map +1 -0
  109. package/dist/index.d.ts +56 -0
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +28 -0
  112. package/package.json +5 -3
@@ -0,0 +1,348 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * VennChart (diagramme de Venn / Euler) - API canonique (référence Svelte,
4
+ * React/Vue doivent s'aligner byte-pour-byte sur la géométrie).
5
+ *
6
+ * Deux ou trois ensembles dessinés en cercles superposés semi-transparents.
7
+ * Chaque zone (region) est décrite par les ensembles qu'elle couvre et une
8
+ * valeur :
9
+ * - { sets: ["A"], value } → membres de A seul ;
10
+ * - { sets: ["A","B"], value } → intersection A∩B ;
11
+ * - { sets: ["A","B","C"], value} → intersection triple.
12
+ *
13
+ * Géométrie DÉTERMINISTE (aucun aléatoire, layout identique entre frameworks) :
14
+ * - Les cercles sont placés à des positions FIXES selon le nombre d'ensembles
15
+ * (2 : côte à côte avec recouvrement ; 3 : triangle équilatéral).
16
+ * - Le rayon de chaque cercle ∝ √(taille totale de l'ensemble), où la taille
17
+ * d'un ensemble est la somme des valeurs de toutes les zones qui le
18
+ * contiennent. Mapping linéaire du √ entre un rayon min et un rayon max.
19
+ *
20
+ * Étiquettes : le nom de chaque ensemble est posé près du cercle ; la valeur de
21
+ * chaque zone d'intersection (sets.length ≥ 2) est posée au centroïde des
22
+ * centres des cercles concernés. Légende des ensembles + tooltip au survol +
23
+ * liste accessible (ChartDataList) hors SVG.
24
+ *
25
+ * Props :
26
+ * data VennChartArea[] - {sets, value}
27
+ * label string - aria-label (role=img)
28
+ * width number (420) - largeur du viewBox
29
+ * height number (360) - hauteur du viewBox
30
+ * class string - classe CSS additionnelle
31
+ */
32
+ export type VennChartTone =
33
+ | "category1"
34
+ | "category2"
35
+ | "category3"
36
+ | "category4"
37
+ | "category5"
38
+ | "category6"
39
+ | "category7"
40
+ | "category8";
41
+
42
+ export type VennChartArea = {
43
+ sets: string[];
44
+ value: number;
45
+ };
46
+
47
+ export type VennChartProps = {
48
+ data: VennChartArea[];
49
+ label: string;
50
+ width?: number;
51
+ height?: number;
52
+ class?: string;
53
+ };
54
+
55
+ const TONES: VennChartTone[] = [
56
+ "category1",
57
+ "category2",
58
+ "category3",
59
+ "category4",
60
+ "category5",
61
+ "category6",
62
+ "category7",
63
+ "category8",
64
+ ];
65
+
66
+ type VennCircle = {
67
+ name: string;
68
+ tone: VennChartTone;
69
+ cx: number;
70
+ cy: number;
71
+ r: number;
72
+ total: number;
73
+ labelX: number;
74
+ labelY: number;
75
+ anchor: "start" | "middle" | "end";
76
+ };
77
+
78
+ type VennRegion = {
79
+ sets: string[];
80
+ value: number;
81
+ x: number;
82
+ y: number;
83
+ };
84
+
85
+ export type VennLayout = {
86
+ circles: VennCircle[];
87
+ regions: VennRegion[];
88
+ items: string[];
89
+ };
90
+
91
+ function safeValue(value: number): number {
92
+ return Number.isFinite(value) && value > 0 ? value : 0;
93
+ }
94
+
95
+ /**
96
+ * Calcule la géométrie déterministe du diagramme. Identique entre frameworks.
97
+ */
98
+ export function vennLayout(data: VennChartArea[], width: number, height: number): VennLayout {
99
+ const areas = data
100
+ .map((d) => ({ sets: Array.isArray(d.sets) ? d.sets.filter((s) => typeof s === "string") : [], value: safeValue(d.value) }))
101
+ .filter((d) => d.sets.length > 0 && d.value > 0);
102
+
103
+ // Ensembles dans l'ordre de première apparition (déterministe).
104
+ const order: string[] = [];
105
+ for (const a of areas) {
106
+ for (const s of a.sets) if (!order.includes(s)) order.push(s);
107
+ }
108
+ const names = order.slice(0, 3);
109
+
110
+ if (names.length === 0) {
111
+ return { circles: [], regions: [], items: [] };
112
+ }
113
+
114
+ // Taille totale d'un ensemble = somme des valeurs des zones qui le contiennent.
115
+ const totals = new Map<string, number>();
116
+ for (const name of names) {
117
+ let sum = 0;
118
+ for (const a of areas) if (a.sets.includes(name)) sum += a.value;
119
+ totals.set(name, sum);
120
+ }
121
+
122
+ const cx = width / 2;
123
+ const cy = height / 2;
124
+ const span = Math.min(width, height);
125
+ const rMax = span * 0.3;
126
+ const rMin = span * 0.2;
127
+ const roots = names.map((n) => Math.sqrt(totals.get(n) ?? 0));
128
+ const rootMin = Math.min(...roots);
129
+ const rootMax = Math.max(...roots);
130
+ const rootSpan = rootMax - rootMin;
131
+ const radiusFor = (root: number) => (rootSpan > 0 ? rMin + ((root - rootMin) / rootSpan) * (rMax - rMin) : rMax);
132
+
133
+ // Positions FIXES des centres selon le nombre d'ensembles.
134
+ let centers: Array<{ cx: number; cy: number }>;
135
+ if (names.length === 1) {
136
+ centers = [{ cx, cy }];
137
+ } else if (names.length === 2) {
138
+ const off = span * 0.16;
139
+ centers = [
140
+ { cx: cx - off, cy },
141
+ { cx: cx + off, cy },
142
+ ];
143
+ } else {
144
+ // Triangle équilatéral (pointe en bas), centré.
145
+ const off = span * 0.17;
146
+ centers = [
147
+ { cx: cx - off, cy: cy - off * 0.6 },
148
+ { cx: cx + off, cy: cy - off * 0.6 },
149
+ { cx, cy: cy + off * 0.85 },
150
+ ];
151
+ }
152
+
153
+ const circles: VennCircle[] = names.map((name, i) => {
154
+ const r = radiusFor(roots[i]);
155
+ const c = centers[i];
156
+ // Étiquette poussée vers l'extérieur depuis le centre du diagramme.
157
+ const dx = c.cx - cx;
158
+ const dy = c.cy - cy;
159
+ const len = Math.hypot(dx, dy) || 1;
160
+ const ux = names.length === 1 ? 0 : dx / len;
161
+ const uy = names.length === 1 ? -1 : dy / len;
162
+ const labelX = c.cx + ux * (r + 6);
163
+ const labelY = c.cy + uy * (r + 6);
164
+ const anchor: "start" | "middle" | "end" = ux > 0.3 ? "start" : ux < -0.3 ? "end" : "middle";
165
+ return {
166
+ name,
167
+ tone: TONES[i % TONES.length],
168
+ cx: c.cx,
169
+ cy: c.cy,
170
+ r,
171
+ total: totals.get(name) ?? 0,
172
+ labelX,
173
+ labelY,
174
+ anchor,
175
+ };
176
+ });
177
+
178
+ const centerByName = new Map(circles.map((c) => [c.name, c]));
179
+ const regions: VennRegion[] = areas
180
+ .filter((a) => a.sets.length >= 2)
181
+ .map((a) => {
182
+ const pts = a.sets.map((s) => centerByName.get(s)).filter((c): c is VennCircle => c !== undefined);
183
+ const px = pts.length > 0 ? pts.reduce((s, c) => s + c.cx, 0) / pts.length : cx;
184
+ const py = pts.length > 0 ? pts.reduce((s, c) => s + c.cy, 0) / pts.length : cy;
185
+ return { sets: a.sets, value: a.value, x: px, y: py };
186
+ });
187
+
188
+ const items = areas.map((a) => `${a.sets.join(" ∩ ")}: ${a.value}`);
189
+
190
+ return { circles, regions, items };
191
+ }
192
+ </script>
193
+
194
+ <script lang="ts">
195
+ import ChartDataList from "./ChartDataList.svelte";
196
+
197
+ let { data, label, width = 420, height = 360, class: className }: VennChartProps = $props();
198
+
199
+ let hoveredIndex: number | null = $state(null);
200
+
201
+ const layout = $derived(vennLayout(data, width, height));
202
+ const classes = () => ["st-vennChart", className].filter(Boolean).join(" ");
203
+
204
+ function handleVisualPointerMove(event: PointerEvent) {
205
+ const target = event.target;
206
+ if (!(target instanceof Element)) {
207
+ hoveredIndex = null;
208
+ return;
209
+ }
210
+ const index = Number(target.getAttribute("data-chart-index"));
211
+ hoveredIndex = Number.isInteger(index) ? index : null;
212
+ }
213
+
214
+ const hovered = $derived(hoveredIndex !== null ? layout.circles[hoveredIndex] : undefined);
215
+ </script>
216
+
217
+ <div class={classes()}>
218
+ <div
219
+ class="st-vennChart__visual"
220
+ role="img"
221
+ aria-label={label}
222
+ onpointermove={handleVisualPointerMove}
223
+ onpointerleave={() => (hoveredIndex = null)}
224
+ >
225
+ <svg
226
+ viewBox="0 0 {width} {height}"
227
+ preserveAspectRatio="xMidYMid meet"
228
+ width="100%"
229
+ height="100%"
230
+ focusable="false"
231
+ aria-hidden="true"
232
+ >
233
+ {#each layout.circles as circle, i (circle.name)}
234
+ <circle
235
+ class="st-vennChart__circle st-vennChart__circle--{circle.tone}"
236
+ class:st-vennChart__circle--dim={hoveredIndex !== null && hoveredIndex !== i}
237
+ cx={circle.cx}
238
+ cy={circle.cy}
239
+ r={circle.r}
240
+ data-chart-index={i}
241
+ />
242
+ {/each}
243
+
244
+ {#each layout.regions as region (region.sets.join("|"))}
245
+ <text class="st-vennChart__value" x={region.x} y={region.y} text-anchor="middle" dominant-baseline="middle">
246
+ {region.value}
247
+ </text>
248
+ {/each}
249
+
250
+ {#each layout.circles as circle (circle.name)}
251
+ <text
252
+ class="st-vennChart__label"
253
+ x={circle.labelX}
254
+ y={circle.labelY}
255
+ text-anchor={circle.anchor}
256
+ dominant-baseline="middle"
257
+ >
258
+ {circle.name}
259
+ </text>
260
+ {/each}
261
+ </svg>
262
+ </div>
263
+
264
+ <ChartDataList {label} items={layout.items} />
265
+
266
+ {#if hovered}
267
+ <div
268
+ class="st-vennChart__tooltip"
269
+ role="presentation"
270
+ style="left: {(hovered.cx / width) * 100}%; top: {(hovered.cy / height) * 100}%;"
271
+ >
272
+ <span class="st-vennChart__tooltipLabel">{hovered.name}</span>
273
+ <span class="st-vennChart__tooltipValue">{hovered.total}</span>
274
+ </div>
275
+ {/if}
276
+ </div>
277
+
278
+ <style>
279
+ .st-vennChart {
280
+ color: var(--st-semantic-text-secondary);
281
+ display: block;
282
+ font-family: inherit;
283
+ max-width: 100%;
284
+ position: relative;
285
+ width: 100%;
286
+ }
287
+
288
+ .st-vennChart svg,
289
+ .st-vennChart__visual {
290
+ display: block;
291
+ overflow: visible;
292
+ }
293
+
294
+ .st-vennChart__circle {
295
+ cursor: pointer;
296
+ fill-opacity: 0.42;
297
+ stroke: var(--st-semantic-surface-default, Canvas);
298
+ stroke-width: 1.5;
299
+ transition: opacity 120ms ease;
300
+ }
301
+
302
+ .st-vennChart__circle--dim {
303
+ opacity: 0.45;
304
+ }
305
+
306
+ .st-vennChart__circle--category1 { fill: var(--st-semantic-data-category1); }
307
+ .st-vennChart__circle--category2 { fill: var(--st-semantic-data-category2); }
308
+ .st-vennChart__circle--category3 { fill: var(--st-semantic-data-category3); }
309
+ .st-vennChart__circle--category4 { fill: var(--st-semantic-data-category4); }
310
+ .st-vennChart__circle--category5 { fill: var(--st-semantic-data-category5); }
311
+ .st-vennChart__circle--category6 { fill: var(--st-semantic-data-category6); }
312
+ .st-vennChart__circle--category7 { fill: var(--st-semantic-data-category7); }
313
+ .st-vennChart__circle--category8 { fill: var(--st-semantic-data-category8); }
314
+
315
+ .st-vennChart__label {
316
+ fill: var(--st-semantic-text-primary);
317
+ font-size: 0.78rem;
318
+ font-weight: 650;
319
+ pointer-events: none;
320
+ }
321
+
322
+ .st-vennChart__value {
323
+ fill: var(--st-semantic-text-primary);
324
+ font-size: 0.72rem;
325
+ font-weight: 600;
326
+ pointer-events: none;
327
+ }
328
+
329
+ .st-vennChart__tooltip {
330
+ background: var(--st-semantic-surface-inverse);
331
+ border-radius: var(--st-radius-sm, 0.25rem);
332
+ color: var(--st-semantic-text-inverse);
333
+ display: inline-flex;
334
+ flex-direction: column;
335
+ font-size: 0.75rem;
336
+ gap: 0.125rem;
337
+ line-height: 1.2;
338
+ padding: 0.375rem 0.5rem;
339
+ pointer-events: none;
340
+ position: absolute;
341
+ transform: translate(-50%, -115%);
342
+ white-space: nowrap;
343
+ z-index: 1;
344
+ }
345
+
346
+ .st-vennChart__tooltipLabel { font-weight: 600; }
347
+ .st-vennChart__tooltipValue { opacity: 0.85; }
348
+ </style>
@@ -0,0 +1,72 @@
1
+ /**
2
+ * VennChart (diagramme de Venn / Euler) - API canonique (référence Svelte,
3
+ * React/Vue doivent s'aligner byte-pour-byte sur la géométrie).
4
+ *
5
+ * Deux ou trois ensembles dessinés en cercles superposés semi-transparents.
6
+ * Chaque zone (region) est décrite par les ensembles qu'elle couvre et une
7
+ * valeur :
8
+ * - { sets: ["A"], value } → membres de A seul ;
9
+ * - { sets: ["A","B"], value } → intersection A∩B ;
10
+ * - { sets: ["A","B","C"], value} → intersection triple.
11
+ *
12
+ * Géométrie DÉTERMINISTE (aucun aléatoire, layout identique entre frameworks) :
13
+ * - Les cercles sont placés à des positions FIXES selon le nombre d'ensembles
14
+ * (2 : côte à côte avec recouvrement ; 3 : triangle équilatéral).
15
+ * - Le rayon de chaque cercle ∝ √(taille totale de l'ensemble), où la taille
16
+ * d'un ensemble est la somme des valeurs de toutes les zones qui le
17
+ * contiennent. Mapping linéaire du √ entre un rayon min et un rayon max.
18
+ *
19
+ * Étiquettes : le nom de chaque ensemble est posé près du cercle ; la valeur de
20
+ * chaque zone d'intersection (sets.length ≥ 2) est posée au centroïde des
21
+ * centres des cercles concernés. Légende des ensembles + tooltip au survol +
22
+ * liste accessible (ChartDataList) hors SVG.
23
+ *
24
+ * Props :
25
+ * data VennChartArea[] - {sets, value}
26
+ * label string - aria-label (role=img)
27
+ * width number (420) - largeur du viewBox
28
+ * height number (360) - hauteur du viewBox
29
+ * class string - classe CSS additionnelle
30
+ */
31
+ export type VennChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
32
+ export type VennChartArea = {
33
+ sets: string[];
34
+ value: number;
35
+ };
36
+ export type VennChartProps = {
37
+ data: VennChartArea[];
38
+ label: string;
39
+ width?: number;
40
+ height?: number;
41
+ class?: string;
42
+ };
43
+ type VennCircle = {
44
+ name: string;
45
+ tone: VennChartTone;
46
+ cx: number;
47
+ cy: number;
48
+ r: number;
49
+ total: number;
50
+ labelX: number;
51
+ labelY: number;
52
+ anchor: "start" | "middle" | "end";
53
+ };
54
+ type VennRegion = {
55
+ sets: string[];
56
+ value: number;
57
+ x: number;
58
+ y: number;
59
+ };
60
+ export type VennLayout = {
61
+ circles: VennCircle[];
62
+ regions: VennRegion[];
63
+ items: string[];
64
+ };
65
+ /**
66
+ * Calcule la géométrie déterministe du diagramme. Identique entre frameworks.
67
+ */
68
+ export declare function vennLayout(data: VennChartArea[], width: number, height: number): VennLayout;
69
+ declare const VennChart: import("svelte").Component<VennChartProps, {}, "">;
70
+ type VennChart = ReturnType<typeof VennChart>;
71
+ export default VennChart;
72
+ //# sourceMappingURL=VennChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VennChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/VennChart.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAaF,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CACpC,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAMF;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CA6F3F;AAiEH,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,279 @@
1
+ <script lang="ts" module>
2
+ export type WordCloudChartTone =
3
+ | "category1" | "category2" | "category3" | "category4"
4
+ | "category5" | "category6" | "category7" | "category8";
5
+
6
+ export type WordCloudChartWord = {
7
+ text: string;
8
+ weight: number;
9
+ tone?: WordCloudChartTone;
10
+ };
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import ChartDataList from "./ChartDataList.svelte";
15
+
16
+ type WordCloudChartProps = {
17
+ /** Mots à placer : la taille de police est proportionnelle au poids. */
18
+ data: WordCloudChartWord[];
19
+ width?: number;
20
+ height?: number;
21
+ label: string;
22
+ class?: string;
23
+ };
24
+
25
+ let {
26
+ data,
27
+ width = 480,
28
+ height = 320,
29
+ label,
30
+ class: className
31
+ }: WordCloudChartProps = $props();
32
+
33
+ const TONES = [
34
+ "category1", "category2", "category3", "category4",
35
+ "category5", "category6", "category7", "category8"
36
+ ] as const;
37
+
38
+ // Constantes de layout — IDENTIQUES entre svelte/react/vue (aucun aléatoire).
39
+ const FONT_MIN = 14;
40
+ const FONT_MAX = 52;
41
+ // Largeur estimée d'un glyphe relative à la taille de police.
42
+ const CHAR_W = 0.6;
43
+ // Spirale d'Archimède : r = SPIRAL_A * theta ; pas angulaire constant.
44
+ const SPIRAL_STEP = 0.25;
45
+ const MAX_TURNS = 60;
46
+ // Marge entre boîtes englobantes (en unités SVG).
47
+ const GAP = 4;
48
+
49
+ type Box = { x: number; y: number; w: number; h: number };
50
+ type Placed = {
51
+ word: WordCloudChartWord;
52
+ tone: WordCloudChartTone;
53
+ fontSize: number;
54
+ cx: number;
55
+ cy: number;
56
+ box: Box;
57
+ };
58
+
59
+ const validWord = (w: WordCloudChartWord): boolean =>
60
+ !!w && typeof w.text === "string" && w.text.length > 0 &&
61
+ Number.isFinite(w.weight) && w.weight > 0;
62
+
63
+ function overlaps(a: Box, b: Box): boolean {
64
+ return !(
65
+ a.x + a.w + GAP <= b.x ||
66
+ b.x + b.w + GAP <= a.x ||
67
+ a.y + a.h + GAP <= b.y ||
68
+ b.y + b.h + GAP <= a.y
69
+ );
70
+ }
71
+
72
+ type Layout = { placed: Placed[]; omitted: WordCloudChartWord[] };
73
+
74
+ const layout = $derived.by<Layout>(() => {
75
+ const words = (data ?? []).filter(validWord);
76
+ if (words.length === 0) return { placed: [], omitted: [] };
77
+
78
+ // Trie par poids décroissant (placement déterministe).
79
+ const sorted = words
80
+ .map((w, i) => ({ w, i }))
81
+ .sort((a, b) => (b.w.weight - a.w.weight) || (a.i - b.i))
82
+ .map((e) => e.w);
83
+
84
+ const minW = sorted[sorted.length - 1].weight;
85
+ const maxW = sorted[0].weight;
86
+ const span = maxW - minW;
87
+
88
+ const fontFor = (weight: number): number => {
89
+ if (span <= 0) return (FONT_MIN + FONT_MAX) / 2;
90
+ const t = (weight - minW) / span;
91
+ return FONT_MIN + t * (FONT_MAX - FONT_MIN);
92
+ };
93
+
94
+ const cx0 = width / 2;
95
+ const cy0 = height / 2;
96
+ const a = Math.min(width, height) / (2 * Math.PI * MAX_TURNS);
97
+
98
+ const placed: Placed[] = [];
99
+ const omitted: WordCloudChartWord[] = [];
100
+
101
+ sorted.forEach((word, idx) => {
102
+ const fontSize = fontFor(word.weight);
103
+ const w = word.text.length * fontSize * CHAR_W;
104
+ const h = fontSize;
105
+ const tone = word.tone ?? TONES[idx % TONES.length];
106
+
107
+ let foundCx = cx0;
108
+ let foundCy = cy0;
109
+ let placedOk = false;
110
+
111
+ // Parcourt la spirale d'Archimède depuis le centre.
112
+ for (let theta = 0; theta <= MAX_TURNS * 2 * Math.PI; theta += SPIRAL_STEP) {
113
+ const r = a * theta;
114
+ const cx = cx0 + r * Math.cos(theta);
115
+ const cy = cy0 + r * Math.sin(theta);
116
+ const box: Box = { x: cx - w / 2, y: cy - h / 2, w, h };
117
+
118
+ // Hors cadre → on continue.
119
+ if (box.x < 0 || box.y < 0 || box.x + box.w > width || box.y + box.h > height) {
120
+ continue;
121
+ }
122
+ let collides = false;
123
+ for (const p of placed) {
124
+ if (overlaps(box, p.box)) {
125
+ collides = true;
126
+ break;
127
+ }
128
+ }
129
+ if (!collides) {
130
+ foundCx = cx;
131
+ foundCy = cy;
132
+ placedOk = true;
133
+ placed.push({ word, tone, fontSize, cx, cy, box });
134
+ break;
135
+ }
136
+ }
137
+
138
+ if (!placedOk) {
139
+ // Centre (theta=0) déjà testé : si rien ne rentre, on omet le mot.
140
+ omitted.push(word);
141
+ }
142
+ void foundCx;
143
+ void foundCy;
144
+ });
145
+
146
+ return { placed, omitted };
147
+ });
148
+
149
+ const placed = $derived(layout.placed);
150
+ const omitted = $derived(layout.omitted);
151
+
152
+ const dataValueItems = $derived([
153
+ ...placed.map((p) => `${p.word.text}: ${p.word.weight}`),
154
+ ...omitted.map((o) => `${o.text}: ${o.weight} (omis)`)
155
+ ]);
156
+
157
+ let hoveredIndex: number | null = $state(null);
158
+
159
+ function handleVisualPointerMove(event: PointerEvent) {
160
+ const target = event.target;
161
+ if (!(target instanceof Element)) {
162
+ hoveredIndex = null;
163
+ return;
164
+ }
165
+ const index = Number(target.getAttribute("data-chart-index"));
166
+ hoveredIndex = Number.isInteger(index) ? index : null;
167
+ }
168
+
169
+ const classes = () => ["st-wordCloudChart", className].filter(Boolean).join(" ");
170
+ </script>
171
+
172
+ <div class={classes()}>
173
+ <div
174
+ class="st-wordCloudChart__visual"
175
+ role="img"
176
+ aria-label={label}
177
+ onpointermove={handleVisualPointerMove}
178
+ onpointerleave={() => (hoveredIndex = null)}
179
+ >
180
+ <svg
181
+ viewBox="0 0 {width} {height}"
182
+ preserveAspectRatio="xMidYMid meet"
183
+ width="100%"
184
+ height="100%"
185
+ focusable="false"
186
+ aria-hidden="true"
187
+ >
188
+ {#each placed as p, i (p.word.text)}
189
+ <text
190
+ class="st-wordCloudChart__word st-wordCloudChart__word--{p.tone}"
191
+ class:st-wordCloudChart__word--dim={hoveredIndex !== null && hoveredIndex !== i}
192
+ x={p.cx}
193
+ y={p.cy}
194
+ text-anchor="middle"
195
+ dominant-baseline="central"
196
+ font-size={p.fontSize}
197
+ data-chart-index={i}
198
+ >
199
+ {p.word.text}
200
+ </text>
201
+ {/each}
202
+ </svg>
203
+ </div>
204
+
205
+ <ChartDataList {label} items={dataValueItems} />
206
+
207
+ {#if hoveredIndex !== null && placed[hoveredIndex]}
208
+ {@const p = placed[hoveredIndex]}
209
+ <div
210
+ class="st-wordCloudChart__tooltip"
211
+ role="presentation"
212
+ style="left: {(p.cx / width) * 100}%; top: {((p.cy - p.fontSize / 2) / height) * 100}%"
213
+ >
214
+ <span class="st-wordCloudChart__tooltipLabel">{p.word.text}</span>
215
+ <span class="st-wordCloudChart__tooltipValue">{p.word.weight}</span>
216
+ </div>
217
+ {/if}
218
+ </div>
219
+
220
+ <style>
221
+ .st-wordCloudChart {
222
+ color: var(--st-semantic-text-secondary);
223
+ display: block;
224
+ font-family: inherit;
225
+ position: relative;
226
+ width: 100%;
227
+ }
228
+
229
+ .st-wordCloudChart svg,
230
+ .st-wordCloudChart__visual {
231
+ display: block;
232
+ overflow: visible;
233
+ }
234
+
235
+ .st-wordCloudChart__word {
236
+ cursor: pointer;
237
+ font-weight: 600;
238
+ transition: opacity 120ms ease;
239
+ }
240
+
241
+ .st-wordCloudChart__word--dim {
242
+ opacity: 0.3;
243
+ }
244
+
245
+ @media (prefers-reduced-motion: reduce) {
246
+ .st-wordCloudChart__word {
247
+ transition: none;
248
+ }
249
+ }
250
+
251
+ .st-wordCloudChart__word--category1 { fill: var(--st-semantic-data-category1); }
252
+ .st-wordCloudChart__word--category2 { fill: var(--st-semantic-data-category2); }
253
+ .st-wordCloudChart__word--category3 { fill: var(--st-semantic-data-category3); }
254
+ .st-wordCloudChart__word--category4 { fill: var(--st-semantic-data-category4); }
255
+ .st-wordCloudChart__word--category5 { fill: var(--st-semantic-data-category5); }
256
+ .st-wordCloudChart__word--category6 { fill: var(--st-semantic-data-category6); }
257
+ .st-wordCloudChart__word--category7 { fill: var(--st-semantic-data-category7); }
258
+ .st-wordCloudChart__word--category8 { fill: var(--st-semantic-data-category8); }
259
+
260
+ .st-wordCloudChart__tooltip {
261
+ background: var(--st-semantic-surface-inverse);
262
+ border-radius: var(--st-radius-sm, 0.25rem);
263
+ color: var(--st-semantic-text-inverse);
264
+ display: inline-flex;
265
+ flex-direction: column;
266
+ font-size: 0.75rem;
267
+ gap: 0.125rem;
268
+ line-height: 1.2;
269
+ padding: 0.375rem 0.5rem;
270
+ pointer-events: none;
271
+ position: absolute;
272
+ transform: translate(-50%, calc(-100% - 8px));
273
+ white-space: nowrap;
274
+ z-index: 1;
275
+ }
276
+
277
+ .st-wordCloudChart__tooltipLabel { font-weight: 600; }
278
+ .st-wordCloudChart__tooltipValue { opacity: 0.85; }
279
+ </style>