Skip to content

📊 Update OECD Time Use Database and refresh static viz - #6678

Draft
paarriagadap wants to merge 21 commits into
masterfrom
data-oecd-timeuse-refresh
Draft

📊 Update OECD Time Use Database and refresh static viz#6678
paarriagadap wants to merge 21 commits into
masterfrom
data-oecd-timeuse-refresh

Conversation

@paarriagadap

@paarriagadap paarriagadap commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Written by Claude Opus 5 — @paarriagadap at the wheel.

Refreshes the OECD Time Use Database to the 2026 edition and rebuilds the "How do people spend their time?" static visualization on top of it.

The previous version of this dataset was a legacy backport (oecd/2020-11-05/time_use_across_activities__oecd_gender_data__2020, private, no catalog path). It is replaced by a normal public ETL chain, so the data is now in the catalog and the chart is reproducible from a step rather than from a one-off file.

The chain

snapshot://oecd/2026-08-14/time_use.xlsx
  → data://meadow/oecd/2026-08-14/time_use
    → data://garden/oecd/2026-08-14/time_use
      → data://grapher/oecd/2026-08-14/time_use
      → export://static_viz/oecd/2026-08-14/time_use_by_country

Snapshot is the OECD's own workbook, published 2026-04-30, accessed 2026-08-14.

Garden emits two tables

Table Shape Contents
time_use 105 × 35 The source as published — 30 activity indicators, plus survey_year and age_of_reference per row
time_use_chart_groups 105 × 14 10 display groups + total_leisure, for the chart

Both cover 35 countries × 3 breakdowns (total, men, women). Survey years run 1999–2024 and differ by country, so each row carries its own survey_year rather than the table pretending to a single reference year.

The regrouping of the OECD's 30 detailed activities into the 10 display groups lives in garden, documented and asserted there, so the static-viz step only lays out what it is given.

The static visualization

One frame, 850×1095, on the vertical static-chart template, as SVG and PNG. Each bar is a country's
day split into the OECD's four top-level categories, ranked by paid work or study, with the activities
each category holds named above it.

Only surveys from 2010 on — 26 countries, 2010–2024. The source gives one survey per country, so a
cutoff drops countries rather than years, and 2010 is where that costs least: six countries sit on 2010
itself, and it buys eleven years of recency for nine others. It also removes every age-of-reference
exception (Australia 15+, China 15–74, Lithuania 20–64 are all pre-2010), so the remaining countries are
15 to 64 throughout. What it costs is India and China, and with them most of the coverage outside
high-income Europe. One constant; None restores all 35.

The step fills the template's slots at the template's own sizes, positions and line heights, so the
render previews the frame rather than only its proportions. The chart band is derived from the
templates' auto-layout rhythm rather than recorded, because every position in the template describes its
placeholder text. The design team rebuilt those headers in August: the wrappers lost their inner padding
and the logo became a sibling rather than a child of the title row, so a one-line title genuinely shrinks
the header and this frame's band top went from 82.5 to 70. Re-verified with /create-figma-chart's
verify_templates.js, which returns ok.

Labels are anchored where they must stay, not where they start, because Figma re-renders them in
Lato: middle over a segment, end on the content edge, one call per line for a stacked label (a
multi-line label carries no text-anchor at all). Row text sits on an explicit baseline half a
cap-height below the row's centre — va="center" splits the font's line box and left every value a
tenth of a bar high.

Four categories, not the source's ten display groups

This is the substantive decision in the PR. The ten resolve more than the harmonization supports:
the residual buckets inside each category vary two- to threefold across countries (other unpaid work
runs 39 minutes in France to 132 in Ireland) while the categories themselves vary far less — personal
care is 665 minutes ± 31. Aggregating takes the mean coefficient of variation across segments from
0.22 to 0.12, so most of what the finer split resolves is where each survey drew its coding lines,
not how people differ. A ten-group version and a 540×824 mobile one were built and dropped; the workings
are in ai/time_use_comparability/.

Two things aggregating does not fix, and one worry it retires:

  • A boundary that runs between categories — childcare against leisure with children, eating at work —
    still moves minutes. It stays in the Note.
  • The survey spread turns out not to bias the ranking: the correlation between a country's survey
    year and any of the four categories is +0.09 at most.

Five categories were considered and four kept. The source publishes five (paid work or study, unpaid
work, personal care, leisure, other) and this chart folds the fifth — religious, civic and unclassified
time, 18.5 min/day and up to 40 — into unpaid work, which is what the category's "& other" name
discloses. Drawn as its own segment it is 7.8px wide on average and 2.1px on seven countries, so it
can carry neither a value nor a legend column; adding a fifth colour takes the composited colour-vision
floor from 15.3 to 14.9 and no arrangement of five clears the grayscale seam (best 1.18:1 against
1.6); and two-thirds of it is the source's own "other (no categories)".

The legend names what the source names

Seventeen activities, ordered by the minutes they take. Which ones get named is a data question rather
than a layout one: several are reported by a minority of countries (job search by 10 of 35, other unpaid
work by 12), so naming all 22 would imply detail most bars do not have.

Re-reading the workbook's "Activity categories & sources" sheet then showed that two of the three
"Other" lines were not the source's residuals at all — we had relabelled them:

Code The OECD's own label Min/day Now reads
3.3 personal, household, and medical services + travel related to personal care 61 (9% of personal care) Grooming & health
4.5 Other leisure activities — games, hobbies, arts and crafts, reading and leisure travel, per the country mappings 96 (33% of leisure, second only to TV and radio) Hobbies & other

One "Other" is left, in the category whose own name already says "& other".

Colors, and the color-vision check

The Default Palette's first four, bound to the [Chart Colors] Library — Denim, Camel, Rusty Orange,
Light Teal — with the middle two swapped so touching segments differ in lightness, and drawn at grapher's
own bar opacity of 0.8 (GRAPHER_AREA_OPACITY_DEFAULT, which both its stacked and its discrete bar
chart apply; labels stay at 1). The library's order puts two same-lightness pairs side by side (Denim
beside Rusty Orange at 1.14:1, Camel beside Light Teal at 1.08:1, against a 1.6:1 floor) — fine in colour,
invisible in black and white. Swapping costs no colour, since the CVD numbers are identical for any
arrangement of the same four.

Two constraints shaped the set, and the second is easy to miss:

  • Touching fills need that seam, which forces the stack to alternate light and dark. No all-light set
    exists
    : everything light in the palette sits at L 60–65, where two of them have no seam at all.
  • A category's colour is also the colour its legend name prints in, at 11px on cream. Camel measures
    2.8:1 as text and Light Teal 2.6:1. The library's answer is its "Line and Slope Charts" group, a darker
    variant of each light fill, every one of which measures exactly 4.4:1 on this cream — so the colour
    spec names a fill and, where needed, that variant, and the frame binds both.

Three accepted deviations, all measured at the opacity the bars are drawn with, since the composite
over the canvas is what a reader sees:

opaque at 0.8
seams 1.86 / 2.12 / 2.28 1.57 / 1.86 / 1.95
ΔE floor (normal/deuteranopia/protanopia) 19.2 15.3
in-bar label, Denim 5.46 white 3.74 dark
in-bar label, Rusty Orange 6.21 white 4.30 white

Those labels are 12.15px Lato Regular, so 4.5:1 applies with no large-text allowance. The label contrast
is the cost of the opacity, not of the palette
— grapher can afford 0.8 because it puts no labels inside
its stacked segments, and this chart puts one in every segment. At 0.9 they measure 4.46 and 5.20.

In Figma

One frame, how-do-people-spend-their-time,
on page 20260817 How do people spend their time? (Pablo A),
cloned from the rebuilt Static Chart Template_Vertical with its unstyled import parked to the left. The
full recipe — import mechanics, the exact scale factor, every text slot and its bold ranges, the colour
keys, the restyle order — is in the step's docstring, so a later session can redo it from the file alone.

Retiring the old dataset

The migrated dataset's steps and backport snapshot mirrors are deleted, its dag entry removed, and it is recorded in dag/archive/migrated.yml with the commit where it was last active.

Verification

  • make check green; tests/test_static_viz.py passes (31 tests); the contract verifier reports 0 clip
    paths and 267 named layers
  • The render is byte-identical through the whole cleanup. Dropping the two other charts removed 523
    lines — the listed and flowed legends, the total-leisure column, the bracket rule, three layout
    switches, five functions and seven constants — and the approved SVG was diffed after every pass
  • Colour audit on the composited fills, with the three deviations above recorded in the step
  • Measured in the frame after the rebuild: header bottom 70 against the template's own 70, subtitle one
    line clear of the logo by 134px, 104 segments at 0.8 opacity, every coloured fill bound to a library
    style

Two corrections worth noting, both caught by measuring rather than by review:

  • The step measured in DejaVu and drew in Arialsns.set_style installs an Arial-first stack but
    runs after the title and subtitle are wrapped. Measuring in the stack it draws with puts the slot
    allowances back where the skill records them (Playfair 3.2% wider, Lato Regular 0.8% narrower), plus
    one the skill does not: Arial Bold sets 6.6% wider than Lato Bold, which is why the footer's
    mixed-weight row needs its own allowance.
  • These rebuilt footers grow downward from a fixed top, where the step's model grew the Note upward.
    They agree at two lines and diverge past them, so the Note is capped at two.

Open before merge

  • The in-bar labels measure 3.74:1 and 4.30:1 against a 4.5:1 bar, at grapher's 0.8 bar opacity.
    Raising it to 0.9 fixes both (4.46 and 5.20) and keeps most of the softening. Needs a call.
  • Promote the category sums into garden. The export step still sums garden's ten display groups
    into four; now that four is what ships, the aggregate belongs in the catalog.
  • Design sign-off: the palette with its three recorded deviations, and the two legend renames taken
    from the source's own labels
  • Chart 2541 (time-women-and-men-spend-on-leisure) — remapped on staging to the new variable IDs
    (1207880 → 1296121, 1207882 → 1296123); chart diff shows it and needs approval, which is the
    only thing keeping CI red
  • Chart 2541's subtitle is now wrong: it says surveys were conducted between 2009 and 2016, but the
    2026 edition spans 1999–2024
  • Topic-page image swap, and the surrounding prose: the cited survey years all moved (United States
    2018→2024, Japan 2016→2021, Mexico 2014→2024, and nine others), and Japan now tops paid work

@owidbot

owidbot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Quick links (staging server):

Site Dev Site Preview Admin Wizard Docs Docs Preview

Login: ssh owid@staging-site-data-oecd-timeuse-refresh

chart-diff: ❌
  • 0/1 reviewed charts
  • Modified: 0/1
  • New: 0/0
  • Rejected: 0
  • Data changes: 0
  • Metadata changes: 1
data-diff: ✅ no differences

No differences found.

Automatically updated datasets matching excess_mortality|covid|fluid|flunet|country_profile|garden/ihme_gbd/2019/gbd_risk are not included. Run locally with etl diff REMOTE data/ --include <dataset> --verbose.

Edited: 2026-08-17 19:27:43 UTC
Execution time: 8.20 seconds

Replace the private migrated 2020 Gender Data Portal dataset with a public
snapshot → meadow → garden → grapher chain built on the OECD Time Use Database
2026 edition (published 2026-04-30): 35 countries, 30 detailed activities, and
newer surveys for 12 countries (US and Mexico 2024, Poland 2023, Canada,
Germany and Norway 2022, Croatia 2022/23, Japan 2021, Austria 2021/22, Estonia,
Finland and Bulgaria).

Garden also builds the display buckets the "How do people spend their time?"
chart uses. The grouping is verified numerically against the previous edition
of that chart and asserted for China 2008, whose survey has not changed.

The export://static_viz step renders the refreshed chart at the static-chart
templates' proportions, desktop and mobile, for the Figma handoff.
…ategories

Order the ten display groups so each of the OECD's four top-level categories is
contiguous, colour each family from one hue, and bracket each category above
the bars. On the narrow mobile frame, where the bracket cannot fit, list the
same names by category above the chart.

Group labels are now placed by a solver rather than by hand: it assigns tiers,
wraps and nudges labels, and routes each leader through a corridor its
neighbours leave free, asserting that no label, leader or bracket collides. The
header therefore survives a data update that changes the top row's proportions,
which hand-tuned offsets would not.
…up last

Category names now bracket the bars from above and the group names are listed
by category below them. Ten group names and four category names could not share
one strip: the category-contiguous ordering packs six group names into the right
third of the bar, where their labels run several times wider than the segments
they name, so the solver had to stack them five tiers deep. Splitting the two
halves leaves the top clear, gives every row about a tenth more height, and lets
desktop and mobile explain the chart the same way.

Within a category, named activities come first and a residual 'other' group
comes last, with the deep hue leading and tints following in bar order. An
assertion keeps it that way.
A category name wider than the bars it covers now wraps onto a second line
before it is moved to a row of its own, and a wide name only keeps its one-line
form if the narrowest form of every category to its right still fits beside it.
Without that lookahead the widest name took the room and its neighbour was the
one bumped, which is backwards.

So 'Unpaid work & other' wraps and 'Leisure' returns to the same baseline as the
other three, where it sits beside its own bracket and needs no stem.
@paarriagadap

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: c372227d76

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Each category's activities are now listed inside its own bracket, stacked one per
line, so every name stands over the run of bars it belongs to instead of in a
legend the reader has to carry back to the chart. Inside the bracket the category
is already named, so the residual buckets read as just 'Other'; that alone took
the header from five lines to three, and what the two least self-explanatory
groups contain moved to the Note, where there is room for the words.

Labelling each activity over its own segment was tried first and dropped. The top
row is Japan's, whose 'Seeing friends' is the narrowest segment in the dataset at
4px, and threading ten names past each other there needed elbow leaders, a search
over placement orders and a budget for how many leaders may cross — for a header
no easier to read than this one. What it needed is gone with it.

Values inside the segments are now drawn per activity only where three quarters
of countries can hold one, so none carries numbers on a handful of rows. Education
fitted on 1 of 35, and that one number said nothing except that Korea's segment
happened to be wide enough.

The ranking is an explicit constant that also accepts a category, and the sort is
stable, so the four tied countries keep the source's order between runs instead of
churning the SVG. Paid work stays: it is the original chart's ranking, and the one
column education cannot distort, since education time is depressed wherever the
survey's age floor excludes teenagers.

Also stops the mobile legend running into the 'Total leisure' column header, with
an assertion so a longer legend fails the build instead of overprinting it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@paarriagadap

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: e8271c722c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

paarriagadap and others added 2 commits August 17, 2026 15:02
The desktop template's license slot is 263px at 11px and shares its row with the
467px tagline inside 818px of content, so it holds about fifty characters. Two
author names do not fit: the line measured 387px and printed on top of the
tagline. Phrasing gives rather than names — 'by <names>' instead of 'by the
authors <names>' is 329px and clears by 22px.

Also removes LABEL_WRAP_WIDTHS, which fed the positional label solver deleted in
e8271c7 and had been left behind with a comment about leader corridors. Ruff does
not flag unused module-level constants, so nothing caught it; the rebuild after
removing it produced byte-identical art, which is what proves it was dead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The plot's band came from this step's own text metrics, and this step draws its
title, subtitle and footer smaller than the template does — so the header ink sat
flush under its own subtitle and, once the frame carried the template's larger
text, cleared it by only 7px where the design asks for 12-16. The band now comes
from the template's own text edges, inset by BAND_INSET at each end. Both edges
are ink, not frames: the footer frame already starts 14px above its note, so
insetting from the frame would inset twice.

The right-hand column is right-aligned on the content edge rather than
left-aligned at a fixed gap past the bars, so the total-leisure numbers, the logo
above them and the note below all share one vertical. The left already lined up:
right-aligned country labels put the widest one on the margin.

Separators between activity names are their own run in one neutral color. Appended
to the name before them they inherited that name's fill, so they changed color
down the list and all but vanished after a pale tint. The space before a separator
rides with the name, because TextPath measures ink and drops a leading space from
a run's advance while matplotlib still draws it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
paarriagadap and others added 12 commits August 17, 2026 17:34
…p can't move it

The step drew its copies of the template's text slots smaller than the template
does. Nothing in the frame uses them — the import drops them — but the plot's band
is derived from where they end, so the render showed a hole the frame did not have,
and each round of review spent itself on spacing that was only wrong in the preview.
It now fills each slot at the template's own size, line height and position, so the
render previews the frame instead of only its proportions.

The band follows from the templates' auto-layout rhythm rather than from their
shipped positions, which describe placeholder text nobody has: a title row hugging
whichever is taller of the title and the logo, a 6px gap, the subtitle. Two
consequences worth naming. A one-line title cannot shrink the header past the logo,
which is why the band's top is 82.47 here and not the 67 arithmetic alone suggests.
And the footer is pinned to the frame's bottom margin, so this Note taking three
lines at the template's 12px — where the step's smaller footer took two — costs the
chart a row's height rather than moving the rows beneath it.

Labels are now anchored where they have to stay rather than where they start, since
Figma re-renders them in Lato: middle over a bracket or a segment, end on the
content edge, and one call per line for a stacked label, which otherwise carries no
anchor at all. A run's trailing space is kept in the layout and out of the glyphs,
because SVG centres the trimmed ink. Row text sits on an explicit baseline half a
cap-height below the row's centre; `va="center"` splits the font's line box instead,
which reserves descender room digits never use and left every value sitting a tenth
of a bar high.

The license row is drawn as runs so the license and the names it credits can be
bold, as the templates set their own. The total-leisure column is derived from its
widest value plus one gap — the slack in it was the distance between the numbers and
the bars they belong to — and spells the unit out where that still leaves the column
a small share of the frame. Its header is centred over those numbers.

Wrapping is predicted with a measured allowance per face: the template's Lato sets
2-3% narrower than the font this step measures in, Playfair Display 3% wider. The
single conservative margin that preceded it broke both footer rows onto second lines
the frame does not have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The title row hugs whichever is taller, the title or the logo beside it, and both
are top-aligned — so with a one-line title the logo's surplus height lands between
the title and the subtitle, and the gap comes out at 18px where every finished page
in the Charts file shows 6. The templates were only ever exercised with two-line
titles, which are taller than the logo.

The frame now takes the logo out of its auto-layout, which keeps it exactly where it
is and stops it padding the block, so `logo_px` is 0 here and the derived band
follows the header up: the plot starts 12px higher and gains that much height.

Mobile keeps the logo in the flow. Raising that header would put the subtitle's
first line at the logo's height, and mobile's subtitle runs the full 508px — its
first line reaches x=493 against a logo starting at 476. Desktop is only safe
because its first line ends at 588 with the logo at 770.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the smallest group

The four categories now come from the OWID palette rather than from seaborn positions
mapped by hand: Coral, Denim, Copper and Dark Olive Green, bound to their Chart colors
library styles in the frame. Personal care had been carrying a color that is not in the
palette at all, which is what prompted this.

Which category takes which color is the part worth explaining. The best-scoring set
puts a near-black green somewhere, and on personal care — half of every bar — that made
the chart a green chart. Leisure is a fifth of the bar, so the blue and the green are
swapped: every audit number is identical, since it is the same ten fills, and the
heaviest one now sits on the smallest family.

The tint weights move with the dark base for a related reason. The seams that merged in
print were between a base and its own tints, not between categories — 1.46:1 and 1.52:1
against a 1.6:1 floor — so the family carrying the dark base takes the wider spread
(0.42/0.78) and the other keeps 0.3/0.68. Together with the palette change that takes
the color-vision floor from dE 6.4 to 14.2 and leaves no seam merging.

Red beside green is worth the usual suspicion, and the answer is in the lightness: the
red and the green here differ 2.39:1 in grayscale, where the red and the mid-green that
scored well on paper differ 1.01:1 and duly fail protanopia.

The two longest row labels are shortened to US and UK, the forms the style guide
sanctions. The row labels set their column's width, so that is 15px of bar on desktop
and 13px on mobile — the gap between those two names and "New Zealand".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nning the frame

The ten-segment chart resolves more than the harmonization can support. Measured
across the 35 countries, the residual buckets inside each category vary two- to
threefold — other unpaid work runs 39 minutes in France to 132 in Ireland, other
leisure 78 in Bulgaria to 179 in Japan — while the categories themselves vary far
less: personal care is 665 minutes give or take 31, a coefficient of variation of
0.05 against 0.25 for its own residual. Aggregating to the four top-level categories
takes the mean CV across the segments from 0.22 to 0.12. So most of what the detailed
split shows at that level is where each survey drew its coding lines, not how people
differ, and this adds the four-category chart as a candidate rather than a
replacement. The workings are in `ai/time_use_comparability/`.

Two things the aggregation does not fix, and one worry it retires. It cannot help
where a boundary runs *between* categories — childcare against leisure with children,
eating at work — and it cannot touch the age ranges, which is why the Note still
carries them. But the survey years, spread over 1999-2024, turn out not to bias the
ranking at all: the correlation between a country's survey year and any of the four
categories is +0.09 at most.

The chart is a third layout on the same step, not a copy, so the template machinery
has one implementation. `GROUPS` and `CATEGORIES` are now read off the layout, and the
two shipped charts came out byte-identical afterwards, which is the only proof worth
having that a refactor of this size changed nothing. Each layout also ranks by its own
leading segment, which for the detailed pair is the same key as before.

Three things the new chart needed, each opt-in so the shipped pair is untouched: a
category name wraps when it overhangs the segment it names rather than only when it
would hit a neighbour; a segment's values take one form per column, so a row that
cannot fit the long form does not read as a mistake; and every value is hours and
minutes, compactly, because one category set differently from its neighbours reads as
a difference in kind when the only difference is that its bar is wider.

The Note fix is a bug in the chart that already shipped. Wrapping it against the slot
*times* the font slack allowed lines of 832.0 and 821.8 pixels against a content width
of 817.57 — two of its three lines printed past the frame's edge. That slack exists to
predict how many lines the template's narrower Lato takes; applying it to ink this
step actually draws is what let it overrun. It now wraps to the content width, still
three lines, so the band does not move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pre-commit formatter rewrapped this after the previous commit was assembled, so it
lands on its own rather than as a force-push over a pushed commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…efresh

# Conflicts:
#	dag/archive/migrated.yml
Three changes to the four-category chart, and one that reaches the other two.

Only surveys from 2010 on are drawn. The source gives one survey per country, so
this drops countries rather than years, and 2010 is where that costs least: six
countries sit on 2010 itself, so the cut lands on a cluster rather than slicing
mid-run, and it buys eleven years of recency for nine countries. It also removes
every age-of-reference exception, because Australia (15+), China (15-74) and
Lithuania (20-64) are all pre-2010 surveys — so the remaining 26 countries are 15
to 64 throughout, and the Note lost a clause it had carried since the first draft.
What it costs is India and China, and with them most of the coverage outside
high-income Europe. The cutoff is one constant, applied where the table loads so all
three layouts draw the same countries; None restores all 35.

The legend names what each category holds — seventeen activities, ordered by the
minutes they take. Which activities get named is a data question rather than a
layout one: several are reported by a minority of countries (job search by 10 of 35,
other unpaid work by 12, childcare by 28), so naming all 22 would imply detail most
bars do not have. The Note now carries that caveat instead of the "covers ..."
clauses the legend has taken over.

Each list hangs off the bars rather than off the top of the band. Top-aligned, a
short list ended two lines above the plot while the deepest reached down to it; now
every list ends level with the bars and the air sits under the category name, where
half a line of it reads as space beneath a heading.

The subtitle states the survey window, with the years read from the data so it cannot
drift from what is drawn. It says "Average", not "Averages of", for a reason worth
keeping: in Lato the longer opening measured 826px against an 817.57px slot, so the
frame wrapped a subtitle this step had drawn on one line, and the plot would have been
laid over it. This step's font is a percent or two off Lato in either direction
depending on the string, so a slot filled past about 97% is a coin flip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er's bar opacity

The palette is the Default Palette's first four — Denim, Rusty Orange, Camel, Light
Teal — with the middle two swapped, and the fills carry grapher's own bar opacity of
0.8 (GRAPHER_AREA_OPACITY_DEFAULT, which both its stacked and its discrete bar chart
apply, labels at 1).

The swap is because the library's order puts two same-lightness pairs side by side:
Denim beside Rusty Orange at 1.14:1 and Camel beside Light Teal at 1.08:1, against a
1.6:1 floor. That reads fine in colour and vanishes in black and white. Swapping costs
no colour — which category carries which is free, since the colour-vision numbers are
identical for any arrangement of the same four — and takes the opaque seams to
1.86 / 2.12 / 2.28.

Three deviations are recorded in the step, all measured at the opacity the bars are
actually drawn with, because the composite over the canvas is what a reader sees and
not one of these numbers survives being read off the library hex. Seams come out
1.57 / 1.86 / 1.95, the first 2% under the floor. The colour-vision floor drops from
19.2 to 15.3, Camel against Light Teal being the weak pair. And the in-bar labels lose
the 4.5:1 they need at 12.15px regular: the best available colour measures 3.74:1 on
Denim and 4.30:1 on Rusty Orange, where at full opacity both clear it in white. That
last one is the cost of the opacity rather than of the palette — grapher can afford 0.8
because it puts no labels inside its stacked segments, and this chart puts one in every
segment. At 0.9 they measure 4.46 and 5.20.

Two things had to change for a palette this light to be possible at all. A category's
colour is also the colour its legend name and member list print in at 11px, which rules
out every light entry in the palette — Camel measures 2.8:1 as text and Light Teal
2.6:1. The library's answer is its "Line and Slope Charts" group, a darker variant of
each light fill, every one of which measures exactly 4.4:1 on this cream; the four
colours now name a fill and, where needed, that variant, and the frame binds both. And
the in-bar label colour is now chosen by measuring white and dark against the composited
fill rather than by a luminance cutoff, which had put white on a fill at 4.48:1 where the
dark scored 4.68:1.

These four also name library colours outright instead of the seaborn placeholders the
ten-group version uses. The reason for placeholders is that the fonts cannot be
reproduced here; colours can, so a render showing the real ones is simply a truer preview
of the frame. The SVG's fill census now matches the frame's node census exactly.

The subtitle no longer runs under the logo. On the 850-wide template the frame takes the
logo out of the header's auto-layout, which is what keeps the title and subtitle 6px
apart — but it leaves the logo hanging into the subtitle's own band, ending at y=57.5
where the subtitle's line box starts at 51.2. So the subtitle has to stop short of it
sideways, and the template now says so: `logo_reserve_px` is the logo's width plus a gap,
and the subtitle wraps against the slot minus that. The four-category subtitle also drops
its age clause, which the Note already carries, so it fits on one line — 134px of
clearance where it had been overlapping by 47px. Mobile keeps the logo in the flow and
carries the ages in its own note line instead.

Last, the two font allowances become one measured ratio each, because one constant was
answering two different questions. This step measures in DejaVu Sans, since neither Lato
nor Playfair Display is installed, and probe nodes set in the Charts file put DejaVu at
1.15x Lato Regular, 1.26x Lato Bold and 1.10x Playfair SemiBold — not the 2-3% recorded
before, which I could not reproduce. Wrapping is judged in DejaVu against the slot itself,
so a line that fits here fits the frame with room to spare. Whether a design fits the
template is a fact about the frame, so it is judged in Lato: the footer's
tagline-and-license row needs 838px of an 818px row in DejaVu and 792px of it in Lato, and
the old constant rejected a row that sets correctly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…abels

Keeps the version without subcategory segments and drops the other two. The ten-group
desktop chart and the 540x824 mobile one are gone — renders, layouts and the machinery
only they reached — and the survivor takes the plain name, since "main_categories" no
longer distinguishes it from anything.

The cut is 523 lines: the listed and flowed legends (mobile's header), the total-leisure
column, the bracket rule, the `full_footer` / `category_side` / `group_labels`
alternatives, five functions and seven constants that had no caller left. **The rendered
SVG is byte-identical through every one of those removals** — the approved render was kept
aside and diffed after each pass, which is the only proof worth having that a cut this
size cut nothing.

`GROUPS` and `CATEGORIES` stay, in a smaller role: garden still hands over ten display
groups, so they are the recipe for summing them into four and nothing else. If this
version ships, that sum belongs in garden.

Two legend entries now say what they are, which came out of re-reading the workbook's
"Activity categories & sources" sheet rather than our regrouping of it. Neither was the
source's residual; we had relabelled them:

  3.3  "personal, household, and medical services + travel related to personal care"
       -> Grooming & health   (61 min/day, 9% of personal care)
  4.5  "Other leisure activities", which the country mappings show is games, hobbies,
       arts and crafts, reading and leisure travel
       -> Hobbies & other     (96 min/day, 33% of leisure, second only to TV and radio)

One "Other" is left, in the category whose own name already says "& other".

The source's five top-level categories were considered and four kept, which is worth
recording because the fifth is a real thing the chart folds away. Category 5 is 18.5
min/day of religious, civic and unclassified time, counted here within unpaid work.
Drawn as its own segment it is 7.8px wide on average and 2.1px on seven countries, so it
can hold neither a value nor a legend column; adding a fifth colour takes the composited
colour-vision floor from 15.3 to 14.9 and no arrangement of five clears the grayscale
seam (best 1.18:1 against 1.6); and two-thirds of it is the source's own "other (no
categories)". The category name discloses the fold, which is the honest part.

Also here, from following the design team's template rebuild (verified with
create-figma-chart's `verify_templates.js`, which returns ok): the header wrappers lost
their inner padding and the logo became a sibling on both families, so `origin_y` is 16,
`logo_px` is 0 and a one-line title takes the band top from 82.5 to 70. That dissolved
the subtitle/logo collision, so the age clause is back in the subtitle and the width
reserve is gone.

And the measurement fix underneath it: `sns.set_style` installs an Arial-first stack but
runs after the title and subtitle are wrapped, so the step measured in DejaVu and drew in
Arial. Measuring in the stack it draws with puts the ratios back where the skill records
them — Playfair 3.2% wider, Lato Regular 0.8% narrower — with one addition: Arial Bold
sets 6.6% wider than Lato Bold, which is why the footer's mixed-weight row needs its own
allowance and why it was rejecting a row that sets correctly in the frame.

The step also carries a Figma handoff recipe now, which it never had.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ebuilt footer holds

Both come from lessons master added this week.

A link given in chat is lost by the next session, so the file, the page and the frame go
in the docstring as deep links, with the point the lesson turns on: the node ids are a
convenience and the frame NAME is the durable join. It is the kebab-case slug the website
exports the PNG by, so it is the one string shared by the layer panel, the exported file
and this step — and re-cloning a rebuilt template mints new ids, which is exactly the
moment someone comes looking for the frame.

The second lesson found a real error here. These rebuilt footers are pinned by their TOP
(`constraints.vertical: MIN`) and grow downward, while this step's comment said the rows
are pinned to the frame's bottom margin and its band arithmetic grows the Note upward into
the chart. The two agree at the template's own two lines, which is why nothing looked
wrong, and diverge past them: a third line would leave the frame's licence row 2px from
the bottom edge where the template puts 16. So the Note's cap comes down from three lines
to two, and both the comment and the assertion now say why, and what to change together if
a longer Note is ever wanted — re-pin the footer in the frame and raise the cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants