A day view puts overlapping meetings side by side. Underneath it is interval-graph colouring: cluster the day, give every event the lowest free column, then let each one expand rightwards over the columns nothing blocks.
Drag the events around and watch the checkers run: https://dev48.infy.uk/design/day67-event-layout.html
Greedy is optimal in exactly one order
Place events one at a time into the lowest-numbered column with nothing conflicting in it. Sorted by start time this is provably optimal: everything already placed that conflicts is live at that instant, so those events pairwise overlap and occupy that many distinct columns. Take the sort away and the argument evaporates.
11:15-12:15 11:45-13:00 12:45-13:45 13:30-15:00
most at once (sweep line): 2
by start time : 2 columns, 0 overlaps, mean width 50.0%
in array order: 3 columns, 0 overlaps, mean width 41.7%
Never more than two meetings at once, so two columns is optimal. Array order spends three, with no overlap, no error, and three of the four boxes a third narrower. This is exactly the line that gets refactored away in a cleanup: it looks decorative, and removing it breaks nothing you can see.
Interval graphs are perfect, so there is a free oracle
χ = ω here: the minimum number of columns equals the largest number of events happening at one instant. Not an approximation — an identity. Which gives a sweep line sharing no code with the layout.
pts.sort((p,q) => p.t - q.t || p.d - q.d); // ends before starts at equal t
That || p.d - q.d is the half-open rule: at 10:00 the meeting that ends must be counted out before the one that starts is counted in.
The column that matters, over 12,000 schedules
| variant | days with overlapping boxes | days using extra columns | mean width |
|---|---|---|---|
| correct | 0 | 0 | 41.9% |
| no expansion | 0 | 0 | 39.6% |
| closed intervals | 0 | 3,417 (28.5%) | 37.0% |
| columns in array order | 0 | 865 (7.2%) | 41.5% |
| longest event first | 0 | 334 (2.8%) | 42.2% |
| cluster against previous end | 4,954 | 0 | 43.9% |
| expand without checking | 11,414 | 0 | 74.0% |
Four of the six wrong versions never produce a single overlapping box. The one property everyone tests cannot see them; they are narrower, not broken. The two that are visible break on 41% and 95% of days and would never have shipped anyway.
Mean width does not rank them either: longest-first spends an extra column on 2.8% of days and still reads marginally wider on average, because the days it gets wrong are not the days that dominate the mean.
What the measurement killed
Expansion was going to be sold as the thing that makes the layout look right rather than merely be right. Measured, it widens 6.2% of events, and those get 2.7× wider. The mechanism says why: expansion is a whole-event property, so one blocked minute anywhere stops a meeting expanding for its entire duration, and in a dense cluster almost every minute is blocked somewhere.
The assertion I care about most is the one that says the bugs are invisible:
assert(arrayOrder.extraColumnCases > 0);
assert(arrayOrder.collisionCases === 0); // <- the point of the whole page
A wrong version that fails the obvious test is not dangerous; it never ships. If that second line ever starts failing, the harness has drifted, not the bug.
Part of a from-scratch series — one component a day, vanilla JS, one file, offline: https://dev48.infy.uk/designfromzero.php
Top comments (0)