import React, { useEffect, useRef } from 'react'; import './Chart.css'; const Chart = ({ data = [], type = 'line', width = 400, height = 300, title = '', xLabel = '', yLabel = '', color = '#2E7D32', showGrid = true, showLegend = true, animate = true }) => { const canvasRef = useRef(null); const containerRef = useRef(null); useEffect(() => { if (!data.length) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const container = containerRef.current; // Set canvas size const rect = container.getBoundingClientRect(); canvas.width = rect.width; canvas.height = rect.height; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw chart based on type switch (type) { case 'line': drawLineChart(ctx, data, canvas.width, canvas.height, color, showGrid); break; case 'bar': drawBarChart(ctx, data, canvas.width, canvas.height, color, showGrid); break; case 'pie': drawPieChart(ctx, data, canvas.width, canvas.height); break; case 'area': drawAreaChart(ctx, data, canvas.width, canvas.height, color, showGrid); break; default: drawLineChart(ctx, data, canvas.width, canvas.height, color, showGrid); } // Add labels if provided if (title) { drawTitle(ctx, title, canvas.width); } }, [data, type, color, showGrid, title]); const drawLineChart = (ctx, data, width, height, color, showGrid) => { const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; // Find min/max values const values = data.map(d => d.value || d.y || d); const minValue = Math.min(...values); const maxValue = Math.max(...values); const valueRange = maxValue - minValue || 1; // Draw grid if (showGrid) { ctx.strokeStyle = '#e0e0e0'; ctx.lineWidth = 1; // Horizontal grid lines for (let i = 0; i <= 5; i++) { const y = padding + (chartHeight / 5) * i; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } // Vertical grid lines for (let i = 0; i <= data.length - 1; i++) { const x = padding + (chartWidth / (data.length - 1)) * i; ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); } } // Draw line ctx.strokeStyle = color; ctx.lineWidth = 3; ctx.beginPath(); data.forEach((point, index) => { const value = point.value || point.y || point; const x = padding + (chartWidth / (data.length - 1)) * index; const y = height - padding - ((value - minValue) / valueRange) * chartHeight; if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); // Draw points ctx.fillStyle = color; data.forEach((point, index) => { const value = point.value || point.y || point; const x = padding + (chartWidth / (data.length - 1)) * index; const y = height - padding - ((value - minValue) / valueRange) * chartHeight; ctx.beginPath(); ctx.arc(x, y, 4, 0, 2 * Math.PI); ctx.fill(); }); }; const drawBarChart = (ctx, data, width, height, color, showGrid) => { const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const values = data.map(d => d.value || d.y || d); const maxValue = Math.max(...values); const barWidth = chartWidth / data.length * 0.8; const barSpacing = chartWidth / data.length * 0.2; // Draw grid if (showGrid) { ctx.strokeStyle = '#e0e0e0'; ctx.lineWidth = 1; for (let i = 0; i <= 5; i++) { const y = padding + (chartHeight / 5) * i; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } } // Draw bars ctx.fillStyle = color; data.forEach((point, index) => { const value = point.value || point.y || point; const barHeight = (value / maxValue) * chartHeight; const x = padding + index * (barWidth + barSpacing) + barSpacing / 2; const y = height - padding - barHeight; ctx.fillRect(x, y, barWidth, barHeight); }); }; const drawPieChart = (ctx, data, width, height) => { const centerX = width / 2; const centerY = height / 2; const radius = Math.min(width, height) / 2 - 40; const total = data.reduce((sum, d) => sum + (d.value || d.y || d), 0); let currentAngle = -Math.PI / 2; const colors = [ '#2E7D32', '#4CAF50', '#66BB6A', '#81C784', '#A5D6A7', '#FF9800', '#FFA726', '#FFB74D', '#FFCC02', '#FFD54F' ]; data.forEach((point, index) => { const value = point.value || point.y || point; const sliceAngle = (value / total) * 2 * Math.PI; ctx.fillStyle = colors[index % colors.length]; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + sliceAngle); ctx.closePath(); ctx.fill(); // Draw label const labelAngle = currentAngle + sliceAngle / 2; const labelX = centerX + Math.cos(labelAngle) * (radius * 0.7); const labelY = centerY + Math.sin(labelAngle) * (radius * 0.7); ctx.fillStyle = '#fff'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(`${Math.round((value / total) * 100)}%`, labelX, labelY); currentAngle += sliceAngle; }); }; const drawAreaChart = (ctx, data, width, height, color, showGrid) => { const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const values = data.map(d => d.value || d.y || d); const minValue = Math.min(...values); const maxValue = Math.max(...values); const valueRange = maxValue - minValue || 1; // Draw grid if (showGrid) { ctx.strokeStyle = '#e0e0e0'; ctx.lineWidth = 1; for (let i = 0; i <= 5; i++) { const y = padding + (chartHeight / 5) * i; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } } // Create gradient const gradient = ctx.createLinearGradient(0, padding, 0, height - padding); gradient.addColorStop(0, color + '80'); gradient.addColorStop(1, color + '20'); // Draw area ctx.fillStyle = gradient; ctx.beginPath(); // Start from bottom left ctx.moveTo(padding, height - padding); // Draw line to each point data.forEach((point, index) => { const value = point.value || point.y || point; const x = padding + (chartWidth / (data.length - 1)) * index; const y = height - padding - ((value - minValue) / valueRange) * chartHeight; ctx.lineTo(x, y); }); // Close path to bottom right ctx.lineTo(width - padding, height - padding); ctx.closePath(); ctx.fill(); // Draw line on top ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.beginPath(); data.forEach((point, index) => { const value = point.value || point.y || point; const x = padding + (chartWidth / (data.length - 1)) * index; const y = height - padding - ((value - minValue) / valueRange) * chartHeight; if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); }; const drawTitle = (ctx, title, width) => { ctx.fillStyle = '#333'; ctx.font = 'bold 16px Arial'; ctx.textAlign = 'center'; ctx.fillText(title, width / 2, 30); }; return (
{showLegend && data.length > 0 && data[0].label && (
{data.map((item, index) => (
{item.label}
))}
)}
); }; export default Chart;