Skip to content

Commit 15951cc

Browse files
author
Oussama
committed
fix: use collection thumbnails in scene 1
1 parent fd6b566 commit 15951cc

8 files changed

Lines changed: 89 additions & 60 deletions

File tree

data/build_aggregates.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,39 @@ def add_count_alias(records: list[dict[str, Any]]) -> list[dict[str, Any]]:
472472
return records
473473

474474

475+
def build_medium_sample_records(artworks: pd.DataFrame, medium_names: list[str]) -> list[dict[str, Any]]:
476+
"""Pick deterministic collection records with images for Scene 1 treemap leaves."""
477+
top_names = [name for name in medium_names if name != "Other"]
478+
records: list[dict[str, Any]] = []
479+
for medium in medium_names:
480+
if medium == "Other":
481+
subset = artworks[~artworks["Department"].isin(top_names)]
482+
else:
483+
subset = artworks[artworks["Department"] == medium]
484+
if subset.empty:
485+
continue
486+
487+
has_image = subset["ImageURL"].notna() & subset["ImageURL"].astype(str).str.startswith("http")
488+
candidates = subset[has_image] if bool(has_image.any()) else subset
489+
cataloged = candidates[candidates["Cataloged"] == "Y"]
490+
if not cataloged.empty:
491+
candidates = cataloged
492+
candidates = candidates.sort_values(["Artist", "Title", "ObjectID"], na_position="last")
493+
sample = candidates.iloc[seeded_index(f"scene1-{medium}", len(candidates))]
494+
records.append(
495+
{
496+
"medium": medium,
497+
"title": clean_token(sample["Title"]),
498+
"artist": clean_token(sample["Artist"]),
499+
"date": clean_token(sample["Date"]),
500+
"object_id": int(sample["ObjectID"]),
501+
"url": clean_token(sample["URL"]),
502+
"image_url": clean_token(sample["ImageURL"]),
503+
}
504+
)
505+
return records
506+
507+
475508
def main() -> None:
476509
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
477510
for stale_json in OUTPUT_DIR.glob("*.json"):
@@ -532,6 +565,8 @@ def main() -> None:
532565
top_mediums["pct"] = (top_mediums["n"] / n_total * 100).round(4)
533566
medium_total_records = records_from_df(top_mediums)
534567
write_json("medium_totals.json", medium_total_records)
568+
medium_sample_records = build_medium_sample_records(artworks, top_mediums["medium"].tolist())
569+
write_json("medium_samples.json", medium_sample_records)
535570

536571
timeline = artworks.groupby("decade", dropna=False).size().reset_index(name="n")
537572
timeline_records = add_count_alias(records_from_df(timeline.sort_values("decade")))

data/build_report.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
DArts aggregate build report
2-
generated_at: 2026-05-08T23:00:58+00:00
2+
generated_at: 2026-05-25T10:27:37+00:00
33

44
raw_artworks: 160,248
55
cleaned_artworks: 144,149
@@ -47,6 +47,7 @@ outputs:
4747
gender_by_decade_country.json: 79,461 bytes
4848
gender_by_decade_department.json: 21,341 bytes
4949
medium_breakdown.json: 588,699 bytes
50+
medium_samples.json: 2,423 bytes
5051
medium_totals.json: 438 bytes
5152
summary.json: 288 bytes
5253
timeline.json: 999 bytes
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"medium":"Drawings & Prints","title":"Curly Soil (Le Sol boucl\u00e9) from Elements, Moments (El\u00e9ments, Moments) from Phenomena (Les Ph\u00e9nom\u00e8nes","artist":"Jean Dubuffet","date":"1958","object_id":94727,"url":"https://www.moma.org/collection/works/94727","image_url":"https://www.moma.org/media/W1siZiIsIjI1NjYyMSJdLFsicCIsImNvbnZlcnQiLCItcmVzaXplIDEwMjR4MTAyNFx1MDAzZSJdXQ.jpg?sha=fddb86719fdb164e"},{"medium":"Architecture & Design","title":"Poster for The Cabinet of Dr. Caligari (Das Cabinet des Dr. Caligari","artist":"Otto Arpke, Erich Ludwig Stahl","date":"1919","object_id":4978,"url":"https://www.moma.org/collection/works/4978","image_url":"https://www.moma.org/media/W1siZiIsIjE2Njk2OCJdLFsicCIsImNvbnZlcnQiLCItcmVzaXplIDEwMjR4MTAyNFx1MDAzZSJdXQ.jpg?sha=2d9c0ec7aa7e5f29"},{"medium":"Photography","title":"Winter","artist":"Alexander Henderson","date":"1865","object_id":139586,"url":"https://www.moma.org/collection/works/139586","image_url":"https://www.moma.org/media/W1siZiIsIjM1NTgyOCJdLFsicCIsImNvbnZlcnQiLCItcmVzaXplIDEwMjR4MTAyNFx1MDAzZSJdXQ.jpg?sha=b2c474edf2a6746c"},{"medium":"Painting & Sculpture","title":"Altar/Engine","artist":"Tauba Auerbach","date":"2015","object_id":214522,"url":"https://www.moma.org/collection/works/214522","image_url":"https://www.moma.org/media/W1siZiIsIjQyNDY3MSJdLFsicCIsImNvbnZlcnQiLCItcmVzaXplIDEwMjR4MTAyNFx1MDAzZSJdXQ.jpg?sha=4287612f1dfb4e64"},{"medium":"Media and Performance","title":"Have you ever seen the snow?","artist":"Mario Garc\u00eda Torres","date":"2010","object_id":220086,"url":"https://www.moma.org/collection/works/220086","image_url":"https://www.moma.org/media/W1siZiIsIjM5ODQ5MiJdLFsicCIsImNvbnZlcnQiLCItcmVzaXplIDEwMjR4MTAyNFx1MDAzZSJdXQ.jpg?sha=faeadff45bce2fbc"},{"medium":"Fluxus Collection","title":"Concert 4/26/82 and Lecture 4/30/82","artist":"Henry Flynt","date":"1982","object_id":137496,"url":null,"image_url":null},{"medium":"Film","title":"The Unknown","artist":"Batiste Madalena","date":"1927","object_id":118899,"url":"https://www.moma.org/collection/works/118899","image_url":"https://www.moma.org/media/W1siZiIsIjEzNDc0OCJdLFsicCIsImNvbnZlcnQiLCItcmVzaXplIDEwMjR4MTAyNFx1MDAzZSJdXQ.jpg?sha=20da9ebe07567bf4"},{"medium":"Architecture & Design - Image Archive","title":"Reichsbank project, Berlin-Mitte, Germany","artist":"Ludwig Mies van der Rohe","date":"ca. 1933","object_id":116797,"url":null,"image_url":null}]

website/public/data/summary.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"n_artworks_total":144149,"n_artworks_dated":141884,"n_artist_credits":160035,"n_artists_total":11879,"year_min":1768,"year_max":2026,"generated_at":"2026-05-08T23:00:58+00:00","total_artworks":144149,"total_artists":11879,"unique_nationalities":129,"top_department":"Drawings & Prints"}
1+
{"n_artworks_total":144149,"n_artworks_dated":141884,"n_artist_credits":160035,"n_artists_total":11879,"year_min":1768,"year_max":2026,"generated_at":"2026-05-25T10:27:37+00:00","total_artworks":144149,"total_artists":11879,"unique_nationalities":129,"top_department":"Drawings & Prints"}

website/src/App.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
let genderByDepartment = $state([]);
1010
let timelineData = $state([]);
1111
let mediumTotals = $state([]);
12+
let mediumSamples = $state([]);
1213
let countryByDecade = $state([]);
1314
let countrySummary = $state([]);
1415
let genderByCountry = $state([]);
@@ -42,6 +43,7 @@
4243
'gender_by_decade',
4344
'timeline',
4445
'medium_totals',
46+
'medium_samples',
4547
'country_by_decade',
4648
'country_summary',
4749
'gender_by_decade_country',
@@ -80,6 +82,7 @@
8082
genderByDecade,
8183
timelineData,
8284
mediumTotals,
85+
mediumSamples,
8386
countryByDecade,
8487
countrySummary,
8588
genderByCountry,
@@ -107,7 +110,7 @@
107110
{@const Scene3 = sceneModules.Scene3Gender}
108111
{@const Scene4 = sceneModules.Scene4Explorer}
109112
{@const Scene5 = sceneModules.Scene5Quiz}
110-
<Scene1 data={mediumTotals} />
113+
<Scene1 data={mediumTotals} samples={mediumSamples} />
111114
<Scene2 {countryByDecade} {countrySummary} timeline={timelineData} />
112115
<Scene3 {genderByDecade} {genderByDepartment} {genderByCountry} {countrySummary} timeline={timelineData} />
113116
<Scene4 data={mediumBreakdown} {countrySummary} timeline={timelineData} />

website/src/lib/charts/Treemap.svelte

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,27 @@
2929
return null;
3030
}
3131
32+
function escapeHtml(value = '') {
33+
return String(value)
34+
.replaceAll('&', '&amp;')
35+
.replaceAll('<', '&lt;')
36+
.replaceAll('>', '&gt;')
37+
.replaceAll('"', '&quot;')
38+
.replaceAll("'", '&#039;');
39+
}
40+
3241
function showTooltip(event, leaf) {
3342
const pct = Number(leaf.data.pct).toFixed(1);
43+
const sample = leaf.data.sample;
44+
const sampleLine = sample?.title
45+
? `<br><span>Shown: <em>${escapeHtml(sample.title)}</em>${sample.artist ? `, ${escapeHtml(sample.artist)}` : ''}</span>`
46+
: '';
3447
window.dispatchEvent(
3548
new CustomEvent('darts:tooltip', {
3649
detail: {
3750
x: event.clientX,
3851
y: event.clientY,
39-
content: `<strong>${leaf.data.medium}</strong><br><span class="num">${leaf.data.n.toLocaleString()}</span> works<br><span class="num">${pct}%</span> of cleaned collection`,
52+
content: `<strong>${escapeHtml(leaf.data.medium)}</strong><br><span class="num">${leaf.data.n.toLocaleString()}</span> works<br><span class="num">${pct}%</span> of cleaned collection${sampleLine}`,
4053
},
4154
}),
4255
);
@@ -75,19 +88,11 @@
7588
}));
7689
}
7790
78-
function pictureType(medium = '') {
79-
if (medium.includes('Photography')) return 'photo';
80-
if (medium.includes('Architecture') || medium.includes('Design')) return 'design';
81-
if (medium.includes('Painting') || medium.includes('Sculpture')) return 'painting';
82-
if (medium.includes('Film') || medium.includes('Media')) return 'film';
83-
return 'paper';
84-
}
85-
8691
function pictureBox(leaf) {
8792
const w = leaf.x1 - leaf.x0;
8893
const h = leaf.y1 - leaf.y0;
89-
if (w < 58 || h < 46) return null;
90-
const size = Math.min(w * 0.42, h * 0.48, 130);
94+
if (w < 70 || h < 58) return null;
95+
const size = Math.min(w * 0.44, h * 0.5, 142);
9196
return {
9297
x: leaf.x1 - size - Math.max(10, w * 0.06),
9398
y: leaf.y1 - size * 0.72 - Math.max(10, h * 0.06),
@@ -111,6 +116,7 @@
111116
{@const leafHeight = leaf.y1 - leaf.y0}
112117
{@const area = leafWidth * leafHeight}
113118
{@const box = pictureBox(leaf)}
119+
{@const sample = leaf.data.sample}
114120
<g class="leaf" style:--rank={leaf.rank}>
115121
<rect
116122
class="mark"
@@ -144,18 +150,16 @@
144150
<line x1={line.x1} y1={line.y1} x2={line.x2} y2={line.y2} />
145151
{/each}
146152
</g>
147-
{#if box}
148-
<g class={`picture ${pictureType(leaf.data.medium)}`} clip-path={`url(#leaf-clip-${leaf.rank})`}>
149-
<rect x={box.x} y={box.y} width={box.w} height={box.h} />
150-
<path
151-
d={`M ${box.x + box.w * 0.12} ${box.y + box.h * 0.72} C ${box.x + box.w * 0.34} ${box.y + box.h * 0.28}, ${box.x + box.w * 0.52} ${box.y + box.h * 0.76}, ${box.x + box.w * 0.88} ${box.y + box.h * 0.26}`}
152-
/>
153-
<circle cx={box.x + box.w * 0.72} cy={box.y + box.h * 0.34} r={Math.max(3, box.w * 0.08)} />
154-
<line
155-
x1={box.x + box.w * 0.16}
156-
x2={box.x + box.w * 0.86}
157-
y1={box.y + box.h * 0.84}
158-
y2={box.y + box.h * 0.84}
153+
{#if box && sample?.image_url}
154+
<g class="picture" clip-path={`url(#leaf-clip-${leaf.rank})`} aria-hidden="true">
155+
<rect class="mat" x={box.x} y={box.y} width={box.w} height={box.h} />
156+
<image
157+
href={sample.image_url}
158+
x={box.x + 4}
159+
y={box.y + 4}
160+
width={Math.max(0, box.w - 8)}
161+
height={Math.max(0, box.h - 8)}
162+
preserveAspectRatio="xMidYMid slice"
159163
/>
160164
</g>
161165
{/if}

website/src/lib/charts/treemap.css

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -58,46 +58,22 @@
5858
}
5959

6060
.treemap .picture {
61-
opacity: 0.56;
61+
opacity: 0.78;
6262
}
6363

64-
.treemap .picture rect {
65-
fill: color-mix(in srgb, var(--bg-paper) 24%, transparent);
66-
stroke: color-mix(in srgb, var(--fg-on-light-strong) 28%, transparent);
67-
stroke-width: 1.4;
68-
}
69-
70-
.treemap .picture path,
71-
.treemap .picture line {
72-
fill: none;
73-
stroke: color-mix(in srgb, var(--fg-on-light-strong) 38%, transparent);
74-
stroke-width: 2;
75-
stroke-linecap: round;
76-
}
77-
78-
.treemap .picture circle {
79-
fill: color-mix(in srgb, var(--accent-primary) 32%, transparent);
80-
stroke: color-mix(in srgb, var(--fg-on-light-strong) 24%, transparent);
64+
.treemap .picture .mat {
65+
fill: var(--bg-paper);
66+
stroke: color-mix(in srgb, var(--fg-on-light-strong) 34%, transparent);
8167
stroke-width: 1.2;
8268
}
8369

84-
.treemap .picture.photo circle {
85-
fill: color-mix(in srgb, var(--region-namerica) 32%, transparent);
86-
}
87-
88-
.treemap .picture.design circle {
89-
fill: color-mix(in srgb, var(--region-europe) 30%, transparent);
90-
}
91-
92-
.treemap .picture.film circle {
93-
fill: color-mix(in srgb, var(--region-africa) 30%, transparent);
70+
.treemap .picture image {
71+
opacity: 0.86;
9472
}
9573

9674
.treemap .leaf:has(.mark.highlight) .motif line,
9775
.treemap .leaf:has(.mark.highlight) .inner-frame,
98-
.treemap .leaf:has(.mark.highlight) .picture rect,
99-
.treemap .leaf:has(.mark.highlight) .picture path,
100-
.treemap .leaf:has(.mark.highlight) .picture line {
76+
.treemap .leaf:has(.mark.highlight) .picture .mat {
10177
stroke: color-mix(in srgb, var(--fg-on-dark-strong) 42%, transparent);
10278
}
10379

website/src/lib/scenes/Scene1Mediums.svelte

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import ScrollStep from '../components/ScrollStep.svelte';
66
import Treemap from '../charts/Treemap.svelte';
77
8-
let { data = [] } = $props();
8+
let { data = [], samples = [] } = $props();
99
1010
let lockedMedium = $state('');
1111
12+
let sampleByMedium = $derived(new Map(samples.map((sample) => [sample.medium, sample])));
13+
let treemapData = $derived(data.map((item) => ({ ...item, sample: sampleByMedium.get(item.medium) })));
1214
let step = $derived($filters.activeScene === 1 ? $filters.activeStep : 0);
1315
let highlighted = $derived(lockedMedium ? [lockedMedium] : stepHighlight(step));
1416
let annotation = $derived(stepAnnotation(step));
@@ -71,9 +73,16 @@
7173
<FilterChip label="Selected" value={lockedMedium} onRemove={() => (lockedMedium = '')} />
7274
</div>
7375
{/if}
74-
<Treemap {data} {highlighted} {annotation} {lockedMedium} onLock={(medium) => (lockedMedium = medium)} />
76+
<Treemap
77+
data={treemapData}
78+
{highlighted}
79+
{annotation}
80+
{lockedMedium}
81+
onLock={(medium) => (lockedMedium = medium)}
82+
/>
7583
<p class="source">
76-
Source: MoMA Collection. n = {data.reduce((sum, d) => sum + d.n, 0).toLocaleString()} cleaned works.
84+
Source: MoMA Collection. n = {data.reduce((sum, d) => sum + d.n, 0).toLocaleString()} cleaned works. Thumbnails use
85+
MoMA collection image URLs where available.
7786
</p>
7887
</div>
7988
<div class="text-rail">

0 commit comments

Comments
 (0)