Download index.html from mauryaR8/realtime-api-data-visualization: direct link, hf CLI and curl.
- Browser
- Download file 24.8 kB
-
https://huggingface.co/spaces/mauryaR8/realtime-api-data-visualization/resolve/main/index.html
- Command line
-
hf download hf://spaces/mauryaR8/realtime-api-data-visualization/index.html
-
curl -L -o index.html https://huggingface.co/spaces/mauryaR8/realtime-api-data-visualization/resolve/main/index.html
24.8 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Real-Time API Data Visualization</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #6b73ff 0%, #000dff 100%); | |
| } | |
| .chart-container { | |
| position: relative; | |
| height: 300px; | |
| width: 100%; | |
| } | |
| .api-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| 100% { opacity: 1; } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="gradient-bg text-white py-6 shadow-lg"> | |
| <div class="container mx-auto px-4"> | |
| <div class="flex justify-between items-center"> | |
| <h1 class="text-3xl font-bold">Real-Time API Data Visualization</h1> | |
| <div class="flex items-center space-x-2"> | |
| <span id="connection-status" class="flex items-center"> | |
| <span class="h-3 w-3 rounded-full bg-green-500 mr-2"></span> | |
| <span>Connected</span> | |
| </span> | |
| </div> | |
| </div> | |
| <p class="mt-2 opacity-90">Visualize data from multiple APIs in real-time with interactive charts</p> | |
| </div> | |
| </div> | |
| <div class="container mx-auto px-4 py-8"> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- API Configuration Panel --> | |
| <div class="bg-white rounded-xl shadow-md p-6 lg:col-span-1"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">API Configuration</h2> | |
| <div class="mb-6"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="api-select"> | |
| Select API Source | |
| </label> | |
| <select id="api-select" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="crypto">Crypto Prices (CoinGecko)</option> | |
| <option value="weather">Weather Data (OpenWeather)</option> | |
| <option value="stocks">Stock Prices (Alpha Vantage)</option> | |
| <option value="custom">Custom API</option> | |
| </select> | |
| </div> | |
| <div id="api-config-section"> | |
| <!-- Crypto API Config --> | |
| <div id="crypto-config" class="api-config"> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2"> | |
| Cryptocurrency | |
| </label> | |
| <select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="bitcoin">Bitcoin (BTC)</option> | |
| <option value="ethereum">Ethereum (ETH)</option> | |
| <option value="ripple">Ripple (XRP)</option> | |
| <option value="cardano">Cardano (ADA)</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2"> | |
| Currency | |
| </label> | |
| <select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="usd">US Dollar (USD)</option> | |
| <option value="eur">Euro (EUR)</option> | |
| <option value="gbp">British Pound (GBP)</option> | |
| <option value="jpy">Japanese Yen (JPY)</option> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Custom API Config --> | |
| <div id="custom-config" class="api-config hidden"> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="custom-url"> | |
| API Endpoint URL | |
| </label> | |
| <input type="text" id="custom-url" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="https://api.example.com/data"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="custom-key"> | |
| API Key (if required) | |
| </label> | |
| <input type="text" id="custom-key" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="Your API key"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2" for="data-path"> | |
| Data Path (JSON path to numeric values) | |
| </label> | |
| <input type="text" id="data-path" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="$.data.values"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <label class="block text-gray-700 text-sm font-medium mb-2"> | |
| Refresh Interval (seconds) | |
| </label> | |
| <div class="flex items-center"> | |
| <input type="range" id="refresh-interval" min="1" max="60" value="5" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <span id="interval-value" class="ml-4 text-gray-700 w-12 text-center">5s</span> | |
| </div> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button id="start-btn" class="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md font-medium transition duration-150 ease-in-out"> | |
| <i class="fas fa-play mr-2"></i> Start | |
| </button> | |
| <button id="stop-btn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md font-medium transition duration-150 ease-in-out"> | |
| <i class="fas fa-stop mr-2"></i> Stop | |
| </button> | |
| <button id="add-chart-btn" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md font-medium transition duration-150 ease-in-out"> | |
| <i class="fas fa-plus mr-2"></i> Add Chart | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Charts Display Area --> | |
| <div class="bg-white rounded-xl shadow-md p-6 lg:col-span-2"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Data Visualization</h2> | |
| <div class="flex space-x-2"> | |
| <button id="export-btn" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-1 px-3 rounded-md text-sm font-medium"> | |
| <i class="fas fa-download mr-1"></i> Export | |
| </button> | |
| <button id="clear-btn" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-1 px-3 rounded-md text-sm font-medium"> | |
| <i class="fas fa-trash-alt mr-1"></i> Clear All | |
| </button> | |
| </div> | |
| </div> | |
| <div id="charts-container"> | |
| <!-- Placeholder when no charts are added --> | |
| <div id="empty-state" class="flex flex-col items-center justify-center py-12 text-center"> | |
| <i class="fas fa-chart-line text-4xl text-gray-300 mb-4"></i> | |
| <h3 class="text-lg font-medium text-gray-500">No charts added yet</h3> | |
| <p class="text-gray-400 mt-1">Configure an API and click "Add Chart" to visualize data</p> | |
| </div> | |
| <!-- Chart templates will be added here dynamically --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- API Status Cards --> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6"> | |
| <div class="api-card bg-white rounded-xl shadow-md p-4 transition duration-300 ease-in-out"> | |
| <div class="flex items-center"> | |
| <div class="p-3 rounded-full bg-indigo-100 text-indigo-600 mr-4"> | |
| <i class="fas fa-coins text-xl"></i> | |
| </div> | |
| <div> | |
| <h3 class="font-medium text-gray-800">Crypto API</h3> | |
| <p class="text-sm text-gray-500">CoinGecko</p> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">Status</span> | |
| <span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">Active</span> | |
| </div> | |
| </div> | |
| <div class="api-card bg-white rounded-xl shadow-md p-4 transition duration-300 ease-in-out"> | |
| <div class="flex items-center"> | |
| <div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4"> | |
| <i class="fas fa-cloud-sun text-xl"></i> | |
| </div> | |
| <div> | |
| <h3 class="font-medium text-gray-800">Weather API</h3> | |
| <p class="text-sm text-gray-500">OpenWeather</p> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">Status</span> | |
| <span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">Active</span> | |
| </div> | |
| </div> | |
| <div class="api-card bg-white rounded-xl shadow-md p-4 transition duration-300 ease-in-out"> | |
| <div class="flex items-center"> | |
| <div class="p-3 rounded-full bg-purple-100 text-purple-600 mr-4"> | |
| <i class="fas fa-chart-line text-xl"></i> | |
| </div> | |
| <div> | |
| <h3 class="font-medium text-gray-800">Stocks API</h3> | |
| <p class="text-sm text-gray-500">Alpha Vantage</p> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">Status</span> | |
| <span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">Active</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // DOM Elements | |
| const apiSelect = document.getElementById('api-select'); | |
| const apiConfigSections = document.querySelectorAll('.api-config'); | |
| const refreshInterval = document.getElementById('refresh-interval'); | |
| const intervalValue = document.getElementById('interval-value'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const stopBtn = document.getElementById('stop-btn'); | |
| const addChartBtn = document.getElementById('add-chart-btn'); | |
| const clearBtn = document.getElementById('clear-btn'); | |
| const exportBtn = document.getElementById('export-btn'); | |
| const chartsContainer = document.getElementById('charts-container'); | |
| const emptyState = document.getElementById('empty-state'); | |
| // Chart counter and data storage | |
| let chartCounter = 0; | |
| let charts = []; | |
| let fetchInterval = null; | |
| let isRunning = false; | |
| // Event Listeners | |
| apiSelect.addEventListener('change', handleApiSelectChange); | |
| refreshInterval.addEventListener('input', updateIntervalValue); | |
| startBtn.addEventListener('click', startFetching); | |
| stopBtn.addEventListener('click', stopFetching); | |
| addChartBtn.addEventListener('click', addNewChart); | |
| clearBtn.addEventListener('click', clearAllCharts); | |
| exportBtn.addEventListener('click', exportData); | |
| // Functions | |
| function handleApiSelectChange() { | |
| const selectedApi = apiSelect.value; | |
| // Hide all config sections | |
| apiConfigSections.forEach(section => { | |
| section.classList.add('hidden'); | |
| }); | |
| // Show selected config section | |
| document.getElementById(`${selectedApi}-config`).classList.remove('hidden'); | |
| } | |
| function updateIntervalValue() { | |
| intervalValue.textContent = `${refreshInterval.value}s`; | |
| } | |
| function startFetching() { | |
| if (charts.length === 0) { | |
| alert('Please add at least one chart before starting.'); | |
| return; | |
| } | |
| if (isRunning) return; | |
| isRunning = true; | |
| startBtn.classList.remove('bg-indigo-600', 'hover:bg-indigo-700'); | |
| startBtn.classList.add('bg-indigo-400', 'cursor-not-allowed'); | |
| stopBtn.classList.remove('bg-gray-200', 'hover:bg-gray-300'); | |
| stopBtn.classList.add('bg-gray-100', 'cursor-not-allowed'); | |
| // Initial fetch | |
| fetchDataForAllCharts(); | |
| // Set up interval | |
| const interval = parseInt(refreshInterval.value) * 1000; | |
| fetchInterval = setInterval(fetchDataForAllCharts, interval); | |
| // Update connection status | |
| document.querySelector('#connection-status span').classList.remove('bg-green-500'); | |
| document.querySelector('#connection-status span').classList.add('bg-yellow-500', 'pulse'); | |
| document.querySelector('#connection-status span + span').textContent = 'Fetching data...'; | |
| } | |
| function stopFetching() { | |
| if (!isRunning) return; | |
| isRunning = false; | |
| clearInterval(fetchInterval); | |
| startBtn.classList.add('bg-indigo-600', 'hover:bg-indigo-700'); | |
| startBtn.classList.remove('bg-indigo-400', 'cursor-not-allowed'); | |
| stopBtn.classList.add('bg-gray-200', 'hover:bg-gray-300'); | |
| stopBtn.classList.remove('bg-gray-100', 'cursor-not-allowed'); | |
| // Update connection status | |
| document.querySelector('#connection-status span').classList.remove('bg-yellow-500', 'pulse'); | |
| document.querySelector('#connection-status span').classList.add('bg-green-500'); | |
| document.querySelector('#connection-status span + span').textContent = 'Connected'; | |
| } | |
| function addNewChart() { | |
| // Hide empty state if it's the first chart | |
| if (charts.length === 0) { | |
| emptyState.classList.add('hidden'); | |
| } | |
| chartCounter++; | |
| const chartId = `chart-${chartCounter}`; | |
| // Create chart container | |
| const chartDiv = document.createElement('div'); | |
| chartDiv.className = 'mb-8'; | |
| chartDiv.id = `container-${chartId}`; | |
| // Chart header with title and controls | |
| const chartHeader = document.createElement('div'); | |
| chartHeader.className = 'flex justify-between items-center mb-2'; | |
| const chartTitle = document.createElement('h3'); | |
| chartTitle.className = 'font-medium text-gray-800'; | |
| chartTitle.textContent = `Chart ${chartCounter} - ${apiSelect.options[apiSelect.selectedIndex].text}`; | |
| const chartControls = document.createElement('div'); | |
| chartControls.className = 'flex space-x-2'; | |
| const removeBtn = document.createElement('button'); | |
| removeBtn.className = 'text-red-500 hover:text-red-700 text-sm'; | |
| removeBtn.innerHTML = '<i class="fas fa-times"></i>'; | |
| removeBtn.onclick = () => removeChart(chartId); | |
| chartControls.appendChild(removeBtn); | |
| chartHeader.appendChild(chartTitle); | |
| chartHeader.appendChild(chartControls); | |
| // Chart canvas | |
| const chartCanvas = document.createElement('canvas'); | |
| chartCanvas.id = chartId; | |
| // Assemble the chart container | |
| chartDiv.appendChild(chartHeader); | |
| chartDiv.appendChild(document.createElement('div').appendChild(chartCanvas).parentNode); | |
| // Add to DOM | |
| chartsContainer.appendChild(chartDiv); | |
| // Create Chart.js instance | |
| const ctx = document.getElementById(chartId).getContext('2d'); | |
| const newChart = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: [], | |
| datasets: [{ | |
| label: 'Value', | |
| data: [], | |
| borderColor: getRandomColor(), | |
| backgroundColor: 'rgba(0, 0, 0, 0.1)', | |
| borderWidth: 2, | |
| tension: 0.1, | |
| fill: true | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| beginAtZero: false | |
| } | |
| }, | |
| plugins: { | |
| legend: { | |
| display: false | |
| }, | |
| tooltip: { | |
| mode: 'index', | |
| intersect: false | |
| } | |
| }, | |
| hover: { | |
| mode: 'nearest', | |
| intersect: true | |
| } | |
| } | |
| }); | |
| // Store chart reference | |
| charts.push({ | |
| id: chartId, | |
| instance: newChart, | |
| apiType: apiSelect.value, | |
| config: getCurrentConfig() | |
| }); | |
| } | |
| function removeChart(chartId) { | |
| // Find and destroy chart | |
| const chartIndex = charts.findIndex(chart => chart.id === chartId); | |
| if (chartIndex !== -1) { | |
| charts[chartIndex].instance.destroy(); | |
| charts.splice(chartIndex, 1); | |
| // Remove from DOM | |
| document.getElementById(`container-${chartId}`).remove(); | |
| // Show empty state if no charts left | |
| if (charts.length === 0) { | |
| emptyState.classList.remove('hidden'); | |
| } | |
| } | |
| } | |
| function clearAllCharts() { | |
| // Stop any running fetches | |
| stopFetching(); | |
| // Destroy all charts | |
| charts.forEach(chart => { | |
| chart.instance.destroy(); | |
| }); | |
| // Clear the array | |
| charts = []; | |
| chartCounter = 0; | |
| // Clear the container | |
| chartsContainer.innerHTML = ''; | |
| emptyState.classList.remove('hidden'); | |
| } | |
| function fetchDataForAllCharts() { | |
| if (charts.length === 0) return; | |
| // For demo purposes, we'll simulate API calls with random data | |
| charts.forEach(chart => { | |
| // In a real app, you would make actual API calls here | |
| // For now, we'll simulate with random data | |
| simulateApiCall(chart); | |
| }); | |
| } | |
| function simulateApiCall(chart) { | |
| // Simulate API delay | |
| setTimeout(() => { | |
| const now = new Date(); | |
| const timeLabel = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; | |
| // Generate random data based on API type | |
| let newValue; | |
| switch(chart.apiType) { | |
| case 'crypto': | |
| newValue = Math.random() * 10000 + 20000; // BTC-like prices | |
| break; | |
| case 'weather': | |
| newValue = Math.random() * 30 + 10; // Temperature-like values | |
| break; | |
| case 'stocks': | |
| newValue = Math.random() * 100 + 150; // Stock-like prices | |
| break; | |
| default: | |
| newValue = Math.random() * 100; | |
| } | |
| // Update chart | |
| const chartInstance = chart.instance; | |
| const labels = chartInstance.data.labels; | |
| const data = chartInstance.data.datasets[0].data; | |
| // Keep only last 20 data points for performance | |
| if (labels.length >= 20) { | |
| labels.shift(); | |
| data.shift(); | |
| } | |
| labels.push(timeLabel); | |
| data.push(newValue); | |
| chartInstance.update(); | |
| }, 500); // Simulate network delay | |
| } | |
| function getCurrentConfig() { | |
| // In a real app, this would return the current configuration | |
| // from the form inputs for the selected API | |
| return { | |
| api: apiSelect.value, | |
| // Add other config properties here | |
| }; | |
| } | |
| function getRandomColor() { | |
| const colors = [ | |
| '#3B82F6', // blue-500 | |
| '#EF4444', // red-500 | |
| '#10B981', // emerald-500 | |
| '#F59E0B', // amber-500 | |
| '#8B5CF6', // violet-500 | |
| '#EC4899', // pink-500 | |
| '#14B8A6', // teal-500 | |
| '#F97316' // orange-500 | |
| ]; | |
| return colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| function exportData() { | |
| if (charts.length === 0) { | |
| alert('No data to export. Add charts and fetch data first.'); | |
| return; | |
| } | |
| // In a real app, this would export the chart data | |
| alert('Export functionality would save chart data here.\nIn a real implementation, this would download a CSV or image.'); | |
| } | |
| // Initialize | |
| handleApiSelectChange(); | |
| updateIntervalValue(); | |
| </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=mauryaR8/realtime-api-data-visualization" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |