Skip to content

Commit 828598b

Browse files
author
Oussama
committed
fix: stabilize treemap annotation positions
1 parent d3d9cce commit 828598b

3 files changed

Lines changed: 135 additions & 105 deletions

File tree

website/src/lib/charts/Treemap.svelte

Lines changed: 14 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script>
22
import { hierarchy, treemap, treemapSquarify } from 'd3-hierarchy';
33
import Annotation from '../components/Annotation.svelte';
4+
import './treemap.css';
45
56
export let data = [];
67
export let highlighted = [];
@@ -18,7 +19,15 @@
1819
$: layout = treemap().size([width, height]).paddingInner(1).tile(treemapSquarify)(root);
1920
$: leaves = layout.leaves().map((leaf, index) => ({ ...leaf, rank: index }));
2021
$: activeSet = new Set(lockedMedium ? [lockedMedium] : highlighted);
21-
$: activeLeaf = leaves.find((leaf) => activeSet.has(leaf.data.medium));
22+
$: annotationLeaf = annotation?.medium ? leaves.find((leaf) => leaf.data.medium === annotation.medium) : null;
23+
$: annotationPoint = getAnnotationPoint(annotation, annotationLeaf);
24+
25+
function getAnnotationPoint(note, leaf) {
26+
if (!note || lockedMedium) return null;
27+
if (Number.isFinite(note.x) && Number.isFinite(note.y)) return { x: note.x, y: note.y };
28+
if (leaf) return { x: (leaf.x0 + leaf.x1) / 2, y: (leaf.y0 + leaf.y1) / 2 };
29+
return null;
30+
}
2231
2332
function showTooltip(event, leaf) {
2433
const pct = Number(leaf.data.pct).toFixed(1);
@@ -122,15 +131,15 @@
122131
{/if}
123132
</g>
124133
{/each}
125-
{#if activeLeaf && annotation}
134+
{#if annotation && annotationPoint}
126135
<Annotation
127-
x={(activeLeaf.x0 + activeLeaf.x1) / 2}
128-
y={(activeLeaf.y0 + activeLeaf.y1) / 2}
136+
x={annotationPoint.x}
137+
y={annotationPoint.y}
129138
dx={annotation.dx}
130139
dy={annotation.dy}
131140
value={annotation.value}
132141
label={annotation.label}
133-
emphasis="strong"
142+
emphasis={annotation.emphasis ?? 'strong'}
134143
width={annotation.width}
135144
/>
136145
{/if}
@@ -147,99 +156,3 @@
147156
</tbody>
148157
</table>
149158
</figure>
150-
151-
<style>
152-
.treemap {
153-
margin: 0;
154-
width: 100%;
155-
}
156-
157-
svg {
158-
width: 100%;
159-
height: auto;
160-
display: block;
161-
padding: var(--space-1);
162-
background: var(--bg-paper);
163-
border: 1px solid var(--rule-on-light);
164-
}
165-
166-
.mark {
167-
fill: var(--seq-1);
168-
stroke: var(--bg-light);
169-
stroke-width: 1;
170-
cursor: pointer;
171-
transform-box: fill-box;
172-
transform-origin: center;
173-
animation: grow-leaf 900ms var(--ease-out) both;
174-
animation-delay: calc(var(--rank) * 35ms);
175-
transition: fill var(--duration-tooltip-in) var(--ease-out);
176-
}
177-
178-
.mark.highlight {
179-
fill: var(--accent-primary);
180-
}
181-
182-
.mark:hover,
183-
.mark:focus-visible {
184-
fill: var(--seq-2);
185-
outline: none;
186-
}
187-
188-
.mark.highlight:hover,
189-
.mark.highlight:focus-visible {
190-
fill: var(--accent-primary);
191-
}
192-
193-
.inner-frame,
194-
.motif {
195-
pointer-events: none;
196-
}
197-
198-
.inner-frame {
199-
fill: none;
200-
stroke: color-mix(in srgb, var(--fg-on-light-strong) 20%, transparent);
201-
stroke-width: 1;
202-
}
203-
204-
.motif line {
205-
stroke: color-mix(in srgb, var(--fg-on-light-strong) 16%, transparent);
206-
stroke-width: 1.2;
207-
stroke-linecap: round;
208-
}
209-
210-
.leaf:has(.mark.highlight) .motif line,
211-
.leaf:has(.mark.highlight) .inner-frame {
212-
stroke: color-mix(in srgb, var(--fg-on-dark-strong) 42%, transparent);
213-
}
214-
215-
text {
216-
fill: var(--fg-on-light-strong);
217-
font-family: var(--font-ui);
218-
font-size: var(--type-small-size);
219-
font-weight: 600;
220-
pointer-events: none;
221-
}
222-
223-
tspan + tspan {
224-
fill: var(--fg-on-light-mute);
225-
font-family: var(--font-mono);
226-
font-weight: 500;
227-
font-variant-numeric: tabular-nums;
228-
}
229-
230-
@keyframes grow-leaf {
231-
from {
232-
scale: 0;
233-
}
234-
to {
235-
scale: 1;
236-
}
237-
}
238-
239-
@media (prefers-reduced-motion: reduce) {
240-
rect {
241-
animation: none;
242-
transition: none;
243-
}
244-
}
245-
</style>

website/src/lib/charts/treemap.css

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
.treemap {
2+
margin: 0;
3+
width: 100%;
4+
}
5+
6+
.treemap svg {
7+
width: 100%;
8+
height: auto;
9+
display: block;
10+
padding: var(--space-1);
11+
background: var(--bg-paper);
12+
border: 1px solid var(--rule-on-light);
13+
}
14+
15+
.treemap .mark {
16+
fill: var(--seq-1);
17+
stroke: var(--bg-light);
18+
stroke-width: 1;
19+
cursor: pointer;
20+
transform-box: fill-box;
21+
transform-origin: center;
22+
animation: grow-leaf 900ms var(--ease-out) both;
23+
animation-delay: calc(var(--rank) * 35ms);
24+
transition: fill var(--duration-tooltip-in) var(--ease-out);
25+
}
26+
27+
.treemap .mark.highlight {
28+
fill: var(--accent-primary);
29+
}
30+
31+
.treemap .mark:hover,
32+
.treemap .mark:focus-visible {
33+
fill: var(--seq-2);
34+
outline: none;
35+
}
36+
37+
.treemap .mark.highlight:hover,
38+
.treemap .mark.highlight:focus-visible {
39+
fill: var(--accent-primary);
40+
}
41+
42+
.treemap .inner-frame,
43+
.treemap .motif {
44+
pointer-events: none;
45+
}
46+
47+
.treemap .inner-frame {
48+
fill: none;
49+
stroke: color-mix(in srgb, var(--fg-on-light-strong) 20%, transparent);
50+
stroke-width: 1;
51+
}
52+
53+
.treemap .motif line {
54+
stroke: color-mix(in srgb, var(--fg-on-light-strong) 16%, transparent);
55+
stroke-width: 1.2;
56+
stroke-linecap: round;
57+
}
58+
59+
.treemap .leaf:has(.mark.highlight) .motif line,
60+
.treemap .leaf:has(.mark.highlight) .inner-frame {
61+
stroke: color-mix(in srgb, var(--fg-on-dark-strong) 42%, transparent);
62+
}
63+
64+
.treemap text {
65+
fill: var(--fg-on-light-strong);
66+
font-family: var(--font-ui);
67+
font-size: var(--type-small-size);
68+
font-weight: 600;
69+
pointer-events: none;
70+
}
71+
72+
.treemap tspan + tspan {
73+
fill: var(--fg-on-light-mute);
74+
font-family: var(--font-mono);
75+
font-weight: 500;
76+
font-variant-numeric: tabular-nums;
77+
}
78+
79+
@keyframes grow-leaf {
80+
from {
81+
scale: 0;
82+
}
83+
to {
84+
scale: 1;
85+
}
86+
}
87+
88+
@media (prefers-reduced-motion: reduce) {
89+
.treemap rect {
90+
animation: none;
91+
transition: none;
92+
}
93+
}

website/src/lib/scenes/Scene1Mediums.svelte

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,39 @@
2222
2323
function stepAnnotation(activeStep) {
2424
if (activeStep === 0) {
25-
return { value: '144,149', label: 'cleaned works anchor the analysis.', dx: 80, dy: -70, width: 190 };
25+
return {
26+
x: 150,
27+
y: 120,
28+
dx: 110,
29+
dy: 64,
30+
value: '144,149',
31+
label: 'cleaned works anchor the analysis.',
32+
width: 205,
33+
};
2634
}
2735
if (activeStep === 1) {
28-
return { value: '1 in 2', label: 'works is on paper.', dx: 90, dy: -50, width: 170 };
36+
return { x: 360, y: 214, dx: 86, dy: -86, value: '1 in 2', label: 'works is on paper.', width: 170 };
2937
}
3038
if (activeStep === 2) {
31-
return { value: '44.2%', label: 'sit in Photography or Architecture & Design.', dx: 70, dy: -94, width: 230 };
39+
return {
40+
x: 260,
41+
y: 508,
42+
dx: 170,
43+
dy: -70,
44+
value: '44.2%',
45+
label: 'sit in Photography or Architecture & Design.',
46+
width: 245,
47+
};
3248
}
33-
return { value: '1 in 30', label: 'works belongs to Painting & Sculpture.', dx: -190, dy: -50, width: 190 };
49+
return {
50+
x: 608,
51+
y: 504,
52+
dx: -244,
53+
dy: -94,
54+
value: '1 in 30',
55+
label: 'works belongs to Painting & Sculpture.',
56+
width: 210,
57+
};
3458
}
3559
</script>
3660

0 commit comments

Comments
 (0)