Files
Art-gallery/Documentation/movement-tree.md

108 lines
6.5 KiB
Markdown

# Tree of Art — alternative start page
An alternative landing layout for the timeline: the year axis runs **bottom → top**
and the art movements are drawn as a **growing tree** instead of parallel streams.
Reached from the **🌳 Tree of art** link at the top-left of every timeline page
(`layoutTree`); the classic and vertical layouts stay untouched and are one click away.
Shareable URL: `?layout=tree` (also `vertical` / omit or `classic` for the other layouts).
| | Classic | Vertical | **Tree** |
|---|---|---|---|
| Component | [`MovementBands`](../client/src/components/MovementBands.tsx) | [`VerticalMovementBands`](../client/src/components/VerticalMovementBands.tsx) | [`MovementTree`](../client/src/components/MovementTree.tsx) |
| Time axis | left → right | bottom → top | bottom → top |
| Layout | temporal lanes | centre-out lanes | spanning tree |
| Recomputed on zoom | yes | yes | **no — structure is fixed** |
| Artist portraits | yes | no | no |
Both bottom-up layouts share the [`VerticalTimeline`](../client/src/components/VerticalTimeline.tsx)
axis, so eras, event marks, zoom and pan behave identically across them.
## Layout rules
The geometry splits in two: [`utils/movementTree.ts`](../client/src/utils/movementTree.ts)
decides the **shape of the tree** (horizontal, view-independent), and `MovementTree`
maps that shape onto the **current year window** (vertical) each frame.
### Structure — `buildMovementTree()`
1. **Time grows upward.** Oldest movements at the bottom, newest at the crown.
Y is a plain `year → pixel` mapping; the layout engine never touches it.
2. **One trunk, at the centre.** `MOVEMENT_LINEAGE` is a DAG, so it is reduced to a
spanning tree: each movement keeps its **most immediate predecessor** (the parent
with the latest start year that still precedes it) as its structural parent.
Ranking by start year first makes cycles impossible by construction.
3. **Extra parents become grafts.** The predecessors that lost step 2 are still drawn —
as thin, low-opacity limbs behind the tree — so `Post-Impressionism → Cubism`
survives even though Cubism hangs structurally off Fauvism.
4. **Children split the parent's slot.** Each node reserves a slot as wide as its whole
subtree (`max(own limb, Σ children)`); children are packed side by side and centred
on the parent. A single-child chain inherits the parent's x exactly — the trunk stays
straight until it forks, forks spread symmetrically, and later generations land
further from the centre.
5. **Leonardo's rule for thickness.** A limb is as thick as the limbs it carries:
`base² = own² + Σ child.base²`. The trunk is the thickest thing on screen and every
branch tapers as it rises and sheds children. A movement's *own* thickness comes from
its `influence_link_count`.
6. **Branches lean outward** across their own lifespan, by at most the slack left inside
their slot — organic, and collision-free by construction.
7. **Unlinked movements are saplings.** A movement with no lineage edge is its own root;
extra roots are planted alternately right and left of the trunk, widest subtree first,
so the main trunk keeps x = 0 (canvas centre). Roots get a small root flare.
Because the structure is built from the **whole catalogue**, zooming never reshuffles the
tree — you keep your bearings, unlike the lane-packed layouts which re-pack on every view
change.
### Rendering — `MovementTree`
8. **Spread follows zoom, not the canvas.** 90 % of the catalogue lives in the last 15 %
of the time axis, so a tree stretched to full width with all of history in view is one
long trunk under a flat bar. The whole-history view draws the tree at
`FULL_VIEW_WIDTH_SHARE` (52 %) of the available width, and each zoom step fans the
crown out (`(totalSpan / visibleSpan) ^ 0.45`, capped at `MAX_FIT_BOOST`). The chart
grows as you walk up it. Whatever is on screen is always clamped to fit the canvas.
9. **Readability floors, never date changes.** A 30-year movement is ~8 px tall with 2 900
years in view. So a limb is drawn at least `MIN_LIMB_RISE_PX` long, a junction climbs at
least `MIN_JUNCTION_RISE_PX` before it spreads sideways, and **a limb is never thicker
than 55 % of its own length**. Positions still come from real years; only the drawn
length and thickness have a floor, and the junction slides down the parent limb (never
off it) to find its rise.
10. **Ribbons, not strokes.** Limbs are filled ribbons sampled along a cubic and offset
along the curve *normal*, so a junction stays solid even when a zoomed-out view
squeezes it almost flat. Shading runs dark → colour → dark across each limb for a
rounded, woody read.
11. **Greedy label declutter.** Every visible movement asks for a name; closest to the
trunk wins, and names that would collide with a placed one — or fall off the canvas —
stay hidden until you zoom in on them.
12. **Hover lights the descent line.** Hovering a movement brightens its whole path back
to the root (grafts included) and dims the rest — the fastest way to read "where did
this come from".
Clicking any limb or label opens the movement's artist picker and then its 3D movement
gallery, exactly as the other two layouts do.
## Colours and captions
- Limb fill/shading uses [`utils/movementColor.ts`](../client/src/utils/movementColor.ts)
(`vividMovementColor`, `shadeMovementColor`) — the same helpers as the classic and
vertical charts. Malformed catalogue hex falls back to the input string; values longer
than six digits keep the first six (`#rrggbbaa``#rrggbb`).
- The chart hint under the canvas is `captionTreeFlow` in
`locales/{en,ru}/home.json` (same pattern as the other layouts).
## Tuning
All constants sit at the top of the two files and are safe to tune:
| Constant | File | Effect |
|---|---|---|
| `LEAF_SLOT_PX`, `LIMB_GAP_PX` | `movementTree.ts` | how far apart branches sit |
| `MIN_LIMB_PX`, `MAX_OWN_LIMB_PX`, `MAX_TRUNK_PX` | `movementTree.ts` | thickness range |
| `LEAN_SLACK`, `MAX_LEAN_PX` | `movementTree.ts` | how much limbs bend outward |
| `FULL_VIEW_WIDTH_SHARE`, `ZOOM_SPREAD_EXPONENT`, `MAX_FIT_BOOST` | `MovementTree.tsx` | crown spread vs. zoom |
| `MIN_LIMB_RISE_PX`, `MIN_JUNCTION_RISE_PX`, `MAX_THICKNESS_OF_LENGTH` | `MovementTree.tsx` | crown legibility at full zoom-out |
New lineage edges only need adding to
[`client/src/data/movement-lineage.ts`](../client/src/data/movement-lineage.ts) — the tree
picks up parents, grafts, thickness and spacing from there automatically.