|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|