ریچارٹس کے ساتھ ڈیٹا ویژولائزیشن: ڈیش بورڈ پیٹرنز
ایک زبردست ڈیش بورڈ صرف ایک ٹیبل میں نمبر نہیں ہوتا ہے - یہ بصری نمونے ہیں جو رجحانات، بے ضابطگیوں اور مواقع کو ایک نظر میں ظاہر کرتے ہیں۔ Recharts React ایکو سسٹم میں سب سے زیادہ استعمال ہونے والی چارٹنگ لائبریری ہے: D3.js بنیادی اصولوں پر بنائی گئی، مکمل طور پر TypeScript ٹائپ، کمپوز ایبل، اور باکس سے باہر ریسپانسیو۔ یہ SVG پیچیدگی کو سنبھالتا ہے تاکہ آپ ڈیٹا کی کہانی پر توجہ مرکوز کرسکیں۔
یہ گائیڈ پروڈکشن ڈیش بورڈز کے لیے ریچارٹس پیٹرن کا احاطہ کرتی ہے — آمدنی کے رجحانات، تبادلوں کے فنلز، ٹائم سیریز میٹرکس، جغرافیائی تقسیم، اور ریئل ٹائم اسٹریمنگ ڈیٹا — ڈارک موڈ سپورٹ، قابل رسائی رنگ پیلیٹس، اور ریسپانسیو لے آؤٹس کے ساتھ۔
اہم ٹیک ویز
- چارٹس کو ہمیشہ
ResponsiveContainerمیںwidth="100%"اور ایک مقررہheightکے ساتھ لپیٹیں — کبھی بھی پکسل کی چوڑائی کو ہارڈ کوڈ نہ کریں۔- پہلے سے طے شدہ کے بجائے
CustomTooltipاجزاء استعمال کریں - فارمیٹنگ اور اسٹائلنگ پر مکمل کنٹرول- ریچارٹس
formatterکال بیک پیرامیٹرز کو کبھی بھی واضح طور پر ٹائپ نہیں کیا جانا چاہئے — تبدیل کرنے کے لئےNumber(value)استعمال کریں- ڈارک موڈ کو متحرک رنگ کی قدروں کی ضرورت ہوتی ہے - CSS متغیرات سے پڑھیں، ہارڈ کوڈڈ ہیکس رنگوں سے نہیں۔
- ٹائم سیریز ڈیٹا کے لیے، XAxis پر ایک
tickFormatterاستعمال کریں تاکہ ٹائم اسٹیمپ کو مستقل طور پر فارمیٹ کریں۔- قابل رسائی: چارٹ کنٹینر میں
aria-labelشامل کریں اور لیجنڈ آئٹمز پرtabIndexاستعمال کریں- کارکردگی:
useMemoکے ساتھ چارٹ ڈیٹا کو میموائز کریں — ہر پیرنٹ رینڈر پر ری چارٹس دوبارہ رینڈر کرتا ہے۔- ریئل ٹائم ڈیٹا کے لیے، ڈیٹا اری ریفرنس کو اپ ڈیٹ کریں — ریچارٹس حوالہ کے لحاظ سے تبدیلیوں کا پتہ لگاتا ہے۔
سیٹ اپ
pnpm add recharts
pnpm add -D @types/recharts # Usually not needed — recharts ships its own types
ریچارٹس 2.x بحری جہاز TypeScript کی اقسام۔ کوئی علیحدہ @types پیکیج درکار نہیں ہے۔
ریونیو ایریا چارٹ
// src/components/charts/revenue-area-chart.tsx
'use client';
import { useMemo } from 'react';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid,
Tooltip, Legend, ResponsiveContainer,
} from 'recharts';
import { format, parseISO } from 'date-fns';
interface RevenueDataPoint {
date: string; // ISO date string
revenue: number;
refunds: number;
net: number;
}
interface CustomTooltipProps {
active?: boolean;
payload?: Array<{ name: string; value: number; color: string }>;
label?: string;
}
function CustomTooltip({ active, payload, label }: CustomTooltipProps) {
if (!active || !payload || !label) return null;
return (
<div className="rounded-lg border bg-background p-3 shadow-md">
<p className="mb-2 text-sm font-medium text-muted-foreground">
{format(parseISO(label), 'MMM d, yyyy')}
</p>
{payload.map((entry) => (
<div key={entry.name} className="flex items-center gap-2 text-sm">
<span
className="inline-block h-2 w-2 rounded-full"
style={{ backgroundColor: entry.color }}
/>
<span className="text-muted-foreground">{entry.name}:</span>
<span className="font-medium">
{new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0,
}).format(entry.value)}
</span>
</div>
))}
</div>
);
}
interface RevenueAreaChartProps {
data: RevenueDataPoint[];
loading?: boolean;
}
export function RevenueAreaChart({ data, loading }: RevenueAreaChartProps) {
// CRITICAL: memoize chart data — Recharts re-renders on every reference change
const chartData = useMemo(() => data, [data]);
if (loading) {
return <div className="h-80 animate-pulse rounded-lg bg-muted" />;
}
return (
<div role="img" aria-label="Monthly revenue area chart">
<ResponsiveContainer width="100%" height={320}>
<AreaChart data={chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="colorRevenue" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#f59e0b" stopOpacity={0.3} />
<stop offset="95%" stopColor="#f59e0b" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorNet" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#22c55e" stopOpacity={0.3} />
<stop offset="95%" stopColor="#22c55e" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray="3 3"
stroke="hsl(var(--border))"
vertical={false}
/>
<XAxis
dataKey="date"
tickFormatter={(value: string) => format(parseISO(value), 'MMM d')}
tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false}
tickLine={false}
/>
<YAxis
tickFormatter={(value) => `$${(Number(value) / 1000).toFixed(0)}k`}
tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false}
tickLine={false}
width={60}
/>
<Tooltip content={<CustomTooltip />} />
<Legend
wrapperStyle={{ fontSize: '14px', paddingTop: '16px' }}
formatter={(value: string) =>
<span className="text-foreground">{value}</span>
}
/>
<Area
type="monotone"
dataKey="revenue"
name="Gross Revenue"
stroke="#f59e0b"
strokeWidth={2}
fill="url(#colorRevenue)"
/>
<Area
type="monotone"
dataKey="net"
name="Net Revenue"
stroke="#22c55e"
strokeWidth={2}
fill="url(#colorNet)"
/>
</AreaChart>
</ResponsiveContainer>
</div>
);
}
بار چارٹ: ماہانہ موازنہ
// src/components/charts/monthly-bar-chart.tsx
'use client';
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid,
Tooltip, Legend, ResponsiveContainer, Cell,
} from 'recharts';
interface MonthlyData {
month: string;
current: number;
previous: number;
target: number;
}
function CustomBarTooltip({ active, payload, label }: any) {
if (!active || !payload) return null;
return (
<div className="rounded-lg border bg-background p-3 shadow-md">
<p className="mb-2 font-medium">{label}</p>
{payload.map((entry: { name: string; value: number; fill: string }) => (
<div key={entry.name} className="flex items-center gap-2 text-sm">
<span
className="inline-block h-2 w-2 rounded-full"
style={{ backgroundColor: entry.fill }}
/>
<span className="text-muted-foreground">{entry.name}:</span>
<span className="font-medium">
{new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0,
}).format(entry.value)}
</span>
</div>
))}
</div>
);
}
export function MonthlyBarChart({ data }: { data: MonthlyData[] }) {
return (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" vertical={false} />
<XAxis
dataKey="month"
tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false}
tickLine={false}
/>
<YAxis
tickFormatter={(value) => `$${(Number(value) / 1000).toFixed(0)}k`}
tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false}
tickLine={false}
/>
<Tooltip content={<CustomBarTooltip />} />
<Legend />
<Bar dataKey="previous" name="Previous Year" fill="hsl(var(--muted))" radius={[4, 4, 0, 0]} />
<Bar dataKey="current" name="Current Year" fill="#f59e0b" radius={[4, 4, 0, 0]} />
<Bar dataKey="target" name="Target" fill="#22c55e" opacity={0.4} radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
);
}
ڈونٹ / پائی چارٹ
// src/components/charts/platform-pie-chart.tsx
'use client';
import { useState } from 'react';
import {
PieChart, Pie, Cell, Sector,
ResponsiveContainer, Legend, Tooltip,
} from 'recharts';
interface PieDataPoint {
name: string;
value: number;
color: string;
}
const PLATFORM_COLORS: Record<string, string> = {
'Odoo': '#714b67',
'Shopify': '#96bf48',
'GoHighLevel': '#f59e0b',
'Power BI': '#f2c811',
'OpenClaw': '#0ea5e9',
'Other': '#94a3b8',
};
function renderActiveShape(props: any) {
const {
cx, cy, innerRadius, outerRadius, startAngle, endAngle,
fill, payload, percent, value,
} = props;
return (
<g>
<text x={cx} y={cy - 12} textAnchor="middle" className="text-base font-bold fill-foreground">
{payload.name}
</text>
<text x={cx} y={cy + 12} textAnchor="middle" className="text-sm fill-muted-foreground">
{`${(percent * 100).toFixed(1)}%`}
</text>
<text x={cx} y={cy + 32} textAnchor="middle" className="text-sm fill-muted-foreground">
{`$${(Number(value) / 1000).toFixed(0)}k`}
</text>
<Sector
cx={cx} cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius + 8}
startAngle={startAngle}
endAngle={endAngle}
fill={fill}
/>
<Sector
cx={cx} cy={cy}
innerRadius={outerRadius + 10}
outerRadius={outerRadius + 12}
startAngle={startAngle}
endAngle={endAngle}
fill={fill}
/>
</g>
);
}
export function PlatformPieChart({ data }: { data: PieDataPoint[] }) {
const [activeIndex, setActiveIndex] = useState(0);
const chartData = data.map((item) => ({
...item,
color: PLATFORM_COLORS[item.name] ?? '#94a3b8',
}));
return (
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
activeIndex={activeIndex}
activeShape={renderActiveShape}
data={chartData}
cx="50%"
cy="50%"
innerRadius={70}
outerRadius={100}
dataKey="value"
onMouseEnter={(_, index) => setActiveIndex(index)}
>
{chartData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Legend
formatter={(value: string) =>
<span className="text-sm text-foreground">{value}</span>
}
/>
</PieChart>
</ResponsiveContainer>
);
}
ریئل ٹائم لائن چارٹ
// src/components/charts/realtime-chart.tsx
'use client';
import { useState, useEffect, useRef } from 'react';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid,
Tooltip, ResponsiveContainer, ReferenceLine,
} from 'recharts';
interface MetricPoint {
time: string;
requests: number;
errors: number;
p95: number;
}
const MAX_DATA_POINTS = 60; // 60 seconds of data
export function RealtimeChart({ endpoint }: { endpoint: string }) {
const [data, setData] = useState<MetricPoint[]>([]);
const intervalRef = useRef<ReturnType<typeof setInterval>>();
useEffect(() => {
const fetchMetrics = async () => {
try {
const response = await fetch(endpoint);
const point: MetricPoint = await response.json();
setData((prev) => {
const updated = [
...prev,
{ ...point, time: new Date().toLocaleTimeString() },
];
// Keep only the last MAX_DATA_POINTS — sliding window
return updated.slice(-MAX_DATA_POINTS);
});
} catch {
// Metrics fetch failure is non-critical — log but do not crash
}
};
fetchMetrics();
intervalRef.current = setInterval(fetchMetrics, 1000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [endpoint]);
return (
<ResponsiveContainer width="100%" height={200}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis
dataKey="time"
tick={{ fontSize: 10, fill: 'hsl(var(--muted-foreground))' }}
interval="preserveStartEnd"
/>
<YAxis
tick={{ fontSize: 10, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false}
/>
<Tooltip />
{/* Reference line for SLO threshold */}
<ReferenceLine
y={200}
label={{ value: 'SLO 200ms', position: 'right', fontSize: 10 }}
stroke="#ef4444"
strokeDasharray="4 4"
/>
<Line
type="monotone"
dataKey="p95"
name="P95 Latency (ms)"
stroke="#f59e0b"
strokeWidth={2}
dot={false}
isAnimationActive={false} // Disable animation for real-time data
/>
<Line
type="monotone"
dataKey="errors"
name="Errors/s"
stroke="#ef4444"
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
);
}
سٹیٹس کارڈز کے ساتھ ڈیش بورڈ لے آؤٹ
// src/components/dashboard/metrics-dashboard.tsx
'use client';
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api';
import { RevenueAreaChart } from './revenue-area-chart';
import { MonthlyBarChart } from './monthly-bar-chart';
import { PlatformPieChart } from './platform-pie-chart';
export function MetricsDashboard() {
const { data: metrics, isLoading } = useQuery({
queryKey: ['dashboard-metrics'],
queryFn: () => apiFetch<DashboardMetrics>('/analytics/dashboard'),
refetchInterval: 5 * 60 * 1000, // Refetch every 5 minutes
});
return (
<div className="space-y-6">
{/* KPI Cards */}
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
{[
{ label: 'MRR', value: metrics?.mrr, format: 'currency', trend: '+12%' },
{ label: 'ARR', value: metrics?.arr, format: 'currency', trend: '+8%' },
{ label: 'Customers', value: metrics?.customers, format: 'number', trend: '+23' },
{ label: 'Churn', value: metrics?.churnRate, format: 'percent', trend: '-0.3%' },
].map(({ label, value, format, trend }) => (
<div key={label} className="rounded-lg border bg-card p-4">
<p className="text-sm text-muted-foreground">{label}</p>
<p className="mt-1 text-2xl font-bold">
{isLoading ? (
<span className="inline-block h-8 w-24 animate-pulse rounded bg-muted" />
) : (
formatMetric(value, format)
)}
</p>
<p className="mt-1 text-sm text-green-500">{trend} this month</p>
</div>
))}
</div>
{/* Charts */}
<div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2 rounded-lg border bg-card p-6">
<h3 className="mb-4 text-lg font-semibold">Revenue Trend</h3>
<RevenueAreaChart data={metrics?.revenueByDay ?? []} loading={isLoading} />
</div>
<div className="rounded-lg border bg-card p-6">
<h3 className="mb-4 text-lg font-semibold">Revenue by Platform</h3>
<PlatformPieChart data={metrics?.revenueByPlatform ?? []} />
</div>
</div>
<div className="rounded-lg border bg-card p-6">
<h3 className="mb-4 text-lg font-semibold">Monthly Comparison</h3>
<MonthlyBarChart data={metrics?.monthlyComparison ?? []} />
</div>
</div>
);
}
function formatMetric(value: number | undefined, format: string): string {
if (value === undefined) return '—';
const fmt = new Intl.NumberFormat('en-US');
if (format === 'currency') return `$${fmt.format(value)}`;
if (format === 'percent') return `${value.toFixed(1)}%`;
return fmt.format(value);
}
اکثر پوچھے گئے سوالات
Recharts Tooltip فارمیٹر کال بیکس کو اپنے پیرامیٹرز کو واضح طور پر کیوں نہیں ٹائپ کرنا چاہیے؟
formatter prop کے لیے Recharts کی TypeScript کی قسمیں ان طریقوں سے اوورلوڈ ہوتی ہیں جو آپ کے پیرامیٹرز کی تشریح کرتے وقت قسم کے تنازعات کا باعث بنتی ہیں۔ صحیح نمونہ یہ ہے کہ Number(value) کا استعمال value کی قسم کی تشریح کیے بغیر قدر کو محفوظ طریقے سے تبدیل کریں۔ اگر آپ اسے (value: number) کے بطور تشریح کرتے ہیں، تو ریچارٹس کے داخلی دستخط میں یونین کی قسم کی وجہ سے ٹائپ اسکرپٹ میں خرابی ہوسکتی ہے۔ TypeScript کو قسم کا اندازہ لگانے دیں اور Number(value) کے ساتھ تبدیل کریں۔
میں Recharts چارٹس کو ڈارک موڈ میں کیسے کام کروں؟
ہارڈ کوڈ شدہ ہیکس ویلیوز کے بجائے تمام رنگوں کے لیے CSS کسٹم پراپرٹیز (متغیرات) استعمال کریں۔ گرڈ لائنوں کے لیے hsl(var(--border))، محور متن کے لیے hsl(var(--muted-foreground))، اور ٹول ٹپ پس منظر کے لیے hsl(var(--background)) کے ساتھ ان کا حوالہ دیں۔ یہ متغیرات آپ کے Tailwind تھیم میں بیان کیے گئے ہیں اور <html> پر dark کلاس کے ذریعے لائٹ اور ڈارک موڈ کے درمیان اقدار کو خود بخود سوئچ کر دیتے ہیں۔ کبھی بھی ہارڈ کوڈ نہ کریں #374151 - یہ تھیم کے ساتھ اپ ڈیٹ نہیں ہوگا۔
میں چارٹس کے لیے خالی یا لوڈنگ اسٹیٹس کو کیسے ہینڈل کروں؟
لوڈنگ کے لیے: چارٹ کو سکیلیٹن (animate-pulse bg-muted rounded-lg h-80) سے تبدیل کریں۔ خالی ڈیٹا کے لیے: ایک ResponsiveContainer کے اندر مرکز میں خالی حالت کا پیغام بھیجیں۔ جزوی ڈیٹا کے لیے: ریچارٹس خوبصورتی سے ویرل ڈیٹا کو ہینڈل کرتا ہے — جو آپ کے پاس ہے اسے صرف پاس کریں اور یہ نقطوں کو جوڑتا ہے (یا connectNulls={false} کے ساتھ خالی اقدار کے لیے خلا دکھاتا ہے)۔
کیا مجھے Recharts یا Chart.js یا D3 استعمال کرنا چاہیے؟
ری ایکٹ کے مقامی ڈیش بورڈز کے لیے ریچارٹس — یہ اعلانیہ، کمپوز ایبل، اور قدرتی طور پر React کے رینڈرنگ ماڈل کے ساتھ مربوط ہوتا ہے۔ Chart.js آسان استعمال کے معاملات کے لیے جہاں آپ کینوس پر مبنی رینڈرر چاہتے ہیں (10,000+ ڈیٹا پوائنٹس کے لیے بہتر کارکردگی)۔ D3 جب آپ کو اپنی مرضی کے مطابق، غیر معیاری چارٹ کی قسموں کی ضرورت ہوتی ہے جن کا احاطہ کوئی لائبریری نہیں کرتا — نمایاں طور پر مزید کوڈ کی توقع کریں۔ ایک درخواست میں متعدد چارٹنگ لائبریریوں کو ملانے سے گریز کریں۔
میں رپورٹس کے لیے تصاویر کے بطور چارٹ کیسے برآمد کروں؟
چارٹ کنٹینر کو بطور تصویر کیپچر کرنے کے لیے html2canvas لائبریری کا استعمال کریں: const canvas = await html2canvas(chartRef.current); const url = canvas.toDataURL('image/png');۔ متبادل طور پر، Recharts کا بلٹ ان SVG آؤٹ پٹ استعمال کریں — چونکہ Recharts SVG کو رینڈر کرتا ہے، آپ ویکٹر ایکسپورٹ کے لیے براہ راست SVG عنصر کو سیریلائز کر سکتے ہیں۔ پی ڈی ایف رپورٹس کے لیے، Puppeteer یا ہیڈ لیس براؤزر کا استعمال کرتے ہوئے چارٹس سرور سائیڈ رینڈر کریں۔
اگلے اقدامات
ڈیٹا ویژولائزیشن خام میٹرکس کو کاروباری ذہانت میں بدل دیتی ہے۔ اس گائیڈ میں پیٹرن — ریسپانسیو کنٹینرز، حسب ضرورت ٹول ٹِپس، ریئل ٹائم اسٹریمنگ، اور تھیم سے آگاہ رنگ — آپ کو ڈیش بورڈ UIs کی بنیاد فراہم کرتے ہیں جو کسی بھی ڈیوائس پر اور کسی بھی رنگ سکیم میں واضح طور پر بات چیت کرتے ہیں۔
ECOSIRE مالی، آپریشنل، اور کارکردگی کے میٹرکس کے لیے 8 ڈیش بورڈ ماڈیولز پر ریچارٹس کے ساتھ تجزیاتی ڈیش بورڈز بناتا ہے — NestJS APIs اور Drizzle سے چلنے والے PostgreSQL کے لائیو ڈیٹا کے ساتھ۔ کاروباری ذہانت اور پاور BI کے نفاذ کے لیے، ہماری Power BI سروسز کو دریافت کریں یا ہماری تمام سروسز دیکھیں۔
تحریر
ECOSIRE Research and Development Team
ECOSIRE میں انٹرپرائز گریڈ ڈیجیٹل مصنوعات بنانا۔ Odoo انٹیگریشنز، ای کامرس آٹومیشن، اور AI سے چلنے والے کاروباری حل پر بصیرت شیئر کرنا۔
متعلقہ مضامین
Building Financial Dashboards with Power BI
Step-by-step guide to building financial dashboards in Power BI covering data connections to accounting systems, DAX measures for KPIs, P&L visualisations, and best practices.
GoHighLevel Reporting and Analytics: Measuring What Matters
Master GoHighLevel reporting and analytics. Learn to build custom dashboards, track ROI across channels, measure funnel conversion, and make data-driven marketing decisions.
Next.js 16 App Router: Production Patterns and Pitfalls
Production-ready Next.js 16 App Router patterns: server components, caching strategies, metadata API, error boundaries, and performance pitfalls to avoid.