Document Gariff influence workbook and pack timeline lanes by time span.

Add Inputs/gariff_influential_painters_influences.xlsx for curator import, document Inputs/Output conventions, and let non-overlapping movements share horizontal lanes on the timeline.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-27 10:32:59 +03:00
co-authored by Cursor
parent 8005252881
commit 6656f91f25
7 changed files with 50 additions and 49 deletions
+12 -37
View File
@@ -45,7 +45,6 @@ interface BranchSegment {
const MAX_STREAM_STROKE_PX = 54;
const MIN_STREAM_STROKE_PX = 28;
const LANE_GAP_PX = 10;
const DEPTH_GAP_PX = 14;
const CANVAS_TOP_PAD = 38;
const CANVAS_BOTTOM_PAD = 24;
const DEFAULT_CANVAS_HEIGHT = 360;
@@ -961,51 +960,29 @@ export default function MovementBands({
const nameToId = new Map(movements.map((m) => [m.name, m.id]));
const lineageParents = buildLineageParentMap(visibleMovements, nameToId);
const depths = assignDepths(visibleMovements, lineageParents);
const maxDepth = Math.max(...depths.values());
const layoutHeight = Math.max(200, canvasHeight);
const usableHeight = layoutHeight - CANVAS_TOP_PAD - CANVAS_BOTTOM_PAD;
const byDepth = new Map<number, ArtMovement[]>();
// Pack all visible movements into shared horizontal lanes whenever their
// clipped time spans do not overlap (lineage depth is kept for branch curves only).
const laneIndex = assignTemporalLanes(visibleMovements, viewStart, viewEnd);
let maxLanes = 0;
const laneOccupancy = new Map<number, number>();
for (const movement of visibleMovements) {
const depth = depths.get(movement.id) ?? 0;
const list = byDepth.get(depth) || [];
list.push(movement);
byDepth.set(depth, list);
const lane = laneIndex.get(movement.id) ?? 0;
maxLanes = Math.max(maxLanes, lane + 1);
laneOccupancy.set(lane, (laneOccupancy.get(lane) ?? 0) + 1);
}
maxLanes = Math.max(1, maxLanes);
const laneIndex = new Map<number, number>();
const maxLanesByDepth = new Map<number, number>();
for (const [depth, group] of byDepth.entries()) {
const temporalLanes = assignTemporalLanes(group, viewStart, viewEnd);
let maxLane = 0;
for (const movement of group) {
const lane = temporalLanes.get(movement.id) ?? 0;
laneIndex.set(movement.id, lane);
maxLane = Math.max(maxLane, lane + 1);
}
maxLanesByDepth.set(depth, Math.max(1, maxLane));
}
const depthCount = maxDepth + 1;
const depthGaps = Math.max(0, depthCount - 1) * DEPTH_GAP_PX;
const totalLaneSlots = [...maxLanesByDepth.values()].reduce((sum, n) => sum + n, 0);
const minLaneStep = MIN_STREAM_STROKE_PX + LANE_GAP_PX;
const laneStep = Math.max(minLaneStep, (usableHeight - depthGaps) / Math.max(1, totalLaneSlots));
const laneStep = Math.max(minLaneStep, usableHeight / maxLanes);
let streamStrokePx = Math.min(MAX_STREAM_STROKE_PX, laneStep - LANE_GAP_PX);
streamStrokePx = Math.max(MIN_STREAM_STROKE_PX, streamStrokePx);
const portraitSizePx = Math.max(28, Math.min(52, streamStrokePx * 0.96));
const streamCurveOffset = Math.min(12, Math.max(2, (laneStep - streamStrokePx) * 0.35));
const depthBaseY = new Map<number, number>();
let yCursor = CANVAS_TOP_PAD;
for (let depth = 0; depth <= maxDepth; depth++) {
depthBaseY.set(depth, yCursor);
const bandLanes = maxLanesByDepth.get(depth) ?? 1;
yCursor += bandLanes * laneStep;
if (depth < maxDepth) yCursor += DEPTH_GAP_PX;
}
const layoutById = new Map<number, MovementLayout>();
const branchList: BranchSegment[] = [];
const childIdsByParent = new Map<number, number[]>();
@@ -1017,10 +994,8 @@ export default function MovementBands({
const depth = depths.get(movement.id) ?? 0;
const lane = laneIndex.get(movement.id) ?? 0;
const lanesAtDepth = maxLanesByDepth.get(depth) ?? 1;
const bandTop = depthBaseY.get(depth) ?? CANVAS_TOP_PAD;
const y = bandTop + lane * laneStep + laneStep / 2;
const allowDrift = lanesAtDepth === 1 && laneStep >= 80;
const y = CANVAS_TOP_PAD + lane * laneStep + laneStep / 2;
const allowDrift = (laneOccupancy.get(lane) ?? 0) === 1 && laneStep >= 80;
const yEnd = y + (allowDrift ? organicDrift(movement.id + 1000) * 0.22 : 0);
const parentIds = lineageParents.get(movement.id) || [];