Spaces:
Sleeping
Sleeping
| /** | |
| * Composants de graphiques pour AfriDataHub | |
| * Created by BlackBenAI Team - AfriDataHub Platform | |
| */ | |
| import { | |
| BarChart, | |
| Bar, | |
| LineChart, | |
| Line, | |
| PieChart, | |
| Pie, | |
| Cell, | |
| XAxis, | |
| YAxis, | |
| CartesianGrid, | |
| Tooltip, | |
| Legend, | |
| ResponsiveContainer | |
| } from 'recharts' | |
| // Palette de couleurs AfriDataHub | |
| const COLORS = { | |
| primary: '#7c3aed', // violet | |
| secondary: '#ec4899', // rose | |
| success: '#10b981', // vert | |
| warning: '#f59e0b', // orange | |
| info: '#3b82f6', // bleu | |
| danger: '#ef4444', // rouge | |
| gray: '#6b7280' // gris | |
| } | |
| const CHART_COLORS = [ | |
| COLORS.primary, | |
| COLORS.secondary, | |
| COLORS.success, | |
| COLORS.warning, | |
| COLORS.info, | |
| COLORS.danger, | |
| '#8b5cf6', | |
| '#f472b6', | |
| '#34d399', | |
| '#fbbf24' | |
| ] | |
| // Composant Tooltip personnalisé | |
| const CustomTooltip = ({ active, payload, label }) => { | |
| if (active && payload && payload.length) { | |
| return ( | |
| <div className="bg-card p-3 border border-border rounded-xl shadow-2xl"> | |
| <p className="font-bold text-foreground mb-1">{label}</p> | |
| {payload.map((entry, index) => ( | |
| <p key={index} style={{ color: entry.color }} className="text-xs font-black"> | |
| {entry.name}: {entry.value?.toLocaleString()} | |
| </p> | |
| ))} | |
| </div> | |
| ) | |
| } | |
| return null | |
| } | |
| // Graphique en barres pour les données par pays | |
| export const CountryBarChart = ({ data, title, dataKey, xAxisKey = 'country' }) => { | |
| return ( | |
| <div className="bg-card rounded-[2rem] border border-border p-6 shadow-lg"> | |
| <h3 className="text-lg font-black text-foreground mb-6 uppercase tracking-widest">{title}</h3> | |
| <ResponsiveContainer width="100%" height={300}> | |
| <BarChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="currentColor" opacity={0.1} vertical={false} /> | |
| <XAxis | |
| dataKey={xAxisKey} | |
| tick={{ fontSize: 10, fill: 'currentColor', opacity: 0.6, fontWeight: 'bold' }} | |
| axisLine={{ stroke: 'currentColor', opacity: 0.2 }} | |
| /> | |
| <YAxis | |
| tick={{ fontSize: 10, fill: 'currentColor', opacity: 0.6, fontWeight: 'bold' }} | |
| axisLine={{ stroke: 'currentColor', opacity: 0.2 }} | |
| /> | |
| <Tooltip content={<CustomTooltip />} /> | |
| <Bar | |
| dataKey={dataKey} | |
| fill="var(--primary)" | |
| radius={[6, 6, 0, 0]} | |
| /> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </div> | |
| ) | |
| } | |
| // Graphique en ligne pour les tendances temporelles | |
| export const TrendLineChart = ({ data, title, lines = [] }) => { | |
| return ( | |
| <div className="bg-card rounded-[2rem] border border-border p-6 shadow-lg"> | |
| <h3 className="text-lg font-black text-foreground mb-6 uppercase tracking-widest">{title}</h3> | |
| <ResponsiveContainer width="100%" height={300}> | |
| <LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="currentColor" opacity={0.1} /> | |
| <XAxis | |
| dataKey="date" | |
| tick={{ fontSize: 10, fill: 'currentColor', opacity: 0.6, fontWeight: 'bold' }} | |
| axisLine={{ stroke: 'currentColor', opacity: 0.2 }} | |
| /> | |
| <YAxis | |
| tick={{ fontSize: 10, fill: 'currentColor', opacity: 0.6, fontWeight: 'bold' }} | |
| axisLine={{ stroke: 'currentColor', opacity: 0.2 }} | |
| /> | |
| <Tooltip content={<CustomTooltip />} /> | |
| <Legend wrapperStyle={{ paddingTop: '20px', fontWeight: 'bold', fontSize: '12px' }} /> | |
| {lines.map((line, index) => ( | |
| <Line | |
| key={line.dataKey} | |
| type="monotone" | |
| dataKey={line.dataKey} | |
| stroke={CHART_COLORS[index % CHART_COLORS.length]} | |
| strokeWidth={3} | |
| dot={{ r: 4, strokeWidth: 2, fill: 'var(--card)' }} | |
| activeDot={{ r: 6, strokeWidth: 0 }} | |
| name={line.name} | |
| /> | |
| ))} | |
| </LineChart> | |
| </ResponsiveContainer> | |
| </div> | |
| ) | |
| } | |
| // Graphique en secteurs pour la répartition | |
| export const DomainPieChart = ({ data, title }) => { | |
| return ( | |
| <div className="bg-card rounded-[2rem] border border-border p-6 shadow-lg"> | |
| <h3 className="text-lg font-black text-foreground mb-6 uppercase tracking-widest">{title}</h3> | |
| <ResponsiveContainer width="100%" height={300}> | |
| <PieChart> | |
| <Pie | |
| data={data} | |
| cx="50%" | |
| cy="50%" | |
| labelLine={false} | |
| label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`} | |
| outerRadius={80} | |
| stroke="var(--card)" | |
| strokeWidth={2} | |
| fill="#8884d8" | |
| dataKey="value" | |
| > | |
| {data.map((entry, index) => ( | |
| <Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} /> | |
| ))} | |
| </Pie> | |
| <Tooltip content={<CustomTooltip />} /> | |
| </PieChart> | |
| </ResponsiveContainer> | |
| </div> | |
| ) | |
| } | |
| // Graphique de comparaison multi-barres | |
| export const ComparisonBarChart = ({ data, title, bars = [] }) => { | |
| return ( | |
| <div className="bg-card rounded-[2rem] border border-border p-6 shadow-lg"> | |
| <h3 className="text-lg font-black text-foreground mb-6 uppercase tracking-widest">{title}</h3> | |
| <ResponsiveContainer width="100%" height={300}> | |
| <BarChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="currentColor" opacity={0.1} /> | |
| <XAxis | |
| dataKey="name" | |
| tick={{ fontSize: 10, fill: 'currentColor', opacity: 0.6, fontWeight: 'bold' }} | |
| axisLine={{ stroke: 'currentColor', opacity: 0.2 }} | |
| /> | |
| <YAxis | |
| tick={{ fontSize: 10, fill: 'currentColor', opacity: 0.6, fontWeight: 'bold' }} | |
| axisLine={{ stroke: 'currentColor', opacity: 0.2 }} | |
| /> | |
| <Tooltip content={<CustomTooltip />} /> | |
| <Legend wrapperStyle={{ paddingTop: '20px', fontWeight: 'bold', fontSize: '12px' }} /> | |
| {bars.map((bar, index) => ( | |
| <Bar | |
| key={bar.dataKey} | |
| dataKey={bar.dataKey} | |
| fill={CHART_COLORS[index % CHART_COLORS.length]} | |
| name={bar.name} | |
| radius={[4, 4, 0, 0]} | |
| /> | |
| ))} | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </div> | |
| ) | |
| } | |
| // Graphique de métriques avec indicateurs | |
| export const MetricsChart = ({ data, title, metric }) => { | |
| const formatValue = (value) => { | |
| if (value >= 1000000) { | |
| return `${(value / 1000000).toFixed(1)}M` | |
| } else if (value >= 1000) { | |
| return `${(value / 1000).toFixed(1)}K` | |
| } | |
| return value.toString() | |
| } | |
| return ( | |
| <div className="bg-card rounded-[2rem] border border-border p-6 shadow-lg"> | |
| <h3 className="text-lg font-black text-foreground mb-6 uppercase tracking-widest">{title}</h3> | |
| <div className="space-y-4"> | |
| {data.map((item, index) => ( | |
| <div key={index} className="flex items-center justify-between p-3 rounded-2xl hover:bg-muted/50 transition-colors"> | |
| <div className="flex items-center"> | |
| <div | |
| className="w-4 h-4 rounded mr-3" | |
| style={{ backgroundColor: CHART_COLORS[index % CHART_COLORS.length] }} | |
| /> | |
| <span className="text-sm font-bold text-foreground/80">{item.name}</span> | |
| </div> | |
| <div className="flex items-center"> | |
| <span className="text-lg font-black text-foreground mr-3"> | |
| {formatValue(item[metric])} | |
| </span> | |
| {item.change && ( | |
| <span className={`text-[10px] px-3 py-1 rounded-full font-black ${item.change > 0 | |
| ? 'bg-emerald-500/10 text-emerald-500' | |
| : 'bg-red-500/10 text-red-500' | |
| }`}> | |
| {item.change > 0 ? '+' : ''}{item.change}% | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ) | |
| } | |
| export default { | |
| CountryBarChart, | |
| TrendLineChart, | |
| DomainPieChart, | |
| ComparisonBarChart, | |
| MetricsChart | |
| } | |