// Generated, do not edit. sha256:eb749d1dca35ea9b39abff8cba70a365f5bed2b3d725946f3e7eb2d00e35dd60 import { esc } from './escape.js'; import { fmtPct } from './format.js'; let uid = 0; const VIEW_W = 100, VIEW_H = 100, PAD_X = 0, PAD_Y = 3; const DAY_MS = 86400000; const GRAD_FADE_AT = 0.85; const SNAP_PX = 10; export function labelLeft(x, labelW, chartW, gap = 4) { const half = labelW / 2 + gap; if (half * 2 >= chartW) return chartW / 2; return Math.min(chartW - half, Math.max(half, x)); } export function magnetischeIndex(punten, markeringen, fx, tolerantie) { let bestI = 0, bestD = Infinity; punten.forEach((p, i) => { const d = Math.abs(p.fx - fx); if (d < bestD) { bestD = d; bestI = i; } }); let markI = -1, markD = Infinity; for (const i of markeringen) { if (!punten[i]) continue; const d = Math.abs(punten[i].fx - fx); if (d < markD) { markD = d; markI = i; } } return (markI >= 0 && markD <= tolerantie) ? markI : bestI; } export function trapPunten(punten) { const uit = []; for (let i = 0; i < punten.length; i++) { if (i > 0 && Math.abs(punten[i - 1].py - punten[i].py) >= 0.01) { uit.push({ px: punten[i].px, py: punten[i - 1].py }); } uit.push(punten[i]); } return uit; } export function smoothPath(punten) { const p = punten; const n = p.length; if (!n) return ''; const f = v => v.toFixed(2); if (n < 3) return `M${f(p[0].px)},${f(p[0].py)}` + (n === 2 ? ` L${f(p[1].px)},${f(p[1].py)}` : ''); const h = [], s = []; for (let i = 0; i < n - 1; i++) { h.push(p[i + 1].px - p[i].px); s.push(h[i] ? (p[i + 1].py - p[i].py) / h[i] : 0); } const m = [s[0]]; for (let i = 1; i < n - 1; i++) { if (s[i - 1] * s[i] <= 0) { m.push(0); continue; } const w1 = 2 * h[i] + h[i - 1], w2 = h[i] + 2 * h[i - 1]; m.push((w1 + w2) / (w1 / s[i - 1] + w2 / s[i])); } m.push(s[n - 2]); let d = `M${f(p[0].px)},${f(p[0].py)}`; for (let i = 0; i < n - 1; i++) { const t = h[i] / 3; d += ` C${f(p[i].px + t)},${f(p[i].py + m[i] * t)} ${f(p[i + 1].px - t)},${f(p[i + 1].py - m[i + 1] * t)} ${f(p[i + 1].px)},${f(p[i + 1].py)}`; } return d; } export function mountSparkline(host, points, { tone = 'accent', formatValue = String, markers = [], vergelijk = null, snap = [], onPick = null, yRange = null, smooth = true } = {}) { host.innerHTML = ''; if (!points || points.length < 2) return; const tweede = Array.isArray(vergelijk) && vergelijk.length === points.length ? vergelijk : null; const ys = points.map(p => p[1]).concat(tweede ? tweede.map(p => p[1]) : []); const minX = points[0][0], maxX = points[points.length - 1][0]; const minY = yRange ? yRange[0] : Math.min(...ys), maxY = yRange ? yRange[1] : Math.max(...ys); const spanX = (maxX - minX) || 1, spanY = (maxY - minY) || 1; const pts = points.map(([x, y]) => { const px = PAD_X + ((x - minX) / spanX) * (VIEW_W - 2 * PAD_X); const py = VIEW_H - PAD_Y - ((y - minY) / spanY) * (VIEW_H - 2 * PAD_Y); return { fx: px / VIEW_W, fy: py / VIEW_H, px, py, price: y, x }; }); const cmpPts2 = tweede ? tweede.map(([x, y]) => { const px = PAD_X + ((x - minX) / spanX) * (VIEW_W - 2 * PAD_X); const py = VIEW_H - PAD_Y - ((y - minY) / spanY) * (VIEW_H - 2 * PAD_Y); return { fx: px / VIEW_W, fy: py / VIEW_H, px, py }; }) : null; const markPts = cmpPts2 || pts; const markIdx = []; const gezien = new Set(); let markHtml = ''; for (const m of markers) { const n = dichtstbij(pts, m?.x); const variant = String(m?.variant || '').replace(/[^a-z0-9-]/gi, ''); const label = m?.label == null ? '' : String(m.label); const key = m?.key == null ? '' : String(m.key).replace(/[^a-z0-9_:-]/gi, ''); const sleutel = `${variant}|${n}`; if (n < 0 || gezien.has(sleutel)) continue; gezien.add(sleutel); markIdx.push(n); markHtml += `
`; } for (const x of snap) { const n = dichtstbij(pts, x); if (n >= 0) markIdx.push(n); } const linePts = pts.map(p => `${p.px.toFixed(2)},${p.py.toFixed(2)}`).join(' '); const cmpPts = cmpPts2 ? trapPunten(cmpPts2).map(p => `${p.px.toFixed(2)},${p.py.toFixed(2)}`).join(' ') : ''; const areaPts = `${pts[0].px.toFixed(2)},${VIEW_H} ${linePts} ${pts[pts.length - 1].px.toFixed(2)},${VIEW_H}`; const gid = `spk-grad-${++uid}`; const curve = smooth ? smoothPath(pts) : ''; const lineAttrs = `fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" vector-effect="non-scaling-stroke"`; const areaSvg = smooth ? `` : ``; const lineSvg = smooth ? `` : ``; const root = document.createElement('div'); root.className = `spark-mini is-${tone}${onPick ? ' is-pickable' : ''}`; root.innerHTML = `
${markHtml}
`; host.appendChild(root); const vline = root.querySelector('.spark-mini-vline'); const dot = root.querySelector('.spark-mini-dot'); const label = root.querySelector('.spark-mini-label'); function indexAt(e) { const rect = root.getBoundingClientRect(); if (!rect.width) return -1; const fx = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width)); return magnetischeIndex(pts, markIdx, fx, SNAP_PX / rect.width); } function show(e) { const bestI = indexAt(e); if (bestI < 0) return; const best = pts[bestI]; const leftPct = best.fx * 100, topPct = best.fy * 100; vline.style.left = `${leftPct}%`; dot.style.setProperty('--spark-x', `${leftPct}%`); dot.style.top = `${topPct}%`; label.textContent = formatValue(best.price, best.x, tweede ? tweede[bestI]?.[1] : undefined); label.classList.toggle('is-below', best.fy < 0.5); vline.hidden = dot.hidden = label.hidden = false; const rw = root.clientWidth; label.style.left = rw ? `${labelLeft(best.fx * rw, label.offsetWidth, rw)}px` : `${Math.min(88, Math.max(12, leftPct))}%`; label.style.top = `${topPct}%`; } function hide() { vline.hidden = dot.hidden = label.hidden = true; } root.addEventListener('pointermove', show); root.addEventListener('pointerdown', show); root.addEventListener('pointerleave', hide); root.addEventListener('pointercancel', hide); if (onPick) root.addEventListener('click', e => { const i = indexAt(e); if (i >= 0) onPick(pts[i].x, i); }); } function dichtstbij(pts, x) { let best = -1, bestD = Infinity; for (let i = 0; i < pts.length; i++) { const d = Math.abs(pts[i].x - x); if (d < bestD) { bestD = d; best = i; } } return best; } export function withSpot(points, price, nowMs = Date.now()) { if (!Array.isArray(points) || !points.length) return points; if (!Number.isFinite(price) || price <= 0) return points; const vandaag = Math.floor(nowMs / DAY_MS) * DAY_MS; const laatste = points[points.length - 1][0]; if (laatste > vandaag) return points; if (laatste === vandaag) return [...points.slice(0, -1), [vandaag, price]]; return [...points, [vandaag, price]]; } export const sliceTone = slice => (slice[slice.length - 1][1] >= slice[0][1] ? 'up' : 'down'); export function changePctHtml(slice, suffixHtml = '', { fine = false, signs = 'negative' } = {}) { if (!slice || slice.length < 2) return ''; const first = slice[0][1], last = slice[slice.length - 1][1]; if (!(first > 0)) return ''; const change = (last - first) / first; return `${fmtPct(change, { fine, signs })}${suffixHtml}`; }