Adds IndexedDB and operation queue for robust offline synchronization Introduces SyncManager to handle background data synchronization Supports local event operations with automatic remote sync queuing Enhances application reliability and user experience in low/no connectivity scenarios
1836 lines
74 KiB
HTML
1836 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;
|
|
}
|
|
|
|
.test-badge {
|
|
display: inline-block;
|
|
margin-left: 15px;
|
|
padding: 6px 14px;
|
|
border-radius: 6px;
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
vertical-align: middle;
|
|
}
|
|
.test-passed {
|
|
background: #4caf50;
|
|
color: white;
|
|
}
|
|
.test-failed {
|
|
background: #f44336;
|
|
color: white;
|
|
}
|
|
.test-pending {
|
|
background: #ff9800;
|
|
color: white;
|
|
}
|
|
</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:
|
|
<swp-event-group class="cols-2 stack-level-0" style="top: 0px; margin-left: 0px; z-index: 100;">
|
|
<swp-event data-event-id="A" style="height: 120px;">Event A</swp-event>
|
|
<swp-event data-event-id="B" style="height: 150px; top: 10%;">Event B</swp-event>
|
|
</swp-event-group></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>
|