tr / index.html
Sanzhar7's picture
Add 3 files
c8eca3d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TradingView-like Charting Software</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/papaparse.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.chart-container {
position: relative;
height: 70vh;
width: 100%;
}
.candle {
position: relative;
width: 6px;
}
.candle::before {
content: '';
position: absolute;
width: 100%;
height: 3px;
background-color: currentColor;
top: 50%;
transform: translateY(-50%);
}
.candle.up {
color: #26a69a;
}
.candle.down {
color: #ef5350;
}
.indicator-line {
position: absolute;
height: 1px;
width: 100%;
background-color: rgba(255,255,255,0.2);
}
.tooltip {
position: absolute;
padding: 8px;
background: rgba(0, 0, 0, 0.8);
color: white;
border-radius: 4px;
pointer-events: none;
font-size: 12px;
z-index: 100;
display: none;
}
.resize-handle {
position: absolute;
right: 0;
bottom: 0;
width: 10px;
height: 10px;
background: #3b82f6;
cursor: nwse-resize;
z-index: 10;
}
.chartjs-tooltip {
opacity: 1;
position: absolute;
background: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 3px;
-webkit-transition: all .1s ease;
transition: all .1s ease;
pointer-events: none;
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
padding: 4px;
font-size: 12px;
}
</style>
</head>
<body class="bg-gray-900 text-gray-200">
<div class="container mx-auto px-4 py-6">
<header class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-blue-400">TradeVision Charting</h1>
<div class="flex space-x-4">
<button id="saveLayoutBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md">
<i class="fas fa-save mr-2"></i>Save Layout
</button>
<button id="loadLayoutBtn" class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md">
<i class="fas fa-folder-open mr-2"></i>Load Layout
</button>
</div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
<!-- Left sidebar -->
<div class="lg:col-span-1 bg-gray-800 rounded-lg p-4">
<div class="mb-6">
<h2 class="text-lg font-semibold mb-3 border-b border-gray-700 pb-2">Data Import</h2>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium mb-1">Upload CSV File</label>
<input type="file" id="csvFileInput" accept=".csv" class="block w-full text-sm text-gray-400
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-blue-500 file:text-white
hover:file:bg-blue-600
cursor-pointer
bg-gray-700 rounded-md">
</div>
<div>
<label class="block text-sm font-medium mb-1">Chart Type</label>
<select id="chartTypeSelect" class="w-full bg-gray-700 border border-gray-600 rounded-md px-3 py-2">
<option value="candlestick">Candlestick</option>
<option value="line">Line</option>
<option value="ohlc">OHLC Bars</option>
<option value="area">Area</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Timeframe</label>
<select id="timeframeSelect" class="w-full bg-gray-700 border border-gray-600 rounded-md px-3 py-2">
<option value="1m">1 Minute</option>
<option value="5m">5 Minutes</option>
<option value="15m">15 Minutes</option>
<option value="30m">30 Minutes</option>
<option value="1h">1 Hour</option>
<option value="4h">4 Hours</option>
<option value="1d">1 Day</option>
<option value="1w">1 Week</option>
</select>
</div>
<button id="loadDataBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md">
<i class="fas fa-chart-line mr-2"></i>Load Data
</button>
</div>
</div>
<div class="mb-6">
<h2 class="text-lg font-semibold mb-3 border-b border-gray-700 pb-2">Indicators</h2>
<div class="space-y-3">
<select id="indicatorSelect" class="w-full bg-gray-700 border border-gray-600 rounded-md px-3 py-2">
<option value="">Select Indicator...</option>
<option value="sma">Simple Moving Average</option>
<option value="ema">Exponential Moving Average</option>
<option value="rsi">Relative Strength Index</option>
<option value="macd">MACD</option>
<option value="bollinger">Bollinger Bands</option>
</select>
<div id="indicatorParams" class="hidden space-y-2 p-2 bg-gray-700 rounded-md">
<!-- Parameters will be added dynamically -->
</div>
<button id="addIndicatorBtn" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md">
<i class="fas fa-plus mr-2"></i>Add Indicator
</button>
</div>
</div>
<div>
<h2 class="text-lg font-semibold mb-3 border-b border-gray-700 pb-2">Active Indicators</h2>
<ul id="activeIndicatorsList" class="space-y-2">
<!-- Indicators will be added here -->
</ul>
</div>
</div>
<!-- Main chart area -->
<div class="lg:col-span-3 bg-gray-800 rounded-lg p-4">
<div class="flex justify-between items-center mb-4">
<div class="flex space-x-2">
<button id="zoomInBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 rounded-md">
<i class="fas fa-search-plus"></i>
</button>
<button id="zoomOutBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 rounded-md">
<i class="fas fa-search-minus"></i>
</button>
<button id="zoomFitBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 rounded-md">
<i class="fas fa-expand"></i>
</button>
</div>
<div class="flex space-x-2">
<button id="drawLineBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 rounded-md">
<i class="fas fa-slash"></i> Line
</button>
<button id="drawHorizontalBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 rounded-md">
<i class="fas fa-grip-lines"></i> Horizontal
</button>
<button id="clearDrawingsBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 rounded-md">
<i class="fas fa-trash-alt"></i> Clear
</button>
</div>
</div>
<div class="chart-container">
<canvas id="priceChart"></canvas>
<div id="tooltip" class="tooltip"></div>
</div>
<div class="mt-4">
<div class="chart-container" style="height: 150px;">
<canvas id="volumeChart"></canvas>
</div>
</div>
<div class="mt-4 flex justify-between items-center text-sm text-gray-400">
<div id="timeRangeDisplay">Time range: -</div>
<div id="priceDisplay">Price: -</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Chart setup
const priceChartCtx = document.getElementById('priceChart').getContext('2d');
const volumeChartCtx = document.getElementById('volumeChart').getContext('2d');
let priceChart, volumeChart;
let chartData = [];
let activeIndicators = [];
let drawings = [];
let isDrawing = false;
let currentDrawing = null;
// Initialize charts
function initCharts() {
priceChart = new Chart(priceChartCtx, {
type: 'candlestick',
data: {
datasets: [{
label: 'Price',
data: [],
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
color: {
up: '#26a69a',
down: '#ef5350',
unchanged: '#999',
},
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'time',
time: {
unit: 'day'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
y: {
position: 'right',
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false,
external: function(context) {
const tooltip = document.getElementById('tooltip');
if (context.tooltip.opacity === 0) {
tooltip.style.display = 'none';
return;
}
const data = context.tooltip.dataPoints[0].raw;
tooltip.innerHTML = `
<div><strong>Date:</strong> ${new Date(data.x).toLocaleString()}</div>
<div><strong>Open:</strong> ${data.o}</div>
<div><strong>High:</strong> ${data.h}</div>
<div><strong>Low:</strong> ${data.l}</div>
<div><strong>Close:</strong> ${data.c}</div>
<div><strong>Volume:</strong> ${data.v || 0}</div>
`;
tooltip.style.display = 'block';
tooltip.style.left = context.tooltip.caretX + 'px';
tooltip.style.top = context.tooltip.caretY + 'px';
}
},
zoom: {
pan: {
enabled: true,
mode: 'xy',
modifierKey: 'ctrl'
},
zoom: {
wheel: {
enabled: true,
},
pinch: {
enabled: true
},
mode: 'xy',
}
}
},
interaction: {
intersect: false,
mode: 'index',
},
onHover: function(event, chartElement) {
if (event.native) {
const x = event.native.x;
const y = event.native.y;
document.getElementById('priceDisplay').textContent = `Price: ${y}`;
}
}
}
});
volumeChart = new Chart(volumeChartCtx, {
type: 'bar',
data: {
datasets: [{
label: 'Volume',
data: [],
backgroundColor: function(context) {
const index = context.dataIndex;
if (index > 0) {
const current = chartData[index];
const previous = chartData[index - 1];
return current.c >= previous.c ? 'rgba(38, 166, 154, 0.7)' : 'rgba(239, 83, 80, 0.7)';
}
return 'rgba(153, 153, 153, 0.7)';
},
borderColor: 'rgba(0, 0, 0, 0.1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'time',
display: false,
grid: {
display: false
}
},
y: {
display: false,
beginAtZero: true
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
}
}
}
});
}
// Parse CSV data
function parseCSV(file) {
return new Promise((resolve, reject) => {
Papa.parse(file, {
header: true,
complete: function(results) {
resolve(results.data);
},
error: function(error) {
reject(error);
}
});
});
}
// Process data for chart
function processData(rawData) {
return rawData.map(item => {
// Try to detect date format automatically
let date;
if (item.date) {
date = new Date(item.date);
} else if (item.timestamp) {
date = new Date(parseInt(item.timestamp));
} else if (item.time) {
date = new Date(item.time);
} else {
// Try to find the first column that looks like a date
for (const key in item) {
if (key.match(/date|time|timestamp/i)) {
date = new Date(item[key]);
break;
}
}
}
// Try to detect OHLCV columns
let o, h, l, c, v;
if (item.open && item.high && item.low && item.close) {
o = parseFloat(item.open);
h = parseFloat(item.high);
l = parseFloat(item.low);
c = parseFloat(item.close);
} else {
// Try to find numeric columns
const numericValues = [];
for (const key in item) {
if (!isNaN(parseFloat(item[key]))) {
numericValues.push(parseFloat(item[key]));
}
}
if (numericValues.length >= 4) {
o = numericValues[0];
h = numericValues[1];
l = numericValues[2];
c = numericValues[3];
}
}
v = item.volume ? parseFloat(item.volume) : 0;
return {
x: date,
o: o,
h: h,
l: l,
c: c,
v: v
};
}).filter(item => !isNaN(item.x.getTime()) && !isNaN(item.o) && !isNaN(item.h) && !isNaN(item.l) && !isNaN(item.c));
}
// Update charts with new data
function updateCharts(data) {
chartData = data;
// Update price chart
priceChart.data.datasets[0].data = data;
// Update volume chart
volumeChart.data.datasets[0].data = data.map(item => ({
x: item.x,
y: item.v
}));
// Update time range display
if (data.length > 0) {
const startDate = new Date(data[0].x).toLocaleDateString();
const endDate = new Date(data[data.length - 1].x).toLocaleDateString();
document.getElementById('timeRangeDisplay').textContent = `Time range: ${startDate} to ${endDate}`;
}
priceChart.update();
volumeChart.update();
}
// Handle file upload
document.getElementById('loadDataBtn').addEventListener('click', async function() {
const fileInput = document.getElementById('csvFileInput');
const chartType = document.getElementById('chartTypeSelect').value;
if (fileInput.files.length === 0) {
alert('Please select a CSV file first.');
return;
}
try {
const rawData = await parseCSV(fileInput.files[0]);
const processedData = processData(rawData);
if (processedData.length === 0) {
alert('No valid data found in the CSV file.');
return;
}
// Change chart type
priceChart.config.type = chartType === 'candlestick' ? 'candlestick' : 'line';
updateCharts(processedData);
} catch (error) {
console.error('Error loading CSV:', error);
alert('Error loading CSV file. Please check the file format.');
}
});
// Handle chart type change
document.getElementById('chartTypeSelect').addEventListener('change', function() {
const chartType = this.value;
priceChart.config.type = chartType === 'candlestick' ? 'candlestick' : 'line';
priceChart.update();
});
// Handle indicator selection
document.getElementById('indicatorSelect').addEventListener('change', function() {
const indicator = this.value;
const paramsDiv = document.getElementById('indicatorParams');
if (!indicator) {
paramsDiv.classList.add('hidden');
return;
}
paramsDiv.innerHTML = '';
paramsDiv.classList.remove('hidden');
switch (indicator) {
case 'sma':
paramsDiv.innerHTML = `
<div>
<label class="block text-sm font-medium mb-1">Period</label>
<input type="number" value="20" min="1" max="200" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Color</label>
<input type="color" value="#FFA500" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
`;
break;
case 'ema':
paramsDiv.innerHTML = `
<div>
<label class="block text-sm font-medium mb-1">Period</label>
<input type="number" value="20" min="1" max="200" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Color</label>
<input type="color" value="#FFA500" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
`;
break;
case 'rsi':
paramsDiv.innerHTML = `
<div>
<label class="block text-sm font-medium mb-1">Period</label>
<input type="number" value="14" min="1" max="200" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Overbought</label>
<input type="number" value="70" min="1" max="100" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Oversold</label>
<input type="number" value="30" min="1" max="100" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
`;
break;
case 'macd':
paramsDiv.innerHTML = `
<div>
<label class="block text-sm font-medium mb-1">Fast Period</label>
<input type="number" value="12" min="1" max="50" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Slow Period</label>
<input type="number" value="26" min="1" max="50" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Signal Period</label>
<input type="number" value="9" min="1" max="50" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
`;
break;
case 'bollinger':
paramsDiv.innerHTML = `
<div>
<label class="block text-sm font-medium mb-1">Period</label>
<input type="number" value="20" min="1" max="200" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Standard Deviations</label>
<input type="number" value="2" min="1" max="5" step="0.1" class="w-full bg-gray-600 border border-gray-500 rounded-md px-2 py-1">
</div>
`;
break;
}
});
// Add indicator to chart
document.getElementById('addIndicatorBtn').addEventListener('click', function() {
const indicator = document.getElementById('indicatorSelect').value;
if (!indicator) {
alert('Please select an indicator first.');
return;
}
if (chartData.length === 0) {
alert('Please load data first.');
return;
}
const paramsDiv = document.getElementById('indicatorParams');
const inputs = paramsDiv.querySelectorAll('input');
const params = {};
inputs.forEach(input => {
params[input.previousElementSibling.textContent.trim()] = input.type === 'color' ? input.value : parseFloat(input.value);
});
const indicatorId = Date.now();
const indicatorConfig = {
id: indicatorId,
type: indicator,
params: params,
color: params.Color || '#FFA500'
};
activeIndicators.push(indicatorConfig);
updateIndicatorsList();
applyIndicator(indicatorConfig);
});
// Update active indicators list
function updateIndicatorsList() {
const list = document.getElementById('activeIndicatorsList');
list.innerHTML = '';
activeIndicators.forEach(indicator => {
const li = document.createElement('li');
li.className = 'flex justify-between items-center bg-gray-700 p-2 rounded-md';
li.innerHTML = `
<span>${indicator.type.toUpperCase()} (${Object.entries(indicator.params)
.filter(([key]) => key !== 'Color')
.map(([key, val]) => `${key}: ${val}`).join(', ')})</span>
<button class="text-red-400 hover:text-red-300" data-id="${indicator.id}">
<i class="fas fa-times"></i>
</button>
`;
list.appendChild(li);
});
// Add event listeners to remove buttons
document.querySelectorAll('#activeIndicatorsList button').forEach(button => {
button.addEventListener('click', function() {
const id = parseInt(this.getAttribute('data-id'));
removeIndicator(id);
});
});
}
// Remove indicator
function removeIndicator(id) {
// Remove from active indicators
activeIndicators = activeIndicators.filter(ind => ind.id !== id);
// Remove from chart datasets
priceChart.data.datasets = priceChart.data.datasets.filter(ds => !ds.indicatorId || ds.indicatorId !== id);
updateIndicatorsList();
priceChart.update();
}
// Apply indicator to chart
function applyIndicator(config) {
const data = chartData;
switch (config.type) {
case 'sma':
const smaPeriod = config.params.Period || 20;
const smaValues = calculateSMA(data, smaPeriod);
priceChart.data.datasets.push({
label: `SMA(${smaPeriod})`,
data: data.map((item, i) => ({
x: item.x,
y: smaValues[i]
})),
borderColor: config.color,
borderWidth: 2,
pointRadius: 0,
fill: false,
tension: 0.1,
indicatorId: config.id
});
break;
case 'ema':
const emaPeriod = config.params.Period || 20;
const emaValues = calculateEMA(data, emaPeriod);
priceChart.data.datasets.push({
label: `EMA(${emaPeriod})`,
data: data.map((item, i) => ({
x: item.x,
y: emaValues[i]
})),
borderColor: config.color,
borderWidth: 2,
pointRadius: 0,
fill: false,
tension: 0.1,
indicatorId: config.id
});
break;
case 'rsi':
const rsiPeriod = config.params.Period || 14;
const rsiValues = calculateRSI(data, rsiPeriod);
const overbought = config.params.Overbought || 70;
const oversold = config.params.Oversold || 30;
// Add RSI dataset
priceChart.data.datasets.push({
label: `RSI(${rsiPeriod})`,
data: data.map((item, i) => ({
x: item.x,
y: rsiValues[i]
})),
borderColor: config.color,
borderWidth: 2,
pointRadius: 0,
fill: false,
tension: 0.1,
indicatorId: config.id,
yAxisID: 'rsi-axis'
});
// Add overbought line
priceChart.data.datasets.push({
label: 'Overbought',
data: data.map(item => ({
x: item.x,
y: overbought
})),
borderColor: 'rgba(255, 0, 0, 0.5)',
borderWidth: 1,
borderDash: [5, 5],
pointRadius: 0,
fill: false,
indicatorId: config.id,
yAxisID: 'rsi-axis'
});
// Add oversold line
priceChart.data.datasets.push({
label: 'Oversold',
data: data.map(item => ({
x: item.x,
y: oversold
})),
borderColor: 'rgba(0, 255, 0, 0.5)',
borderWidth: 1,
borderDash: [5, 5],
pointRadius: 0,
fill: false,
indicatorId: config.id,
yAxisID: 'rsi-axis'
});
// Add RSI scale if not already present
if (!priceChart.options.scales['rsi-axis']) {
priceChart.options.scales['rsi-axis'] = {
type: 'linear',
display: false,
min: 0,
max: 100,
grid: {
drawOnChartArea: false
}
};
}
break;
}
priceChart.update();
}
// Indicator calculations
function calculateSMA(data, period) {
const sma = [];
for (let i = 0; i < data.length; i++) {
if (i < period - 1) {
sma.push(null);
continue;
}
let sum = 0;
for (let j = 0; j < period; j++) {
sum += data[i - j].c;
}
sma.push(sum / period);
}
return sma;
}
function calculateEMA(data, period) {
const ema = [];
const multiplier = 2 / (period + 1);
// Start with SMA for the first value
let sum = 0;
for (let i = 0; i < period; i++) {
sum += data[i].c;
ema.push(null);
}
ema[period - 1] = sum / period;
// Calculate EMA for subsequent values
for (let i = period; i < data.length; i++) {
ema[i] = (data[i].c - ema[i - 1]) * multiplier + ema[i - 1];
}
return ema;
}
function calculateRSI(data, period) {
const rsi = [];
let avgGain = 0;
let avgLoss = 0;
// Initial calculations
for (let i = 1; i <= period; i++) {
const change = data[i].c - data[i - 1].c;
if (change > 0) {
avgGain += change;
} else {
avgLoss += Math.abs(change);
}
rsi.push(null);
}
avgGain /= period;
avgLoss /= period;
// First RSI value
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
rsi[period] = 100 - (100 / (1 + rs));
// Subsequent RSI values
for (let i = period + 1; i < data.length; i++) {
const change = data[i].c - data[i - 1].c;
let gain = 0;
let loss = 0;
if (change > 0) {
gain = change;
} else {
loss = Math.abs(change);
}
avgGain = (avgGain * (period - 1) + gain) / period;
avgLoss = (avgLoss * (period - 1) + loss) / period;
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
rsi[i] = 100 - (100 / (1 + rs));
}
return rsi;
}
// Zoom controls
document.getElementById('zoomInBtn').addEventListener('click', function() {
priceChart.zoom(1.1);
});
document.getElementById('zoomOutBtn').addEventListener('click', function() {
priceChart.zoom(0.9);
});
document.getElementById('zoomFitBtn').addEventListener('click', function() {
priceChart.resetZoom();
});
// Drawing tools
document.getElementById('drawLineBtn').addEventListener('click', function() {
activateDrawingTool('line');
});
document.getElementById('drawHorizontalBtn').addEventListener('click', function() {
activateDrawingTool('horizontal');
});
document.getElementById('clearDrawingsBtn').addEventListener('click', function() {
clearDrawings();
});
function activateDrawingTool(tool) {
// In a real implementation, you would set up event listeners
// for mouse events on the chart to draw the selected tool
alert(`Drawing tool activated: ${tool}. In a full implementation, you would be able to draw on the chart.`);
}
function clearDrawings() {
// Remove all drawings from the chart
drawings = [];
// In a real implementation, you would also clear the visual elements
alert('All drawings cleared. In a full implementation, the drawings would be removed from the chart.');
}
// Initialize the charts
initCharts();
});
</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=Sanzhar7/tr" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>