| 1 |
(function () { |
| 2 |
var CDN = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js'; |
| 3 |
|
| 4 |
function loadChart(cb) { |
| 5 |
if (window.Chart) { cb(window.Chart); return; } |
| 6 |
var s = document.createElement('script'); |
| 7 |
s.src = CDN; |
| 8 |
s.onload = function () { cb(window.Chart); }; |
| 9 |
document.head.appendChild(s); |
| 10 |
} |
| 11 |
|
| 12 |
function parsePoints(text) { |
| 13 |
if (!text) return []; |
| 14 |
return text.trim().split('\n').map(function (line) { |
| 15 |
var parts = line.split(','); |
| 16 |
if (parts.length < 2) return null; |
| 17 |
var x = parseFloat(parts[0].trim()); |
| 18 |
var y = parseFloat(parts[1].trim()); |
| 19 |
if (isNaN(x) || isNaN(y)) return null; |
| 20 |
return { x: x, y: y }; |
| 21 |
}).filter(Boolean); |
| 22 |
} |
| 23 |
|
| 24 |
function hexToRgba(hex, alpha) { |
| 25 |
if (!hex) return 'rgba(108,63,181,' + alpha + ')'; |
| 26 |
var c = hex.replace('#', ''); |
| 27 |
if (c.length === 3) c = c[0]+c[0]+c[1]+c[1]+c[2]+c[2]; |
| 28 |
var r = parseInt(c.substr(0,2),16), g = parseInt(c.substr(2,2),16), b = parseInt(c.substr(4,2),16); |
| 29 |
return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; |
| 30 |
} |
| 31 |
|
| 32 |
function initChart(card) { |
| 33 |
if (card.dataset.initialized) return; |
| 34 |
card.dataset.initialized = '1'; |
| 35 |
|
| 36 |
var rawData = card.getAttribute('data-chart'); |
| 37 |
var height = parseInt(card.getAttribute('data-height') || '400', 10); |
| 38 |
if (!rawData) return; |
| 39 |
|
| 40 |
var opts; |
| 41 |
try { opts = JSON.parse(rawData); } catch (e) { return; } |
| 42 |
|
| 43 |
var canvas = card.querySelector('.bkbg-sc-canvas'); |
| 44 |
if (!canvas) { canvas = document.createElement('canvas'); card.appendChild(canvas); } |
| 45 |
canvas.height = height; |
| 46 |
|
| 47 |
loadChart(function (ChartJS) { |
| 48 |
var datasets = []; |
| 49 |
try { |
| 50 |
var raw = JSON.parse(opts.datasetsJson || '[]'); |
| 51 |
raw.forEach(function (ds) { |
| 52 |
var pts = parsePoints(ds.points); |
| 53 |
var alpha = (opts.fillAlpha || 85) / 100; |
| 54 |
datasets.push({ |
| 55 |
label: ds.label, |
| 56 |
data: pts, |
| 57 |
backgroundColor: hexToRgba(ds.color, alpha), |
| 58 |
borderColor: ds.color || '#6c3fb5', |
| 59 |
pointRadius: opts.pointSize || 7, |
| 60 |
pointHoverRadius: (opts.pointSize || 7) + 2, |
| 61 |
pointStyle: opts.pointStyle || 'circle', |
| 62 |
}); |
| 63 |
}); |
| 64 |
} catch (e) {} |
| 65 |
|
| 66 |
var scaleX = { type: 'linear', title: { display: !!opts.xLabel, text: opts.xLabel || '' }, grid: { display: !!opts.showGrid } }; |
| 67 |
var scaleY = { title: { display: !!opts.yLabel, text: opts.yLabel || '' }, grid: { display: !!opts.showGrid } }; |
| 68 |
if (opts.xMin !== undefined && opts.xMin !== '') scaleX.min = parseFloat(opts.xMin); |
| 69 |
if (opts.xMax !== undefined && opts.xMax !== '') scaleX.max = parseFloat(opts.xMax); |
| 70 |
if (opts.yMin !== undefined && opts.yMin !== '') scaleY.min = parseFloat(opts.yMin); |
| 71 |
if (opts.yMax !== undefined && opts.yMax !== '') scaleY.max = parseFloat(opts.yMax); |
| 72 |
|
| 73 |
new ChartJS(canvas, { |
| 74 |
type: 'scatter', |
| 75 |
data: { datasets: datasets }, |
| 76 |
options: { |
| 77 |
responsive: true, |
| 78 |
animation: { duration: opts.animate ? 800 : 0 }, |
| 79 |
plugins: { |
| 80 |
legend: { display: !!opts.showLegend, position: opts.legendPos || 'bottom' }, |
| 81 |
}, |
| 82 |
scales: { x: scaleX, y: scaleY }, |
| 83 |
} |
| 84 |
}); |
| 85 |
}); |
| 86 |
} |
| 87 |
|
| 88 |
function init() { |
| 89 |
var cards = document.querySelectorAll('.bkbg-sc-card'); |
| 90 |
if (!cards.length) return; |
| 91 |
if ('IntersectionObserver' in window) { |
| 92 |
var obs = new IntersectionObserver(function (entries) { |
| 93 |
entries.forEach(function (e) { if (e.isIntersecting) { initChart(e.target); obs.unobserve(e.target); } }); |
| 94 |
}, { threshold: 0.15 }); |
| 95 |
cards.forEach(function (card) { obs.observe(card); }); |
| 96 |
} else { |
| 97 |
cards.forEach(initChart); |
| 98 |
} |
| 99 |
} |
| 100 |
|
| 101 |
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } |
| 102 |
}()); |
| 103 |
|