| <!DOCTYPE html> |
| <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"> |
| |
| <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"> |
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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"> |
| |
| <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> |
|
|
| |
| </div> |
| </div> |
| </div> |
|
|
| |
| <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> |
| |
| 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'); |
| |
| |
| let chartCounter = 0; |
| let charts = []; |
| let fetchInterval = null; |
| let isRunning = false; |
| |
| |
| 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); |
| |
| |
| function handleApiSelectChange() { |
| const selectedApi = apiSelect.value; |
| |
| |
| apiConfigSections.forEach(section => { |
| section.classList.add('hidden'); |
| }); |
| |
| |
| 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'); |
| |
| |
| fetchDataForAllCharts(); |
| |
| |
| const interval = parseInt(refreshInterval.value) * 1000; |
| fetchInterval = setInterval(fetchDataForAllCharts, interval); |
| |
| |
| 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'); |
| |
| |
| 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() { |
| |
| if (charts.length === 0) { |
| emptyState.classList.add('hidden'); |
| } |
| |
| chartCounter++; |
| const chartId = `chart-${chartCounter}`; |
| |
| |
| const chartDiv = document.createElement('div'); |
| chartDiv.className = 'mb-8'; |
| chartDiv.id = `container-${chartId}`; |
| |
| |
| 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); |
| |
| |
| const chartCanvas = document.createElement('canvas'); |
| chartCanvas.id = chartId; |
| |
| |
| chartDiv.appendChild(chartHeader); |
| chartDiv.appendChild(document.createElement('div').appendChild(chartCanvas).parentNode); |
| |
| |
| chartsContainer.appendChild(chartDiv); |
| |
| |
| 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 |
| } |
| } |
| }); |
| |
| |
| charts.push({ |
| id: chartId, |
| instance: newChart, |
| apiType: apiSelect.value, |
| config: getCurrentConfig() |
| }); |
| } |
| |
| function removeChart(chartId) { |
| |
| const chartIndex = charts.findIndex(chart => chart.id === chartId); |
| if (chartIndex !== -1) { |
| charts[chartIndex].instance.destroy(); |
| charts.splice(chartIndex, 1); |
| |
| |
| document.getElementById(`container-${chartId}`).remove(); |
| |
| |
| if (charts.length === 0) { |
| emptyState.classList.remove('hidden'); |
| } |
| } |
| } |
| |
| function clearAllCharts() { |
| |
| stopFetching(); |
| |
| |
| charts.forEach(chart => { |
| chart.instance.destroy(); |
| }); |
| |
| |
| charts = []; |
| chartCounter = 0; |
| |
| |
| chartsContainer.innerHTML = ''; |
| emptyState.classList.remove('hidden'); |
| } |
| |
| function fetchDataForAllCharts() { |
| if (charts.length === 0) return; |
| |
| |
| charts.forEach(chart => { |
| |
| |
| simulateApiCall(chart); |
| }); |
| } |
| |
| function simulateApiCall(chart) { |
| |
| setTimeout(() => { |
| const now = new Date(); |
| const timeLabel = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; |
| |
| |
| let newValue; |
| switch(chart.apiType) { |
| case 'crypto': |
| newValue = Math.random() * 10000 + 20000; |
| break; |
| case 'weather': |
| newValue = Math.random() * 30 + 10; |
| break; |
| case 'stocks': |
| newValue = Math.random() * 100 + 150; |
| break; |
| default: |
| newValue = Math.random() * 100; |
| } |
| |
| |
| const chartInstance = chart.instance; |
| const labels = chartInstance.data.labels; |
| const data = chartInstance.data.datasets[0].data; |
| |
| |
| if (labels.length >= 20) { |
| labels.shift(); |
| data.shift(); |
| } |
| |
| labels.push(timeLabel); |
| data.push(newValue); |
| |
| chartInstance.update(); |
| }, 500); |
| } |
| |
| function getCurrentConfig() { |
| |
| |
| return { |
| api: apiSelect.value, |
| |
| }; |
| } |
| |
| function getRandomColor() { |
| const colors = [ |
| '#3B82F6', |
| '#EF4444', |
| '#10B981', |
| '#F59E0B', |
| '#8B5CF6', |
| '#EC4899', |
| '#14B8A6', |
| '#F97316' |
| ]; |
| 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; |
| } |
| |
| |
| alert('Export functionality would save chart data here.\nIn a real implementation, this would download a CSV or image.'); |
| } |
| |
| |
| 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> |