|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class ChartComponent { |
|
|
constructor(canvasId, type = 'line', options = {}) { |
|
|
this.canvasId = canvasId; |
|
|
this.canvas = document.getElementById(canvasId); |
|
|
this.type = type; |
|
|
this.options = options; |
|
|
this.chart = null; |
|
|
|
|
|
if (!this.canvas) { |
|
|
console.error(`[Chart] Canvas not found: ${canvasId}`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async create(data, customOptions = {}) { |
|
|
if (!this.canvas) return; |
|
|
|
|
|
|
|
|
if (typeof Chart === 'undefined') { |
|
|
console.error('[Chart] Chart.js not loaded'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
this.destroy(); |
|
|
|
|
|
const config = { |
|
|
type: this.type, |
|
|
data: data, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
...this.getDefaultOptions(this.type), |
|
|
...this.options, |
|
|
...customOptions, |
|
|
}, |
|
|
}; |
|
|
|
|
|
this.chart = new Chart(this.canvas, config); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
update(data) { |
|
|
if (!this.chart) { |
|
|
console.warn('[Chart] Chart not initialized'); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.chart.data = data; |
|
|
this.chart.update(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
destroy() { |
|
|
if (this.chart) { |
|
|
this.chart.destroy(); |
|
|
this.chart = null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getDefaultOptions(type) { |
|
|
const common = { |
|
|
plugins: { |
|
|
legend: { |
|
|
display: true, |
|
|
position: 'top', |
|
|
labels: { |
|
|
color: 'var(--text-normal)', |
|
|
font: { |
|
|
family: 'var(--font-family-base)', |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
tooltip: { |
|
|
backgroundColor: 'var(--surface-glass)', |
|
|
titleColor: 'var(--text-strong)', |
|
|
bodyColor: 'var(--text-normal)', |
|
|
borderColor: 'var(--border-default)', |
|
|
borderWidth: 1, |
|
|
}, |
|
|
}, |
|
|
}; |
|
|
|
|
|
const typeDefaults = { |
|
|
line: { |
|
|
scales: { |
|
|
x: { |
|
|
grid: { |
|
|
color: 'var(--border-subtle)', |
|
|
}, |
|
|
ticks: { |
|
|
color: 'var(--text-soft)', |
|
|
}, |
|
|
}, |
|
|
y: { |
|
|
grid: { |
|
|
color: 'var(--border-subtle)', |
|
|
}, |
|
|
ticks: { |
|
|
color: 'var(--text-soft)', |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
bar: { |
|
|
scales: { |
|
|
x: { |
|
|
grid: { |
|
|
display: false, |
|
|
}, |
|
|
ticks: { |
|
|
color: 'var(--text-soft)', |
|
|
}, |
|
|
}, |
|
|
y: { |
|
|
grid: { |
|
|
color: 'var(--border-subtle)', |
|
|
}, |
|
|
ticks: { |
|
|
color: 'var(--text-soft)', |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
doughnut: { |
|
|
plugins: { |
|
|
legend: { |
|
|
position: 'right', |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}; |
|
|
|
|
|
return { |
|
|
...common, |
|
|
...(typeDefaults[type] || {}), |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function loadChartJS() { |
|
|
if (typeof Chart !== 'undefined') { |
|
|
return Promise.resolve(); |
|
|
} |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
const script = document.createElement('script'); |
|
|
script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js'; |
|
|
script.onload = () => { |
|
|
console.log('[Chart] Chart.js loaded from CDN'); |
|
|
resolve(); |
|
|
}; |
|
|
script.onerror = () => { |
|
|
console.error('[Chart] Failed to load Chart.js'); |
|
|
reject(new Error('Failed to load Chart.js')); |
|
|
}; |
|
|
document.head.appendChild(script); |
|
|
}); |
|
|
} |
|
|
|
|
|
export default ChartComponent; |
|
|
|