Files
Binance_Altcoin_Monitor/web/charts.js
T
2026-05-22 13:47:27 +08:00

172 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 迷你日 K 线图(Canvas + 限速队列 */
const chartDataCache = new Map();
const chartQueue = [];
let chartQueueRunning = false;
const CHART_FETCH_GAP_MS = 120;
function enqueueCharts(root) {
root.querySelectorAll(".mini-chart[data-symbol]").forEach((box) => {
const symbol = box.dataset.symbol;
if (!symbol || box.dataset.loaded === "1" || box.dataset.loading === "1") return;
chartQueue.push(box);
});
runChartQueue();
}
async function runChartQueue() {
if (chartQueueRunning) return;
chartQueueRunning = true;
while (chartQueue.length) {
const box = chartQueue.shift();
if (!box || !box.isConnected) continue;
await loadMiniChart(box);
await sleep(CHART_FETCH_GAP_MS);
}
chartQueueRunning = false;
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function loadMiniChart(box) {
const symbol = box.dataset.symbol;
if (!symbol) return;
box.dataset.loading = "1";
const canvas = box.querySelector("canvas");
const status = box.querySelector(".chart-status");
if (status) status.textContent = "加载…";
try {
let candles = chartDataCache.get(symbol);
let source = "cache";
if (!candles) {
const res = await fetch(`/api/chart/${symbol}/daily?limit=300`);
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.detail || res.statusText);
}
const data = await res.json();
candles = data.candles || [];
source = data.source || "db";
chartDataCache.set(symbol, candles);
}
if (!candles.length) throw new Error("无K线数据");
drawCandlestickChart(canvas, candles);
box.dataset.loaded = "1";
const srcLabel =
source === "db" ? "本地" : source === "db_stale" ? "本地(旧)" : source === "cache" ? "缓存" : "同步";
if (status) status.textContent = `${candles.length}日·${srcLabel}`;
box.title = `${symbol} 最近${candles.length}根日K (${srcLabel})`;
} catch (e) {
if (status) status.textContent = "—";
box.title = `${symbol}: ${e.message}`;
drawEmptyChart(canvas);
} finally {
box.dataset.loading = "0";
}
}
function drawEmptyChart(canvas) {
if (!canvas) return;
const ctx = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#3a4558";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "#8b9cb3";
ctx.font = "11px sans-serif";
ctx.fillText("暂无", 8, h / 2 + 4);
}
function drawCandlestickChart(canvas, candles) {
if (!canvas || !candles.length) return;
const ctx = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;
const pad = { t: 4, r: 4, b: 4, l: 4 };
const plotW = w - pad.l - pad.r;
const plotH = h - pad.t - pad.b;
let min = Infinity;
let max = -Infinity;
for (const c of candles) {
min = Math.min(min, c.low);
max = Math.max(max, c.high);
}
const range = max - min || 1;
const n = candles.length;
const step = plotW / n;
const bodyW = Math.max(1, step * 0.65);
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#121820";
ctx.fillRect(0, 0, w, h);
const yOf = (price) => pad.t + plotH * (1 - (price - min) / range);
for (let i = 0; i < n; i++) {
const c = candles[i];
const up = c.close >= c.open;
const x = pad.l + i * step + step / 2;
const yHigh = yOf(c.high);
const yLow = yOf(c.low);
const yOpen = yOf(c.open);
const yClose = yOf(c.close);
const color = up ? "#0ecb81" : "#f6465d";
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, yHigh);
ctx.lineTo(x, yLow);
ctx.stroke();
const top = Math.min(yOpen, yClose);
const bodyH = Math.max(1, Math.abs(yClose - yOpen));
ctx.fillStyle = color;
ctx.fillRect(x - bodyW / 2, top, bodyW, bodyH);
}
}
/** 点击放大 */
function setupChartModal() {
let modal = document.getElementById("chart-modal");
if (!modal) {
modal = document.createElement("div");
modal.id = "chart-modal";
modal.className = "chart-modal hidden";
modal.innerHTML = `
<div class="chart-modal-inner">
<button type="button" class="chart-modal-close" aria-label="关闭">×</button>
<h3 id="chart-modal-title"></h3>
<canvas id="chart-modal-canvas" width="900" height="360"></canvas>
</div>`;
document.body.appendChild(modal);
modal.querySelector(".chart-modal-close").onclick = () =>
modal.classList.add("hidden");
modal.addEventListener("click", (e) => {
if (e.target === modal) modal.classList.add("hidden");
});
}
document.body.addEventListener("click", (e) => {
const box = e.target.closest(".mini-chart[data-symbol]");
if (!box || box.dataset.loaded !== "1") return;
const symbol = box.dataset.symbol;
const candles = chartDataCache.get(symbol);
if (!candles) return;
modal.classList.remove("hidden");
document.getElementById("chart-modal-title").textContent =
`${symbol} · 日K ${candles.length}`;
drawCandlestickChart(
document.getElementById("chart-modal-canvas"),
candles
);
});
}
setupChartModal();