data-visualizer by daffy0208/ai-dev-standards
npx skills add https://github.com/daffy0208/ai-dev-standards --skill data-visualizer我帮助你构建美观、交互式的数据可视化和仪表板。
图表创建:
仪表板构建:
数据呈现:
最适合:
示例:
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'
const data = [
{ month: 'Jan', revenue: 4000, expenses: 2400 },
{ month: 'Feb', revenue: 3000, expenses: 1398 },
{ month: 'Mar', revenue: 2000, expenses: 9800 },
]
function RevenueChart() {
return (
<LineChart width={600} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="revenue" stroke="#8884d8" />
<Line type="monotone" dataKey="expenses" stroke="#82ca9d" />
</LineChart>
)
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
最适合:
示例:
import { Chart } from 'chart.js/auto'
const ctx = document.getElementById('myChart')
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [
{
label: 'Sales',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'rgba(54, 162, 235, 0.5)'
}
]
},
options: {
responsive: true,
plugins: {
legend: { position: 'top' },
title: { display: true, text: 'Monthly Sales' }
}
}
})
最适合:
何时使用:
示例:
import * as d3 from 'd3'
function createBarChart(data: Array<{ name: string; value: number }>) {
const width = 600
const height = 400
const margin = { top: 20, right: 20, bottom: 30, left: 40 }
const svg = d3.select('#chart').append('svg').attr('width', width).attr('height', height)
const x = d3
.scaleBand()
.domain(data.map(d => d.name))
.range([margin.left, width - margin.right])
.padding(0.1)
const y = d3
.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([height - margin.bottom, margin.top])
svg
.selectAll('rect')
.data(data)
.join('rect')
.attr('x', d => x(d.name))
.attr('y', d => y(d.value))
.attr('height', d => y(0) - y(d.value))
.attr('width', x.bandwidth())
.attr('fill', 'steelblue')
// Add axes
svg
.append('g')
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
svg.append('g').attr('transform', `translate(${margin.left},0)`).call(d3.axisLeft(y))
}
使用场景: 包含关键指标的执行仪表板
// components/KPIDashboard.tsx
import { Card } from '@/components/ui/card'
interface KPICardProps {
title: string
value: string | number
change: number
trend: 'up' | 'down'
}
function KPICard({ title, value, change, trend }: KPICardProps) {
const trendColor = trend === 'up' ? 'text-green-600' : 'text-red-600'
const trendIcon = trend === 'up' ? '↑' : '↓'
return (
<Card className="p-6">
<h3 className="text-sm font-medium text-gray-600">{title}</h3>
<div className="mt-2 flex items-baseline">
<p className="text-3xl font-semibold">{value}</p>
<span className={`ml-2 text-sm ${trendColor}`}>
{trendIcon} {Math.abs(change)}%
</span>
</div>
</Card>
)
}
export default function Dashboard() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<KPICard title="Total Revenue" value="$45,231" change={12.5} trend="up" />
<KPICard title="Active Users" value="2,350" change={-5.2} trend="down" />
<KPICard title="Conversion Rate" value="3.24%" change={8.1} trend="up" />
<KPICard title="Avg Order Value" value="$158" change={2.3} trend="up" />
</div>
)
}
使用场景: 实时数据监控
// components/RealtimeDashboard.tsx
'use client'
import { useEffect, useState } from 'react'
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
interface DataPoint {
time: string
value: number
}
export default function RealtimeDashboard() {
const [data, setData] = useState<DataPoint[]>([])
useEffect(() => {
// Fetch initial data
fetch('/api/metrics/realtime')
.then(res => res.json())
.then(setData)
// Subscribe to real-time updates
const eventSource = new EventSource('/api/metrics/stream')
eventSource.onmessage = (event) => {
const newDataPoint = JSON.parse(event.data)
setData(prev => {
const updated = [...prev, newDataPoint]
// Keep last 20 data points
return updated.slice(-20)
})
}
return () => eventSource.close()
}, [])
return (
<div className="p-6 bg-white rounded-lg shadow">
<h2 className="text-xl font-bold mb-4">Live Traffic</h2>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="value"
stroke="#8884d8"
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
)
}
SSE 的 API 路由:
// app/api/metrics/stream/route.ts
export async function GET(req: Request) {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const interval = setInterval(async () => {
const value = Math.floor(Math.random() * 100)
const time = new Date().toLocaleTimeString()
const data = `data: ${JSON.stringify({ time, value })}\n\n`
controller.enqueue(encoder.encode(data))
}, 1000)
// Cleanup on close
req.signal.addEventListener('abort', () => {
clearInterval(interval)
controller.close()
})
}
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
})
}
// components/SalesDashboard.tsx
'use client'
import { useState } from 'react'
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
type Period = '7d' | '30d' | '90d'
type Region = 'all' | 'us' | 'eu' | 'asia'
export default function SalesDashboard() {
const [period, setPeriod] = useState<Period>('30d')
const [region, setRegion] = useState<Region>('all')
const { data, loading } = useSalesData({ period, region })
return (
<div className="space-y-6">
{/* Filters */}
<div className="flex gap-4">
<select
value={period}
onChange={(e) => setPeriod(e.target.value as Period)}
className="px-4 py-2 border rounded"
>
<option value="7d">Last 7 days</option>
<option value="30d">Last 30 days</option>
<option value="90d">Last 90 days</option>
</select>
<select
value={region}
onChange={(e) => setRegion(e.target.value as Region)}
className="px-4 py-2 border rounded"
>
<option value="all">All Regions</option>
<option value="us">United States</option>
<option value="eu">Europe</option>
<option value="asia">Asia</option>
</select>
</div>
{/* Chart */}
{loading ? (
<div>Loading...</div>
) : (
<ResponsiveContainer width="100%" height={400}>
<BarChart data={data}>
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Bar dataKey="sales" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
)}
</div>
)
}
// Custom hook for data fetching
function useSalesData({ period, region }: { period: Period, region: Region }) {
const [data, setData] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(true)
fetch(`/api/sales?period=${period}®ion=${region}`)
.then(res => res.json())
.then(data => {
setData(data)
setLoading(false)
})
}, [period, region])
return { data, loading }
}
最适合: 随时间变化的趋势、连续数据
<LineChart data={data}>
<Line type="monotone" dataKey="value" stroke="#8884d8" />
</LineChart>
何时使用:
最适合: 比较类别
<BarChart data={data}>
<Bar dataKey="value" fill="#8884d8" />
</BarChart>
何时使用:
最适合: 部分与整体的关系
<PieChart>
<Pie data={data} dataKey="value" nameKey="name" fill="#8884d8" />
</PieChart>
何时使用:
⚠️ 避免在以下情况使用:
最适合: 随时间变化的量
<AreaChart data={data}>
<Area type="monotone" dataKey="value" fill="#8884d8" />
</AreaChart>
何时使用:
最适合: 变量之间的相关性
<ScatterChart>
<Scatter data={data} fill="#8884d8" />
</ScatterChart>
何时使用:
最适合: 两个维度上的强度
// Using D3
const colorScale = d3.scaleSequential(d3.interpolateBlues).domain([0, d3.max(data)])
svg
.selectAll('rect')
.data(data)
.join('rect')
.attr('fill', d => colorScale(d.value))
何时使用:
'use client'
import { useEffect, useState } from 'react'
import { LineChart, Line, ResponsiveContainer } from 'recharts'
export default function ResponsiveChart({ data }) {
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => setIsMobile(window.innerWidth < 768)
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
return (
<ResponsiveContainer width="100%" height={isMobile ? 200 : 400}>
<LineChart data={data}>
<Line
dataKey="value"
stroke="#8884d8"
strokeWidth={isMobile ? 1 : 2}
/>
</LineChart>
</ResponsiveContainer>
)
}
// colors.ts
export const chartColors = {
// WCAG AA compliant
primary: '#0066CC', // Blue
success: '#007A3D', // Green
warning: '#C87000', // Orange
danger: '#D32F2F', // Red
// Multi-series (colorblind-safe)
series: [
'#0066CC', // Blue
'#CC6600', // Orange
'#7A00CC', // Purple
'#00CC66', // Green
'#CC0066' // Magenta
]
}
色盲安全调色板:
// For up to 5 data series
const colorblindSafe = [
'#000000', // Black
'#E69F00', // Orange
'#56B4E9', // Sky Blue
'#009E73', // Green
'#F0E442' // Yellow
]
// utils/formatters.ts
export function formatCurrency(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(value)
}
export function formatPercent(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}).format(value / 100)
}
export function formatNumber(value: number): string {
if (value >= 1000000) {
return `${(value / 1000000).toFixed(1)}M`
}
if (value >= 1000) {
return `${(value / 1000).toFixed(1)}K`
}
return value.toFixed(0)
}
// Usage in chart
<YAxis tickFormatter={formatCurrency} />
'use client'
import html2canvas from 'html2canvas'
export function ExportableChart({ children }) {
const chartRef = useRef<HTMLDivElement>(null)
const exportToPNG = async () => {
if (!chartRef.current) return
const canvas = await html2canvas(chartRef.current)
const link = document.createElement('a')
link.download = 'chart.png'
link.href = canvas.toDataURL()
link.click()
}
return (
<div>
<button onClick={exportToPNG} className="mb-4 px-4 py-2 bg-blue-600 text-white rounded">
Export as PNG
</button>
<div ref={chartRef}>
{children}
</div>
</div>
)
}
export function exportToCSV(data: any[], filename: string) {
const headers = Object.keys(data[0])
const csv = [
headers.join(','),
...data.map(row => headers.map(h => row[h]).join(','))
].join('\n')
const blob = new Blob([csv], { type: 'text/csv' })
const link = document.createElement('a')
link.download = `${filename}.csv`
link.href = URL.createObjectURL(blob)
link.click()
}
// Usage
<button onClick={() => exportToCSV(data, 'sales-data')}>
Export to CSV
</button>
// Lazy load chart libraries (reduce initial bundle)
import dynamic from 'next/dynamic'
const LineChart = dynamic(
() => import('recharts').then(mod => mod.LineChart),
{ ssr: false }
)
export default function ChartPage() {
return <LineChart data={data} />
}
import { useVirtualizer } from '@tanstack/react-virtual'
export function LargeDataTable({ data }: { data: any[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: data.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
})
return (
<div ref={parentRef} className="h-96 overflow-auto">
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div key={virtualRow.index} className="py-2 border-b">
{data[virtualRow.index].name}: {data[virtualRow.index].value}
</div>
))}
</div>
</div>
)
}
<LineChart data={data}>
<Line
type="monotone"
dataKey="value"
stroke="#8884d8"
animationDuration={500}
animationEasing="ease-in-out"
/>
</LineChart>
// For real-time dashboards, disable animation
<Line
dataKey="value"
isAnimationActive={false}
/>
'use client'
import { useState } from 'react'
import { BarChart, Bar, XAxis, YAxis } from 'recharts'
export default function DrillDownChart() {
const [level, setLevel] = useState<'year' | 'month' | 'day'>('year')
const [selectedYear, setSelectedYear] = useState<number | null>(null)
const handleBarClick = (data: any) => {
if (level === 'year') {
setSelectedYear(data.year)
setLevel('month')
} else if (level === 'month') {
setLevel('day')
}
}
const goBack = () => {
if (level === 'day') setLevel('month')
else if (level === 'month') {
setLevel('year')
setSelectedYear(null)
}
}
return (
<div>
{level !== 'year' && (
<button onClick={goBack} className="mb-4">← Back</button>
)}
<BarChart data={getData(level, selectedYear)} width={600} height={300}>
<Bar dataKey="value" fill="#8884d8" onClick={handleBarClick} />
<XAxis dataKey="name" />
<YAxis />
</BarChart>
</div>
)
}
非常适合:
我将帮助你:
📊 Charts and Visualizations
📈 KPI Dashboards
🎨 Custom Color Schemes
📱 Responsive Layouts
⚡ Real-Time Updates
💾 Export Functionality
让我们让你的数据变得美观且易于理解!
每周安装量
290
仓库
GitHub Stars
18
首次出现
Jan 20, 2026
安全审计
安装在
opencode252
gemini-cli234
codex230
cursor221
github-copilot213
claude-code195
I help you build beautiful, interactive data visualizations and dashboards.
Chart Creation:
Dashboard Building:
Data Presentation:
Best for:
Example:
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'
const data = [
{ month: 'Jan', revenue: 4000, expenses: 2400 },
{ month: 'Feb', revenue: 3000, expenses: 1398 },
{ month: 'Mar', revenue: 2000, expenses: 9800 },
]
function RevenueChart() {
return (
<LineChart width={600} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="revenue" stroke="#8884d8" />
<Line type="monotone" dataKey="expenses" stroke="#82ca9d" />
</LineChart>
)
}
Best for:
Example:
import { Chart } from 'chart.js/auto'
const ctx = document.getElementById('myChart')
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [
{
label: 'Sales',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'rgba(54, 162, 235, 0.5)'
}
]
},
options: {
responsive: true,
plugins: {
legend: { position: 'top' },
title: { display: true, text: 'Monthly Sales' }
}
}
})
Best for:
When to use:
Example:
import * as d3 from 'd3'
function createBarChart(data: Array<{ name: string; value: number }>) {
const width = 600
const height = 400
const margin = { top: 20, right: 20, bottom: 30, left: 40 }
const svg = d3.select('#chart').append('svg').attr('width', width).attr('height', height)
const x = d3
.scaleBand()
.domain(data.map(d => d.name))
.range([margin.left, width - margin.right])
.padding(0.1)
const y = d3
.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([height - margin.bottom, margin.top])
svg
.selectAll('rect')
.data(data)
.join('rect')
.attr('x', d => x(d.name))
.attr('y', d => y(d.value))
.attr('height', d => y(0) - y(d.value))
.attr('width', x.bandwidth())
.attr('fill', 'steelblue')
// Add axes
svg
.append('g')
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
svg.append('g').attr('transform', `translate(${margin.left},0)`).call(d3.axisLeft(y))
}
Use case: Executive dashboard with key metrics
// components/KPIDashboard.tsx
import { Card } from '@/components/ui/card'
interface KPICardProps {
title: string
value: string | number
change: number
trend: 'up' | 'down'
}
function KPICard({ title, value, change, trend }: KPICardProps) {
const trendColor = trend === 'up' ? 'text-green-600' : 'text-red-600'
const trendIcon = trend === 'up' ? '↑' : '↓'
return (
<Card className="p-6">
<h3 className="text-sm font-medium text-gray-600">{title}</h3>
<div className="mt-2 flex items-baseline">
<p className="text-3xl font-semibold">{value}</p>
<span className={`ml-2 text-sm ${trendColor}`}>
{trendIcon} {Math.abs(change)}%
</span>
</div>
</Card>
)
}
export default function Dashboard() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<KPICard title="Total Revenue" value="$45,231" change={12.5} trend="up" />
<KPICard title="Active Users" value="2,350" change={-5.2} trend="down" />
<KPICard title="Conversion Rate" value="3.24%" change={8.1} trend="up" />
<KPICard title="Avg Order Value" value="$158" change={2.3} trend="up" />
</div>
)
}
Use case: Live data monitoring
// components/RealtimeDashboard.tsx
'use client'
import { useEffect, useState } from 'react'
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
interface DataPoint {
time: string
value: number
}
export default function RealtimeDashboard() {
const [data, setData] = useState<DataPoint[]>([])
useEffect(() => {
// Fetch initial data
fetch('/api/metrics/realtime')
.then(res => res.json())
.then(setData)
// Subscribe to real-time updates
const eventSource = new EventSource('/api/metrics/stream')
eventSource.onmessage = (event) => {
const newDataPoint = JSON.parse(event.data)
setData(prev => {
const updated = [...prev, newDataPoint]
// Keep last 20 data points
return updated.slice(-20)
})
}
return () => eventSource.close()
}, [])
return (
<div className="p-6 bg-white rounded-lg shadow">
<h2 className="text-xl font-bold mb-4">Live Traffic</h2>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="value"
stroke="#8884d8"
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
)
}
API Route for SSE:
// app/api/metrics/stream/route.ts
export async function GET(req: Request) {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const interval = setInterval(async () => {
const value = Math.floor(Math.random() * 100)
const time = new Date().toLocaleTimeString()
const data = `data: ${JSON.stringify({ time, value })}\n\n`
controller.enqueue(encoder.encode(data))
}, 1000)
// Cleanup on close
req.signal.addEventListener('abort', () => {
clearInterval(interval)
controller.close()
})
}
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
})
}
// components/SalesDashboard.tsx
'use client'
import { useState } from 'react'
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
type Period = '7d' | '30d' | '90d'
type Region = 'all' | 'us' | 'eu' | 'asia'
export default function SalesDashboard() {
const [period, setPeriod] = useState<Period>('30d')
const [region, setRegion] = useState<Region>('all')
const { data, loading } = useSalesData({ period, region })
return (
<div className="space-y-6">
{/* Filters */}
<div className="flex gap-4">
<select
value={period}
onChange={(e) => setPeriod(e.target.value as Period)}
className="px-4 py-2 border rounded"
>
<option value="7d">Last 7 days</option>
<option value="30d">Last 30 days</option>
<option value="90d">Last 90 days</option>
</select>
<select
value={region}
onChange={(e) => setRegion(e.target.value as Region)}
className="px-4 py-2 border rounded"
>
<option value="all">All Regions</option>
<option value="us">United States</option>
<option value="eu">Europe</option>
<option value="asia">Asia</option>
</select>
</div>
{/* Chart */}
{loading ? (
<div>Loading...</div>
) : (
<ResponsiveContainer width="100%" height={400}>
<BarChart data={data}>
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Bar dataKey="sales" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
)}
</div>
)
}
// Custom hook for data fetching
function useSalesData({ period, region }: { period: Period, region: Region }) {
const [data, setData] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(true)
fetch(`/api/sales?period=${period}®ion=${region}`)
.then(res => res.json())
.then(data => {
setData(data)
setLoading(false)
})
}, [period, region])
return { data, loading }
}
Best for: Trends over time, continuous data
<LineChart data={data}>
<Line type="monotone" dataKey="value" stroke="#8884d8" />
</LineChart>
Use when:
Best for: Comparing categories
<BarChart data={data}>
<Bar dataKey="value" fill="#8884d8" />
</BarChart>
Use when:
Best for: Part-to-whole relationships
<PieChart>
<Pie data={data} dataKey="value" nameKey="name" fill="#8884d8" />
</PieChart>
Use when:
⚠️ Avoid when:
Best for: Volume over time
<AreaChart data={data}>
<Area type="monotone" dataKey="value" fill="#8884d8" />
</AreaChart>
Use when:
Best for: Correlation between variables
<ScatterChart>
<Scatter data={data} fill="#8884d8" />
</ScatterChart>
Use when:
Best for: Intensity across two dimensions
// Using D3
const colorScale = d3.scaleSequential(d3.interpolateBlues).domain([0, d3.max(data)])
svg
.selectAll('rect')
.data(data)
.join('rect')
.attr('fill', d => colorScale(d.value))
Use when:
'use client'
import { useEffect, useState } from 'react'
import { LineChart, Line, ResponsiveContainer } from 'recharts'
export default function ResponsiveChart({ data }) {
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => setIsMobile(window.innerWidth < 768)
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
return (
<ResponsiveContainer width="100%" height={isMobile ? 200 : 400}>
<LineChart data={data}>
<Line
dataKey="value"
stroke="#8884d8"
strokeWidth={isMobile ? 1 : 2}
/>
</LineChart>
</ResponsiveContainer>
)
}
// colors.ts
export const chartColors = {
// WCAG AA compliant
primary: '#0066CC', // Blue
success: '#007A3D', // Green
warning: '#C87000', // Orange
danger: '#D32F2F', // Red
// Multi-series (colorblind-safe)
series: [
'#0066CC', // Blue
'#CC6600', // Orange
'#7A00CC', // Purple
'#00CC66', // Green
'#CC0066' // Magenta
]
}
Colorblind-Safe Palettes:
// For up to 5 data series
const colorblindSafe = [
'#000000', // Black
'#E69F00', // Orange
'#56B4E9', // Sky Blue
'#009E73', // Green
'#F0E442' // Yellow
]
// utils/formatters.ts
export function formatCurrency(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(value)
}
export function formatPercent(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}).format(value / 100)
}
export function formatNumber(value: number): string {
if (value >= 1000000) {
return `${(value / 1000000).toFixed(1)}M`
}
if (value >= 1000) {
return `${(value / 1000).toFixed(1)}K`
}
return value.toFixed(0)
}
// Usage in chart
<YAxis tickFormatter={formatCurrency} />
'use client'
import html2canvas from 'html2canvas'
export function ExportableChart({ children }) {
const chartRef = useRef<HTMLDivElement>(null)
const exportToPNG = async () => {
if (!chartRef.current) return
const canvas = await html2canvas(chartRef.current)
const link = document.createElement('a')
link.download = 'chart.png'
link.href = canvas.toDataURL()
link.click()
}
return (
<div>
<button onClick={exportToPNG} className="mb-4 px-4 py-2 bg-blue-600 text-white rounded">
Export as PNG
</button>
<div ref={chartRef}>
{children}
</div>
</div>
)
}
export function exportToCSV(data: any[], filename: string) {
const headers = Object.keys(data[0])
const csv = [
headers.join(','),
...data.map(row => headers.map(h => row[h]).join(','))
].join('\n')
const blob = new Blob([csv], { type: 'text/csv' })
const link = document.createElement('a')
link.download = `${filename}.csv`
link.href = URL.createObjectURL(blob)
link.click()
}
// Usage
<button onClick={() => exportToCSV(data, 'sales-data')}>
Export to CSV
</button>
// Lazy load chart libraries (reduce initial bundle)
import dynamic from 'next/dynamic'
const LineChart = dynamic(
() => import('recharts').then(mod => mod.LineChart),
{ ssr: false }
)
export default function ChartPage() {
return <LineChart data={data} />
}
import { useVirtualizer } from '@tanstack/react-virtual'
export function LargeDataTable({ data }: { data: any[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: data.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
})
return (
<div ref={parentRef} className="h-96 overflow-auto">
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div key={virtualRow.index} className="py-2 border-b">
{data[virtualRow.index].name}: {data[virtualRow.index].value}
</div>
))}
</div>
</div>
)
}
<LineChart data={data}>
<Line
type="monotone"
dataKey="value"
stroke="#8884d8"
animationDuration={500}
animationEasing="ease-in-out"
/>
</LineChart>
// For real-time dashboards, disable animation
<Line
dataKey="value"
isAnimationActive={false}
/>
'use client'
import { useState } from 'react'
import { BarChart, Bar, XAxis, YAxis } from 'recharts'
export default function DrillDownChart() {
const [level, setLevel] = useState<'year' | 'month' | 'day'>('year')
const [selectedYear, setSelectedYear] = useState<number | null>(null)
const handleBarClick = (data: any) => {
if (level === 'year') {
setSelectedYear(data.year)
setLevel('month')
} else if (level === 'month') {
setLevel('day')
}
}
const goBack = () => {
if (level === 'day') setLevel('month')
else if (level === 'month') {
setLevel('year')
setSelectedYear(null)
}
}
return (
<div>
{level !== 'year' && (
<button onClick={goBack} className="mb-4">← Back</button>
)}
<BarChart data={getData(level, selectedYear)} width={600} height={300}>
<Bar dataKey="value" fill="#8884d8" onClick={handleBarClick} />
<XAxis dataKey="name" />
<YAxis />
</BarChart>
</div>
)
}
Perfect for:
I'll help you:
📊 Charts and Visualizations
📈 KPI Dashboards
🎨 Custom Color Schemes
📱 Responsive Layouts
⚡ Real-Time Updates
💾 Export Functionality
Let's make your data beautiful and understandable!
Weekly Installs
290
Repository
GitHub Stars
18
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode252
gemini-cli234
codex230
cursor221
github-copilot213
claude-code195
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装