Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Event Timeline Visualizer</title> | |
<meta name="description" content="Visualize event timelines as Gantt charts"> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
.chart-container { | |
overflow-x: auto; | |
overflow-y: visible; | |
} | |
.event-bar { | |
transition: opacity 0.2s; | |
} | |
.event-bar:hover { | |
opacity: 0.8; | |
} | |
.tooltip { | |
position: absolute; | |
background: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 5px 10px; | |
border-radius: 4px; | |
font-size: 12px; | |
pointer-events: none; | |
z-index: 10; | |
transform: translate(-50%, -100%); | |
display: none; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<header class="bg-white shadow-sm"> | |
<div class="container mx-auto px-4 py-6"> | |
<h1 class="text-2xl font-bold text-gray-800">Event Timeline Visualizer</h1> | |
<p class="text-gray-600 mt-2">Paste your event data to generate a Gantt chart</p> | |
</div> | |
</header> | |
<main class="container mx-auto px-4 py-8"> | |
<div class="bg-white rounded-lg shadow-md p-6 mb-8"> | |
<div class="mb-4"> | |
<label for="eventData" class="block text-sm font-medium text-gray-700 mb-2">Event Data (JSON format)</label> | |
<textarea | |
id="eventData" | |
rows="6" | |
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm" | |
placeholder="Paste your event data here (e.g. {'Event name': [[start1, end1], [start2, end2], ...]})">{ | |
'Video game sound': [[0.0, 10.0]], | |
'Male speech, man speaking': [[0.015, 3.829], [4.293, 4.875], [5.089, 7.349], [8.071, 9.978]] | |
}</textarea> | |
</div> | |
<div class="flex justify-between items-center"> | |
<div> | |
<label for="maxTime" class="block text-sm font-medium text-gray-700 mb-1">Max Time (seconds)</label> | |
<input | |
type="number" | |
id="maxTime" | |
value="10" | |
min="1" | |
step="1" | |
class="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
</div> | |
<div class="flex items-center"> | |
<input | |
type="checkbox" | |
id="autoUpdate" | |
checked | |
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"> | |
<label for="autoUpdate" class="ml-2 block text-sm text-gray-700">Auto-update</label> | |
</div> | |
</div> | |
</div> | |
<div id="chartContainer" class="chart-container bg-white rounded-lg shadow-md p-6"> | |
<div id="tooltip" class="tooltip"></div> | |
<div id="chart" class="min-w-full"></div> | |
</div> | |
</main> | |
<footer class="bg-white border-t mt-8 py-6"> | |
<div class="container mx-auto px-4 text-center text-gray-500 text-sm"> | |
<p>Event Timeline Visualizer - Paste JSON data to generate Gantt charts</p> | |
</div> | |
</footer> | |
<script> | |
// Color palette for events | |
const colors = [ | |
'#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', | |
'#EC4899', '#14B8A6', '#F97316', '#84CC16', '#06B6D4' | |
]; | |
// Initialize variables | |
let timeoutId = null; | |
const tooltip = document.getElementById('tooltip'); | |
// Set up event listeners | |
document.getElementById('eventData').addEventListener('input', handleInputChange); | |
document.getElementById('maxTime').addEventListener('input', handleInputChange); | |
function handleInputChange() { | |
if (document.getElementById('autoUpdate').checked) { | |
// Debounce the update to prevent excessive rendering | |
clearTimeout(timeoutId); | |
timeoutId = setTimeout(generateChart, 300); | |
} | |
} | |
function generateChart() { | |
const eventDataText = document.getElementById('eventData').value; | |
const maxTime = parseFloat(document.getElementById('maxTime').value) || 10; | |
try { | |
// Replace single quotes with double quotes for valid JSON | |
const jsonString = eventDataText.replace(/'/g, '"'); | |
const events = JSON.parse(jsonString); | |
renderChart(events, maxTime); | |
} catch (error) { | |
console.error(error); | |
} | |
} | |
function renderChart(events, maxTime) { | |
const chartElement = document.getElementById('chart'); | |
chartElement.innerHTML = ''; | |
const eventNames = Object.keys(events); | |
if (eventNames.length === 0) { | |
chartElement.innerHTML = '<p class="text-gray-500">No events to display</p>'; | |
return; | |
} | |
// Calculate dimensions | |
const labelWidth = 200; // Increased width for labels | |
const chartWidth = Math.max(800, maxTime * 80); | |
const barHeight = 30; | |
const rowGap = 15; | |
const padding = 40; | |
const totalHeight = (barHeight + rowGap) * eventNames.length + padding * 2; | |
// Create SVG element | |
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); | |
svg.setAttribute('width', '100%'); | |
svg.setAttribute('height', totalHeight); | |
svg.setAttribute('viewBox', `0 0 ${chartWidth + labelWidth} ${totalHeight}`); | |
// Add background | |
const bg = document.createElementNS("http://www.w3.org/2000/svg", "rect"); | |
bg.setAttribute('width', '100%'); | |
bg.setAttribute('height', '100%'); | |
bg.setAttribute('fill', 'white'); | |
svg.appendChild(bg); | |
// Add time axis | |
const timeAxis = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
timeAxis.setAttribute('x1', labelWidth); | |
timeAxis.setAttribute('y1', padding); | |
timeAxis.setAttribute('x2', chartWidth + labelWidth - padding); | |
timeAxis.setAttribute('y2', padding); | |
timeAxis.setAttribute('stroke', '#D1D5DB'); | |
timeAxis.setAttribute('stroke-width', '1'); | |
svg.appendChild(timeAxis); | |
// Add time ticks | |
for (let t = 0; t <= maxTime; t++) { | |
const x = labelWidth + (t / maxTime) * (chartWidth - padding); | |
const tick = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
tick.setAttribute('x1', x); | |
tick.setAttribute('y1', padding); | |
tick.setAttribute('x2', x); | |
tick.setAttribute('y2', padding + 5); | |
tick.setAttribute('stroke', '#D1D5DB'); | |
tick.setAttribute('stroke-width', '1'); | |
svg.appendChild(tick); | |
const label = document.createElementNS("http://www.w3.org/2000/svg", "text"); | |
label.setAttribute('x', x); | |
label.setAttribute('y', padding + 20); | |
label.setAttribute('text-anchor', 'middle'); | |
label.setAttribute('font-size', '12'); | |
label.setAttribute('fill', '#6B7280'); | |
label.textContent = t; | |
svg.appendChild(label); | |
} | |
// Add events | |
eventNames.forEach((eventName, index) => { | |
const y = padding + 40 + index * (barHeight + rowGap); | |
// Add event label with ellipsis for long names | |
const label = document.createElementNS("http://www.w3.org/2000/svg", "text"); | |
label.setAttribute('x', labelWidth - 10); | |
label.setAttribute('y', y + barHeight / 2 + 5); | |
label.setAttribute('text-anchor', 'end'); | |
label.setAttribute('font-size', '12'); | |
label.setAttribute('fill', '#374151'); | |
// Truncate long labels | |
const maxChars = 25; | |
const displayName = eventName.length > maxChars | |
? eventName.substring(0, maxChars) + '...' | |
: eventName; | |
label.textContent = displayName; | |
// Add full name as title for hover | |
const title = document.createElementNS("http://www.w3.org/2000/svg", "title"); | |
title.textContent = eventName; | |
label.appendChild(title); | |
svg.appendChild(label); | |
// Add event bars | |
const intervals = events[eventName]; | |
const color = colors[index % colors.length]; | |
intervals.forEach(interval => { | |
const [start, end] = interval; | |
const x1 = labelWidth + (start / maxTime) * (chartWidth - padding); | |
const x2 = labelWidth + (end / maxTime) * (chartWidth - padding); | |
const width = Math.max(1, x2 - x1); | |
const bar = document.createElementNS("http://www.w3.org/2000/svg", "rect"); | |
bar.setAttribute('x', x1); | |
bar.setAttribute('y', y); | |
bar.setAttribute('width', width); | |
bar.setAttribute('height', barHeight); | |
bar.setAttribute('fill', color); | |
bar.setAttribute('rx', '3'); | |
bar.setAttribute('ry', '3'); | |
bar.classList.add('event-bar'); | |
// Add mouse events for tooltip | |
bar.addEventListener('mouseenter', (e) => { | |
const rect = e.target.getBoundingClientRect(); | |
tooltip.textContent = `${eventName}: ${start.toFixed(3)}s - ${end.toFixed(3)}s`; | |
tooltip.style.display = 'block'; | |
tooltip.style.left = `${rect.left + rect.width/2}px`; | |
tooltip.style.top = `${rect.top}px`; | |
}); | |
bar.addEventListener('mouseleave', () => { | |
tooltip.style.display = 'none'; | |
}); | |
svg.appendChild(bar); | |
}); | |
}); | |
chartElement.appendChild(svg); | |
} | |
// Generate initial chart | |
generateChart(); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Juhayna/events-visualizer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |