You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何制作类似Stripe仪表盘的图表?

复刻Stripe仪表盘图表的实现指导

核心视觉特征拆解

先明确目标图表的关键效果,对应你提供的Stripe仪表盘图表:

  • 带半透明渐变填充的平滑面积折线图
  • 关联数据点的精准悬停提示框
  • 自定义样式的时间轴(X轴)与数值轴(Y轴)
  • 可选的同比对比虚线/底层阴影层

工具选择与实操方案

方案1:使用Recharts(上手友好,定制性强)

Recharts是React生态中成熟的图表库,比React-charts更灵活,完全能实现Stripe的图表效果。

基础实现代码示例

import React from 'react';
import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';

// 模拟Stripe风格的数据集
const data = [
  { date: '2024-01-01', value: 1200, prevValue: 900 },
  { date: '2024-01-02', value: 1500, prevValue: 1100 },
  { date: '2024-01-03', value: 1300, prevValue: 1200 },
  { date: '2024-01-04', value: 1800, prevValue: 1400 },
  { date: '2024-01-05', value: 2000, prevValue: 1600 },
  { date: '2024-01-06', value: 1700, prevValue: 1500 },
  { date: '2024-01-07', value: 2200, prevValue: 1800 },
];

// 定义渐变填充
const ChartGradient = () => (
  <defs>
    <linearGradient id="valueGradient" x1="0" y1="0" x2="0" y2="1">
      <stop offset="5%" stopColor="#6366F1" stopOpacity={0.8}/>
      <stop offset="95%" stopColor="#6366F1" stopOpacity={0}/>
    </linearGradient>
  </defs>
);

const StripeStyleChart = () => {
  return (
    <ResponsiveContainer width="100%" height={300}>
      <AreaChart data={data} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
        <ChartGradient />
        {/* 同比对比虚线层 */}
        <Area type="monotone" dataKey="prevValue" stroke="#E5E7EB" strokeWidth={1} fill="none" strokeDasharray="3 3" />
        {/* 主面积折线图 */}
        <Area type="monotone" dataKey="value" stroke="#6366F1" strokeWidth={2} fill="url(#valueGradient)" />
        {/* 自定义X轴样式 */}
        <XAxis dataKey="date" tick={{ fontSize: 12, fill: '#6B7280' }} />
        {/* 自定义Y轴样式 */}
        <YAxis tick={{ fontSize: 12, fill: '#6B7280' }} />
        {/* 贴近Stripe风格的提示框 */}
        <Tooltip 
          contentStyle={{ backgroundColor: '#FFFFFF', border: '1px solid #E5E7EB', borderRadius: '8px', padding: '12px' }}
          labelStyle={{ fontSize: 14, fontWeight: 600, marginBottom: '8px' }}
          formatter={(value) => [`$${value.toLocaleString()}`, '收入']}
        />
      </AreaChart>
    </ResponsiveContainer>
  );
};

export default StripeStyleChart;

关键定制点

  • 渐变填充:通过<defs>定义线性渐变,实现Stripe的半透明渐变效果
  • 平滑曲线:type="monotone"保证折线过渡自然
  • 提示框定制:修改contentStyle和formatter匹配Stripe的提示框样式
  • 同比对比层:添加底层虚线面积图,模拟Stripe的同比数据展示

方案2:使用visx(底层控制,精准定制)

如果坚持用visx,可以拆分基础组件逐步实现,降低复杂度:

核心组件组合思路

  • 用@visx/scale处理数据到像素的映射
  • 用@visx/shape绘制面积折线图
  • 用@visx/tooltip实现悬停交互
  • 用@visx/axis绘制自定义坐标轴

简化实现片段

import React from 'react';
import { scaleTime, scaleLinear } from '@visx/scale';
import { AreaClosed } from '@visx/shape';
import { AxisBottom, AxisLeft } from '@visx/axis';
import { useTooltip, Tooltip, defaultStyles } from '@visx/tooltip';
import { localPoint } from '@visx/event';

const data = [...]; // 同Recharts的数据集

const StripeVisxChart = () => {
  const width = 800;
  const height = 300;
  const margin = { top: 10, right: 30, left: 40, bottom: 30 };
  
  // 定义比例尺
  const xScale = scaleTime({
    domain: [new Date(data[0].date), new Date(data[data.length-1].date)],
    range: [margin.left, width - margin.right],
  });
  
  const yScale = scaleLinear({
    domain: [0, Math.max(...data.map(d => d.value))],
    range: [height - margin.bottom, margin.top],
    nice: true,
  });
  
  // 处理悬停提示逻辑
  const { tooltipData, showTooltip, hideTooltip } = useTooltip();

  return (
    <div style={{ position: 'relative' }}>
      <svg width={width} height={height}>
        {/* 渐变填充定义 */}
        <defs>
          <linearGradient id="areaGradient" x1="0%" y1="0%" x2="0%" y2="100%">
            <stop offset="0%" stopColor="#6366F1" stopOpacity={0.8} />
            <stop offset="100%" stopColor="#6366F1" stopOpacity={0} />
          </linearGradient>
        </defs>
        
        {/* 主面积折线图 */}
        <AreaClosed
          data={data}
          x={d => xScale(new Date(d.date))}
          y={d => yScale(d.value)}
          fill="url(#areaGradient)"
          stroke="#6366F1"
          strokeWidth={2}
          onMouseMove={(event) => {
            const point = localPoint(event);
            if (point) {
              // 匹配最近的数据点(简化逻辑)
              const closestData = data.find(d => Math.abs(xScale(new Date(d.date)) - point.x) < 20);
              if (closestData) showTooltip({ data: closestData, x: point.x, y: point.y });
            }
          }}
          onMouseLeave={hideTooltip}
        />
        
        {/* X轴(时间轴) */}
        <AxisBottom
          scale={xScale}
          top={height - margin.bottom}
          tickFormat={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
          tickStroke="#E5E7EB"
          tickLabelProps={{ fill: '#6B7280', fontSize: 12 }}
        />
        {/* Y轴(数值轴) */}
        <AxisLeft
          scale={yScale}
          left={margin.left}
          tickFormat={(value) => `$${value.toLocaleString()}`}
          tickStroke="#E5E7EB"
          tickLabelProps={{ fill: '#6B7280', fontSize: 12 }}
        />
      </svg>
      
      {/* 悬停提示框 */}
      {tooltipData && (
        <Tooltip
          top={tooltipData.y - 40}
          left={tooltipData.x}
          style={{ ...defaultStyles, backgroundColor: 'white', border: '1px solid #E5E7EB', borderRadius: '8px', padding: '12px' }}
        >
          <div style={{ fontWeight: 600, marginBottom: '8px' }}>{new Date(tooltipData.data.date).toLocaleDateString()}</div>
          <div style={{ color: '#6366F1' }}>${tooltipData.data.value.toLocaleString()}</div>
        </Tooltip>
      )}
    </div>
  );
};

export default StripeVisxChart;

关键注意点

  • visx需要手动处理数据映射和交互逻辑,建议先从基础折线开始,逐步添加填充、交互等功能
  • 渐变、曲线平滑逻辑与Recharts一致,只是需要手动配置参数

常见问题排查

  • React-charts定制能力有限,无法满足Stripe图表的细节需求,直接替换为Recharts更高效
  • 使用visx时,先理解D3比例尺和图形绘制的基础逻辑,拆分小功能逐个实现,避免一次性集成复杂组件

内容的提问来源于stack exchange,提问作者Oussama Jaaouani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 17:40:35