All files / lib/calendar/calendar-content-days layout-algorithm.ts

96.03% Statements 97/101
100% Branches 24/24
80% Functions 4/5
96.03% Lines 97/101

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 1021x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 66x 66x 66x 66x 66x 66x 96x 96x 54x 42x 42x 66x 66x 66x 138x 6x 6x 6x 138x 138x 138x 138x 138x 12x 12x 12x 12x 138x 138x 138x 126x 138x 138x 138x 96x 138x 66x 66x 66x 42x 66x 66x 66x 66x         66x 66x 66x 1x 48x 48x 126x 138x 138x 138x 138x 126x 48x 48x 1x 138x 138x 138x 138x 138x 138x 138x 90x 90x 90x 90x 90x 90x 90x 6x 138x 138x 1x 222x 222x 222x  
interface Dimension {
  readonly min: number;
  readonly max: number;
}
 
export interface LaneLayoutEntry<T> {
  readonly data: T;
  readonly dimension: Dimension;
  readonly lane: { startLane: number; endLane: number; maxLane: number };
}
 
export interface LaneLayout<T> {
  get maxLanes(): number;
  readonly entries: readonly T[];
}
 
export function computeLaneLayout<T extends LaneLayoutEntry<unknown>>(
  layoutEntries: T[],
): LaneLayout<T> {
  let lanes: LaneLayoutEntry<unknown>[][] = [];
  let lastEnding: number | null = null;
 
  const rv: T[] = [...layoutEntries].sort((e1, e2) => {
    const rv = e1.dimension.min - e2.dimension.min;
    if (rv !== 0) {
      return rv;
    }
    return e1.dimension.max - e2.dimension.max;
  });
 
  rv.forEach(e => {
    if (lastEnding !== null && e.dimension.min >= lastEnding) {
      computeLanes(lanes);
      lanes = [];
      lastEnding = null;
    }
 
    let placed = false;
    for (const col of lanes) {
      if (!intersects(col[col.length - 1].dimension, e.dimension)) {
        col.push(e);
        placed = true;
        break;
      }
    }
 
    if (!placed) {
      lanes.push([e]);
    }
 
    if (lastEnding === null || e.dimension.max > lastEnding) {
      lastEnding = e.dimension.max;
    }
  });
 
  if (lanes.length > 0) {
    computeLanes(lanes);
  }
 
  return {
    get maxLanes() {
      return rv
        .map(e => e.lane.maxLane)
        .reduce((a, b) => Math.max(a, b), -Infinity);
    },
    entries: rv,
  };
}
 
function computeLanes(lanes: LaneLayoutEntry<unknown>[][]) {
  lanes.forEach((lane, index) => {
    lane.forEach(layoutEntry => {
      const colSpan = computeLaneSpan(layoutEntry, index, lanes);
      layoutEntry.lane.startLane = index;
      layoutEntry.lane.endLane = index + colSpan;
      layoutEntry.lane.maxLane = lanes.length;
    });
  });
}
 
function computeLaneSpan(
  layoutEntry: LaneLayoutEntry<unknown>,
  laneIndex: number,
  columns: LaneLayoutEntry<unknown>[][],
) {
  let colSpan = 1;
  for (let i = laneIndex + 1; i < columns.length; i++) {
    const col = columns[i];
    for (const e of col) {
      if (intersects(layoutEntry.dimension, e.dimension)) {
        return colSpan;
      }
    }
    colSpan++;
  }
  return colSpan;
}
 
function intersects(a: Dimension, b: Dimension) {
  return a.max > b.min && a.min < b.max;
}