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> |