You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
orario/src/interval-layout.js

101 lines
2.7 KiB
JavaScript

/**
* 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 }
// )