/**
|
|
* Modern Chart Component
|
|
* Replaces jQuery Sparkline with Chart.js
|
|
*/
|
|
|
|
import { Chart, registerables } from 'chart.js';
|
|
import { COLORS } from '../constants/colors';
|
|
|
|
// Register Chart.js components
|
|
Chart.register(...registerables);
|
|
|
|
class ChartComponent {
|
|
constructor() {
|
|
this.charts = new Map(); // Store chart instances
|
|
this.debounceTimer = null;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// Only disable resizing for small sparkline charts
|
|
this.createSparklines();
|
|
this.createOtherCharts();
|
|
this.setupResizeHandler();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Create sparklines (only for dashboard page)
|
|
*/
|
|
createSparklines() {
|
|
// Only create sparklines if we're on a page that has them
|
|
const sparklineExists = document.getElementById('sparklinedash');
|
|
if (!sparklineExists) {
|
|
console.log('Skipping sparklines - not on dashboard page');
|
|
return;
|
|
}
|
|
|
|
const sparklineConfigs = [
|
|
{
|
|
id: 'sparklinedash',
|
|
data: [0, 5, 6, 10, 9, 12, 4, 9],
|
|
color: '#4caf50'
|
|
},
|
|
{
|
|
id: 'sparklinedash2',
|
|
data: [0, 5, 6, 10, 9, 12, 4, 9],
|
|
color: '#9675ce'
|
|
},
|
|
{
|
|
id: 'sparklinedash3',
|
|
data: [0, 5, 6, 10, 9, 12, 4, 9],
|
|
color: '#03a9f3'
|
|
},
|
|
{
|
|
id: 'sparklinedash4',
|
|
data: [0, 5, 6, 10, 9, 12, 4, 9],
|
|
color: '#f96262'
|
|
}
|
|
];
|
|
|
|
sparklineConfigs.forEach(config => {
|
|
// Only create if the target element exists
|
|
if (document.getElementById(config.id)) {
|
|
this.createSparklineChart(config);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create sparkline chart from configuration
|
|
*/
|
|
createSparklineChart({ id, data, color }) {
|
|
let canvas = document.getElementById(id);
|
|
|
|
// Only proceed if we have a valid target element
|
|
if (!canvas) {
|
|
console.log(`Skipping chart ${id} - target element not found`);
|
|
return;
|
|
}
|
|
|
|
// If element exists but isn't a canvas, replace it with canvas
|
|
if (canvas.tagName !== 'CANVAS') {
|
|
const parent = canvas.parentNode;
|
|
if (!parent) {
|
|
console.log(`Skipping chart ${id} - no parent element`);
|
|
return;
|
|
}
|
|
|
|
// Create new canvas element
|
|
const newCanvas = document.createElement('canvas');
|
|
newCanvas.id = id;
|
|
newCanvas.width = 100;
|
|
newCanvas.height = 20;
|
|
newCanvas.style.width = '100px';
|
|
newCanvas.style.height = '20px';
|
|
|
|
// Replace the span with canvas
|
|
parent.replaceChild(newCanvas, canvas);
|
|
canvas = newCanvas;
|
|
} else {
|
|
// Set canvas dimensions to match original sparkline
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: data.map((_, i) => i),
|
|
datasets: [{
|
|
data: data,
|
|
backgroundColor: color,
|
|
borderColor: color,
|
|
borderWidth: 0,
|
|
barPercentage: 0.6,
|
|
categoryPercentage: 0.8
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
animation: false,
|
|
events: [],
|
|
onResize: null,
|
|
scales: {
|
|
x: {
|
|
display: false
|
|
},
|
|
y: {
|
|
display: false
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
enabled: false
|
|
}
|
|
},
|
|
elements: {
|
|
bar: {
|
|
borderRadius: 1
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create other chart types (only if they exist on the page)
|
|
*/
|
|
createOtherCharts() {
|
|
// Determine if we're on the dashboard or charts page
|
|
const isChartsPage = document.getElementById('area-chart') !== null;
|
|
const isDashboard = !isChartsPage && document.getElementById('line-chart') !== null;
|
|
|
|
// Create Monthly Stats chart with enhanced dual-line data (dashboard only)
|
|
if (isDashboard) {
|
|
this.createMonthlyStatsChart();
|
|
}
|
|
|
|
// Charts page specific charts (only on charts page)
|
|
if (isChartsPage) {
|
|
this.createChartsPageCharts();
|
|
}
|
|
|
|
// Only create charts if their target elements exist
|
|
if (document.getElementById('sparkline')) {
|
|
this.createLineChart('sparkline', [5, 6, 7, 9, 9, 5, 3, 2, 2, 4, 6, 7]);
|
|
}
|
|
|
|
if (document.getElementById('compositebar')) {
|
|
this.createCompositeChart('compositebar', [4, 1, 5, 7, 9, 9, 8, 7, 6, 6, 4, 7, 8, 4, 3, 2, 2, 5, 6, 7]);
|
|
}
|
|
|
|
// Regular sparklines with custom colors (only on pages that have them)
|
|
this.createCustomSparklines();
|
|
|
|
// Easy Pie Charts (only if they exist)
|
|
this.createEasyPieCharts();
|
|
}
|
|
|
|
/**
|
|
* Create enhanced Monthly Stats chart with dual lines and more data
|
|
*/
|
|
createMonthlyStatsChart() {
|
|
const canvas = document.getElementById('line-chart');
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Enhanced data for monthly stats
|
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
const salesData = [120, 135, 145, 165, 180, 195, 210, 225, 240, 220, 200, 185];
|
|
const profitData = [45, 52, 58, 62, 68, 75, 82, 88, 92, 85, 78, 72];
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: months,
|
|
datasets: [
|
|
{
|
|
label: 'Sales ($K)',
|
|
data: salesData,
|
|
borderColor: '#4caf50',
|
|
backgroundColor: 'rgba(76, 175, 80, 0.1)',
|
|
borderWidth: 3,
|
|
pointRadius: 5,
|
|
pointHoverRadius: 7,
|
|
pointBackgroundColor: '#4caf50',
|
|
pointBorderColor: '#ffffff',
|
|
pointBorderWidth: 2,
|
|
tension: 0.4,
|
|
fill: false
|
|
},
|
|
{
|
|
label: 'Profit ($K)',
|
|
data: profitData,
|
|
borderColor: '#2196f3',
|
|
backgroundColor: 'rgba(33, 150, 243, 0.1)',
|
|
borderWidth: 3,
|
|
pointRadius: 5,
|
|
pointHoverRadius: 7,
|
|
pointBackgroundColor: '#2196f3',
|
|
pointBorderColor: '#ffffff',
|
|
pointBorderWidth: 2,
|
|
tension: 0.4,
|
|
fill: false
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top',
|
|
labels: {
|
|
padding: 20,
|
|
font: {
|
|
size: 12,
|
|
weight: '600'
|
|
},
|
|
color: '#333'
|
|
}
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
titleColor: '#333',
|
|
bodyColor: '#666',
|
|
borderColor: '#ddd',
|
|
borderWidth: 1,
|
|
cornerRadius: 8,
|
|
displayColors: true,
|
|
intersect: false,
|
|
mode: 'index',
|
|
callbacks: {
|
|
label: function(context) {
|
|
return context.dataset.label + ': $' + context.parsed.y + 'K';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
display: false
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
borderDash: [5, 5]
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
},
|
|
callback: function(value) {
|
|
return '$' + value + 'K';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set('line-chart', chart);
|
|
}
|
|
|
|
/**
|
|
* Create line chart (only if target exists)
|
|
*/
|
|
createLineChart(id, data) {
|
|
let canvas = document.getElementById(id);
|
|
|
|
// Only proceed if target element exists
|
|
if (!canvas) {
|
|
console.log(`Skipping line chart ${id} - target element not found`);
|
|
return;
|
|
}
|
|
|
|
// If element exists but isn't a canvas, replace it with canvas
|
|
if (canvas.tagName !== 'CANVAS') {
|
|
const parent = canvas.parentNode;
|
|
if (!parent) {
|
|
console.log(`Skipping line chart ${id} - no parent element`);
|
|
return;
|
|
}
|
|
|
|
// Create new canvas element
|
|
const newCanvas = document.createElement('canvas');
|
|
newCanvas.id = id;
|
|
newCanvas.width = 100;
|
|
newCanvas.height = 20;
|
|
newCanvas.style.width = '100px';
|
|
newCanvas.style.height = '20px';
|
|
|
|
// Replace element with canvas
|
|
parent.replaceChild(newCanvas, canvas);
|
|
canvas = newCanvas;
|
|
} else {
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.map((_, i) => i),
|
|
datasets: [{
|
|
data: data,
|
|
borderColor: COLORS['blue-500'],
|
|
backgroundColor: 'transparent',
|
|
borderWidth: 1,
|
|
pointRadius: 0,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
animation: false,
|
|
events: [],
|
|
onResize: null,
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: { enabled: false }
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create composite chart (only if target exists)
|
|
*/
|
|
createCompositeChart(id, data) {
|
|
let canvas = document.getElementById(id);
|
|
|
|
// Only proceed if target element exists
|
|
if (!canvas) {
|
|
console.log(`Skipping composite chart ${id} - target element not found`);
|
|
return;
|
|
}
|
|
|
|
// If element exists but isn't a canvas, replace it with canvas
|
|
if (canvas.tagName !== 'CANVAS') {
|
|
const parent = canvas.parentNode;
|
|
if (!parent) {
|
|
console.log(`Skipping composite chart ${id} - no parent element`);
|
|
return;
|
|
}
|
|
|
|
// Create new canvas element
|
|
const newCanvas = document.createElement('canvas');
|
|
newCanvas.id = id;
|
|
newCanvas.width = 100;
|
|
newCanvas.height = 20;
|
|
newCanvas.style.width = '100px';
|
|
newCanvas.style.height = '20px';
|
|
|
|
// Replace element with canvas
|
|
parent.replaceChild(newCanvas, canvas);
|
|
canvas = newCanvas;
|
|
} else {
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: data.map((_, i) => i),
|
|
datasets: [
|
|
{
|
|
type: 'bar',
|
|
data: data,
|
|
backgroundColor: '#aaf',
|
|
borderColor: '#aaf',
|
|
borderWidth: 0
|
|
},
|
|
{
|
|
type: 'line',
|
|
data: data,
|
|
borderColor: 'red',
|
|
backgroundColor: 'transparent',
|
|
borderWidth: 1,
|
|
pointRadius: 0,
|
|
tension: 0.4
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
animation: false,
|
|
events: [],
|
|
onResize: null,
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: { enabled: false }
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create custom sparklines for different elements (only if they exist)
|
|
*/
|
|
createCustomSparklines() {
|
|
const sparklineElements = document.querySelectorAll('.sparkline');
|
|
const sparkbarElements = document.querySelectorAll('.sparkbar');
|
|
const sparktriElements = document.querySelectorAll('.sparktri');
|
|
const sparkdiscElements = document.querySelectorAll('.sparkdisc');
|
|
const sparkbullElements = document.querySelectorAll('.sparkbull');
|
|
const sparkboxElements = document.querySelectorAll('.sparkbox');
|
|
|
|
// Only create if we have elements
|
|
if (sparklineElements.length === 0 && sparkbarElements.length === 0 &&
|
|
sparktriElements.length === 0 && sparkdiscElements.length === 0 &&
|
|
sparkbullElements.length === 0 && sparkboxElements.length === 0) {
|
|
console.log('Skipping custom sparklines - no sparkline elements found');
|
|
return;
|
|
}
|
|
|
|
const values = [5, 4, 5, -2, 0, 3, -5, 6, 7, 9, 9, 5, -3, -2, 2, -4];
|
|
const valuesAlt = [1, 1, 0, 1, -1, -1, 1, -1, 0, 0, 1, 1];
|
|
|
|
sparklineElements.forEach((element, index) => {
|
|
this.createCustomLineChart(element, values, `sparkline-${index}`);
|
|
});
|
|
|
|
sparkbarElements.forEach((element, index) => {
|
|
this.createCustomBarChart(element, values, `sparkbar-${index}`);
|
|
});
|
|
|
|
sparktriElements.forEach((element, index) => {
|
|
this.createTristateChart(element, valuesAlt, `sparktri-${index}`);
|
|
});
|
|
|
|
sparkdiscElements.forEach((element, index) => {
|
|
this.createDiscreteChart(element, values, `sparkdisc-${index}`);
|
|
});
|
|
|
|
sparkbullElements.forEach((element, index) => {
|
|
this.createBulletChart(element, values, `sparkbull-${index}`);
|
|
});
|
|
|
|
sparkboxElements.forEach((element, index) => {
|
|
this.createBoxChart(element, values, `sparkbox-${index}`);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create custom line chart for sparkline elements
|
|
*/
|
|
createCustomLineChart(element, data, id) {
|
|
// Create canvas if it doesn't exist
|
|
let canvas = element.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
element.appendChild(canvas);
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.map((_, i) => i),
|
|
datasets: [{
|
|
data: data,
|
|
borderColor: COLORS['red-500'],
|
|
backgroundColor: 'transparent',
|
|
borderWidth: 2,
|
|
pointRadius: 3,
|
|
pointBackgroundColor: COLORS['red-500'],
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
animation: false, // Disable animations to prevent resize triggers
|
|
events: [], // Disable all events to prevent resize
|
|
onResize: null, // Explicitly disable resize callback
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: { enabled: false } // Disable tooltip to prevent events
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create custom bar chart for sparkbar elements
|
|
*/
|
|
createCustomBarChart(element, data, id) {
|
|
// Create canvas if it doesn't exist
|
|
let canvas = element.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
element.appendChild(canvas);
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: data.map((_, i) => i),
|
|
datasets: [{
|
|
data: data,
|
|
backgroundColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'),
|
|
borderColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'),
|
|
borderWidth: 1,
|
|
barPercentage: 0.8
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
enabled: true,
|
|
callbacks: {
|
|
label: (context) => `${context.parsed.y}°Celsius`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Setup resize handler for charts
|
|
*/
|
|
setupResizeHandler() {
|
|
// Setup responsive resize for large charts only
|
|
window.addEventListener('resize', () => {
|
|
this.debounceResize();
|
|
});
|
|
|
|
// Listen for sidebar toggle events
|
|
window.addEventListener('sidebar:toggle', () => {
|
|
this.debounceResize();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Debounced resize handler
|
|
*/
|
|
debounceResize() {
|
|
if (this.debounceTimer) {
|
|
clearTimeout(this.debounceTimer);
|
|
}
|
|
this.debounceTimer = setTimeout(() => {
|
|
this.redrawLargeChartsOnly();
|
|
}, 150);
|
|
}
|
|
|
|
/**
|
|
* Redraw only large charts, not sparklines
|
|
*/
|
|
redrawLargeChartsOnly() {
|
|
const largeChartIds = [
|
|
'line-chart', 'area-chart', 'scatter-chart', 'bar-chart',
|
|
'doughnut-chart', 'polar-chart', 'radar-chart', 'mixed-chart', 'bubble-chart'
|
|
];
|
|
|
|
largeChartIds.forEach(id => {
|
|
const chart = this.charts.get(id);
|
|
if (chart && chart.options.responsive) {
|
|
chart.resize();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Redraw all charts (used sparingly)
|
|
*/
|
|
redrawCharts() {
|
|
this.charts.forEach((chart, id) => {
|
|
if (chart.options.responsive) {
|
|
chart.resize();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update chart data
|
|
*/
|
|
updateChart(id, newData) {
|
|
const chart = this.charts.get(id);
|
|
if (chart) {
|
|
chart.data.datasets[0].data = newData;
|
|
chart.update();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create charts for the charts.html page
|
|
*/
|
|
createChartsPageCharts() {
|
|
// Line Chart
|
|
this.createLargeChart('line-chart', 'line', {
|
|
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
|
datasets: [{
|
|
label: 'Dataset 1',
|
|
data: [65, 59, 80, 81, 56, 55, 40],
|
|
borderColor: 'rgb(75, 192, 192)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
tension: 0.4
|
|
}]
|
|
});
|
|
|
|
// Area Chart
|
|
this.createLargeChart('area-chart', 'line', {
|
|
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
|
datasets: [{
|
|
label: 'Dataset 1',
|
|
data: [65, 59, 80, 81, 56, 55, 40],
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
backgroundColor: 'rgba(54, 162, 235, 0.4)',
|
|
fill: true,
|
|
tension: 0.4
|
|
}]
|
|
});
|
|
|
|
// Scatter Chart with more data points
|
|
this.createLargeChart('scatter-chart', 'scatter', {
|
|
datasets: [{
|
|
label: 'Dataset 1',
|
|
data: [
|
|
{x: -15, y: 8}, {x: -12, y: 12}, {x: -8, y: 3}, {x: -5, y: 15},
|
|
{x: -2, y: 7}, {x: 0, y: 10}, {x: 3, y: 18}, {x: 6, y: 5},
|
|
{x: 9, y: 22}, {x: 12, y: 8}, {x: 15, y: 14}, {x: 18, y: 19},
|
|
{x: -10, y: 0}, {x: 10, y: 5}, {x: 0.5, y: 5.5}, {x: 7, y: 12},
|
|
{x: -7, y: 17}, {x: 4, y: 9}, {x: 11, y: 16}, {x: -3, y: 11}
|
|
],
|
|
backgroundColor: 'rgba(255, 99, 132, 0.7)',
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
borderWidth: 1
|
|
}, {
|
|
label: 'Dataset 2',
|
|
data: [
|
|
{x: -13, y: 4}, {x: -9, y: 8}, {x: -6, y: 13}, {x: -1, y: 6},
|
|
{x: 2, y: 11}, {x: 5, y: 15}, {x: 8, y: 2}, {x: 13, y: 17},
|
|
{x: 16, y: 9}, {x: -4, y: 14}, {x: 1, y: 20}, {x: 14, y: 4}
|
|
],
|
|
backgroundColor: 'rgba(54, 162, 235, 0.7)',
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
borderWidth: 1
|
|
}]
|
|
});
|
|
|
|
// Bar Chart
|
|
this.createLargeChart('bar-chart', 'bar', {
|
|
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
|
|
datasets: [{
|
|
label: '# of Votes',
|
|
data: [12, 19, 3, 5, 2, 3],
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.6)',
|
|
'rgba(54, 162, 235, 0.6)',
|
|
'rgba(255, 205, 86, 0.6)',
|
|
'rgba(75, 192, 192, 0.6)',
|
|
'rgba(153, 102, 255, 0.6)',
|
|
'rgba(255, 159, 64, 0.6)'
|
|
],
|
|
borderColor: [
|
|
'rgba(255, 99, 132, 1)',
|
|
'rgba(54, 162, 235, 1)',
|
|
'rgba(255, 205, 86, 1)',
|
|
'rgba(75, 192, 192, 1)',
|
|
'rgba(153, 102, 255, 1)',
|
|
'rgba(255, 159, 64, 1)'
|
|
],
|
|
borderWidth: 1
|
|
}]
|
|
});
|
|
|
|
// Doughnut Chart
|
|
this.createLargeChart('doughnut-chart', 'doughnut', {
|
|
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
|
|
datasets: [{
|
|
label: 'My First Dataset',
|
|
data: [300, 50, 100, 75, 120, 60],
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.8)',
|
|
'rgba(54, 162, 235, 0.8)',
|
|
'rgba(255, 205, 86, 0.8)',
|
|
'rgba(75, 192, 192, 0.8)',
|
|
'rgba(153, 102, 255, 0.8)',
|
|
'rgba(255, 159, 64, 0.8)'
|
|
],
|
|
borderColor: [
|
|
'rgba(255, 99, 132, 1)',
|
|
'rgba(54, 162, 235, 1)',
|
|
'rgba(255, 205, 86, 1)',
|
|
'rgba(75, 192, 192, 1)',
|
|
'rgba(153, 102, 255, 1)',
|
|
'rgba(255, 159, 64, 1)'
|
|
],
|
|
borderWidth: 2,
|
|
hoverOffset: 10
|
|
}]
|
|
});
|
|
|
|
// Polar Area Chart
|
|
this.createLargeChart('polar-chart', 'polarArea', {
|
|
labels: ['Red', 'Green', 'Yellow', 'Grey', 'Blue'],
|
|
datasets: [{
|
|
label: 'My First Dataset',
|
|
data: [11, 16, 7, 3, 14],
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.7)',
|
|
'rgba(75, 192, 192, 0.7)',
|
|
'rgba(255, 205, 86, 0.7)',
|
|
'rgba(201, 203, 207, 0.7)',
|
|
'rgba(54, 162, 235, 0.7)'
|
|
],
|
|
borderColor: [
|
|
'rgb(255, 99, 132)',
|
|
'rgb(75, 192, 192)',
|
|
'rgb(255, 205, 86)',
|
|
'rgb(201, 203, 207)',
|
|
'rgb(54, 162, 235)'
|
|
],
|
|
borderWidth: 2
|
|
}]
|
|
});
|
|
|
|
// Radar Chart
|
|
this.createLargeChart('radar-chart', 'radar', {
|
|
labels: ['Speed', 'Reliability', 'Comfort', 'Safety', 'Efficiency', 'Innovation'],
|
|
datasets: [{
|
|
label: 'Product A',
|
|
data: [65, 59, 90, 81, 56, 55],
|
|
fill: true,
|
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
borderWidth: 2,
|
|
pointBackgroundColor: 'rgb(54, 162, 235)',
|
|
pointBorderColor: '#fff',
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: 'rgb(54, 162, 235)'
|
|
}, {
|
|
label: 'Product B',
|
|
data: [28, 48, 40, 95, 86, 27],
|
|
fill: true,
|
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
borderWidth: 2,
|
|
pointBackgroundColor: 'rgb(255, 99, 132)',
|
|
pointBorderColor: '#fff',
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: 'rgb(255, 99, 132)'
|
|
}]
|
|
});
|
|
|
|
// Mixed Chart (Bar + Line)
|
|
this.createLargeChart('mixed-chart', 'bar', {
|
|
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
|
datasets: [{
|
|
type: 'bar',
|
|
label: 'Sales',
|
|
data: [12, 19, 3, 5, 2, 3],
|
|
backgroundColor: 'rgba(54, 162, 235, 0.7)',
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
borderWidth: 1
|
|
}, {
|
|
type: 'line',
|
|
label: 'Revenue',
|
|
data: [18, 25, 8, 15, 12, 18],
|
|
fill: false,
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
|
borderWidth: 3,
|
|
tension: 0.4,
|
|
pointRadius: 5,
|
|
pointHoverRadius: 7
|
|
}]
|
|
});
|
|
|
|
// Bubble Chart
|
|
this.createLargeChart('bubble-chart', 'bubble', {
|
|
datasets: [{
|
|
label: 'First Dataset',
|
|
data: [
|
|
{x: 20, y: 30, r: 15},
|
|
{x: 40, y: 10, r: 10},
|
|
{x: 30, y: 40, r: 20},
|
|
{x: 50, y: 35, r: 12},
|
|
{x: 10, y: 50, r: 8},
|
|
{x: 60, y: 20, r: 18},
|
|
{x: 25, y: 25, r: 14}
|
|
],
|
|
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
borderWidth: 2
|
|
}, {
|
|
label: 'Second Dataset',
|
|
data: [
|
|
{x: 15, y: 45, r: 12},
|
|
{x: 35, y: 15, r: 16},
|
|
{x: 45, y: 25, r: 9},
|
|
{x: 55, y: 45, r: 14},
|
|
{x: 25, y: 35, r: 11}
|
|
],
|
|
backgroundColor: 'rgba(255, 99, 132, 0.6)',
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
borderWidth: 2
|
|
}]
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create large chart for charts page
|
|
*/
|
|
createLargeChart(id, type, data) {
|
|
const canvas = document.getElementById(id);
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Define chart-specific options
|
|
const chartOptions = this.getChartOptions(type);
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: type,
|
|
data: data,
|
|
options: chartOptions
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Get chart-specific options based on chart type
|
|
*/
|
|
getChartOptions(type) {
|
|
const baseOptions = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top',
|
|
labels: {
|
|
padding: 20,
|
|
font: {
|
|
size: 12,
|
|
weight: '600'
|
|
},
|
|
color: '#333'
|
|
}
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
titleColor: '#333',
|
|
bodyColor: '#666',
|
|
borderColor: '#ddd',
|
|
borderWidth: 1,
|
|
cornerRadius: 8,
|
|
displayColors: true
|
|
}
|
|
}
|
|
};
|
|
|
|
// Chart type specific configurations
|
|
switch (type) {
|
|
case 'doughnut':
|
|
case 'pie':
|
|
return {
|
|
...baseOptions,
|
|
plugins: {
|
|
...baseOptions.plugins,
|
|
legend: {
|
|
...baseOptions.plugins.legend,
|
|
position: 'right'
|
|
}
|
|
},
|
|
interaction: {
|
|
intersect: false
|
|
}
|
|
};
|
|
|
|
case 'polarArea':
|
|
return {
|
|
...baseOptions,
|
|
scales: {
|
|
r: {
|
|
pointLabels: {
|
|
display: true,
|
|
centerPointLabels: true,
|
|
font: {
|
|
size: 10
|
|
}
|
|
},
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.1)'
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
case 'radar':
|
|
return {
|
|
...baseOptions,
|
|
scales: {
|
|
r: {
|
|
angleLines: {
|
|
display: true,
|
|
color: 'rgba(0, 0, 0, 0.1)'
|
|
},
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.1)'
|
|
},
|
|
pointLabels: {
|
|
font: {
|
|
size: 11
|
|
},
|
|
color: '#666'
|
|
},
|
|
ticks: {
|
|
display: true,
|
|
color: '#666',
|
|
font: {
|
|
size: 10
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
case 'bubble':
|
|
return {
|
|
...baseOptions,
|
|
scales: {
|
|
x: {
|
|
type: 'linear',
|
|
position: 'bottom',
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
borderDash: [5, 5]
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
borderDash: [5, 5]
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
...baseOptions.plugins,
|
|
tooltip: {
|
|
...baseOptions.plugins.tooltip,
|
|
callbacks: {
|
|
label: function(context) {
|
|
return `${context.dataset.label}: (${context.parsed.x}, ${context.parsed.y}), Size: ${context.parsed._custom}`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
case 'scatter':
|
|
return {
|
|
...baseOptions,
|
|
scales: {
|
|
x: {
|
|
type: 'linear',
|
|
position: 'bottom',
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
borderDash: [5, 5]
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
},
|
|
y: {
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
borderDash: [5, 5]
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
default:
|
|
// For line, bar, area, mixed charts
|
|
return {
|
|
...baseOptions,
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
borderDash: [5, 5]
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
borderDash: [5, 5]
|
|
},
|
|
ticks: {
|
|
color: '#666',
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create tristate chart (for .sparktri elements)
|
|
*/
|
|
createTristateChart(element, data, id) {
|
|
let canvas = element.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
element.appendChild(canvas);
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: data.map((_, i) => i),
|
|
datasets: [{
|
|
data: data.map(val => Math.abs(val)),
|
|
backgroundColor: data.map(val => {
|
|
if (val > 0) return COLORS['light-blue-500'];
|
|
if (val < 0) return '#f90';
|
|
return '#000';
|
|
}),
|
|
borderColor: data.map(val => {
|
|
if (val > 0) return COLORS['light-blue-500'];
|
|
if (val < 0) return '#f90';
|
|
return '#000';
|
|
}),
|
|
borderWidth: 1,
|
|
barPercentage: 0.8
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
enabled: true,
|
|
callbacks: {
|
|
label: (context) => `${context.parsed.y}°Celsius`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create discrete chart (for .sparkdisc elements)
|
|
*/
|
|
createDiscreteChart(element, data, id) {
|
|
let canvas = element.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
element.appendChild(canvas);
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'scatter',
|
|
data: {
|
|
datasets: [{
|
|
data: data.map((val, index) => ({x: index, y: val})),
|
|
backgroundColor: '#9f0',
|
|
borderColor: '#9f0',
|
|
pointRadius: 2,
|
|
showLine: false
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
enabled: true,
|
|
callbacks: {
|
|
label: (context) => `${context.parsed.y}°Celsius`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create bullet chart (for .sparkbull elements)
|
|
*/
|
|
createBulletChart(element, data, id) {
|
|
let canvas = element.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
element.appendChild(canvas);
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Simplified bullet chart as horizontal bar
|
|
const chart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: [''],
|
|
datasets: [{
|
|
data: [Math.max(...data)],
|
|
backgroundColor: COLORS['amber-500'],
|
|
borderColor: COLORS['amber-500'],
|
|
borderWidth: 1,
|
|
barPercentage: 0.6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
indexAxis: 'y',
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
enabled: true,
|
|
callbacks: {
|
|
label: (context) => `${context.parsed.x}°Celsius`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create box chart (for .sparkbox elements)
|
|
*/
|
|
createBoxChart(element, data, id) {
|
|
let canvas = element.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 100;
|
|
canvas.height = 20;
|
|
canvas.style.width = '100px';
|
|
canvas.style.height = '20px';
|
|
element.appendChild(canvas);
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Box plot simplified as bar chart showing quartiles
|
|
const sortedData = [...data].sort((a, b) => a - b);
|
|
const q1 = sortedData[Math.floor(sortedData.length * 0.25)];
|
|
const median = sortedData[Math.floor(sortedData.length * 0.5)];
|
|
const q3 = sortedData[Math.floor(sortedData.length * 0.75)];
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['Q1', 'Med', 'Q3'],
|
|
datasets: [{
|
|
data: [q1, median, q3],
|
|
backgroundColor: '#9f0',
|
|
borderColor: '#9f0',
|
|
borderWidth: 1,
|
|
barPercentage: 0.8
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: { display: false },
|
|
y: { display: false }
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
enabled: true,
|
|
callbacks: {
|
|
label: (context) => `${context.parsed.y}°Celsius`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(id, chart);
|
|
}
|
|
|
|
/**
|
|
* Create Easy Pie Charts (replaces jQuery Easy Pie Chart)
|
|
*/
|
|
createEasyPieCharts() {
|
|
const easyPieElements = document.querySelectorAll('.easy-pie-chart');
|
|
|
|
easyPieElements.forEach((element, index) => {
|
|
const size = parseInt(element.dataset.size) || 80;
|
|
const percent = parseInt(element.dataset.percent) || 0;
|
|
const barColor = element.dataset.barColor || '#f44336';
|
|
|
|
// Create canvas for the pie chart
|
|
let canvas = element.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = size;
|
|
canvas.height = size;
|
|
canvas.style.width = `${size}px`;
|
|
canvas.style.height = `${size}px`;
|
|
element.appendChild(canvas);
|
|
}
|
|
|
|
// Create percentage display
|
|
let percentDisplay = element.querySelector('span');
|
|
if (percentDisplay) {
|
|
percentDisplay.textContent = `${percent}%`;
|
|
percentDisplay.style.position = 'absolute';
|
|
percentDisplay.style.top = '50%';
|
|
percentDisplay.style.left = '50%';
|
|
percentDisplay.style.transform = 'translate(-50%, -50%)';
|
|
percentDisplay.style.fontSize = '14px';
|
|
percentDisplay.style.fontWeight = 'bold';
|
|
}
|
|
|
|
// Set element position to relative for absolute positioning of text
|
|
element.style.position = 'relative';
|
|
element.style.display = 'inline-block';
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const chart = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
datasets: [{
|
|
data: [percent, 100 - percent],
|
|
backgroundColor: [barColor, '#f0f0f0'],
|
|
borderWidth: 0,
|
|
cutout: '70%'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: { enabled: false }
|
|
}
|
|
}
|
|
});
|
|
|
|
this.charts.set(`easy-pie-${index}`, chart);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Destroy all charts
|
|
*/
|
|
destroy() {
|
|
this.charts.forEach(chart => {
|
|
chart.destroy();
|
|
});
|
|
this.charts.clear();
|
|
|
|
if (this.debounceTimer) {
|
|
clearTimeout(this.debounceTimer);
|
|
}
|
|
}
|
|
}
|
|
|
|
export default ChartComponent;
|