/** * charts.js — Graphiques Chart.js pour l'analyse technique. * - Graphique principal : cours de clôture + MM20/MM50/MM200 (toggle) * - Graphique volume : histogramme + moyenne 20j * - Graphique volatilité : courbe 20j * * Utilise un axe X catégoriel (dates en labels) pour éviter toute * dépendance à un adapter temporel. */ document.addEventListener("DOMContentLoaded", () => { const dataCoursEl = document.getElementById("data-cours"); const dataVolEl = document.getElementById("data-volumes"); const dataVolatEl = document.getElementById("data-volatilite"); if (!dataCoursEl) return; // pas de données (page d'accueil) let dataCours, dataVol, dataVolat; try { dataCours = JSON.parse(dataCoursEl.textContent); dataVol = JSON.parse(dataVolEl.textContent); dataVolat = JSON.parse(dataVolatEl.textContent); } catch (e) { console.error("Erreur parsing JSON graphiques:", e); return; } console.log("[charts] données chargées:", "cours=" + dataCours.dates.length + "pts", "vol=" + dataVol.volumes.length + "pts"); // --- Configuration globale Chart.js --- if (typeof Chart === "undefined") { console.error("[charts] Chart.js non chargé"); return; } Chart.defaults.color = "#9ca3af"; Chart.defaults.borderColor = "#1f2937"; Chart.defaults.font.family = "system-ui, sans-serif"; const labels = dataCours.dates; // ===================== GRAPHIQUE PRINCIPAL ===================== const datasetsCours = [ { label: "Cours", data: dataCours.close, borderColor: "#60a5fa", backgroundColor: "rgba(96, 165, 250, 0.1)", borderWidth: 1.5, pointRadius: 0, fill: true, tension: 0.1, }, ]; if (dataCours.MM20) { datasetsCours.push({ label: "MM20", data: dataCours.MM20, borderColor: "#3b82f6", borderWidth: 1.2, pointRadius: 0, fill: false, tension: 0.1, hidden: false, id: "mm20", }); } if (dataCours.MM50) { datasetsCours.push({ label: "MM50", data: dataCours.MM50, borderColor: "#10b981", borderWidth: 1.2, pointRadius: 0, fill: false, tension: 0.1, hidden: false, id: "mm50", }); } if (dataCours.MM200) { datasetsCours.push({ label: "MM200", data: dataCours.MM200, borderColor: "#f59e0b", borderWidth: 1.2, pointRadius: 0, fill: false, tension: 0.1, hidden: false, id: "mm200", }); } const ctxCours = document.getElementById("chartCours"); if (ctxCours) { const chartCours = new Chart(ctxCours, { type: "line", data: { labels: labels, datasets: datasetsCours }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: "index" }, scales: { x: { grid: { color: "#1f2937" }, ticks: { maxTicksLimit: 10, autoSkip: true }, }, y: { position: "right", grid: { color: "#1f2937" }, ticks: { callback: v => v.toFixed(2) }, }, }, plugins: { legend: { display: false }, tooltip: { backgroundColor: "#111827", borderColor: "#374151", borderWidth: 1, callbacks: { label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y !== null ? ctx.parsed.y.toFixed(2) : 'N/A'}` }, }, }, }, }); document.getElementById("toggle-mm20")?.addEventListener("change", e => { const ds = chartCours.data.datasets.find(d => d.id === "mm20"); if (ds) ds.hidden = !e.target.checked; chartCours.update(); }); document.getElementById("toggle-mm50")?.addEventListener("change", e => { const ds = chartCours.data.datasets.find(d => d.id === "mm50"); if (ds) ds.hidden = !e.target.checked; chartCours.update(); }); document.getElementById("toggle-mm200")?.addEventListener("change", e => { const ds = chartCours.data.datasets.find(d => d.id === "mm200"); if (ds) ds.hidden = !e.target.checked; chartCours.update(); }); } // ===================== GRAPHIQUE VOLUME ===================== const ctxVol = document.getElementById("chartVolume"); if (ctxVol) { const volColors = dataVol.volumes.map((v, i) => { const vm = dataVol.moyenne_20j[i]; return vm && v > vm * 2 ? "#f59e0b" : "#3b82f6"; }); new Chart(ctxVol, { type: "bar", data: { labels: dataVol.dates, datasets: [ { label: "Volume", data: dataVol.volumes, backgroundColor: volColors, borderWidth: 0, order: 2 }, { label: "Moyenne 20j", type: "line", data: dataVol.moyenne_20j, borderColor: "#10b981", borderWidth: 1.5, pointRadius: 0, fill: false, tension: 0.1, order: 1, }, ], }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: "index" }, scales: { x: { display: false }, y: { position: "right", grid: { color: "#1f2937" }, ticks: { callback: v => v >= 1e6 ? (v / 1e6).toFixed(1) + "M" : v >= 1e3 ? (v / 1e3).toFixed(0) + "K" : v }, }, }, plugins: { legend: { display: false }, tooltip: { backgroundColor: "#111827", borderColor: "#374151", borderWidth: 1, callbacks: { label: ctx => ctx.dataset.label + ": " + (ctx.parsed.y !== null ? ctx.parsed.y.toLocaleString("fr-FR") : "N/A") }, }, }, }, }); } // ===================== GRAPHIQUE VOLATILITÉ ===================== const ctxVolat = document.getElementById("chartVolatilite"); if (ctxVolat) { new Chart(ctxVolat, { type: "line", data: { labels: dataVolat.dates, datasets: [ { label: "Volatilité 20j", data: dataVolat.volatilite_20j, borderColor: "#f59e0b", backgroundColor: "rgba(245, 158, 11, 0.1)", borderWidth: 1.5, pointRadius: 0, fill: true, tension: 0.2, }, ], }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: "index" }, scales: { x: { grid: { color: "#1f2937" }, ticks: { maxTicksLimit: 10, autoSkip: true } }, y: { position: "right", grid: { color: "#1f2937" }, ticks: { callback: v => v.toFixed(1) + "%" } }, }, plugins: { legend: { display: false }, tooltip: { backgroundColor: "#111827", borderColor: "#374151", borderWidth: 1, callbacks: { label: ctx => "Volatilité: " + (ctx.parsed.y !== null ? ctx.parsed.y.toFixed(2) : "N/A") + "%" }, }, }, }, }); } console.log("[charts] graphiques initialisés"); });