|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const AppState = {
|
|
|
coins: [],
|
|
|
selectedCoin: null,
|
|
|
selectedTimeframe: 7,
|
|
|
selectedColorScheme: 'blue',
|
|
|
charts: {},
|
|
|
lastUpdate: null
|
|
|
};
|
|
|
|
|
|
|
|
|
const ColorSchemes = {
|
|
|
blue: {
|
|
|
primary: '#3B82F6',
|
|
|
secondary: '#06B6D4',
|
|
|
gradient: ['#3B82F6', '#06B6D4']
|
|
|
},
|
|
|
purple: {
|
|
|
primary: '#8B5CF6',
|
|
|
secondary: '#EC4899',
|
|
|
gradient: ['#8B5CF6', '#EC4899']
|
|
|
},
|
|
|
green: {
|
|
|
primary: '#10B981',
|
|
|
secondary: '#34D399',
|
|
|
gradient: ['#10B981', '#34D399']
|
|
|
},
|
|
|
orange: {
|
|
|
primary: '#F97316',
|
|
|
secondary: '#FBBF24',
|
|
|
gradient: ['#F97316', '#FBBF24']
|
|
|
},
|
|
|
rainbow: {
|
|
|
primary: '#3B82F6',
|
|
|
secondary: '#EC4899',
|
|
|
gradient: ['#3B82F6', '#8B5CF6', '#EC4899', '#F97316']
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
Chart.defaults.color = '#E2E8F0';
|
|
|
Chart.defaults.borderColor = 'rgba(255, 255, 255, 0.1)';
|
|
|
Chart.defaults.font.family = "'Manrope', 'Inter', sans-serif";
|
|
|
Chart.defaults.font.size = 13;
|
|
|
Chart.defaults.font.weight = 500;
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
initNavigation();
|
|
|
initCombobox();
|
|
|
initChartControls();
|
|
|
initColorSchemeSelector();
|
|
|
loadInitialData();
|
|
|
startAutoRefresh();
|
|
|
});
|
|
|
|
|
|
|
|
|
function initNavigation() {
|
|
|
const navButtons = document.querySelectorAll('.nav-button');
|
|
|
const pages = document.querySelectorAll('.page');
|
|
|
|
|
|
navButtons.forEach(button => {
|
|
|
button.addEventListener('click', () => {
|
|
|
const targetPage = button.dataset.nav;
|
|
|
|
|
|
|
|
|
navButtons.forEach(btn => btn.classList.remove('active'));
|
|
|
button.classList.add('active');
|
|
|
|
|
|
|
|
|
pages.forEach(page => {
|
|
|
page.classList.toggle('active', page.id === targetPage);
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
function initCombobox() {
|
|
|
const input = document.getElementById('coinSelector');
|
|
|
const dropdown = document.getElementById('coinDropdown');
|
|
|
|
|
|
if (!input || !dropdown) return;
|
|
|
|
|
|
input.addEventListener('focus', () => {
|
|
|
dropdown.classList.add('active');
|
|
|
if (AppState.coins.length === 0) {
|
|
|
loadCoinsForCombobox();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
input.addEventListener('input', (e) => {
|
|
|
const searchTerm = e.target.value.toLowerCase();
|
|
|
filterComboboxOptions(searchTerm);
|
|
|
});
|
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
if (!input.contains(e.target) && !dropdown.contains(e.target)) {
|
|
|
dropdown.classList.remove('active');
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
async function loadCoinsForCombobox() {
|
|
|
try {
|
|
|
const response = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1');
|
|
|
const coins = await response.json();
|
|
|
AppState.coins = coins;
|
|
|
renderComboboxOptions(coins);
|
|
|
} catch (error) {
|
|
|
console.error('Error loading coins:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function renderComboboxOptions(coins) {
|
|
|
const dropdown = document.getElementById('coinDropdown');
|
|
|
if (!dropdown) return;
|
|
|
|
|
|
dropdown.innerHTML = coins.map(coin => `
|
|
|
<div class="combobox-option" data-coin-id="${coin.id}">
|
|
|
<img src="${coin.image}" alt="${coin.name}" class="combobox-option-icon">
|
|
|
<div class="combobox-option-text">
|
|
|
<div class="combobox-option-name">${coin.name}</div>
|
|
|
<div class="combobox-option-symbol">${coin.symbol}</div>
|
|
|
</div>
|
|
|
<div class="combobox-option-price">$${formatNumber(coin.current_price)}</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
|
|
|
|
|
|
dropdown.querySelectorAll('.combobox-option').forEach(option => {
|
|
|
option.addEventListener('click', () => {
|
|
|
const coinId = option.dataset.coinId;
|
|
|
selectCoin(coinId);
|
|
|
dropdown.classList.remove('active');
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function filterComboboxOptions(searchTerm) {
|
|
|
const options = document.querySelectorAll('.combobox-option');
|
|
|
options.forEach(option => {
|
|
|
const name = option.querySelector('.combobox-option-name').textContent.toLowerCase();
|
|
|
const symbol = option.querySelector('.combobox-option-symbol').textContent.toLowerCase();
|
|
|
const matches = name.includes(searchTerm) || symbol.includes(searchTerm);
|
|
|
option.style.display = matches ? 'flex' : 'none';
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function selectCoin(coinId) {
|
|
|
const coin = AppState.coins.find(c => c.id === coinId);
|
|
|
if (!coin) return;
|
|
|
|
|
|
AppState.selectedCoin = coin;
|
|
|
document.getElementById('coinSelector').value = `${coin.name} (${coin.symbol.toUpperCase()})`;
|
|
|
|
|
|
|
|
|
loadCoinChart(coinId, AppState.selectedTimeframe);
|
|
|
}
|
|
|
|
|
|
|
|
|
function initChartControls() {
|
|
|
|
|
|
const timeframeButtons = document.querySelectorAll('[data-timeframe]');
|
|
|
timeframeButtons.forEach(button => {
|
|
|
button.addEventListener('click', () => {
|
|
|
timeframeButtons.forEach(btn => btn.classList.remove('active'));
|
|
|
button.classList.add('active');
|
|
|
|
|
|
AppState.selectedTimeframe = parseInt(button.dataset.timeframe);
|
|
|
|
|
|
if (AppState.selectedCoin) {
|
|
|
loadCoinChart(AppState.selectedCoin.id, AppState.selectedTimeframe);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
function initColorSchemeSelector() {
|
|
|
const schemeOptions = document.querySelectorAll('.color-scheme-option');
|
|
|
schemeOptions.forEach(option => {
|
|
|
option.addEventListener('click', () => {
|
|
|
schemeOptions.forEach(opt => opt.classList.remove('active'));
|
|
|
option.classList.add('active');
|
|
|
|
|
|
AppState.selectedColorScheme = option.dataset.scheme;
|
|
|
|
|
|
if (AppState.selectedCoin) {
|
|
|
loadCoinChart(AppState.selectedCoin.id, AppState.selectedTimeframe);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
async function loadInitialData() {
|
|
|
try {
|
|
|
await Promise.all([
|
|
|
loadMarketStats(),
|
|
|
loadTopCoins(),
|
|
|
loadMainChart()
|
|
|
]);
|
|
|
|
|
|
AppState.lastUpdate = new Date();
|
|
|
updateLastUpdateTime();
|
|
|
} catch (error) {
|
|
|
console.error('Error loading initial data:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async function loadMarketStats() {
|
|
|
try {
|
|
|
const response = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=10&page=1');
|
|
|
const coins = await response.json();
|
|
|
|
|
|
|
|
|
const totalMarketCap = coins.reduce((sum, coin) => sum + coin.market_cap, 0);
|
|
|
const totalVolume = coins.reduce((sum, coin) => sum + coin.total_volume, 0);
|
|
|
const btc = coins.find(c => c.id === 'bitcoin');
|
|
|
const eth = coins.find(c => c.id === 'ethereum');
|
|
|
|
|
|
|
|
|
const statsGrid = document.getElementById('statsGrid');
|
|
|
if (statsGrid) {
|
|
|
statsGrid.innerHTML = `
|
|
|
${createStatCard('Total Market Cap', formatCurrency(totalMarketCap), '+2.5%', 'positive', '#3B82F6')}
|
|
|
${createStatCard('24h Volume', formatCurrency(totalVolume), '+5.2%', 'positive', '#06B6D4')}
|
|
|
${createStatCard('Bitcoin', formatCurrency(btc?.current_price || 0), `${btc?.price_change_percentage_24h?.toFixed(2) || 0}%`, btc?.price_change_percentage_24h >= 0 ? 'positive' : 'negative', '#F7931A')}
|
|
|
${createStatCard('Ethereum', formatCurrency(eth?.current_price || 0), `${eth?.price_change_percentage_24h?.toFixed(2) || 0}%`, eth?.price_change_percentage_24h >= 0 ? 'positive' : 'negative', '#627EEA')}
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
document.getElementById('sidebarMarketCap').textContent = formatCurrency(totalMarketCap);
|
|
|
document.getElementById('sidebarVolume').textContent = formatCurrency(totalVolume);
|
|
|
document.getElementById('sidebarBTC').textContent = formatCurrency(btc?.current_price || 0);
|
|
|
document.getElementById('sidebarETH').textContent = formatCurrency(eth?.current_price || 0);
|
|
|
|
|
|
|
|
|
const btcElement = document.getElementById('sidebarBTC');
|
|
|
const ethElement = document.getElementById('sidebarETH');
|
|
|
|
|
|
if (btc?.price_change_percentage_24h >= 0) {
|
|
|
btcElement.classList.add('positive');
|
|
|
btcElement.classList.remove('negative');
|
|
|
} else {
|
|
|
btcElement.classList.add('negative');
|
|
|
btcElement.classList.remove('positive');
|
|
|
}
|
|
|
|
|
|
if (eth?.price_change_percentage_24h >= 0) {
|
|
|
ethElement.classList.add('positive');
|
|
|
ethElement.classList.remove('negative');
|
|
|
} else {
|
|
|
ethElement.classList.add('negative');
|
|
|
ethElement.classList.remove('positive');
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Error loading market stats:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function createStatCard(label, value, change, changeType, color) {
|
|
|
const changeIcon = changeType === 'positive'
|
|
|
? '<path d="M12 19V5M5 12l7-7 7 7" stroke="currentColor" stroke-width="2"/>'
|
|
|
: '<path d="M12 5v14M19 12l-7 7-7-7" stroke="currentColor" stroke-width="2"/>';
|
|
|
|
|
|
return `
|
|
|
<div class="glass-card stat-card">
|
|
|
<div class="stat-header">
|
|
|
<div class="stat-icon" style="background: linear-gradient(135deg, ${color}, ${color}CC); box-shadow: 0 0 20px ${color}66;">
|
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="white" stroke-width="2"/>
|
|
|
</svg>
|
|
|
</div>
|
|
|
<h3>${label}</h3>
|
|
|
</div>
|
|
|
<div class="stat-value-wrapper">
|
|
|
<div class="stat-value">${value}</div>
|
|
|
<div class="stat-change ${changeType}">
|
|
|
<div class="change-icon-wrapper ${changeType}">
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none">
|
|
|
${changeIcon}
|
|
|
</svg>
|
|
|
</div>
|
|
|
<span class="change-value">${change}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
async function loadTopCoins() {
|
|
|
try {
|
|
|
const response = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=20&page=1&sparkline=true');
|
|
|
const coins = await response.json();
|
|
|
|
|
|
const table = document.getElementById('topCoinsTable');
|
|
|
if (!table) return;
|
|
|
|
|
|
table.innerHTML = coins.map((coin, index) => {
|
|
|
const change24h = coin.price_change_percentage_24h || 0;
|
|
|
const change7d = coin.price_change_percentage_7d_in_currency || 0;
|
|
|
|
|
|
return `
|
|
|
<tr>
|
|
|
<td>${index + 1}</td>
|
|
|
<td>
|
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
|
<img src="${coin.image}" alt="${coin.name}" style="width: 32px; height: 32px; border-radius: 50%;">
|
|
|
<div>
|
|
|
<div style="font-weight: 600;">${coin.name}</div>
|
|
|
<div style="font-size: 12px; color: var(--text-muted);">${coin.symbol.toUpperCase()}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</td>
|
|
|
<td style="font-weight: 600;">$${formatNumber(coin.current_price)}</td>
|
|
|
<td>
|
|
|
<span class="badge ${change24h >= 0 ? 'badge-success' : 'badge-danger'}">
|
|
|
${change24h >= 0 ? '↑' : '↓'} ${Math.abs(change24h).toFixed(2)}%
|
|
|
</span>
|
|
|
</td>
|
|
|
<td>
|
|
|
<span class="badge ${change7d >= 0 ? 'badge-success' : 'badge-danger'}">
|
|
|
${change7d >= 0 ? '↑' : '↓'} ${Math.abs(change7d).toFixed(2)}%
|
|
|
</span>
|
|
|
</td>
|
|
|
<td>$${formatNumber(coin.market_cap)}</td>
|
|
|
<td>$${formatNumber(coin.total_volume)}</td>
|
|
|
<td>
|
|
|
<canvas id="spark-${coin.id}" width="100" height="30"></canvas>
|
|
|
</td>
|
|
|
</tr>
|
|
|
`;
|
|
|
}).join('');
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
coins.forEach(coin => {
|
|
|
if (coin.sparkline_in_7d && coin.sparkline_in_7d.price) {
|
|
|
createSparkline(`spark-${coin.id}`, coin.sparkline_in_7d.price, coin.price_change_percentage_24h >= 0);
|
|
|
}
|
|
|
});
|
|
|
}, 100);
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Error loading top coins:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function createSparkline(canvasId, data, isPositive) {
|
|
|
const canvas = document.getElementById(canvasId);
|
|
|
if (!canvas) return;
|
|
|
|
|
|
const color = isPositive ? '#10B981' : '#EF4444';
|
|
|
|
|
|
new Chart(canvas, {
|
|
|
type: 'line',
|
|
|
data: {
|
|
|
labels: data.map((_, i) => i),
|
|
|
datasets: [{
|
|
|
data: data,
|
|
|
borderColor: color,
|
|
|
backgroundColor: color + '20',
|
|
|
borderWidth: 2,
|
|
|
fill: true,
|
|
|
tension: 0.4,
|
|
|
pointRadius: 0
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: false,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
|
|
scales: { x: { display: false }, y: { display: false } }
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
async function loadMainChart() {
|
|
|
try {
|
|
|
const response = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=10&page=1&sparkline=true');
|
|
|
const coins = await response.json();
|
|
|
|
|
|
const canvas = document.getElementById('mainChart');
|
|
|
if (!canvas) return;
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
if (AppState.charts.main) {
|
|
|
AppState.charts.main.destroy();
|
|
|
}
|
|
|
|
|
|
const colors = ['#3B82F6', '#06B6D4', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#F97316', '#14B8A6', '#6366F1'];
|
|
|
|
|
|
const datasets = coins.slice(0, 10).map((coin, index) => ({
|
|
|
label: coin.name,
|
|
|
data: coin.sparkline_in_7d.price,
|
|
|
borderColor: colors[index],
|
|
|
backgroundColor: colors[index] + '20',
|
|
|
borderWidth: 3,
|
|
|
fill: false,
|
|
|
tension: 0.4,
|
|
|
pointRadius: 0,
|
|
|
pointHoverRadius: 6,
|
|
|
pointHoverBackgroundColor: colors[index],
|
|
|
pointHoverBorderColor: '#fff',
|
|
|
pointHoverBorderWidth: 2
|
|
|
}));
|
|
|
|
|
|
AppState.charts.main = new Chart(ctx, {
|
|
|
type: 'line',
|
|
|
data: {
|
|
|
labels: Array.from({length: 168}, (_, i) => i),
|
|
|
datasets: datasets
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
interaction: {
|
|
|
mode: 'index',
|
|
|
intersect: false,
|
|
|
},
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
display: true,
|
|
|
position: 'top',
|
|
|
align: 'end',
|
|
|
labels: {
|
|
|
usePointStyle: true,
|
|
|
pointStyle: 'circle',
|
|
|
padding: 15,
|
|
|
font: { size: 12, weight: 600 }
|
|
|
}
|
|
|
},
|
|
|
tooltip: {
|
|
|
backgroundColor: 'rgba(15, 23, 42, 0.95)',
|
|
|
titleColor: '#fff',
|
|
|
bodyColor: '#E2E8F0',
|
|
|
borderColor: 'rgba(6, 182, 212, 0.5)',
|
|
|
borderWidth: 1,
|
|
|
padding: 16,
|
|
|
displayColors: true,
|
|
|
boxPadding: 8,
|
|
|
usePointStyle: true
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
grid: { display: false },
|
|
|
ticks: { display: false }
|
|
|
},
|
|
|
y: {
|
|
|
grid: {
|
|
|
color: 'rgba(255, 255, 255, 0.05)',
|
|
|
drawBorder: false
|
|
|
},
|
|
|
ticks: {
|
|
|
color: '#94A3B8',
|
|
|
callback: function(value) {
|
|
|
return '$' + formatNumber(value);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Error loading main chart:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async function loadCoinChart(coinId, days) {
|
|
|
try {
|
|
|
const response = await fetch(`https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=usd&days=${days}`);
|
|
|
const data = await response.json();
|
|
|
|
|
|
const scheme = ColorSchemes[AppState.selectedColorScheme];
|
|
|
|
|
|
|
|
|
const coin = AppState.selectedCoin;
|
|
|
document.getElementById('chartTitle').textContent = `${coin.name} (${coin.symbol.toUpperCase()}) Price Chart`;
|
|
|
document.getElementById('chartPrice').textContent = `$${formatNumber(coin.current_price)}`;
|
|
|
|
|
|
const change = coin.price_change_percentage_24h;
|
|
|
const changeElement = document.getElementById('chartChange');
|
|
|
changeElement.textContent = `${change >= 0 ? '+' : ''}${change.toFixed(2)}%`;
|
|
|
changeElement.className = `badge ${change >= 0 ? 'badge-success' : 'badge-danger'}`;
|
|
|
|
|
|
|
|
|
const priceCanvas = document.getElementById('priceChart');
|
|
|
if (priceCanvas) {
|
|
|
const ctx = priceCanvas.getContext('2d');
|
|
|
|
|
|
if (AppState.charts.price) {
|
|
|
AppState.charts.price.destroy();
|
|
|
}
|
|
|
|
|
|
const labels = data.prices.map(p => new Date(p[0]));
|
|
|
const prices = data.prices.map(p => p[1]);
|
|
|
|
|
|
AppState.charts.price = new Chart(ctx, {
|
|
|
type: 'line',
|
|
|
data: {
|
|
|
labels: labels,
|
|
|
datasets: [{
|
|
|
label: 'Price (USD)',
|
|
|
data: prices,
|
|
|
borderColor: scheme.primary,
|
|
|
backgroundColor: scheme.primary + '20',
|
|
|
borderWidth: 3,
|
|
|
fill: true,
|
|
|
tension: 0.4,
|
|
|
pointRadius: 0,
|
|
|
pointHoverRadius: 8,
|
|
|
pointHoverBackgroundColor: scheme.primary,
|
|
|
pointHoverBorderColor: '#fff',
|
|
|
pointHoverBorderWidth: 3
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: { display: false },
|
|
|
tooltip: {
|
|
|
backgroundColor: 'rgba(15, 23, 42, 0.95)',
|
|
|
padding: 16,
|
|
|
displayColors: false,
|
|
|
callbacks: {
|
|
|
label: function(context) {
|
|
|
return 'Price: $' + formatNumber(context.parsed.y);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
type: 'time',
|
|
|
time: {
|
|
|
unit: days <= 1 ? 'hour' : days <= 7 ? 'day' : days <= 30 ? 'day' : 'week'
|
|
|
},
|
|
|
grid: { display: false },
|
|
|
ticks: { color: '#94A3B8', maxRotation: 0, autoSkip: true, maxTicksLimit: 8 }
|
|
|
},
|
|
|
y: {
|
|
|
grid: { color: 'rgba(255, 255, 255, 0.05)', drawBorder: false },
|
|
|
ticks: {
|
|
|
color: '#94A3B8',
|
|
|
callback: function(value) {
|
|
|
return '$' + formatNumber(value);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
const volumeCanvas = document.getElementById('volumeChart');
|
|
|
if (volumeCanvas) {
|
|
|
const ctx = volumeCanvas.getContext('2d');
|
|
|
|
|
|
if (AppState.charts.volume) {
|
|
|
AppState.charts.volume.destroy();
|
|
|
}
|
|
|
|
|
|
const volumeLabels = data.total_volumes.map(v => new Date(v[0]));
|
|
|
const volumes = data.total_volumes.map(v => v[1]);
|
|
|
|
|
|
AppState.charts.volume = new Chart(ctx, {
|
|
|
type: 'bar',
|
|
|
data: {
|
|
|
labels: volumeLabels,
|
|
|
datasets: [{
|
|
|
label: 'Volume',
|
|
|
data: volumes,
|
|
|
backgroundColor: scheme.secondary + '80',
|
|
|
borderColor: scheme.secondary,
|
|
|
borderWidth: 2,
|
|
|
borderRadius: 6,
|
|
|
borderSkipped: false
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: { display: false },
|
|
|
tooltip: {
|
|
|
backgroundColor: 'rgba(15, 23, 42, 0.95)',
|
|
|
padding: 16,
|
|
|
callbacks: {
|
|
|
label: function(context) {
|
|
|
return 'Volume: $' + formatNumber(context.parsed.y);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
type: 'time',
|
|
|
time: {
|
|
|
unit: days <= 1 ? 'hour' : days <= 7 ? 'day' : days <= 30 ? 'day' : 'week'
|
|
|
},
|
|
|
grid: { display: false },
|
|
|
ticks: { color: '#94A3B8', maxRotation: 0, autoSkip: true, maxTicksLimit: 8 }
|
|
|
},
|
|
|
y: {
|
|
|
grid: { color: 'rgba(255, 255, 255, 0.05)', drawBorder: false },
|
|
|
ticks: {
|
|
|
color: '#94A3B8',
|
|
|
callback: function(value) {
|
|
|
return '$' + formatNumber(value);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Error loading coin chart:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function startAutoRefresh() {
|
|
|
setInterval(() => {
|
|
|
loadMarketStats();
|
|
|
AppState.lastUpdate = new Date();
|
|
|
updateLastUpdateTime();
|
|
|
}, 60000);
|
|
|
}
|
|
|
|
|
|
function updateLastUpdateTime() {
|
|
|
const element = document.getElementById('lastUpdate');
|
|
|
if (!element) return;
|
|
|
|
|
|
const now = new Date();
|
|
|
const diff = Math.floor((now - AppState.lastUpdate) / 1000);
|
|
|
|
|
|
if (diff < 60) {
|
|
|
element.textContent = 'Just now';
|
|
|
} else if (diff < 3600) {
|
|
|
element.textContent = `${Math.floor(diff / 60)}m ago`;
|
|
|
} else {
|
|
|
element.textContent = `${Math.floor(diff / 3600)}h ago`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
window.refreshData = function() {
|
|
|
loadInitialData();
|
|
|
};
|
|
|
|
|
|
|
|
|
function formatNumber(num) {
|
|
|
if (num === null || num === undefined || isNaN(num)) {
|
|
|
return '0.00';
|
|
|
}
|
|
|
num = Number(num);
|
|
|
if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T';
|
|
|
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
|
|
|
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
|
|
|
if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
|
|
|
return num.toFixed(2);
|
|
|
}
|
|
|
|
|
|
function formatCurrency(num) {
|
|
|
return '$' + formatNumber(num);
|
|
|
}
|
|
|
|
|
|
|
|
|
window.AppState = AppState;
|
|
|
window.selectCoin = selectCoin;
|
|
|
|