mcpField
D
data-visualizer

data-visualizer

github.com/daffy0208数据与数据库38572 次安装44 周前更新
代码仓库

关于 data-visualizer

面向构建数据图表和仪表盘的技能,适用于开发数据可视化的团队与开发者。内容涵盖图表库选择、交互式与实时仪表盘、响应式布局、无障碍配色及导出。

主要功能

  1. 01多种图表类型
  2. 02交互式仪表盘与筛选
  3. 03实时数据展示模式
  4. 04图表库选择建议
  5. 05响应式布局与无障碍配色
  6. 06导出图表和 CSV 数据

适用场景

  1. 01构建 KPI 分析仪表盘
  2. 02制作实时监控面板
  3. 03展示类别比较或时间趋势
  4. 04为图表添加筛选与下钻

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/daffy0208/ai-dev-standards --skill data-visualizer

安装后在 Claude Code 中输入/data-visualizer确认已加载。

附带文件manifest.yamlREADME.md

详细介绍

数据可视化技能

我能帮你构建精美、交互式的数据可视化图表和仪表板。

我能做什么

图表创建:

  • 折线图、条形图、饼图
  • 面积图、散点图、热力图
  • 复杂可视化图表(桑基图、矩形树图、网络图)

仪表板构建:

  • KPI 卡片和指标
  • 实时数据仪表板
  • 交互式筛选器和下钻功能
  • 响应式布局

数据展示:

  • 数据叙事
  • 配色方案和无障碍设计
  • 动画和交互
  • 导出功能

库选择指南

Recharts(推荐用于 React)

最适合:

  • 快速创建简单图表
  • React/Next.js 项目
  • 标准图表类型
  • 响应式设计

示例:

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>
  )
}

Chart.js(推荐用于 Vue/Angular)

最适合:

  • 与框架无关
  • 简洁的 API
  • 完善的文档
  • 标准图表类型

示例:

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' }
    }
  }
})

D3.js(高级)

最适合:

  • 自定义可视化图表
  • 复杂交互
  • 完全控制渲染
  • 数据驱动文档

适用场景:

  • 需要自定义图表类型
  • 复杂的数据转换
  • 高级交互
  • 出版级图形

示例:

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))
}

仪表板模式

模式 1:KPI 仪表板

使用场景: 包含关键指标的高管仪表板

// 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>
  )
}

模式 2:实时仪表板

使用场景: 实时数据监控

// 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'
    }
  })
}

模式 3:带筛选器的交互式仪表板
// 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}&region=${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>

适用场景:

  • 市场份额、预算分配
  • 占比(最多 5-7 个扇区)
  • 简单的百分比

⚠️ 避免用于:

  • 类别过多(> 7)
  • 需要精确比较(请使用条形图)

面积图

最适合: 展示随时间变化的数量

<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} />

导出功能

将图表导出为 PNG
'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>
  )
}
将数据导出为 CSV
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

让你的数据变得美观且易于理解!

评论

支持 Markdown