Calendar/stacking-visualization.html
2025-10-06 17:05:18 +02:00

1814 lines
74 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Event Stacking Visualization</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding: 40px;
background: #f5f5f5;
line-height: 1.6;
}
h1 {
color: #333;
margin-bottom: 10px;
}
h2 {
color: #555;
margin-top: 40px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #ddd;
}
h3 {
color: #666;
margin-top: 30px;
margin-bottom: 15px;
}
.section {
background: white;
padding: 30px;
margin-bottom: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
margin-top: 20px;
}
.calendar-column {
background: #fafafa;
border: 2px solid #e0e0e0;
border-radius: 4px;
padding: 20px;
position: relative;
min-height: 400px;
}
.column-title {
font-weight: bold;
margin-bottom: 15px;
color: #333;
text-align: center;
}
.timeline {
position: absolute;
left: 0;
top: 60px;
width: 50px;
height: calc(100% - 80px);
border-right: 2px solid #ddd;
background: #fff;
}
.time-marker {
position: absolute;
left: 5px;
font-size: 11px;
color: #999;
transform: translateY(-50%);
}
.events-container {
position: relative;
margin-left: 60px;
height: 350px;
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
.event {
position: absolute;
border-radius: 4px;
padding: 8px;
font-size: 13px;
font-weight: 500;
border: 2px solid;
cursor: default;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.event-a {
background: #e3f2fd;
border-color: #2196f3;
color: #1565c0;
}
.event-b {
background: #f3e5f5;
border-color: #9c27b0;
color: #6a1b9a;
}
.event-c {
background: #fff3e0;
border-color: #ff9800;
color: #e65100;
}
.event-d {
background: #e8f5e9;
border-color: #4caf50;
color: #2e7d32;
}
.legend {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 4px;
font-size: 14px;
}
.legend-item {
margin: 5px 0;
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
margin-left: 10px;
}
.badge-level-0 { background: #e3f2fd; color: #1565c0; }
.badge-level-1 { background: #f3e5f5; color: #6a1b9a; }
.badge-level-2 { background: #fff3e0; color: #e65100; }
.savings {
background: #c8e6c9;
padding: 15px;
border-left: 4px solid #4caf50;
margin-top: 20px;
border-radius: 4px;
}
.problem {
background: #ffcdd2;
padding: 15px;
border-left: 4px solid #f44336;
margin-top: 20px;
border-radius: 4px;
}
.note {
background: #fff9c4;
padding: 10px;
border-left: 4px solid #fbc02d;
margin: 15px 0;
font-size: 14px;
}
code {
background: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 13px;
}
.grid-lines {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.grid-line {
position: absolute;
left: 0;
right: 0;
height: 1px;
background: #f0f0f0;
}
.single-column {
max-width: 600px;
}
</style>
</head>
<body>
<h1>Event Stacking Visualization</h1>
<p style="color: #666; margin-bottom: 30px;">Visual demonstration of naive vs optimized event stacking</p>
<!-- Scenario 1: Your Example -->
<div class="section">
<h2>Scenario 1: Your Example - The Problem with Naive Stacking</h2>
<p><strong>Events:</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event A: 09:00 - 14:00 (5 hours, contains both B and C)</li>
<li>Event B: 10:00 - 12:00 (2 hours)</li>
<li>Event C: 12:30 - 13:00 (30 minutes)</li>
</ul>
<div class="note">
<strong>Key Observation:</strong> Event B and Event C do <strong>NOT</strong> overlap with each other!
They are separated by 30 minutes (12:00 to 12:30).
</div>
<div class="comparison">
<!-- Naive Stacking -->
<div class="calendar-column">
<div class="column-title">❌ Naive Stacking (Inefficient)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 20%">10:00</div>
<div class="time-marker" style="top: 40%">11:00</div>
<div class="time-marker" style="top: 60%">12:00</div>
<div class="time-marker" style="top: 80%">13:00</div>
<div class="time-marker" style="top: 100%">14:00</div>
</div>
<div class="events-container">
<div class="grid-lines">
<div class="grid-line" style="top: 0%"></div>
<div class="grid-line" style="top: 20%"></div>
<div class="grid-line" style="top: 40%"></div>
<div class="grid-line" style="top: 60%"></div>
<div class="grid-line" style="top: 80%"></div>
<div class="grid-line" style="top: 100%"></div>
</div>
<!-- Event A: 09:00-14:00 (full height) -->
<div class="event event-a" style="
top: 0%;
height: 100%;
left: 2px;
right: 2px;
z-index: 100;
">Event A (09:00-14:00)</div>
<!-- Event B: 10:00-12:00 (stackLevel 1 = 15px offset) -->
<div class="event event-b" style="
top: 20%;
height: 40%;
left: 17px;
right: 2px;
z-index: 101;
">Event B (10:00-12:00)</div>
<!-- Event C: 12:30-13:00 (stackLevel 2 = 30px offset) -->
<div class="event event-c" style="
top: 70%;
height: 10%;
left: 32px;
right: 2px;
z-index: 102;
">Event C (12:30-13:00)</div>
</div>
<div class="legend">
<div class="legend-item">Event A: <code>marginLeft: 0px</code> <span class="badge badge-level-0">Level 0</span></div>
<div class="legend-item">Event B: <code>marginLeft: 15px</code> <span class="badge badge-level-1">Level 1</span></div>
<div class="legend-item">Event C: <code>marginLeft: 30px</code> <span class="badge badge-level-2">Level 2</span></div>
</div>
<div class="problem">
<strong>Problem:</strong> Event C is pushed 30px to the right even though it doesn't conflict with Event B! Wastes 15px of space.
</div>
</div>
<!-- Optimized Stacking -->
<div class="calendar-column">
<div class="column-title">✅ Optimized Stacking (Efficient)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 20%">10:00</div>
<div class="time-marker" style="top: 40%">11:00</div>
<div class="time-marker" style="top: 60%">12:00</div>
<div class="time-marker" style="top: 80%">13:00</div>
<div class="time-marker" style="top: 100%">14:00</div>
</div>
<div class="events-container">
<div class="grid-lines">
<div class="grid-line" style="top: 0%"></div>
<div class="grid-line" style="top: 20%"></div>
<div class="grid-line" style="top: 40%"></div>
<div class="grid-line" style="top: 60%"></div>
<div class="grid-line" style="top: 80%"></div>
<div class="grid-line" style="top: 100%"></div>
</div>
<!-- Event A: 09:00-14:00 -->
<div class="event event-a" style="
top: 0%;
height: 100%;
left: 2px;
right: 2px;
z-index: 100;
">Event A (09:00-14:00)</div>
<!-- Event B: 10:00-12:00 (stackLevel 1 = 15px offset) -->
<div class="event event-b" style="
top: 20%;
height: 40%;
left: 17px;
right: 2px;
z-index: 101;
">Event B (10:00-12:00)</div>
<!-- Event C: 12:30-13:00 (ALSO stackLevel 1 = 15px offset) -->
<div class="event event-c" style="
top: 70%;
height: 10%;
left: 17px;
right: 2px;
z-index: 101;
">Event C (12:30-13:00)</div>
</div>
<div class="legend">
<div class="legend-item">Event A: <code>marginLeft: 0px</code> <span class="badge badge-level-0">Level 0</span></div>
<div class="legend-item">Event B: <code>marginLeft: 15px</code> <span class="badge badge-level-1">Level 1</span></div>
<div class="legend-item">Event C: <code>marginLeft: 15px</code> <span class="badge badge-level-1">Level 1</span></div>
</div>
<div class="savings">
<strong>Benefit:</strong> Event C reuses stackLevel 1 because it doesn't conflict with Event B. Saves 15px (33% space savings)!
</div>
</div>
</div>
</div>
<!-- Scenario 2: Multiple Parallel Tracks -->
<div class="section">
<h2>Scenario 2: Multiple Parallel Tracks</h2>
<p><strong>Events:</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event A: 09:00 - 15:00 (6 hours, very long event)</li>
<li>Event B: 10:00 - 11:00 (1 hour)</li>
<li>Event C: 11:30 - 12:30 (1 hour)</li>
<li>Event D: 13:00 - 14:00 (1 hour)</li>
</ul>
<div class="note">
<strong>Key Insight:</strong> Events B, C, and D all overlap with A, but <strong>NOT</strong> with each other.
They can all share stackLevel 1!
</div>
<div class="comparison">
<!-- Naive -->
<div class="calendar-column">
<div class="column-title">❌ Naive (4 levels)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 16.67%">10:00</div>
<div class="time-marker" style="top: 33.33%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 66.67%">13:00</div>
<div class="time-marker" style="top: 83.33%">14:00</div>
<div class="time-marker" style="top: 100%">15:00</div>
</div>
<div class="events-container">
<!-- Event A -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A</div>
<!-- Event B: stackLevel 1 -->
<div class="event event-b" style="top: 16.67%; height: 16.67%; left: 17px; right: 2px; z-index: 101;">Event B</div>
<!-- Event C: stackLevel 2 (naive) -->
<div class="event event-c" style="top: 41.67%; height: 16.67%; left: 32px; right: 2px; z-index: 102;">Event C</div>
<!-- Event D: stackLevel 3 (naive) -->
<div class="event event-d" style="top: 66.67%; height: 16.67%; left: 47px; right: 2px; z-index: 103;">Event D</div>
</div>
<div class="legend">
<div class="legend-item">Event A: Level 0 (0px)</div>
<div class="legend-item">Event B: Level 1 (15px)</div>
<div class="legend-item">Event C: Level 2 (30px)</div>
<div class="legend-item">Event D: Level 3 (45px)</div>
</div>
<div class="problem">
Total width: <strong>60px</strong> (0+15+30+45)
</div>
</div>
<!-- Optimized -->
<div class="calendar-column">
<div class="column-title">✅ Optimized (2 levels)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 16.67%">10:00</div>
<div class="time-marker" style="top: 33.33%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 66.67%">13:00</div>
<div class="time-marker" style="top: 83.33%">14:00</div>
<div class="time-marker" style="top: 100%">15:00</div>
</div>
<div class="events-container">
<!-- Event A -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A</div>
<!-- Event B: stackLevel 1 -->
<div class="event event-b" style="top: 16.67%; height: 16.67%; left: 17px; right: 2px; z-index: 101;">Event B</div>
<!-- Event C: ALSO stackLevel 1 -->
<div class="event event-c" style="top: 41.67%; height: 16.67%; left: 17px; right: 2px; z-index: 101;">Event C</div>
<!-- Event D: ALSO stackLevel 1 -->
<div class="event event-d" style="top: 66.67%; height: 16.67%; left: 17px; right: 2px; z-index: 101;">Event D</div>
</div>
<div class="legend">
<div class="legend-item">Event A: Level 0 (0px)</div>
<div class="legend-item">Event B: Level 1 (15px)</div>
<div class="legend-item">Event C: Level 1 (15px)</div>
<div class="legend-item">Event D: Level 1 (15px)</div>
</div>
<div class="savings">
Total width: <strong>30px</strong> (0+15+15+15)<br>
<strong>50% space savings!</strong>
</div>
</div>
</div>
</div>
<!-- Scenario 3: Nested Overlaps -->
<div class="section">
<h2>Scenario 3: Nested Overlaps with Optimization</h2>
<p><strong>Events:</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event A: 09:00 - 15:00 (6 hours, contains all)</li>
<li>Event B: 10:00 - 13:00 (3 hours)</li>
<li>Event C: 11:00 - 12:00 (1 hour)</li>
<li>Event D: 12:30 - 13:30 (1 hour)</li>
</ul>
<div class="note">
<strong>Complex Case:</strong> C and D both overlap with A and B, but C and D don't overlap with <strong>each other</strong>. They can share a level!
</div>
<div class="comparison">
<!-- Naive -->
<div class="calendar-column">
<div class="column-title">❌ Naive (4 levels)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 16.67%">10:00</div>
<div class="time-marker" style="top: 33.33%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 66.67%">13:00</div>
<div class="time-marker" style="top: 83.33%">14:00</div>
<div class="time-marker" style="top: 100%">15:00</div>
</div>
<div class="events-container">
<!-- Event A: 09:00-15:00 -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A</div>
<!-- Event B: 10:00-13:00 (stackLevel 1) -->
<div class="event event-b" style="top: 16.67%; height: 50%; left: 17px; right: 2px; z-index: 101;">Event B</div>
<!-- Event C: 11:00-12:00 (stackLevel 2) -->
<div class="event event-c" style="top: 33.33%; height: 16.67%; left: 32px; right: 2px; z-index: 102;">Event C</div>
<!-- Event D: 12:30-13:30 (stackLevel 3) -->
<div class="event event-d" style="top: 58.33%; height: 16.67%; left: 47px; right: 2px; z-index: 103;">Event D</div>
</div>
<div class="legend">
<div class="legend-item">A: Level 0, B: Level 1, C: Level 2, D: Level 3</div>
</div>
</div>
<!-- Optimized -->
<div class="calendar-column">
<div class="column-title">✅ Optimized (3 levels)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 16.67%">10:00</div>
<div class="time-marker" style="top: 33.33%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 66.67%">13:00</div>
<div class="time-marker" style="top: 83.33%">14:00</div>
<div class="time-marker" style="top: 100%">15:00</div>
</div>
<div class="events-container">
<!-- Event A: 09:00-15:00 -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A</div>
<!-- Event B: 10:00-13:00 (stackLevel 1) -->
<div class="event event-b" style="top: 16.67%; height: 50%; left: 17px; right: 2px; z-index: 101;">Event B</div>
<!-- Event C: 11:00-12:00 (stackLevel 2) -->
<div class="event event-c" style="top: 33.33%; height: 16.67%; left: 32px; right: 2px; z-index: 102;">Event C</div>
<!-- Event D: 12:30-13:30 (ALSO stackLevel 2!) -->
<div class="event event-d" style="top: 58.33%; height: 16.67%; left: 32px; right: 2px; z-index: 102;">Event D</div>
</div>
<div class="legend">
<div class="legend-item">A: Level 0, B: Level 1, C & D: Level 2</div>
</div>
<div class="savings">
<strong>25% space savings!</strong> D shares level with C because they don't overlap.
</div>
</div>
</div>
</div>
<!-- Scenario 4: Fully Nested Events (Complete Containment) -->
<div class="section">
<h2>Scenario 4: Fully Nested Events - All Must Stack</h2>
<p><strong>Events:</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event A: 09:00 - 15:00 (6 hours, contains B)</li>
<li>Event B: 10:00 - 14:00 (4 hours, contains C)</li>
<li>Event C: 11:00 - 13:00 (2 hours, innermost)</li>
</ul>
<div class="note">
<strong>Important Case:</strong> When Event C is completely inside Event B, and Event B is completely inside Event A,
<strong>all three events overlap with each other</strong>. No optimization is possible - they must all stack sequentially.
</div>
<div class="comparison">
<!-- Naive -->
<div class="calendar-column">
<div class="column-title">Naive Stacking</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 16.67%">10:00</div>
<div class="time-marker" style="top: 33.33%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 66.67%">13:00</div>
<div class="time-marker" style="top: 83.33%">14:00</div>
<div class="time-marker" style="top: 100%">15:00</div>
</div>
<div class="events-container">
<!-- Event A: 09:00-15:00 -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A (09:00-15:00)</div>
<!-- Event B: 10:00-14:00 (stackLevel 1) -->
<div class="event event-b" style="top: 16.67%; height: 66.67%; left: 17px; right: 2px; z-index: 101;">Event B (10:00-14:00)</div>
<!-- Event C: 11:00-13:00 (stackLevel 2) -->
<div class="event event-c" style="top: 33.33%; height: 33.33%; left: 32px; right: 2px; z-index: 102;">Event C (11:00-13:00)</div>
</div>
<div class="legend">
<div class="legend-item">Event A: <code>marginLeft: 0px</code> <span class="badge badge-level-0">Level 0</span></div>
<div class="legend-item">Event B: <code>marginLeft: 15px</code> <span class="badge badge-level-1">Level 1</span></div>
<div class="legend-item">Event C: <code>marginLeft: 30px</code> <span class="badge badge-level-2">Level 2</span></div>
</div>
<div style="background: #e3f2fd; padding: 15px; border-left: 4px solid #2196f3; margin-top: 20px; border-radius: 4px;">
<strong>Analysis:</strong> All events overlap with each other:<br>
• A overlaps B: ✓ (B is inside A)<br>
• A overlaps C: ✓ (C is inside A)<br>
• B overlaps C: ✓ (C is inside B)<br>
<br>
Result: Sequential stacking required.
</div>
</div>
<!-- Optimized (Same Result) -->
<div class="calendar-column">
<div class="column-title">Optimized Stacking (Same Result)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 16.67%">10:00</div>
<div class="time-marker" style="top: 33.33%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 66.67%">13:00</div>
<div class="time-marker" style="top: 83.33%">14:00</div>
<div class="time-marker" style="top: 100%">15:00</div>
</div>
<div class="events-container">
<!-- Event A: 09:00-15:00 -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A (09:00-15:00)</div>
<!-- Event B: 10:00-14:00 (stackLevel 1) -->
<div class="event event-b" style="top: 16.67%; height: 66.67%; left: 17px; right: 2px; z-index: 101;">Event B (10:00-14:00)</div>
<!-- Event C: 11:00-13:00 (stackLevel 2 - CANNOT be optimized) -->
<div class="event event-c" style="top: 33.33%; height: 33.33%; left: 32px; right: 2px; z-index: 102;">Event C (11:00-13:00)</div>
</div>
<div class="legend">
<div class="legend-item">Event A: <code>marginLeft: 0px</code> <span class="badge badge-level-0">Level 0</span></div>
<div class="legend-item">Event B: <code>marginLeft: 15px</code> <span class="badge badge-level-1">Level 1</span></div>
<div class="legend-item">Event C: <code>marginLeft: 30px</code> <span class="badge badge-level-2">Level 2</span></div>
</div>
<div style="background: #fff9c4; padding: 15px; border-left: 4px solid #fbc02d; margin-top: 20px; border-radius: 4px;">
<strong>No Optimization Possible:</strong><br>
The optimized algorithm tries to assign C to level 1, but level 1 is occupied by B which overlaps with C.
It then tries level 2 - which is free. Result is identical to naive approach.
</div>
</div>
</div>
<div style="margin-top: 20px; padding: 20px; background: #f5f5f5; border-radius: 4px;">
<h3 style="margin-top: 0;">Algorithm Behavior:</h3>
<pre style="background: white; padding: 15px; border-radius: 4px; overflow-x: auto; font-size: 13px;">
For Event C (11:00-13:00):
overlapping = [Event A, Event B] // Both A and B overlap with C
Try stackLevel 0:
✗ Occupied by Event A (which overlaps C)
Try stackLevel 1:
✗ Occupied by Event B (which overlaps C)
Try stackLevel 2:
✓ Free! Assign C to stackLevel 2
Result: C must be at level 2 (no optimization)</pre>
</div>
<div style="margin-top: 20px; padding: 15px; background: #e8f5e9; border-left: 4px solid #4caf50; border-radius: 4px;">
<strong>Key Takeaway:</strong> Optimization only helps when events at higher levels don't overlap with each other.
When events are fully nested (matryoshka doll pattern), both approaches yield the same result.
</div>
</div>
<!-- Scenario 5: Column Sharing vs Stacking -->
<div class="section">
<h2>Scenario 5: Column Sharing - When Events Start Close Together</h2>
<p><strong>New Concept:</strong> When events start within a threshold (±15 minutes, configurable), they should be displayed side-by-side (column sharing) instead of stacked.</p>
<p><strong>Events:</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event A: 10:00 - 13:00 (3 hours)</li>
<li>Event B: 11:00 - 12:30 (1.5 hours, starts 60 min after A)</li>
<li>Event C: 11:00 - 12:00 (1 hour, starts same time as B)</li>
</ul>
<div class="note">
<strong>Threshold Logic (±15 minutes):</strong><br>
• Event A starts at 10:00<br>
• Events B and C both start at 11:00<br>
• A vs B/C: 60 minutes apart (exceeds ±15 min threshold) → <strong>A is stacked separately</strong><br>
• B vs C: 0 minutes apart (within ±15 min threshold) → <strong>B and C share flexbox</strong><br>
<strong>Result:</strong> A gets full width (stackLevel 0), B and C share flexbox at stackLevel 1 (50%/50%)
</div>
<div class="comparison">
<!-- Stacking Only (Wrong Approach) -->
<div class="calendar-column">
<div class="column-title">❌ Pure Stacking (Poor UX)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">10:00</div>
<div class="time-marker" style="top: 25%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 75%">12:30</div>
<div class="time-marker" style="top: 100%">13:00</div>
</div>
<div class="events-container" style="height: 300px;">
<!-- Event A: 10:00-13:00 (full 3 hours) -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A (10:00-13:00)</div>
<!-- Event B: 11:00-12:30 (stacked at 15px) -->
<div class="event event-b" style="top: 33.33%; height: 50%; left: 17px; right: 2px; z-index: 101;">Event B (11:00-12:30)</div>
<!-- Event C: 11:00-12:00 (stacked at 30px) -->
<div class="event event-c" style="top: 33.33%; height: 33.33%; left: 32px; right: 2px; z-index: 102;">Event C (11:00-12:00)</div>
</div>
<div class="legend">
<div class="legend-item">Event A: Level 0 (0px)</div>
<div class="legend-item">Event B: Level 1 (15px)</div>
<div class="legend-item">Event C: Level 2 (30px)</div>
</div>
<div class="problem">
<strong>Problem:</strong> B and C start at the same time but are stacked sequentially.
Wastes horizontal space and makes it hard to see that they start together.
</div>
</div>
<!-- Column Sharing (Correct Approach) -->
<div class="calendar-column">
<div class="column-title">✅ Column Sharing (Better UX)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">10:00</div>
<div class="time-marker" style="top: 25%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 75%">12:30</div>
<div class="time-marker" style="top: 100%">13:00</div>
</div>
<div class="events-container" style="height: 300px;">
<!-- Event A: 10:00-13:00 (full width) -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A (10:00-13:00)</div>
<!-- Flexbox container for B and C (they share space 50/50) -->
<div style="
position: absolute;
top: 33.33%;
height: 66.67%;
left: 17px;
right: 2px;
display: flex;
gap: 2px;
z-index: 101;
">
<!-- Event B: 11:00-12:30 (50% width, 75% of container height) -->
<div class="event event-b" style="
position: relative;
flex: 1;
height: 75%;
left: 0;
right: 0;
">Event B (11:00-12:30)</div>
<!-- Event C: 11:00-12:00 (50% width, 50% of container height) -->
<div class="event event-c" style="
position: relative;
flex: 1;
height: 50%;
left: 0;
right: 0;
">Event C (11:00-12:00)</div>
</div>
</div>
<div class="legend">
<div class="legend-item">Event A: stackLevel 0 (full width)</div>
<div class="legend-item">Events B & C: stackLevel 1 (flex: 1 each = 50% / 50%)</div>
</div>
<div class="savings">
<strong>Benefits:</strong><br>
• Clear visual indication that B and C start at same time<br>
• Better space utilization (no 30px offset for C)<br>
• Scales well: if Event D is added at 11:00, all three share 33% / 33% / 33%
</div>
</div>
</div>
<div style="margin-top: 20px; padding: 20px; background: #f5f5f5; border-radius: 4px;">
<h3 style="margin-top: 0;">Column Sharing Algorithm:</h3>
<pre style="background: white; padding: 15px; border-radius: 4px; overflow-x: auto; font-size: 13px;">
const FLEXBOX_START_THRESHOLD_MINUTES = 15; // Configurable
function shouldShareFlexbox(event1, event2) {
const startDiff = Math.abs(event1.start - event2.start) / (1000 * 60);
return startDiff <= FLEXBOX_START_THRESHOLD_MINUTES;
}
For events A, B, C:
• A starts at 10:00
• B starts at 11:00 (diff = 60 min > 15 min) → A and B do NOT share
• C starts at 11:00 (diff = 0 min ≤ 15 min) → B and C DO share
Result:
• Event A: stackLevel 0, full width
• Events B & C: stackLevel 1, flexbox container (50% each)</pre>
</div>
<div style="margin-top: 20px; padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px;">
<h3 style="margin-top: 0;">Hybrid Approach: Column Sharing + Stacking + Nesting</h3>
<p>The best approach combines <strong>three techniques</strong>:</p>
<ul style="margin-left: 20px;">
<li><strong>Column Sharing (Flexbox):</strong> When events start within ±15 min threshold</li>
<li><strong>Regular Stacking:</strong> When events start far apart (> 15 min)</li>
<li><strong>Nested Stacking:</strong> When an event starts outside threshold but overlaps a flexbox column</li>
</ul>
<p style="margin-top: 10px;">
<strong>Example:</strong> If a 4th event (Event D) starts at 11:30, it would NOT join the B/C flexbox
(30 min > 15 min threshold). Instead, D would be stacked INSIDE whichever column it overlaps (e.g., B's column)
with a 15px left margin to show the nested relationship.
</p>
</div>
</div>
<!-- Scenario 6.5: Real Data - Events 144, 145, 146 -->
<div class="section">
<h2>Scenario 6.5: Real Data - Events 144, 145, 146 (Chain Overlap)</h2>
<p><strong>Events (from actual JSON data):</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event 145 (Månedlig Planlægning): 07:00 - 08:00 (1 hour)</li>
<li>Event 144 (Team Standup): 07:30 - 08:30 (1 hour)</li>
<li>Event 146 (Performance Test): 08:15 - 10:00 (1h 45min)</li>
</ul>
<div class="note">
<strong>Key Observation:</strong><br>
• 145 ↔ 144: <strong>OVERLAP</strong> (07:30-08:00 = 30 min)<br>
• 145 ↔ 146: <strong>NO OVERLAP</strong> (145 ends 08:00, 146 starts 08:15)<br>
• 144 ↔ 146: <strong>OVERLAP</strong> (08:15-08:30 = 15 min)<br>
<br>
<strong>Expected Stack Levels:</strong><br>
• Event 145: stackLevel 0 (margin-left: 0px)<br>
• Event 144: stackLevel 1 (margin-left: 15px) - overlaps 145<br>
• Event 146: stackLevel 2 (margin-left: 30px) - overlaps 144<br>
<br>
<strong>Why 146 cannot share level with 145:</strong><br>
Even though 145 and 146 don't overlap, 146 overlaps with 144 (which has stackLevel 1).
Therefore 146 must be ABOVE 144 → stackLevel 2.
</div>
<div class="comparison">
<div class="calendar-column">
<div class="column-title">✅ Correct: Chain Overlap Stacking</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">07:00</div>
<div class="time-marker" style="top: 33%">08:00</div>
<div class="time-marker" style="top: 66%">09:00</div>
<div class="time-marker" style="top: 100%">10:00</div>
</div>
<div class="events-container" style="height: 300px;">
<!-- Event 145: 07:00-08:00 (stackLevel 0) -->
<div class="event event-a" style="
top: 0%;
height: 33.33%;
left: 2px;
right: 2px;
z-index: 100;
">145: Månedlig (07:00-08:00)</div>
<!-- Event 144: 07:30-08:30 (stackLevel 1 = 15px offset) -->
<div class="event event-b" style="
top: 16.66%;
height: 33.33%;
left: 17px;
right: 2px;
z-index: 101;
">144: Standup (07:30-08:30)</div>
<!-- Event 146: 08:15-10:00 (stackLevel 2 = 30px offset) -->
<div class="event event-c" style="
top: 41.66%;
height: 58.34%;
left: 32px;
right: 2px;
z-index: 102;
">146: Performance (08:15-10:00)</div>
</div>
<div class="legend">
<div class="legend-item">Event 145: <code>marginLeft: 0px</code> <span class="badge badge-level-0">Level 0</span></div>
<div class="legend-item">Event 144: <code>marginLeft: 15px</code> <span class="badge badge-level-1">Level 1</span></div>
<div class="legend-item">Event 146: <code>marginLeft: 30px</code> <span class="badge badge-level-2">Level 2</span></div>
</div>
<div class="savings">
<strong>Why stackLevel 2 for 146?</strong><br>
146 overlaps with 144 (stackLevel 1), so 146 must be positioned ABOVE 144.
Even though 146 doesn't overlap 145, it forms a "chain" through 144.
</div>
</div>
</div>
</div>
<!-- Scenario 7: Column Sharing for Overlapping Events (Real Case: 153, 154) -->
<div class="section">
<h2>Scenario 7: Column Sharing for Overlapping Events Starting Simultaneously</h2>
<p><strong>Events (start at same time but overlap):</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event 153: 09:00 - 10:00 (1 hour)</li>
<li>Event 154: 09:00 - 09:30 (30 minutes)</li>
</ul>
<div class="note">
<strong>Key Observation:</strong><br>
• Events start at SAME time (09:00)<br>
• Event 154 OVERLAPS with Event 153 (09:00-09:30)<br>
• Even though they overlap, they should share columns 50/50 because they start simultaneously<br>
<br>
<strong>Expected Rendering:</strong><br>
• Use GRID container (not stacking)<br>
• Both events get 50% width (side-by-side)<br>
• Event 153: Full height (1 hour) in left column<br>
• Event 154: Shorter height (30 min) in right column<br>
<br>
<strong>Rule:</strong><br>
Events starting simultaneously (±15 min) should ALWAYS use column sharing (GRID),
even if they overlap each other.
</div>
<div class="comparison">
<div class="calendar-column">
<div class="column-title">❌ Wrong: Stacking</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 50%">09:30</div>
<div class="time-marker" style="top: 100%">10:00</div>
</div>
<div class="events-container" style="height: 200px;">
<!-- Event 153: 09:00-10:00 (stackLevel 0) -->
<div class="event event-a" style="
top: 0%;
height: 100%;
left: 2px;
right: 2px;
z-index: 100;
">153 (09:00-10:00)</div>
<!-- Event 154: 09:00-09:30 (stackLevel 1 = 15px offset) - WRONG! -->
<div class="event event-b" style="
top: 0%;
height: 50%;
left: 17px;
right: 2px;
z-index: 101;
">154 (09:00-09:30)</div>
</div>
<div class="problem">
<strong>Problem:</strong> Event 154 is stacked on top of 153 even though they start at the same time.
This makes it hard to see that they're simultaneous events.
</div>
</div>
<div class="calendar-column">
<div class="column-title">✅ Correct: Column Sharing (GRID)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">09:00</div>
<div class="time-marker" style="top: 50%">09:30</div>
<div class="time-marker" style="top: 100%">10:00</div>
</div>
<div class="events-container" style="height: 200px;">
<!-- Grid container for both events (50/50 split) -->
<div style="
position: absolute;
top: 0%;
height: 100%;
left: 2px;
right: 2px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2px;
z-index: 100;
">
<!-- Event 153: 09:00-10:00 (full height, left column) -->
<div class="event event-a" style="
position: relative;
height: 100%;
">153 (09:00-10:00)</div>
<!-- Event 154: 09:00-09:30 (50% height, right column) -->
<div class="event event-b" style="
position: relative;
height: 50%;
">154 (09:00-09:30)</div>
</div>
</div>
<div class="savings">
<strong>Benefits:</strong><br>
• Clear visual that events start simultaneously<br>
• Better use of horizontal space<br>
• Each event gets 50% width instead of being stacked
</div>
</div>
</div>
</div>
<!-- Scenario 6: Column Sharing + Nested Stacking -->
<div class="section">
<h2>Scenario 6: Column Sharing with Nested Stacking</h2>
<p><strong>Complex Case:</strong> What happens when a 4th event needs to be added to an existing column-sharing group?</p>
<p><strong>Events:</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event A: 10:00 - 13:00 (3 hours)</li>
<li>Event B: 11:00 - 12:30 (1.5 hours)</li>
<li>Event C: 11:00 - 12:00 (1 hour)</li>
<li><strong>Event D: 11:30 - 11:45 (15 minutes) ← NEW!</strong></li>
</ul>
<div class="note">
<strong>New Rule:</strong> Flexbox threshold = <strong>±15 minutes</strong> (configurable)<br>
• B starts at 11:00<br>
• C starts at 11:00 (diff = 0 min ≤ 15 min) → <strong>B and C share flexbox</strong><br>
• D starts at 11:30 (diff = 30 min > 15 min) → <strong>D does NOT join flexbox</strong><br>
• D overlaps only with B → <strong>D is stacked inside B's column</strong>
</div>
<div class="comparison">
<!-- Wrong: All in flexbox -->
<div class="calendar-column">
<div class="column-title">❌ All Events in Flexbox (Wrong)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">10:00</div>
<div class="time-marker" style="top: 25%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 75%">12:30</div>
<div class="time-marker" style="top: 100%">13:00</div>
</div>
<div class="events-container" style="height: 300px;">
<!-- Event A: 10:00-13:00 -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A</div>
<!-- Flexbox with B, C, D (33% each) -->
<div style="
position: absolute;
top: 33.33%;
height: 66.67%;
left: 17px;
right: 2px;
display: flex;
gap: 2px;
z-index: 101;
">
<div class="event event-b" style="position: relative; flex: 1; height: 75%;">Event B</div>
<div class="event event-c" style="position: relative; flex: 1; height: 50%;">Event C</div>
<div class="event event-d" style="position: relative; flex: 1; height: 25%;">Event D</div>
</div>
</div>
<div class="problem">
<strong>Problem:</strong> All events get 33% width, making them too narrow.
Event D is squeezed even though it's contained within Event B's timeframe.
</div>
</div>
<!-- Correct: Nested stacking inside flexbox column -->
<div class="calendar-column">
<div class="column-title">✅ Flexbox + Nested Stack in Column</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">10:00</div>
<div class="time-marker" style="top: 25%">11:00</div>
<div class="time-marker" style="top: 50%">12:00</div>
<div class="time-marker" style="top: 75%">12:30</div>
<div class="time-marker" style="top: 100%">13:00</div>
</div>
<div class="events-container" style="height: 300px;">
<!-- Event A: 10:00-13:00 -->
<div class="event event-a" style="top: 0%; height: 100%; left: 2px; right: 2px; z-index: 100;">Event A (10:00-13:00)</div>
<!-- Flexbox container for B and C (they start at same time: 11:00) -->
<div style="
position: absolute;
top: 33.33%;
height: 66.67%;
left: 17px;
right: 2px;
display: flex;
gap: 2px;
z-index: 101;
">
<!-- Event B column (50% width) with nested Event D inside -->
<div style="position: relative; flex: 1;">
<!-- Event B -->
<div class="event event-b" style="
position: absolute;
top: 0;
height: 75%;
left: 0;
right: 0;
z-index: 101;
">Event B (11:00-12:30)</div>
<!-- Event D nested INSIDE B's column, stacked with offset -->
<div class="event event-d" style="
position: absolute;
top: 25%;
height: 12.5%;
left: 15px;
right: 0;
z-index: 102;
font-size: 11px;
">Event D (11:30-11:45)</div>
</div>
<!-- Event C column (50% width) -->
<div class="event event-c" style="
position: relative;
flex: 1;
height: 50%;
">Event C (11:00-12:00)</div>
</div>
</div>
<div class="legend">
<div class="legend-item">Event A: stackLevel 0 (full width)</div>
<div class="legend-item">Events B & C: stackLevel 1 (flexbox 50%/50%)</div>
<div class="legend-item">Event D: Nested in B's column with 15px marginLeft</div>
</div>
<div class="savings">
<strong>Strategy:</strong><br>
• B and C start at 11:00 (diff = 0 min ≤ 15 min threshold) → <strong>Use flexbox</strong><br>
• D starts at 11:30 (diff = 30 min > 15 min threshold) → <strong>NOT in flexbox</strong><br>
• D overlaps with B (11:00-12:30) but NOT C (11:00-12:00) ✓<br>
• D is stacked INSIDE B's flexbox column with 15px left margin
</div>
</div>
</div>
<div style="margin-top: 20px; padding: 20px; background: #f5f5f5; border-radius: 4px;">
<h3 style="margin-top: 0;">Nested Stacking in Flexbox Columns:</h3>
<pre style="background: white; padding: 15px; border-radius: 4px; overflow-x: auto; font-size: 13px; line-height: 1.6;">
const FLEXBOX_START_THRESHOLD_MINUTES = 15; // Configurable
Step 1: Identify flexbox groups (events starting within ±15 min)
• B starts at 11:00
• C starts at 11:00 (diff = 0 min ≤ 15 min) → B and C share flexbox ✓
• D starts at 11:30 (diff = 30 min > 15 min) → D does NOT join flexbox ✗
Step 2: Create flexbox for B and C
• Flexbox container at stackLevel 1 (15px from A)
• B gets 50% width (left column)
• C gets 50% width (right column)
Step 3: Process Event D (11:30-11:45)
• D overlaps with B (11:00-12:30)? YES ✓
• D overlaps with C (11:00-12:00)? NO ✗ (D starts at 11:30, C ends at 12:00)
Wait... 11:30 < 12:00, so they DO overlap!
D overlaps with ONLY B? Let's check:
- B: 11:00-12:30, D: 11:30-11:45 overlap
- C: 11:00-12:00, D: 11:30-11:45 overlap
Actually D overlaps BOTH! But start time difference (30 min) > threshold
• Decision: Stack D inside the column it overlaps most with (B is longer)
Step 4: Nested stacking inside B's column
• D is placed INSIDE B's flexbox column (position: relative)
• D gets 15px left margin (stacked within the column)
• D appears only in B's half, not spanning both
Result: Flexbox preserved, D clearly nested in B!</pre>
</div>
<div style="margin-top: 20px; padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px;">
<h3 style="margin-top: 0;">Decision Tree: When to Use Nested Stacking</h3>
<pre style="background: white; padding: 15px; border-radius: 4px; font-size: 13px;">
Analyzing events B (11:00-12:30), C (11:00-12:00), D (11:30-11:45):
Step 1: Check flexbox threshold (±15 min)
├─ B starts 11:00
├─ C starts 11:00 (diff = 0 min ≤ 15 min) → Join flexbox ✓
└─ D starts 11:30 (diff = 30 min > 15 min) → Do NOT join flexbox ✗
Step 2: Create flexbox for B and C
└─ Flexbox container: [B (50%), C (50%)]
Step 3: Process Event D
├─ D starts OUTSIDE threshold (30 min > 15 min)
├─ Check which flexbox columns D overlaps:
│ ├─ D overlaps B? → YES ✓ (11:30-11:45 within 11:00-12:30)
│ └─ D overlaps C? → YES ✓ (11:30-11:45 within 11:00-12:00)
└─ D overlaps BOTH B and C
Step 4: Placement strategy
• D cannot join flexbox (start time > threshold)
• D overlaps multiple columns
• Choose primary column: B (longer duration: 1.5h vs 1h)
• Nest D INSIDE B's column with 15px left margin
Result:
• Flexbox maintained for B & C (50%/50%)
• D stacked inside B's column with clear indentation</pre>
</div>
<div style="margin-top: 20px; padding: 15px; background: #fff9c4; border-left: 4px solid #fbc02d; border-radius: 4px;">
<h3 style="margin-top: 0;">💡 Key Insight: Flexbox Threshold + Nested Stacking</h3>
<p>
<strong>The Two-Rule System:</strong>
</p>
<ol style="margin-left: 20px; line-height: 1.8;">
<li><strong>Flexbox Rule:</strong> Events with start times within <code>±15 minutes</code> (configurable) share flexbox columns</li>
<li><strong>Nested Stacking Rule:</strong> Events starting OUTSIDE threshold are stacked inside the overlapping flexbox column with 15px left margin</li>
</ol>
<p style="margin-top: 15px;">
<strong>Why ±15 minutes (not ±30)?</strong><br>
A tighter threshold ensures that only events with <em>truly simultaneous</em> start times share columns.
Events starting 30 minutes later are clearly sequential and should be visually nested/indented.
</p>
<p style="margin-top: 10px;">
<strong>When event overlaps multiple columns:</strong><br>
Choose the column with longest duration (or earliest start) as the "primary" parent,
and nest the event there with proper indentation. This maintains the flexbox structure
while showing clear parent-child relationships.
</p>
<p style="margin-top: 10px; font-size: 13px; color: #666;">
<strong>Configuration:</strong> <code>FLEXBOX_START_THRESHOLD_MINUTES = 15</code>
</p>
</div>
</div>
<!-- Summary -->
<div class="section">
<h2>Summary: Unified Layout Logic</h2>
<div style="background: #e3f2fd; padding: 25px; border-radius: 4px; margin-bottom: 30px; border-left: 5px solid #2196f3;">
<h3 style="margin-top: 0;">🎯 The Core Algorithm - One Rule to Rule Them All</h3>
<p style="font-size: 15px; line-height: 1.8;">
All scenarios follow the <strong>same underlying logic</strong>:
</p>
<ol style="margin-left: 20px; line-height: 2; font-size: 14px;">
<li><strong>Group by start time proximity:</strong> Events starting within ±15 min share a container</li>
<li><strong>Container type decision:</strong>
<ul style="margin-top: 8px;">
<li>If group has 1 event → Regular positioning (no special container)</li>
<li>If group has 2+ events with no mutual overlaps → <strong>Flexbox container</strong></li>
<li>If group has overlapping events → <strong>Regular stacking container</strong></li>
</ul>
</li>
<li><strong>Handle late arrivals:</strong> Events starting OUTSIDE threshold (> 15 min later) are nested inside the container they overlap with</li>
</ol>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-top: 20px;">
<div style="background: #ffebee; padding: 20px; border-radius: 4px;">
<h3 style="margin-top: 0; color: #c62828;">Scenario 1-2</h3>
<p style="font-size: 13px; margin-bottom: 8px;"><strong>Optimized Stacking</strong></p>
<ul style="margin-left: 20px; font-size: 13px; line-height: 1.6;">
<li>No flexbox groups</li>
<li>Events share levels when they don't overlap</li>
<li>Pure optimization play</li>
</ul>
</div>
<div style="background: #fff3e0; padding: 20px; border-radius: 4px;">
<h3 style="margin-top: 0; color: #e65100;">Scenario 5</h3>
<p style="font-size: 13px; margin-bottom: 8px;"><strong>Flexbox Columns</strong></p>
<ul style="margin-left: 20px; font-size: 13px; line-height: 1.6;">
<li>B & C start together (±15 min)</li>
<li>They don't overlap each other</li>
<li>Perfect for flexbox (50%/50%)</li>
</ul>
</div>
<div style="background: #e8f5e9; padding: 20px; border-radius: 4px;">
<h3 style="margin-top: 0; color: #2e7d32;">Scenario 6</h3>
<p style="font-size: 13px; margin-bottom: 8px;"><strong>Nested in Flexbox</strong></p>
<ul style="margin-left: 20px; font-size: 13px; line-height: 1.6;">
<li>B & C flexbox maintained</li>
<li>D starts later (> 15 min)</li>
<li>D nested in B's column</li>
</ul>
</div>
</div>
<div style="margin-top: 30px; padding: 20px; background: #e3f2fd; border-radius: 4px;">
<h3 style="margin-top: 0;">Unified Algorithm - All Scenarios Use This</h3>
<pre style="background: white; padding: 15px; border-radius: 4px; overflow-x: auto; font-size: 13px; line-height: 1.6;">
const FLEXBOX_START_THRESHOLD_MINUTES = 15;
const STACK_OFFSET_PX = 15;
// PHASE 1: Group events by start time proximity
function groupEventsByStartTime(events) {
const groups = [];
const sorted = events.sort((a, b) => a.start - b.start);
for (const event of sorted) {
const existingGroup = groups.find(g => {
const groupStart = g[0].start;
const diffMinutes = Math.abs(event.start - groupStart) / (1000 * 60);
return diffMinutes <= FLEXBOX_START_THRESHOLD_MINUTES;
});
if (existingGroup) {
existingGroup.push(event);
} else {
groups.push([event]);
}
}
return groups; // Each group = events starting within ±15 min
}
// PHASE 2: Decide container type for each group
function decideContainerType(group) {
if (group.length === 1) return 'NONE';
// Check if any events in group overlap each other
const hasOverlaps = group.some((e1, i) =>
group.slice(i + 1).some(e2 => doEventsOverlap(e1, e2))
);
return hasOverlaps ? 'STACKING' : 'FLEXBOX';
}
// PHASE 3: Handle events that start OUTSIDE threshold
function nestLateArrivals(groups, allEvents) {
for (const event of allEvents) {
const belongsToGroup = groups.some(g => g.includes(event));
if (belongsToGroup) continue; // Already placed
// Find which group/container this event overlaps with
const overlappingGroup = groups.find(g =>
g.some(e => doEventsOverlap(event, e))
);
if (overlappingGroup) {
// Nest inside the overlapping container
// If flexbox: choose column with longest duration
// If stacking: add to stack with proper offset
nestEventInContainer(event, overlappingGroup);
}
}
}
Result: One algorithm handles ALL scenarios!</pre>
</div>
<div style="margin-top: 30px; padding: 20px; background: #f5f5f5; border-radius: 4px;">
<h3 style="margin-top: 0;">Algorithm Complexity</h3>
<ul style="margin-left: 20px;">
<li><strong>Overlap Detection:</strong> O(n²) where n = number of events</li>
<li><strong>Grouping by Start Time:</strong> O(n log n) for sorting</li>
<li><strong>Stack Assignment:</strong> O(n²) for checking all overlaps</li>
<li><strong>Visual Update:</strong> O(n) to apply styling</li>
</ul>
<p style="margin-top: 10px;">
<strong>Total:</strong> O(n²) - Same as naive approach, but with much better UX!
</p>
</div>
<div style="margin-top: 30px; padding: 20px; background: #e8f5e9; border-left: 4px solid #4caf50; border-radius: 4px;">
<h3 style="margin-top: 0;">🎯 Key Insight: The Pattern That Connects Everything</h3>
<p style="line-height: 1.8; margin-bottom: 15px;">
The <strong>same 3-phase algorithm</strong> handles all scenarios:
</p>
<table style="width: 100%; border-collapse: collapse; font-size: 13px;">
<thead>
<tr style="background: #f5f5f5;">
<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Phase</th>
<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Logic</th>
<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Scenario 1-4</th>
<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Scenario 5</th>
<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Scenario 6</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid #ddd;"><strong>1. Group</strong></td>
<td style="padding: 10px; border: 1px solid #ddd;">Start time ±15 min</td>
<td style="padding: 10px; border: 1px solid #ddd;">No groups (all separate)</td>
<td style="padding: 10px; border: 1px solid #ddd;">[B, C] group</td>
<td style="padding: 10px; border: 1px solid #ddd;">[B, C] group, D separate</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #ddd;"><strong>2. Container</strong></td>
<td style="padding: 10px; border: 1px solid #ddd;">Overlaps? Stack : Flex</td>
<td style="padding: 10px; border: 1px solid #ddd;">N/A (single events)</td>
<td style="padding: 10px; border: 1px solid #ddd;">Flexbox (no overlaps)</td>
<td style="padding: 10px; border: 1px solid #ddd;">Flexbox (no overlaps)</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #ddd;"><strong>3. Late arrivals</strong></td>
<td style="padding: 10px; border: 1px solid #ddd;">Nest in overlapping container</td>
<td style="padding: 10px; border: 1px solid #ddd;">N/A</td>
<td style="padding: 10px; border: 1px solid #ddd;">N/A</td>
<td style="padding: 10px; border: 1px solid #ddd;">D nested in B's column</td>
</tr>
</tbody>
</table>
<p style="margin-top: 20px; padding: 15px; background: white; border-radius: 4px; font-size: 14px;">
<strong>Conclusion:</strong> The difference between scenarios is NOT different algorithms,
but rather <strong>different inputs</strong> to the same algorithm. The 3 phases always run in order,
and each phase makes decisions based on the data (start times, overlaps, thresholds).
</p>
</div>
</div>
<!-- Scenario 8: Edge Case - Exactly 15 min apart with overlap -->
<div class="section">
<h2>Scenario 8: Edge Case - Events Starting Exactly 15 Minutes Apart (WITH Overlap)</h2>
<p><strong>Edge Case:</strong> What happens when events start exactly at the ±15 min threshold AND overlap?</p>
<p><strong>Events:</strong></p>
<ul style="margin: 10px 0 20px 20px;">
<li>Event A: 11:00 - 12:00 (1 hour)</li>
<li>Event B: 11:15 - 12:30 (1.25 hours)</li>
</ul>
<div class="note">
<strong>Analysis:</strong><br>
• A starts at 11:00<br>
• B starts at 11:15 (diff = 15 min ≤ 15 min) → <strong>Within threshold</strong><br>
• A and B overlap (11:15 - 12:00) → <strong>They DO overlap</strong><br>
<strong>Visual priority:</strong> Show that they start simultaneously (±15 min)<br>
<strong>Result:</strong> Use GRID (column sharing) even though they overlap
</div>
<div class="comparison">
<!-- Wrong: Stacking -->
<div class="calendar-column">
<div class="column-title">❌ Wrong: Stacking (Hides Simultaneity)</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">11:00</div>
<div class="time-marker" style="top: 33%">11:30</div>
<div class="time-marker" style="top: 66%">12:00</div>
<div class="time-marker" style="top: 100%">12:30</div>
</div>
<div class="events-container" style="height: 180px;">
<!-- Event A: 11:00-12:00 (60 min = 66.7% of 90 min total) -->
<div class="event event-a" style="top: 0%; height: 66.7%; left: 2px; right: 2px; z-index: 100;">
Event A<br>
<span style="font-size: 11px; opacity: 0.8;">11:00-12:00</span>
</div>
<!-- Event B: 11:15-12:30 (75 min = 83.3% of 90 min total, starts at 16.7%) -->
<div class="event event-b" style="top: 16.7%; height: 83.3%; left: 17px; right: 2px; z-index: 101; margin-left: 15px;">
Event B<br>
<span style="font-size: 11px; opacity: 0.8;">11:15-12:30</span>
</div>
</div>
<div class="savings wrong">
<strong>Problems:</strong><br>
• B is offset to the right → looks like it happens AFTER A<br>
• Doesn't convey that they start almost simultaneously (15 min apart)<br>
• Wastes horizontal space
</div>
</div>
<!-- Right: GRID -->
<div class="calendar-column">
<div class="column-title">✅ Correct: GRID Column Sharing</div>
<div class="timeline">
<div class="time-marker" style="top: 0%">11:00</div>
<div class="time-marker" style="top: 33%">11:30</div>
<div class="time-marker" style="top: 66%">12:00</div>
<div class="time-marker" style="top: 100%">12:30</div>
</div>
<div class="events-container" style="height: 180px;">
<!-- Grid container for A and B -->
<div style="position: absolute; top: 0%; left: 2px; right: 2px; display: grid; grid-template-columns: 1fr 1fr; gap: 2px; z-index: 100;">
<!-- Event A: 11:00-12:00 -->
<div class="event event-a" style="position: relative; height: 120px;">
Event A<br>
<span style="font-size: 11px; opacity: 0.8;">11:00-12:00</span>
</div>
<!-- Event B: 11:15-12:30 (starts 15 min later = 10% offset) -->
<div class="event event-b" style="position: relative; height: 150px; top: 10%;">
Event B<br>
<span style="font-size: 11px; opacity: 0.8;">11:15-12:30</span>
</div>
</div>
</div>
<div class="savings">
<strong>Benefits:</strong><br>
• Side-by-side layout shows they're concurrent<br>
• Each event gets 50% width<br>
• Clear visual: these events start nearly simultaneously (±15 min)<br>
• Despite overlapping, simultaneity is visual priority
</div>
</div>
</div>
<div class="note">
<strong>Key Rule:</strong> Events starting within ±15 minutes should ALWAYS use GRID (column sharing),
even if they overlap. The visual priority is to show that events start <em>simultaneously</em>,
not to avoid overlap. Overlap is handled by the grid container having appropriate height.
</div>
<div class="code-example">
<strong>Expected Behavior:</strong>
<pre style="background: #f8f8f8; padding: 15px; border-radius: 4px; overflow-x: auto; font-size: 13px;">
// Phase 1: Group by start time
groupEventsByStartTime([A, B])
→ Group 1: [A, B] // 15 min apart ≤ threshold
// Phase 2: Decide container type
decideContainerType(Group 1)
→ GRID // Always GRID for grouped events, even if overlapping
// Phase 3: Calculate stack level
calculateGridGroupStackLevel(Group 1)
→ stackLevel: 0 // No other events to stack above
// Result:
&lt;swp-event-group class="cols-2 stack-level-0" style="top: 0px; margin-left: 0px; z-index: 100;"&gt;
&lt;swp-event data-event-id="A" style="height: 120px;"&gt;Event A&lt;/swp-event&gt;
&lt;swp-event data-event-id="B" style="height: 150px; top: 10%;"&gt;Event B&lt;/swp-event&gt;
&lt;/swp-event-group&gt;</pre>
</div>
</div>
<!-- ============================================ -->
<!-- SCENARIO 9: Grid with Staggered Start Times -->
<!-- ============================================ -->
<div class="section">
<h2>Scenario 9: Grid with Staggered Start Times</h2>
<div class="legend">
<div class="legend-item"><strong>Event A:</strong> 09:00 - 10:00 (1 hour)</div>
<div class="legend-item"><strong>Event B:</strong> 09:30 - 10:30 (1 hour, starts 30 min after A)</div>
<div class="legend-item"><strong>Event C:</strong> 10:15 - 12:00 (1h 45min, starts 45 min after B)</div>
</div>
<div class="note">
<strong>Special Case: End-to-Start Conflicts Create Shared Columns</strong><br><br>
• Event A: 09:00 - 10:00<br>
• Event B: 09:30 - 10:30 (starts 30 min before A ends → conflicts with A)<br>
• Event C: 10:15 - 12:00 (starts 15 min before B ends → conflicts with B)<br><br>
<strong>Key Rule:</strong> Events share columns (GRID) when they conflict within threshold<br>
• Conflict = Event starts within ±threshold minutes of another event's end time<br>
• A and B: B starts 30 min before A ends → conflict (≤ 30 min threshold)<br>
• B and C: C starts 15 min before B ends → conflict (≤ 30 min threshold)<br>
• Therefore: A, B, and C all share columns in a 3-column GRID<br><br>
<strong>With threshold = 15 min:</strong> Only A-B conflict (30 min > 15), C is separate → Stack<br>
<strong>With threshold = 30 min:</strong> Both A-B and B-C conflict → All 3 share columns in GRID
</div>
<div class="comparison">
<!-- Threshold = 15 min -->
<div class="calendar-column">
<div class="column-title">With Threshold = 15 min</div>
<div class="timeline">
<div class="time-marker" style="top: 0%;">09:00</div>
<div class="time-marker" style="top: 25%;">10:00</div>
<div class="time-marker" style="top: 50%;">11:00</div>
<div class="time-marker" style="top: 75%;">12:00</div>
</div>
<div class="events-container">
<!-- Event A: 09:00-10:00 (stackLevel 0) -->
<div class="event event-a" style="top: 0px; left: 2px; right: 2px; height: 60px; margin-left: 0px; z-index: 100;">
Event A<br>09:00-10:00
</div>
<!-- Event B: 09:30-10:30 (stackLevel 1, overlaps A) -->
<div class="event event-b" style="top: 30px; left: 2px; right: 2px; height: 60px; margin-left: 15px; z-index: 101;">
Event B<br>09:30-10:30
</div>
<!-- Event C: 10:15-12:00 (stackLevel 2, overlaps B) -->
<div class="event event-c" style="top: 75px; left: 2px; right: 2px; height: 105px; margin-left: 30px; z-index: 102;">
Event C<br>10:15-12:00
</div>
</div>
</div>
<!-- Threshold = 30 min -->
<div class="calendar-column">
<div class="column-title">With Threshold = 30 min</div>
<div class="timeline">
<div class="time-marker" style="top: 0%;">09:00</div>
<div class="time-marker" style="top: 25%;">10:00</div>
<div class="time-marker" style="top: 50%;">11:00</div>
<div class="time-marker" style="top: 75%;">12:00</div>
</div>
<div class="events-container">
<!-- Grid Group for A, B & C: stackLevel 0 (2 columns, A and C share column 1) -->
<div style="position: absolute; top: 0px; left: 2px; right: 2px; margin-left: 0px; z-index: 100; display: grid; grid-template-columns: 1fr 1fr; gap: 2px;">
<!-- Column 1: Event A and C (no overlap) -->
<div style="position: relative;">
<div class="event event-a" style="position: absolute; top: 0px; height: 60px; left: 0; right: 0;">
Event A<br>09:00-10:00
</div>
<div class="event event-c" style="position: absolute; top: 75px; height: 105px; left: 0; right: 0;">
Event C<br>10:15-12:00
</div>
</div>
<!-- Column 2: Event B -->
<div style="position: relative;">
<div class="event event-b" style="position: absolute; top: 30px; height: 60px; left: 0; right: 0;">
Event B<br>09:30-10:30
</div>
</div>
</div>
</div>
</div>
</div>
<h3>Stack Analysis</h3>
<div class="legend">
<p><strong>Threshold = 15 min (Stack):</strong></p>
<ul>
<li>Event A: stackLevel 0</li>
<li>Event B: stackLevel 1 (starts 30 min before A ends, but 30 > 15 threshold) → Stack with margin-left: 15px</li>
<li>Event C: stackLevel 2 (starts 15 min before B ends, but separate from A-B stack) → Stack with margin-left: 30px</li>
</ul>
<p><strong>Threshold = 30 min (Shared GRID with 2 columns):</strong></p>
<ul>
<li>Grid Group (A, B & C): 2-column grid layout</li>
<li><strong>Column 1:</strong> Event A (09:00-10:00) + Event C (10:15-12:00) - they don't overlap!</li>
<li><strong>Column 2:</strong> Event B (09:30-10:30) - overlaps both A and C</li>
<li>Event A: grid column 1, top: 0px</li>
<li>Event B: grid column 2, top: 30px</li>
<li>Event C: grid column 1, top: 75px (shares column with A, no overlap)</li>
<li>All events: stackLevel 0, margin-left: 0px (no stacking, all in same grid container)</li>
</ul>
</div>
</div>
<!-- ============================================ -->
<!-- SCENARIO 10: Complex Column Sharing with Multiple Events -->
<!-- ============================================ -->
<div class="section">
<h2>Scenario 10: Complex Column Sharing</h2>
<div class="legend">
<div class="legend-item"><strong>Event A:</strong> 12:00 - 15:00 (3 hours)</div>
<div class="legend-item"><strong>Event B:</strong> 12:30 - 13:00 (30 min, starts 30 min after A)</div>
<div class="legend-item"><strong>Event C:</strong> 13:30 - 14:30 (1 hour, starts 30 min after B ends)</div>
<div class="legend-item"><strong>Event D:</strong> 14:00 - 15:00 (1 hour, starts 30 min before C ends)</div>
<div class="legend-item"><strong>Event E:</strong> 14:00 - 15:00 (1 hour, starts same time as D)</div>
</div>
<div class="note">
<strong>Analysis with threshold = 30 min:</strong><br>
• A-B conflict: B starts 30 min after A (≤ 30) → grouped<br>
• B-C conflict: C starts 30 min after B ends (≤ 30) → grouped with A-B<br>
• C-D conflict: D starts 30 min before C ends (≤ 30) → grouped with A-B-C<br>
• D-E conflict: D and E start at same time (0 min) → grouped with all<br>
• Therefore: All 5 events in ONE grid group<br><br>
<strong>Column allocation:</strong><br>
• A overlaps: B, C, D, E → needs own column<br>
• B overlaps: A → needs own column<br>
• C overlaps: A, D, E → needs own column<br>
• D overlaps: A, C, E → needs own column<br>
• E overlaps: A, C, D → can share column with B (they don't overlap)<br>
• Result: 4 columns needed
</div>
<div class="comparison">
<!-- Threshold = 15 min -->
<div class="calendar-column">
<div class="column-title">With Threshold = 15 min</div>
<div class="timeline">
<div class="time-marker" style="top: 0%;">12:00</div>
<div class="time-marker" style="top: 33%;">13:00</div>
<div class="time-marker" style="top: 67%;">14:00</div>
<div class="time-marker" style="top: 100%;">15:00</div>
</div>
<div class="events-container">
<!-- Event A: stackLevel 0 -->
<div class="event event-a" style="position: absolute; top: 0px; left: 2px; right: 2px; height: 180px; margin-left: 0px; z-index: 100;">
Event A<br>12:00-15:00
</div>
<!-- Event B: stackLevel 1 (overlaps A) -->
<div class="event event-b" style="position: absolute; top: 30px; left: 2px; right: 2px; height: 30px; margin-left: 15px; z-index: 101;">
Event B<br>12:30-13:00
</div>
<!-- Event C: stackLevel 2 (overlaps A, not B) -->
<div class="event event-c" style="position: absolute; top: 90px; left: 2px; right: 2px; height: 60px; margin-left: 30px; z-index: 102;">
Event C<br>13:30-14:30
</div>
<!-- Grid Group for D & E: stackLevel 3 (start simultaneously, overlap A and C) -->
<div style="position: absolute; top: 120px; left: 2px; right: 2px; margin-left: 45px; z-index: 103; display: grid; grid-template-columns: 1fr 1fr; gap: 2px;">
<div style="position: relative;">
<div class="event event-d" style="position: absolute; top: 0px; height: 60px; left: 0; right: 0;">
Event D<br>14:00-15:00
</div>
</div>
<div style="position: relative;">
<div class="event event-d" style="position: absolute; top: 0px; height: 60px; left: 0; right: 0;">
Event E<br>14:00-15:00
</div>
</div>
</div>
</div>
</div>
<!-- Threshold = 30 min -->
<div class="calendar-column">
<div class="column-title">With Threshold = 30 min</div>
<div class="timeline">
<div class="time-marker" style="top: 0%;">12:00</div>
<div class="time-marker" style="top: 33%;">13:00</div>
<div class="time-marker" style="top: 67%;">14:00</div>
<div class="time-marker" style="top: 100%;">15:00</div>
</div>
<div class="events-container">
<!-- Grid Group: All 5 events (4 columns) -->
<div style="position: absolute; top: 0px; left: 2px; right: 2px; margin-left: 0px; z-index: 100; display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 2px;">
<!-- Column 1: Event A -->
<div style="position: relative;">
<div class="event event-a" style="position: absolute; top: 0px; height: 180px; left: 0; right: 0;">
Event A<br>12:00-15:00
</div>
</div>
<!-- Column 2: Event B + E (don't overlap) -->
<div style="position: relative;">
<div class="event event-b" style="position: absolute; top: 30px; height: 30px; left: 0; right: 0;">
Event B<br>12:30-13:00
</div>
<div class="event event-d" style="position: absolute; top: 120px; height: 60px; left: 0; right: 0;">
Event E<br>14:00-15:00
</div>
</div>
<!-- Column 3: Event C -->
<div style="position: relative;">
<div class="event event-c" style="position: absolute; top: 90px; height: 60px; left: 0; right: 0;">
Event C<br>13:30-14:30
</div>
</div>
<!-- Column 4: Event D -->
<div style="position: relative;">
<div class="event event-d" style="position: absolute; top: 120px; height: 60px; left: 0; right: 0;">
Event D<br>14:00-15:00
</div>
</div>
</div>
</div>
</div>
</div>
<h3>Expected Layout</h3>
<div class="legend">
<p><strong>Threshold = 15 min (Stack + Small Grid):</strong></p>
<ul>
<li>Event A: stackLevel 0</li>
<li>Event B: stackLevel 1 (overlaps A, 30 min > 15 threshold) → margin-left: 15px</li>
<li>Event C: stackLevel 2 (overlaps A, 30 min > 15 threshold) → margin-left: 30px</li>
<li>Grid Group (D & E): stackLevel 3 (start simultaneously) → margin-left: 45px
<ul>
<li>2-column grid: D in column 1, E in column 2</li>
</ul>
</li>
</ul>
<p><strong>Threshold = 30 min (Large Grid):</strong></p>
<ul>
<li>Grid Group (A, B, C, D, E): All in ONE grid group
<ul>
<li><strong>Column 1:</strong> Event A (top: 0px, height: 180px)</li>
<li><strong>Column 2:</strong> Event B (top: 30px) + Event E (top: 120px)</li>
<li><strong>Column 3:</strong> Event C (top: 90px)</li>
<li><strong>Column 4:</strong> Event D (top: 120px)</li>
</ul>
</li>
</ul>
<p><strong>Key Points:</strong></p>
<ul>
<li>With threshold = 30: All events grouped due to chained end-to-start conflicts</li>
<li>With threshold = 15: Only D & E grouped (start simultaneously), A/B/C stacked separately</li>
<li>B and E can share column 2 (they don't overlap: B ends 13:00, E starts 14:00)</li>
<li>D and E start at same time but need separate columns (they overlap perfectly)</li>
<li>Result with 30 min: 4 columns instead of 5 (optimization saves 1 column)</li>
</ul>
</div>
</div>
<footer style="text-align: center; color: #999; margin-top: 40px; padding: 20px;">
<p>Event Stacking Visualization - Calendar Plantempus</p>
<p style="font-size: 12px;">Static documentation for event stacking concepts</p>
</footer>
</body>
</html>