|
|
|
/**
|
|
|
|
* Returns a compact layout of a list of intervals
|
|
|
|
*
|
|
|
|
* Second options parameter
|
|
|
|
* - `tight` places an interval with same ending as previous next to each other.
|
|
|
|
*/
|
|
|
|
export function layoutIntervals(intervals, { tight } = {}) {
|
|
|
|
tight ??= true
|
|
|
|
|
|
|
|
const canPlaceInterval = tight
|
|
|
|
? ({ start }, place) => place <= start
|
|
|
|
: ({ start }, place) => place < start
|
|
|
|
|
|
|
|
if (intervals.length === 0) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort intervals by ".start"
|
|
|
|
intervals.sort((a, b) => a.start - b.start)
|
|
|
|
|
|
|
|
const stack = [{ lastEnd: -Infinity, intervals: [] }]
|
|
|
|
|
|
|
|
for (const interval of intervals) {
|
|
|
|
const s = stack.find(level => canPlaceInterval(interval, level.lastEnd))
|
|
|
|
|
|
|
|
if (s) {
|
|
|
|
s.intervals.push(interval)
|
|
|
|
s.lastEnd = Math.max(s.lastEnd, interval.end)
|
|
|
|
} else {
|
|
|
|
stack.push({ lastEnd: interval.end, intervals: [interval] })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return stack.map(({ intervals }) => intervals)
|
|
|
|
}
|
|
|
|
|
|
|
|
function layoutBlockEvents(events) {
|
|
|
|
events.sort((a, b) => a.start - b.start)
|
|
|
|
|
|
|
|
let result = []
|
|
|
|
for (const event of events) {
|
|
|
|
let viableIndex = 0
|
|
|
|
while (
|
|
|
|
result.filter(
|
|
|
|
e => e.index === viableIndex && e.start < event.end && event.start < e.end
|
|
|
|
).length !== 0
|
|
|
|
) {
|
|
|
|
viableIndex += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
result.push({ ...event, index: viableIndex })
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
export function layoutEvents(events) {
|
|
|
|
const overlap = (event, block) => event.start < block.end && block.start < event.end
|
|
|
|
|
|
|
|
events.sort((a, b) => a.start - b.start)
|
|
|
|
|
|
|
|
let layout = []
|
|
|
|
for (const event of events) {
|
|
|
|
const blocks = layout.filter(block => overlap(event, block))
|
|
|
|
if (blocks.length > 0) {
|
|
|
|
layout = layout.filter(block => !overlap(event, block))
|
|
|
|
layout.push({
|
|
|
|
start: Math.min(event.start, ...blocks.map(block => block.start)),
|
|
|
|
end: Math.max(event.end, ...blocks.map(block => block.end)),
|
|
|
|
events: blocks.flatMap(block => block.events).concat([event]),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
layout.push({ start: event.start, end: event.end, events: [event] })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return layout.map(block => {
|
|
|
|
const events = layoutBlockEvents(block.events)
|
|
|
|
return {
|
|
|
|
...block,
|
|
|
|
layers: Math.max(...events.map(event => event.index)) + 1,
|
|
|
|
events: events,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// //
|
|
|
|
// // Testing...
|
|
|
|
// //
|
|
|
|
|
|
|
|
// console.dir(
|
|
|
|
// layoutIntervals([
|
|
|
|
// { start: 0, end: 2 },
|
|
|
|
// { start: 2, end: 4 },
|
|
|
|
// { start: 1, end: 3 },
|
|
|
|
// { start: 4, end: 6 },
|
|
|
|
// { start: 3, end: 5 },
|
|
|
|
// { start: 2, end: 4 },
|
|
|
|
// ]),
|
|
|
|
// { depth: null }
|
|
|
|
// )
|