Files
Retribusi/public/dashboard/js/charts.js

259 lines
7.1 KiB
JavaScript
Raw Permalink Normal View History

// public/dashboard/js/charts.js
// Chart.js helpers: create & update charts without recreating canvases.
let dailyLineChart = null;
let categoryChart = null;
// Export chart instances untuk akses dari dashboard.js
export function getDailyChart() {
return dailyLineChart;
}
export function getCategoryChart() {
return categoryChart;
}
export function initDailyChart(ctx) {
if (dailyLineChart) return dailyLineChart;
dailyLineChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'Jumlah',
data: [],
borderColor: '#111827',
backgroundColor: 'rgba(17, 24, 39, 0.06)',
borderWidth: 2,
tension: 0.35,
fill: true,
pointRadius: 2.5,
pointBackgroundColor: '#111827'
},
{
label: 'Pendapatan',
data: [],
borderColor: '#6b7280',
backgroundColor: 'rgba(156, 163, 175, 0.08)',
borderWidth: 2,
tension: 0.35,
fill: true,
pointRadius: 2.5,
pointBackgroundColor: '#6b7280',
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 1500,
easing: 'linear',
x: {
duration: 0 // Tidak ada animasi horizontal (kanan ke kiri)
},
y: {
type: 'number',
easing: 'linear',
duration: 1500,
from: (ctx) => {
// Animasi naik dari bawah (0) untuk efek naik turun yang smooth
try {
const chart = ctx.chart;
if (chart && chart.scales && chart.scales.y) {
return chart.scales.y.getPixelForValue(0);
}
} catch (e) {
console.warn('[Charts] Error getting scale for animation:', e);
}
return 0;
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
y: {
beginAtZero: true,
grid: { color: '#e5e7eb' },
ticks: { font: { size: 11 } }
},
y1: {
beginAtZero: true,
position: 'right',
grid: { drawOnChartArea: false },
ticks: {
font: { size: 11 },
callback: value => 'Rp ' + new Intl.NumberFormat('id-ID').format(value)
}
},
x: {
grid: { display: false },
ticks: { font: { size: 11 } }
}
},
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 14,
boxHeight: 4
}
}
}
}
});
return dailyLineChart;
}
export function updateDailyChart({ labels, counts, amounts }) {
if (!dailyLineChart) {
console.warn('[Charts] Daily chart belum di-init, skip update');
// Try to init if canvas exists
const canvas = document.getElementById('daily-chart');
if (canvas) {
console.log('[Charts] Attempting to init daily chart from update function...');
initDailyChart(canvas.getContext('2d'));
} else {
console.error('[Charts] Daily chart canvas not found!');
return;
}
}
if (!dailyLineChart) {
console.error('[Charts] Failed to init daily chart');
return;
}
// Pastikan data arrays memiliki length yang sama
const safeLabels = labels || [];
const safeCounts = counts || [];
const safeAmounts = amounts || [];
// Pastikan semua array memiliki length yang sama (24 jam)
const maxLength = Math.max(safeLabels.length, safeCounts.length, safeAmounts.length, 24);
const finalLabels = Array.from({ length: maxLength }, (_, i) => {
if (i < safeLabels.length) return safeLabels[i];
return `${String(i).padStart(2, '0')}:00`;
});
const finalCounts = Array.from({ length: maxLength }, (_, i) => {
if (i < safeCounts.length) return Number(safeCounts[i]) || 0;
return 0;
});
const finalAmounts = Array.from({ length: maxLength }, (_, i) => {
if (i < safeAmounts.length) return Number(safeAmounts[i]) || 0;
return 0;
});
// Update chart data
dailyLineChart.data.labels = finalLabels;
dailyLineChart.data.datasets[0].data = finalCounts;
dailyLineChart.data.datasets[1].data = finalAmounts;
// Update chart dengan animasi naik turun yang smooth
dailyLineChart.update('active');
console.log('[Charts] Daily chart updated:', {
labelsCount: finalLabels.length,
countsCount: finalCounts.length,
amountsCount: finalAmounts.length,
totalCount: finalCounts.reduce((a, b) => a + b, 0),
totalAmount: finalAmounts.reduce((a, b) => a + b, 0)
});
}
export function initCategoryChart(ctx) {
if (categoryChart) return categoryChart;
categoryChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: [],
datasets: [
{
label: 'Per Kategori',
data: [],
backgroundColor: ['#111827', '#4b5563', '#9ca3af'],
borderWidth: 0
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '60%',
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 14,
boxHeight: 6
}
}
}
}
});
return categoryChart;
}
export function updateCategoryChart({ labels, values }) {
if (!categoryChart) {
console.warn('[Charts] Category chart belum di-init, skip update');
// Try to init if canvas exists
const canvas = document.getElementById('category-chart');
if (canvas) {
console.log('[Charts] Attempting to init category chart from update function...');
initCategoryChart(canvas.getContext('2d'));
} else {
console.error('[Charts] Category chart canvas not found!');
return;
}
}
if (!categoryChart) {
console.error('[Charts] Failed to init category chart');
return;
}
// Pastikan labels dan values tidak kosong
// Default categories: Orang, Motor, Mobil
const defaultLabels = ['Orang', 'Motor', 'Mobil'];
const defaultValues = [0, 0, 0];
const finalLabels = labels && labels.length > 0 ? labels : defaultLabels;
const finalValues = values && values.length > 0 ? values : defaultValues;
// Pastikan length sama (minimal 3 untuk 3 kategori)
const maxLength = Math.max(finalLabels.length, finalValues.length, 3);
const safeLabels = Array.from({ length: maxLength }, (_, i) => {
if (i < finalLabels.length) return finalLabels[i];
return defaultLabels[i] || `Kategori ${i + 1}`;
});
const safeValues = Array.from({ length: maxLength }, (_, i) => {
if (i < finalValues.length) return Number(finalValues[i]) || 0;
return 0;
});
// Update chart data
categoryChart.data.labels = safeLabels;
categoryChart.data.datasets[0].data = safeValues;
// Update chart dengan mode 'none' untuk menghindari animasi yang tidak perlu
categoryChart.update('active');
console.log('[Charts] Category chart updated:', {
labels: safeLabels,
values: safeValues,
total: safeValues.reduce((a, b) => a + b, 0)
});
}