@sentropic/design-system-svelte 0.34.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.
- package/dist/AppHeader.svelte +159 -1
- package/dist/AppHeader.svelte.d.ts +18 -1
- package/dist/AppHeader.svelte.d.ts.map +1 -1
- package/dist/ArcDiagramChart.svelte +380 -0
- package/dist/ArcDiagramChart.svelte.d.ts +43 -0
- package/dist/ArcDiagramChart.svelte.d.ts.map +1 -0
- package/dist/AreaRangeChart.svelte +487 -0
- package/dist/AreaRangeChart.svelte.d.ts +38 -0
- package/dist/AreaRangeChart.svelte.d.ts.map +1 -0
- package/dist/AreaSplineRangeChart.svelte +478 -0
- package/dist/AreaSplineRangeChart.svelte.d.ts +37 -0
- package/dist/AreaSplineRangeChart.svelte.d.ts.map +1 -0
- package/dist/BellCurveChart.svelte +487 -0
- package/dist/BellCurveChart.svelte.d.ts +40 -0
- package/dist/BellCurveChart.svelte.d.ts.map +1 -0
- package/dist/Calendar.svelte +11 -0
- package/dist/ChatThread.svelte +32 -1
- package/dist/ChatThread.svelte.d.ts +14 -0
- package/dist/ChatThread.svelte.d.ts.map +1 -1
- package/dist/ColumnPyramidChart.svelte +332 -0
- package/dist/ColumnPyramidChart.svelte.d.ts +35 -0
- package/dist/ColumnPyramidChart.svelte.d.ts.map +1 -0
- package/dist/ColumnRangeChart.svelte +432 -0
- package/dist/ColumnRangeChart.svelte.d.ts +42 -0
- package/dist/ColumnRangeChart.svelte.d.ts.map +1 -0
- package/dist/Combobox.svelte +3 -0
- package/dist/ContentSwitcher.svelte +1 -1
- package/dist/DataTable.svelte.d.ts +1 -1
- package/dist/DatePicker.svelte +3 -0
- package/dist/DependencyWheelChart.svelte +413 -0
- package/dist/DependencyWheelChart.svelte.d.ts +42 -0
- package/dist/DependencyWheelChart.svelte.d.ts.map +1 -0
- package/dist/DumbbellChart.svelte +403 -0
- package/dist/DumbbellChart.svelte.d.ts +44 -0
- package/dist/DumbbellChart.svelte.d.ts.map +1 -0
- package/dist/ErrorBarChart.svelte +428 -0
- package/dist/ErrorBarChart.svelte.d.ts +40 -0
- package/dist/ErrorBarChart.svelte.d.ts.map +1 -0
- package/dist/GanttChart.svelte +410 -0
- package/dist/GanttChart.svelte.d.ts +39 -0
- package/dist/GanttChart.svelte.d.ts.map +1 -0
- package/dist/HLCChart.svelte +330 -0
- package/dist/HLCChart.svelte.d.ts +32 -0
- package/dist/HLCChart.svelte.d.ts.map +1 -0
- package/dist/HeikinAshiChart.svelte +365 -0
- package/dist/HeikinAshiChart.svelte.d.ts +37 -0
- package/dist/HeikinAshiChart.svelte.d.ts.map +1 -0
- package/dist/HollowCandlestickChart.svelte +357 -0
- package/dist/HollowCandlestickChart.svelte.d.ts +34 -0
- package/dist/HollowCandlestickChart.svelte.d.ts.map +1 -0
- package/dist/Input.svelte +3 -0
- package/dist/ItemChart.svelte +389 -0
- package/dist/ItemChart.svelte.d.ts +67 -0
- package/dist/ItemChart.svelte.d.ts.map +1 -0
- package/dist/LollipopChart.svelte +1 -1
- package/dist/MultiSelect.svelte +3 -0
- package/dist/NumberInput.svelte +3 -0
- package/dist/OHLCChart.svelte +343 -0
- package/dist/OHLCChart.svelte.d.ts +33 -0
- package/dist/OHLCChart.svelte.d.ts.map +1 -0
- package/dist/OrganizationChart.svelte +284 -0
- package/dist/OrganizationChart.svelte.d.ts +19 -0
- package/dist/OrganizationChart.svelte.d.ts.map +1 -0
- package/dist/PasswordInput.svelte +3 -0
- package/dist/PolygonChart.svelte +189 -0
- package/dist/PolygonChart.svelte.d.ts +17 -0
- package/dist/PolygonChart.svelte.d.ts.map +1 -0
- package/dist/Search.svelte +7 -5
- package/dist/Select.svelte +3 -0
- package/dist/StreamgraphChart.svelte +283 -0
- package/dist/StreamgraphChart.svelte.d.ts +23 -0
- package/dist/StreamgraphChart.svelte.d.ts.map +1 -0
- package/dist/StreamingMessage.svelte +44 -2
- package/dist/StreamingMessage.svelte.d.ts +18 -1
- package/dist/StreamingMessage.svelte.d.ts.map +1 -1
- package/dist/TileMapChart.svelte +314 -0
- package/dist/TileMapChart.svelte.d.ts +45 -0
- package/dist/TileMapChart.svelte.d.ts.map +1 -0
- package/dist/TimePicker.svelte +3 -0
- package/dist/TimelineChart.svelte +362 -0
- package/dist/TimelineChart.svelte.d.ts +22 -0
- package/dist/TimelineChart.svelte.d.ts.map +1 -0
- package/dist/TreegraphChart.svelte +281 -0
- package/dist/TreegraphChart.svelte.d.ts +19 -0
- package/dist/TreegraphChart.svelte.d.ts.map +1 -0
- package/dist/VariablePieChart.svelte +313 -0
- package/dist/VariablePieChart.svelte.d.ts +52 -0
- package/dist/VariablePieChart.svelte.d.ts.map +1 -0
- package/dist/VennChart.svelte +348 -0
- package/dist/VennChart.svelte.d.ts +72 -0
- package/dist/VennChart.svelte.d.ts.map +1 -0
- package/dist/WordCloudChart.svelte +279 -0
- package/dist/WordCloudChart.svelte.d.ts +18 -0
- package/dist/WordCloudChart.svelte.d.ts.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -0
- package/package.json +5 -3
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type TimelineChartTone =
|
|
3
|
+
| "category1"
|
|
4
|
+
| "category2"
|
|
5
|
+
| "category3"
|
|
6
|
+
| "category4"
|
|
7
|
+
| "category5"
|
|
8
|
+
| "category6"
|
|
9
|
+
| "category7"
|
|
10
|
+
| "category8";
|
|
11
|
+
|
|
12
|
+
export type TimelineChartEvent = {
|
|
13
|
+
/** Point on the axis (year, day index, ordinal step…). */
|
|
14
|
+
position: number;
|
|
15
|
+
/** Required short label, shown above/below the marker (alternated). */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Optional longer description, surfaced in the accessible list + tooltip. */
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Optional explicit categorical tone; otherwise cycles category1..8. */
|
|
20
|
+
tone?: TimelineChartTone;
|
|
21
|
+
};
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
import ChartDataList from "./ChartDataList.svelte";
|
|
26
|
+
|
|
27
|
+
type TimelineChartProps = {
|
|
28
|
+
data: TimelineChartEvent[];
|
|
29
|
+
label: string;
|
|
30
|
+
width?: number;
|
|
31
|
+
height?: number;
|
|
32
|
+
class?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
let { data, label, width = 640, height = 240, class: className }: TimelineChartProps = $props();
|
|
36
|
+
|
|
37
|
+
const MARGIN = { top: 12, right: 24, bottom: 32, left: 24 };
|
|
38
|
+
const CATEGORY_COUNT = 8;
|
|
39
|
+
// Max characters before the label is ellipsised (keeps connectors readable).
|
|
40
|
+
const LABEL_MAX = 18;
|
|
41
|
+
|
|
42
|
+
function niceTicks(min: number, max: number, target = 5): number[] {
|
|
43
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || min === max) {
|
|
44
|
+
const base = Number.isFinite(max) ? max : 0;
|
|
45
|
+
return [base];
|
|
46
|
+
}
|
|
47
|
+
const range = max - min;
|
|
48
|
+
const rough = range / Math.max(target - 1, 1);
|
|
49
|
+
const pow = Math.pow(10, Math.floor(Math.log10(rough)));
|
|
50
|
+
const norm = rough / pow;
|
|
51
|
+
let step: number;
|
|
52
|
+
if (norm < 1.5) step = 1 * pow;
|
|
53
|
+
else if (norm < 3) step = 2 * pow;
|
|
54
|
+
else if (norm < 7) step = 5 * pow;
|
|
55
|
+
else step = 10 * pow;
|
|
56
|
+
const start = Math.floor(min / step) * step;
|
|
57
|
+
const end = Math.ceil(max / step) * step;
|
|
58
|
+
const ticks: number[] = [];
|
|
59
|
+
for (let v = start; v <= end + step / 2; v += step) {
|
|
60
|
+
ticks.push(Number(v.toFixed(10)));
|
|
61
|
+
}
|
|
62
|
+
return ticks;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function scaleLinear(v: number, d0: number, d1: number, r0: number, r1: number) {
|
|
66
|
+
if (d1 === d0) return r0;
|
|
67
|
+
return r0 + ((v - d0) * (r1 - r0)) / (d1 - d0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatTick(v: number): string {
|
|
71
|
+
if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(v % 1000 === 0 ? 0 : 1)}k`;
|
|
72
|
+
if (Number.isInteger(v)) return String(v);
|
|
73
|
+
return v.toFixed(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function toneClass(tone: TimelineChartTone | undefined, index: number): TimelineChartTone {
|
|
77
|
+
if (tone) return tone;
|
|
78
|
+
return `category${(index % CATEGORY_COUNT) + 1}` as TimelineChartTone;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function truncate(text: string): string {
|
|
82
|
+
return text.length > LABEL_MAX ? `${text.slice(0, LABEL_MAX - 1)}…` : text;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let hoveredIndex: number | null = $state(null);
|
|
86
|
+
|
|
87
|
+
const plotWidth = $derived(Math.max(width - MARGIN.left - MARGIN.right, 1));
|
|
88
|
+
const axisY = $derived(MARGIN.top + Math.max(height - MARGIN.top - MARGIN.bottom, 1) / 2);
|
|
89
|
+
|
|
90
|
+
// Sorted, finite-position events. `label` is required so missing labels drop.
|
|
91
|
+
const events = $derived(
|
|
92
|
+
data
|
|
93
|
+
.filter((e) => Number.isFinite(e.position) && typeof e.label === "string")
|
|
94
|
+
.slice()
|
|
95
|
+
.sort((a, b) => a.position - b.position)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const positionDomain = $derived.by(() => {
|
|
99
|
+
if (events.length === 0) return { min: 0, max: 1 };
|
|
100
|
+
const xs = events.map((e) => e.position);
|
|
101
|
+
const min = Math.min(...xs);
|
|
102
|
+
const max = Math.max(...xs);
|
|
103
|
+
return min === max ? { min: min - 1, max: max + 1 } : { min, max };
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const ticks = $derived(niceTicks(positionDomain.min, positionDomain.max, 5));
|
|
107
|
+
|
|
108
|
+
// Domain extended to the tick range so markers + axis share a frame.
|
|
109
|
+
const frame = $derived({
|
|
110
|
+
min: Math.min(positionDomain.min, ticks[0]),
|
|
111
|
+
max: Math.max(positionDomain.max, ticks[ticks.length - 1])
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const markers = $derived(
|
|
115
|
+
events.map((e, i) => {
|
|
116
|
+
const x = MARGIN.left + scaleLinear(e.position, frame.min, frame.max, 0, plotWidth);
|
|
117
|
+
const above = i % 2 === 0;
|
|
118
|
+
return {
|
|
119
|
+
index: i,
|
|
120
|
+
x,
|
|
121
|
+
above,
|
|
122
|
+
tone: toneClass(e.tone, i),
|
|
123
|
+
label: truncate(e.label),
|
|
124
|
+
fullLabel: e.label,
|
|
125
|
+
description: e.description,
|
|
126
|
+
position: e.position
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const tickEntries = $derived(
|
|
132
|
+
ticks.map((tick) => ({
|
|
133
|
+
value: tick,
|
|
134
|
+
x: MARGIN.left + scaleLinear(tick, frame.min, frame.max, 0, plotWidth)
|
|
135
|
+
}))
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const dataValueItems = $derived(
|
|
139
|
+
events.map((e) =>
|
|
140
|
+
e.description ? `${e.position}: ${e.label} — ${e.description}` : `${e.position}: ${e.label}`
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
function handleLeave() {
|
|
145
|
+
hoveredIndex = null;
|
|
146
|
+
}
|
|
147
|
+
function handleVisualPointerMove(event: PointerEvent) {
|
|
148
|
+
const target = event.target;
|
|
149
|
+
if (!(target instanceof Element)) {
|
|
150
|
+
hoveredIndex = null;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const index = Number(target.getAttribute("data-chart-index"));
|
|
154
|
+
hoveredIndex = Number.isInteger(index) ? index : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const classes = () => ["st-timelineChart", className].filter(Boolean).join(" ");
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<div class={classes()}>
|
|
161
|
+
<div
|
|
162
|
+
class="st-timelineChart__visual"
|
|
163
|
+
role="img"
|
|
164
|
+
aria-label={label}
|
|
165
|
+
onpointermove={handleVisualPointerMove}
|
|
166
|
+
onpointerleave={handleLeave}
|
|
167
|
+
>
|
|
168
|
+
<svg
|
|
169
|
+
viewBox="0 0 {width} {height}"
|
|
170
|
+
preserveAspectRatio="xMidYMid meet"
|
|
171
|
+
width="100%"
|
|
172
|
+
height="100%"
|
|
173
|
+
focusable="false"
|
|
174
|
+
aria-hidden="true"
|
|
175
|
+
>
|
|
176
|
+
<!-- central timeline -->
|
|
177
|
+
<line
|
|
178
|
+
class="st-timelineChart__axis"
|
|
179
|
+
x1={MARGIN.left}
|
|
180
|
+
x2={width - MARGIN.right}
|
|
181
|
+
y1={axisY}
|
|
182
|
+
y2={axisY}
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
<!-- graduated ticks -->
|
|
186
|
+
{#each tickEntries as tick (tick.value)}
|
|
187
|
+
<line
|
|
188
|
+
class="st-timelineChart__tick"
|
|
189
|
+
x1={tick.x}
|
|
190
|
+
x2={tick.x}
|
|
191
|
+
y1={axisY}
|
|
192
|
+
y2={axisY + 5}
|
|
193
|
+
/>
|
|
194
|
+
<text
|
|
195
|
+
class="st-timelineChart__tickLabel"
|
|
196
|
+
x={tick.x}
|
|
197
|
+
y={height - MARGIN.bottom + 18}
|
|
198
|
+
text-anchor="middle"
|
|
199
|
+
>
|
|
200
|
+
{formatTick(tick.value)}
|
|
201
|
+
</text>
|
|
202
|
+
{/each}
|
|
203
|
+
|
|
204
|
+
<!-- events: connector + marker + alternated label -->
|
|
205
|
+
{#each markers as m (m.index)}
|
|
206
|
+
{@const labelY = m.above ? axisY - 26 : axisY + 26}
|
|
207
|
+
{@const connectorY = m.above ? axisY - 12 : axisY + 12}
|
|
208
|
+
<line
|
|
209
|
+
class={`st-timelineChart__connector st-timelineChart__connector--${m.tone}`}
|
|
210
|
+
x1={m.x}
|
|
211
|
+
x2={m.x}
|
|
212
|
+
y1={axisY}
|
|
213
|
+
y2={connectorY}
|
|
214
|
+
/>
|
|
215
|
+
<text
|
|
216
|
+
class="st-timelineChart__eventLabel"
|
|
217
|
+
x={m.x}
|
|
218
|
+
y={labelY}
|
|
219
|
+
text-anchor="middle"
|
|
220
|
+
dominant-baseline={m.above ? "auto" : "hanging"}
|
|
221
|
+
>
|
|
222
|
+
{m.label}
|
|
223
|
+
</text>
|
|
224
|
+
<circle
|
|
225
|
+
class={`st-timelineChart__marker st-timelineChart__marker--${m.tone}`}
|
|
226
|
+
cx={m.x}
|
|
227
|
+
cy={axisY}
|
|
228
|
+
r="6"
|
|
229
|
+
data-chart-index={m.index}
|
|
230
|
+
/>
|
|
231
|
+
{/each}
|
|
232
|
+
</svg>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<ChartDataList {label} items={dataValueItems} />
|
|
236
|
+
|
|
237
|
+
{#if hoveredIndex !== null && markers[hoveredIndex]}
|
|
238
|
+
{@const m = markers[hoveredIndex]}
|
|
239
|
+
<div
|
|
240
|
+
class="st-timelineChart__tooltip"
|
|
241
|
+
role="presentation"
|
|
242
|
+
style="left: {(m.x / width) * 100}%; top: {(axisY / height) * 100}%"
|
|
243
|
+
>
|
|
244
|
+
<span class="st-timelineChart__tooltipLabel">{m.fullLabel}</span>
|
|
245
|
+
<span class="st-timelineChart__tooltipValue">{m.position}</span>
|
|
246
|
+
{#if m.description}
|
|
247
|
+
<span class="st-timelineChart__tooltipDesc">{m.description}</span>
|
|
248
|
+
{/if}
|
|
249
|
+
</div>
|
|
250
|
+
{/if}
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<style>
|
|
254
|
+
.st-timelineChart {
|
|
255
|
+
color: var(--st-semantic-text-secondary);
|
|
256
|
+
display: block;
|
|
257
|
+
font-family: inherit;
|
|
258
|
+
position: relative;
|
|
259
|
+
width: 100%;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.st-timelineChart svg {
|
|
263
|
+
display: block;
|
|
264
|
+
overflow: visible;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.st-timelineChart__visual {
|
|
268
|
+
display: block;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.st-timelineChart__axis {
|
|
272
|
+
stroke: var(--st-semantic-border-strong);
|
|
273
|
+
stroke-width: 2;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.st-timelineChart__tick {
|
|
277
|
+
stroke: var(--st-semantic-border-subtle);
|
|
278
|
+
stroke-width: 1;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.st-timelineChart__tickLabel {
|
|
282
|
+
fill: var(--st-semantic-text-secondary);
|
|
283
|
+
font-size: 0.6875rem;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.st-timelineChart__connector {
|
|
287
|
+
stroke: currentColor;
|
|
288
|
+
stroke-width: 1.5;
|
|
289
|
+
opacity: 0.6;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.st-timelineChart__eventLabel {
|
|
293
|
+
fill: var(--st-semantic-text-primary);
|
|
294
|
+
font-size: 0.75rem;
|
|
295
|
+
font-weight: 600;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.st-timelineChart__marker {
|
|
299
|
+
fill: currentColor;
|
|
300
|
+
stroke: var(--st-semantic-surface-default);
|
|
301
|
+
stroke-width: 2;
|
|
302
|
+
cursor: pointer;
|
|
303
|
+
transition: r 120ms ease;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.st-timelineChart__marker:hover {
|
|
307
|
+
r: 7.5;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@media (prefers-reduced-motion: reduce) {
|
|
311
|
+
.st-timelineChart__marker {
|
|
312
|
+
transition: none;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.st-timelineChart__connector--category1,
|
|
317
|
+
.st-timelineChart__marker--category1 { color: var(--st-semantic-data-category1); }
|
|
318
|
+
.st-timelineChart__connector--category2,
|
|
319
|
+
.st-timelineChart__marker--category2 { color: var(--st-semantic-data-category2); }
|
|
320
|
+
.st-timelineChart__connector--category3,
|
|
321
|
+
.st-timelineChart__marker--category3 { color: var(--st-semantic-data-category3); }
|
|
322
|
+
.st-timelineChart__connector--category4,
|
|
323
|
+
.st-timelineChart__marker--category4 { color: var(--st-semantic-data-category4); }
|
|
324
|
+
.st-timelineChart__connector--category5,
|
|
325
|
+
.st-timelineChart__marker--category5 { color: var(--st-semantic-data-category5); }
|
|
326
|
+
.st-timelineChart__connector--category6,
|
|
327
|
+
.st-timelineChart__marker--category6 { color: var(--st-semantic-data-category6); }
|
|
328
|
+
.st-timelineChart__connector--category7,
|
|
329
|
+
.st-timelineChart__marker--category7 { color: var(--st-semantic-data-category7); }
|
|
330
|
+
.st-timelineChart__connector--category8,
|
|
331
|
+
.st-timelineChart__marker--category8 { color: var(--st-semantic-data-category8); }
|
|
332
|
+
|
|
333
|
+
.st-timelineChart__tooltip {
|
|
334
|
+
background: var(--st-semantic-surface-inverse);
|
|
335
|
+
border-radius: var(--st-radius-sm, 0.25rem);
|
|
336
|
+
color: var(--st-semantic-text-inverse);
|
|
337
|
+
display: inline-flex;
|
|
338
|
+
flex-direction: column;
|
|
339
|
+
font-size: 0.75rem;
|
|
340
|
+
gap: 0.125rem;
|
|
341
|
+
line-height: 1.2;
|
|
342
|
+
max-width: 16rem;
|
|
343
|
+
padding: 0.375rem 0.5rem;
|
|
344
|
+
pointer-events: none;
|
|
345
|
+
position: absolute;
|
|
346
|
+
transform: translate(-50%, calc(-100% - 10px));
|
|
347
|
+
white-space: normal;
|
|
348
|
+
z-index: 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.st-timelineChart__tooltipLabel {
|
|
352
|
+
font-weight: 600;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.st-timelineChart__tooltipValue {
|
|
356
|
+
opacity: 0.85;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.st-timelineChart__tooltipDesc {
|
|
360
|
+
opacity: 0.85;
|
|
361
|
+
}
|
|
362
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type TimelineChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
|
|
2
|
+
export type TimelineChartEvent = {
|
|
3
|
+
/** Point on the axis (year, day index, ordinal step…). */
|
|
4
|
+
position: number;
|
|
5
|
+
/** Required short label, shown above/below the marker (alternated). */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Optional longer description, surfaced in the accessible list + tooltip. */
|
|
8
|
+
description?: string;
|
|
9
|
+
/** Optional explicit categorical tone; otherwise cycles category1..8. */
|
|
10
|
+
tone?: TimelineChartTone;
|
|
11
|
+
};
|
|
12
|
+
type TimelineChartProps = {
|
|
13
|
+
data: TimelineChartEvent[];
|
|
14
|
+
label: string;
|
|
15
|
+
width?: number;
|
|
16
|
+
height?: number;
|
|
17
|
+
class?: string;
|
|
18
|
+
};
|
|
19
|
+
declare const TimelineChart: import("svelte").Component<TimelineChartProps, {}, "">;
|
|
20
|
+
type TimelineChart = ReturnType<typeof TimelineChart>;
|
|
21
|
+
export default TimelineChart;
|
|
22
|
+
//# sourceMappingURL=TimelineChart.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimelineChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/TimelineChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,IAAI,CAAC,EAAE,iBAAiB,CAAC;CAC1B,CAAC;AAMF,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAiLJ,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type TreegraphChartTone =
|
|
3
|
+
| "category1" | "category2" | "category3" | "category4"
|
|
4
|
+
| "category5" | "category6" | "category7" | "category8";
|
|
5
|
+
|
|
6
|
+
export type TreegraphChartNode = {
|
|
7
|
+
id: string;
|
|
8
|
+
parentId?: string | null;
|
|
9
|
+
label: string;
|
|
10
|
+
tone?: TreegraphChartTone;
|
|
11
|
+
};
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
import ChartDataList from "./ChartDataList.svelte";
|
|
16
|
+
|
|
17
|
+
type TreegraphChartProps = {
|
|
18
|
+
/** Nœuds plats : `parentId` null/absent = racine. Plusieurs racines acceptées. */
|
|
19
|
+
data: TreegraphChartNode[];
|
|
20
|
+
width?: number;
|
|
21
|
+
height?: number;
|
|
22
|
+
label: string;
|
|
23
|
+
class?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
data,
|
|
28
|
+
width = 640,
|
|
29
|
+
height = 360,
|
|
30
|
+
label,
|
|
31
|
+
class: className
|
|
32
|
+
}: TreegraphChartProps = $props();
|
|
33
|
+
|
|
34
|
+
const TONES = [
|
|
35
|
+
"category1", "category2", "category3", "category4",
|
|
36
|
+
"category5", "category6", "category7", "category8"
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
type Node = {
|
|
40
|
+
id: string;
|
|
41
|
+
label: string;
|
|
42
|
+
tone: TreegraphChartTone;
|
|
43
|
+
depth: number;
|
|
44
|
+
x: number; // centre X (profondeur → horizontal)
|
|
45
|
+
y: number; // centre Y
|
|
46
|
+
parentId: string | null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type Link = { id: string; x1: number; y1: number; x2: number; y2: number };
|
|
50
|
+
|
|
51
|
+
const R = 7; // rayon du cercle
|
|
52
|
+
const PAD_X = 80; // marge latérale (place pour les labels)
|
|
53
|
+
const PAD_Y = 12; // marge verticale
|
|
54
|
+
|
|
55
|
+
type Layout = { nodes: Node[]; links: Link[]; rowH: number };
|
|
56
|
+
|
|
57
|
+
const layout = $derived.by<Layout>(() => {
|
|
58
|
+
if (!data || data.length === 0) return { nodes: [], links: [], rowH: 0 };
|
|
59
|
+
|
|
60
|
+
// Index + détection des racines (parentId invalide/cyclique → traité comme racine).
|
|
61
|
+
const byId = new Map<string, TreegraphChartNode>();
|
|
62
|
+
for (const n of data) if (n.id != null && !byId.has(n.id)) byId.set(n.id, n);
|
|
63
|
+
|
|
64
|
+
const validParent = (n: TreegraphChartNode): string | null => {
|
|
65
|
+
const p = n.parentId;
|
|
66
|
+
if (p == null) return null;
|
|
67
|
+
if (!byId.has(p) || p === n.id) return null;
|
|
68
|
+
// Évite les cycles : remonte la chaîne, si on revient sur n.id → racine.
|
|
69
|
+
let cursor: string | null = p;
|
|
70
|
+
const seen = new Set<string>([n.id]);
|
|
71
|
+
while (cursor != null) {
|
|
72
|
+
if (seen.has(cursor)) return null;
|
|
73
|
+
seen.add(cursor);
|
|
74
|
+
const parent: TreegraphChartNode | undefined = byId.get(cursor);
|
|
75
|
+
cursor = parent ? (parent.parentId ?? null) : null;
|
|
76
|
+
}
|
|
77
|
+
return p;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const childrenOf = new Map<string, string[]>();
|
|
81
|
+
const roots: string[] = [];
|
|
82
|
+
for (const n of byId.values()) {
|
|
83
|
+
const p = validParent(n);
|
|
84
|
+
if (p == null) {
|
|
85
|
+
roots.push(n.id);
|
|
86
|
+
} else {
|
|
87
|
+
const list = childrenOf.get(p) ?? [];
|
|
88
|
+
list.push(n.id);
|
|
89
|
+
childrenOf.set(p, list);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Profondeur par nœud + position des feuilles (parcours en profondeur, ordre stable).
|
|
94
|
+
const depthOf = new Map<string, number>();
|
|
95
|
+
const leafOrder = new Map<string, number>();
|
|
96
|
+
let leafCounter = 0;
|
|
97
|
+
let maxDepth = 0;
|
|
98
|
+
|
|
99
|
+
const visit = (id: string, depth: number) => {
|
|
100
|
+
depthOf.set(id, depth);
|
|
101
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
102
|
+
const kids = childrenOf.get(id) ?? [];
|
|
103
|
+
if (kids.length === 0) {
|
|
104
|
+
leafOrder.set(id, leafCounter);
|
|
105
|
+
leafCounter += 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const k of kids) visit(k, depth + 1);
|
|
109
|
+
};
|
|
110
|
+
for (const r of roots) visit(r, 0);
|
|
111
|
+
|
|
112
|
+
const leafCount = Math.max(leafCounter, 1);
|
|
113
|
+
|
|
114
|
+
// Y (centre) : feuille = sa rangée ; parent = moyenne des enfants.
|
|
115
|
+
const usableY = Math.max(height - PAD_Y * 2, 1);
|
|
116
|
+
const rowH = usableY / leafCount;
|
|
117
|
+
|
|
118
|
+
const centerY = new Map<string, number>();
|
|
119
|
+
const computeY = (id: string): number => {
|
|
120
|
+
const cached = centerY.get(id);
|
|
121
|
+
if (cached != null) return cached;
|
|
122
|
+
const kids = childrenOf.get(id) ?? [];
|
|
123
|
+
let cy: number;
|
|
124
|
+
if (kids.length === 0) {
|
|
125
|
+
const row = leafOrder.get(id) ?? 0;
|
|
126
|
+
cy = PAD_Y + row * rowH + rowH / 2;
|
|
127
|
+
} else {
|
|
128
|
+
const ys = kids.map((k) => computeY(k));
|
|
129
|
+
cy = ys.reduce((s, v) => s + v, 0) / ys.length;
|
|
130
|
+
}
|
|
131
|
+
centerY.set(id, cy);
|
|
132
|
+
return cy;
|
|
133
|
+
};
|
|
134
|
+
for (const r of roots) computeY(r);
|
|
135
|
+
|
|
136
|
+
// X : une colonne par niveau, répartie horizontalement.
|
|
137
|
+
const levels = maxDepth + 1;
|
|
138
|
+
const usableX = Math.max(width - PAD_X * 2, 1);
|
|
139
|
+
const colGap = levels > 1 ? usableX / (levels - 1) : 0;
|
|
140
|
+
const xForDepth = (d: number) => (levels > 1 ? PAD_X + d * colGap : width / 2);
|
|
141
|
+
|
|
142
|
+
const nodes: Node[] = [];
|
|
143
|
+
let toneIdx = 0;
|
|
144
|
+
// Ordre : profondeur croissante puis ordre d'insertion → rendu stable.
|
|
145
|
+
const ordered = [...byId.values()].filter((n) => depthOf.has(n.id));
|
|
146
|
+
for (const n of ordered) {
|
|
147
|
+
const depth = depthOf.get(n.id) ?? 0;
|
|
148
|
+
const tone = n.tone ?? TONES[toneIdx % TONES.length];
|
|
149
|
+
if (!n.tone) toneIdx += 1;
|
|
150
|
+
nodes.push({
|
|
151
|
+
id: n.id,
|
|
152
|
+
label: n.label,
|
|
153
|
+
tone,
|
|
154
|
+
depth,
|
|
155
|
+
x: xForDepth(depth),
|
|
156
|
+
y: centerY.get(n.id) ?? height / 2,
|
|
157
|
+
parentId: validParent(n)
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const nodeById = new Map(nodes.map((b) => [b.id, b]));
|
|
162
|
+
const links: Link[] = [];
|
|
163
|
+
for (const b of nodes) {
|
|
164
|
+
if (b.parentId == null) continue;
|
|
165
|
+
const parent = nodeById.get(b.parentId);
|
|
166
|
+
if (!parent) continue;
|
|
167
|
+
links.push({
|
|
168
|
+
id: b.id,
|
|
169
|
+
x1: parent.x + R,
|
|
170
|
+
y1: parent.y,
|
|
171
|
+
x2: b.x - R,
|
|
172
|
+
y2: b.y
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { nodes, links, rowH };
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Police adaptée à la hauteur de rangée (plus petite si serré).
|
|
180
|
+
const fontSize = $derived(layout.rowH < 18 ? 9 : layout.rowH < 26 ? 10 : 11);
|
|
181
|
+
// Nombre de caractères affichables avant ellipsis (approx. largeur de glyphe).
|
|
182
|
+
const maxChars = $derived(Math.max(2, Math.floor((PAD_X - R - 6) / (fontSize * 0.58))));
|
|
183
|
+
|
|
184
|
+
const clip = (s: string, n: number) => (s.length > n ? `${s.slice(0, Math.max(1, n - 1))}…` : s);
|
|
185
|
+
|
|
186
|
+
const dataValueItems = $derived(
|
|
187
|
+
layout.nodes.map((b) => {
|
|
188
|
+
const parent = b.parentId != null ? layout.nodes.find((p) => p.id === b.parentId) : undefined;
|
|
189
|
+
return parent ? `${b.label} (${b.id}) → ${parent.label}` : `${b.label} (${b.id})`;
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const classes = () => ["st-treegraphChart", className].filter(Boolean).join(" ");
|
|
194
|
+
</script>
|
|
195
|
+
|
|
196
|
+
<div class={classes()}>
|
|
197
|
+
<div class="st-treegraphChart__visual" role="img" aria-label={label}>
|
|
198
|
+
<svg
|
|
199
|
+
viewBox="0 0 {width} {height}"
|
|
200
|
+
preserveAspectRatio="xMidYMid meet"
|
|
201
|
+
width="100%"
|
|
202
|
+
height="100%"
|
|
203
|
+
focusable="false"
|
|
204
|
+
aria-hidden="true"
|
|
205
|
+
>
|
|
206
|
+
{#each layout.links as link (link.id)}
|
|
207
|
+
{@const cx = (link.x1 + link.x2) / 2}
|
|
208
|
+
<path
|
|
209
|
+
class="st-treegraphChart__link"
|
|
210
|
+
d="M {link.x1} {link.y1} C {cx} {link.y1} {cx} {link.y2} {link.x2} {link.y2}"
|
|
211
|
+
fill="none"
|
|
212
|
+
/>
|
|
213
|
+
{/each}
|
|
214
|
+
|
|
215
|
+
{#each layout.nodes as node (node.id)}
|
|
216
|
+
{@const isLeaf = !layout.nodes.some((c) => c.parentId === node.id)}
|
|
217
|
+
<g class="st-treegraphChart__node">
|
|
218
|
+
<circle
|
|
219
|
+
class="st-treegraphChart__dot st-treegraphChart__dot--{node.tone}"
|
|
220
|
+
cx={node.x}
|
|
221
|
+
cy={node.y}
|
|
222
|
+
r={R}
|
|
223
|
+
/>
|
|
224
|
+
<text
|
|
225
|
+
class="st-treegraphChart__label"
|
|
226
|
+
x={isLeaf ? node.x + R + 4 : node.x - R - 4}
|
|
227
|
+
y={node.y}
|
|
228
|
+
text-anchor={isLeaf ? "start" : "end"}
|
|
229
|
+
dominant-baseline="central"
|
|
230
|
+
style="font-size: {fontSize}px"
|
|
231
|
+
>
|
|
232
|
+
{clip(node.label, maxChars)}
|
|
233
|
+
</text>
|
|
234
|
+
</g>
|
|
235
|
+
{/each}
|
|
236
|
+
</svg>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<ChartDataList {label} items={dataValueItems} />
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<style>
|
|
243
|
+
.st-treegraphChart {
|
|
244
|
+
color: var(--st-semantic-text-secondary);
|
|
245
|
+
display: block;
|
|
246
|
+
font-family: inherit;
|
|
247
|
+
position: relative;
|
|
248
|
+
width: 100%;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.st-treegraphChart svg,
|
|
252
|
+
.st-treegraphChart__visual {
|
|
253
|
+
display: block;
|
|
254
|
+
overflow: visible;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.st-treegraphChart__link {
|
|
258
|
+
stroke: var(--st-semantic-border-default);
|
|
259
|
+
stroke-width: 1.5;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.st-treegraphChart__dot {
|
|
263
|
+
stroke: var(--st-semantic-surface-default);
|
|
264
|
+
stroke-width: 1.5;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.st-treegraphChart__dot--category1 { fill: var(--st-semantic-data-category1); }
|
|
268
|
+
.st-treegraphChart__dot--category2 { fill: var(--st-semantic-data-category2); }
|
|
269
|
+
.st-treegraphChart__dot--category3 { fill: var(--st-semantic-data-category3); }
|
|
270
|
+
.st-treegraphChart__dot--category4 { fill: var(--st-semantic-data-category4); }
|
|
271
|
+
.st-treegraphChart__dot--category5 { fill: var(--st-semantic-data-category5); }
|
|
272
|
+
.st-treegraphChart__dot--category6 { fill: var(--st-semantic-data-category6); }
|
|
273
|
+
.st-treegraphChart__dot--category7 { fill: var(--st-semantic-data-category7); }
|
|
274
|
+
.st-treegraphChart__dot--category8 { fill: var(--st-semantic-data-category8); }
|
|
275
|
+
|
|
276
|
+
.st-treegraphChart__label {
|
|
277
|
+
fill: var(--st-semantic-text-primary);
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
pointer-events: none;
|
|
280
|
+
}
|
|
281
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type TreegraphChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
|
|
2
|
+
export type TreegraphChartNode = {
|
|
3
|
+
id: string;
|
|
4
|
+
parentId?: string | null;
|
|
5
|
+
label: string;
|
|
6
|
+
tone?: TreegraphChartTone;
|
|
7
|
+
};
|
|
8
|
+
type TreegraphChartProps = {
|
|
9
|
+
/** Nœuds plats : `parentId` null/absent = racine. Plusieurs racines acceptées. */
|
|
10
|
+
data: TreegraphChartNode[];
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
label: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
};
|
|
16
|
+
declare const TreegraphChart: import("svelte").Component<TreegraphChartProps, {}, "">;
|
|
17
|
+
type TreegraphChart = ReturnType<typeof TreegraphChart>;
|
|
18
|
+
export default TreegraphChart;
|
|
19
|
+
//# sourceMappingURL=TreegraphChart.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TreegraphChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/TreegraphChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,kBAAkB,CAAC;CAC3B,CAAC;AAMF,KAAK,mBAAmB,GAAG;IACzB,kFAAkF;IAClF,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA+MJ,QAAA,MAAM,cAAc,yDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|