一、图表类型概览与代码实现

以下是仪表盘、漏斗图、桑基图、热力图、K线图、水球图、旭日图在 Python(主要使用 Matplotlib 和 Pyecharts)中的实现代码及效果说明。

Matplotlib 作为基础绘图库,对复杂图表支持有限,部分图表需结合其他库实现。

图表类型 主要用途 Matplotlib 实现难度 推荐库 效果特点
仪表盘 进度/指标展示 中等(需自定义绘制) Pyecharts / Matplotlib 模拟汽车仪表盘,显示单值指标
漏斗图 转化流程分析 中等(需计算坐标) Pyecharts / Matplotlib 梯形递减图形,展示各阶段转化率
桑基图 流量/能量流转 困难(布局复杂) Pyecharts / Plotly 流线图,展示多节点间流量关系
热力图 密度/相关性展示 简单 Matplotlib / Seaborn 颜色矩阵,表示二维数据密度
K线图 金融价格走势 中等(需特殊数据格式) Mplfinance / Pyecharts 蜡烛图,展示开盘、收盘、高低价
水球图 百分比进度展示 困难(需自定义形状) Pyecharts 球形填充效果,表示完成度
旭日图 层次结构数据 困难(环形分层布局) Pyecharts / Plotly 多层环形图,展示层级关系

1. 仪表盘(Gauge Chart)

仪表盘用于展示单个指标的完成度或状态,常见于监控面板。Matplotlib 实现需要手动绘制圆弧和指针。

import matplotlib.pyplot as plt
import numpy as np

def create_gauge_matplotlib(value=75, max_value=100):
    """使用Matplotlib绘制仪表盘"""
    fig, ax = plt.subplots(figsize=(8, 6), subplot_kw={'projection': 'polar'})
    
    # 绘制背景圆弧(绿色到红色渐变)
    angles = np.linspace(np.pi * 0.75, np.pi * 2.25, 100)
    colors = plt.cm.RdYlGn(np.linspace(0, 1, len(angles)))
    
    for i in range(len(angles)-1):
        ax.fill_between([angles[i], angles[i+1]], 0, 1, 
                        color=colors[i], alpha=0.7)
    
    # 绘制指针
    pointer_angle = np.pi * 0.75 + (value / max_value) * (np.pi * 1.5)
    ax.plot([pointer_angle, pointer_angle], [0, 0.8], 
            color='black', linewidth=4, solid_capstyle='round')
    
    # 添加中心圆点
    ax.plot(0, 0, 'o', markersize=15, color='white', 
            markeredgecolor='black', markeredgewidth=2)
    
    # 设置刻度
    ax.set_xticks(np.linspace(np.pi*0.75, np.pi*2.25, 6))
    ax.set_xticklabels(['0', '20', '40', '60', '80', '100'])
    ax.set_ylim(0, 1.2)
    ax.set_title(f'仪表盘示例 - 当前值: {value}', fontsize=14, pad=20)
    
    plt.tight_layout()
    plt.show()

# 生成仪表盘
create_gauge_matplotlib(75, 100)

2. 漏斗图(Funnel Chart)

漏斗图展示流程中各阶段的转化情况,常用于销售漏斗或用户行为分析。Matplotlib 需要手动计算梯形坐标。

import matplotlib.pyplot as plt
import matplotlib.patches as patches

def create_funnel_matplotlib():
    """使用Matplotlib绘制漏斗图"""
    fig, ax = plt.subplots(figsize=(10, 8))
    
    # 漏斗各阶段数据
    stages = ['曝光', '点击', '注册', '下单', '支付']
    values = [10000, 3000, 1000, 500, 300]
    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7']
    
    # 计算每个梯形的位置和大小
    total_height = 0.8
    stage_height = total_height / len(stages)
    
    for i, (stage, value, color) in enumerate(zip(stages, values, colors)):
        # 计算梯形宽度(基于数值比例)
        width_top = 0.8 - (i * 0.15)
        width_bottom = 0.8 - ((i + 1) * 0.15)
        bottom = i * stage_height
        
        # 创建梯形补丁
        trapezoid = patches.Polygon([
            [0.5 - width_top/2, bottom + stage_height],
            [0.5 + width_top/2, bottom + stage_height],
            [0.5 + width_bottom/2, bottom],
            [0.5 - width_bottom/2, bottom]
        ], facecolor=color, alpha=0.7, edgecolor='black')
        
        ax.add_patch(trapezoid)
        
        # 添加文本标签
        ax.text(0.5, bottom + stage_height/2, 
                f'{stage}
{value} ({values[i]/values[0]*100:.1f}%)',
                ha='center', va='center', fontsize=11, fontweight='bold')
    
    ax.set_xlim(0, 1)
    ax.set_ylim(0, total_height)
    ax.set_title('销售漏斗图示例', fontsize=14, pad=20)
    ax.axis('off')
    
    plt.tight_layout()
    plt.show()

create_funnel_matplotlib()

3. 桑基图(Sankey Diagram)

桑基图展示流量或资源的转移路径,Matplotlib 原生支持有限,通常使用 Pyecharts 或 Plotly。

from pyecharts import options as opts
from pyecharts.charts import Sankey

def create_sankey_pyecharts():
    """使用Pyecharts绘制桑基图(Matplotlib实现复杂)"""
    nodes = [
        {"name": "网站访问"},
        {"name": "产品页浏览"},
        {"name": "加入购物车"},
        {"name": "开始结算"},
        {"name": "完成支付"},
        {"name": "放弃"}
    ]
    
    links = [
        {"source": "网站访问", "target": "产品页浏览", "value": 10000},
        {"source": "产品页浏览", "target": "加入购物车", "value": 3000},
        {"source": "加入购物车", "target": "开始结算", "value": 1500},
        {"source": "开始结算", "target": "完成支付", "value": 800},
        {"source": "产品页浏览", "target": "放弃", "value": 7000},
        {"source": "加入购物车", "target": "放弃", "value": 1500},
        {"source": "开始结算", "target": "放弃", "value": 700}
    ]
    
    sankey = (
        Sankey()
        .add(
            "用户转化路径",
            nodes,
            links,
            linestyle_opt=opts.LineStyleOpts(opacity=0.2, curve=0.5, color="source"),
            label_opts=opts.LabelOpts(position="right"),
        )
        .set_global_opts(title_opts=opts.TitleOpts(title="用户购买行为桑基图"))
    )
    
    return sankey

# 在Jupyter中显示
# create_sankey_pyecharts().render_notebook()

4. 热力图(Heatmap)

热力图用于展示二维数据的密度或相关性,Matplotlib 通过 imshow 函数简单实现。

import matplotlib.pyplot as plt
import numpy as np

def create_heatmap_matplotlib():
    """使用Matplotlib绘制热力图"""
    # 生成示例数据(7天×24小时的活动热度)
    np.random.seed(42)
    data = np.random.randn(24, 7)
    data = np.cumsum(data, axis=0)  # 添加时间趋势
    
    days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
    hours = [f'{i:02d}:00' for i in range(24)]
    
    fig, ax = plt.subplots(figsize=(12, 8))
    
    # 绘制热力图
    im = ax.imshow(data, cmap='YlOrRd', aspect='auto')
    
    # 设置坐标轴
    ax.set_xticks(np.arange(len(days)))
    ax.set_yticks(np.arange(len(hours)))
    ax.set_xticklabels(days)
    ax.set_yticklabels(hours)
    
    # 添加颜色条
    cbar = ax.figure.colorbar(im, ax=ax)
    cbar.ax.set_ylabel('活动强度', rotation=-90, va="bottom")
    
    # 添加数值文本
    for i in range(len(hours)):
        for j in range(len(days)):
            text = ax.text(j, i, f'{data[i, j]:.1f}',
                          ha="center", va="center", 
                          color="black" if data[i, j] < 0.5 else "white")
    
    ax.set_title("一周活动热力图", fontsize=14, pad=20)
    plt.tight_layout()
    plt.show()

create_heatmap_matplotlib()

5. K线图(Candlestick Chart)

K线图是金融分析的核心图表,展示开盘、收盘、最高、最低价。Matplotlib 需配合 mplfinance 库。

import mplfinance as mpf
import pandas as pd
import numpy as np

def create_candlestick_matplotlib():
    """使用mplfinance绘制K线图(基于Matplotlib)"""
    # 生成示例金融数据
    dates = pd.date_range('2024-01-01', periods=30, freq='D')
    np.random.seed(42)
    
    # 模拟价格数据
    base_price = 100
    prices = []
    for i in range(30):
        open_price = base_price + np.random.randn() * 5
        close_price = open_price + np.random.randn() * 3
        high_price = max(open_price, close_price) + abs(np.random.randn() * 2)
        low_price = min(open_price, close_price) - abs(np.random.randn() * 2)
        prices.append([open_price, high_price, low_price, close_price])
        base_price = close_price
    
    # 创建DataFrame
    df = pd.DataFrame(prices, index=dates, 
                      columns=['Open', 'High', 'Low', 'Close'])
    df['Volume'] = np.random.randint(1000, 10000, size=30)
    
    # 绘制K线图
    mpf.plot(df, 
             type='candle',
             style='charles',
             title='股票K线图示例',
             ylabel='价格',
             ylabel_lower='成交量',
             volume=True,
             mav=(5, 10),  # 移动平均线
             figsize=(12, 8))

# 注意:mplfinance需要在独立窗口中显示
# create_candlestick_matplotlib()

6. 水球图(Liquid Fill Chart)

水球图用液体填充效果展示百分比,Matplotlib 实现困难,推荐使用 Pyecharts。

from pyecharts import options as opts
from pyecharts.charts import Liquid

def create_liquid_pyecharts():
    """使用Pyecharts绘制水球图"""
    liquid = (
        Liquid()
        .add("完成率", [0.68, 0.5],  # 两个波浪效果
             color=["#294D99", "#156ACF"],
             background_color="#FFFFFF",
             is_outline_show=False,
             shape="diamond")  # 可选形状: circle, rect, roundRect, triangle, diamond
        .set_global_opts(
            title_opts=opts.TitleOpts(title="项目进度水球图"),
            tooltip_opts=opts.TooltipOpts(formatter="{a}: {c}")
        )
    )
    return liquid

# 在Jupyter中显示
# create_liquid_pyecharts().render_notebook()

7. 旭日图(Sunburst Chart)

旭日图展示层级数据关系,Matplotlib 无原生支持,使用 Pyecharts 实现。

from pyecharts import options as opts
from pyecharts.charts import Sunburst

def create_sunburst_pyecharts():
    """使用Pyecharts绘制旭日图"""
    data = [
        {"name": "公司收入", "value": 1000, "children": [
            {"name": "产品A", "value": 600, "children": [
                {"name": "线上销售", "value": 400},
                {"name": "线下销售", "value": 200}
            ]},
            {"name": "产品B", "value": 300, "children": [
                {"name": "国内", "value": 200},
                {"name": "国际", "value": 100}
            ]},
            {"name": "服务", "value": 100}
        ]}
    ]
    
    sunburst = (
        Sunburst()
        .add(series_name="收入结构", data_pair=data, radius=[0, "90%"])
        .set_global_opts(
            title_opts=opts.TitleOpts(title="公司收入旭日图"),
            tooltip_opts=opts.TooltipOpts(formatter="{b}: {c} ({d}%)")
        )
        .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}"))
    )
    return sunburst

# 在Jupyter中显示
# create_sunburst_pyecharts().render_notebook()

二、Matplotlib 中不常用且难绘制的图表技术详解

1. 极坐标复杂图表

Matplotlib 的极坐标投影可用于创建雷达图、风向图等特殊图表,但自定义程度高且坐标转换复杂。

import matplotlib.pyplot as plt
import numpy as np

def complex_polar_plot():
    """复杂的极坐标图表示例"""
    fig = plt.figure(figsize=(12, 10))
    
    # 创建极坐标子图
    ax = fig.add_subplot(111, projection='polar')
    
    # 生成多组数据
    theta = np.linspace(0, 2*np.pi, 8, endpoint=False)
    values1 = [3, 4, 2, 5, 6, 3, 4, 5]
    values2 = [2, 3, 4, 3, 4, 5, 3, 4]
    
    # 闭合数据
    theta = np.append(theta, theta[0])
    values1 = np.append(values1, values1[0])
    values2 = np.append(values2, values2[0])
    
    # 绘制填充区域
    ax.fill(theta, values1, alpha=0.3, color='blue', label='数据集1')
    ax.fill(theta, values2, alpha=0.3, color='red', label='数据集2')
    
    # 绘制线图
    ax.plot(theta, values1, 'o-', color='blue', linewidth=2)
    ax.plot(theta, values2, 's-', color='red', linewidth=2)
    
    # 添加径向网格和标签
    ax.set_rgrids([1, 2, 3, 4, 5, 6], angle=45)
    ax.set_thetagrids(np.degrees(theta[:-1]), 
                      ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
    
    ax.set_title("复杂极坐标图表示例", fontsize=14, pad=20)
    ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
    
    plt.tight_layout()
    plt.show()

complex_polar_plot()

2. 3D曲面与等高线组合图

结合 3D 曲面和二维等高线需要复杂的坐标转换和图层管理。

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

def surface_contour_combo():
    """3D曲面与等高线组合图"""
    # 生成数据
    x = np.linspace(-5, 5, 100)
    y = np.linspace(-5, 5, 100)
    X, Y = np.meshgrid(x, y)
    Z = np.sin(np.sqrt(X**2 + Y**2)) * np.exp(-0.1*(X**2 + Y**2))
    
    fig = plt.figure(figsize=(14, 6))
    
    # 1. 3D曲面图
    ax1 = fig.add_subplot(121, projection='3d')
    surf = ax1.plot_surface(X, Y, Z, cmap='viridis', 
                           alpha=0.8, linewidth=0, 
                           antialiased=True)
    ax1.set_xlabel('X轴')
    ax1.set_ylabel('Y轴')
    ax1.set_zlabel('Z轴')
    ax1.set_title('3D曲面图', fontsize=12)
    fig.colorbar(surf, ax=ax1, shrink=0.5)
    
    # 2. 等高线图
    ax2 = fig.add_subplot(122)
    contour = ax2.contour(X, Y, Z, 20, cmap='viridis', linewidths=1)
    ax2.clabel(contour, inline=True, fontsize=8)
    ax2.set_xlabel('X轴')
    ax2.set_ylabel('Y轴')
    ax2.set_title('等高线图', fontsize=12)
    fig.colorbar(contour, ax=ax2)
    
    plt.suptitle('3D曲面与等高线组合图', fontsize=14)
    plt.tight_layout()
    plt.show()

surface_contour_combo()

3. 自定义路径补丁与复杂形状

Matplotlib 的 patches 模块允许创建任意形状,但需要手动计算路径坐标。

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as mpath
import numpy as np

def custom_shape_patches():
    """自定义形状补丁示例"""
    fig, ax = plt.subplots(figsize=(10, 8))
    
    # 1. 自定义路径创建星形
    star_vertices = []
    for i in range(10):
        angle = 2 * np.pi * i / 10
        r = 0.5 if i % 2 == 0 else 0.2
        x = r * np.cos(angle)
        y = r * np.sin(angle)
        star_vertices.append([x, y])
    
    star_path = mpath.Path(star_vertices)
    star_patch = patches.PathPatch(star_path, 
                                   facecolor='gold', 
                                   edgecolor='darkorange',
                                   linewidth=2,
                                   alpha=0.8)
    ax.add_patch(star_patch)
    
    # 2. 贝塞尔曲线形状
    bezier_path = mpath.Path(
        vertices=[
            (0, 0),   # 起点
            (0.5, 1), # 控制点1
            (1, 0),   # 控制点2
            (1.5, 1), # 控制点3
            (2, 0)    # 终点
        ],
        codes=[
            mpath.Path.MOVETO,
            mpath.Path.CURVE4,
            mpath.Path.CURVE4,
            mpath.Path.CURVE4,
            mpath.Path.CURVE4
        ]
    )
    bezier_patch = patches.PathPatch(bezier_path, 
                                     facecolor='lightblue',
                                     edgecolor='blue',
                                     linewidth=2,
                                     alpha=0.6)
    ax.add_patch(bezier_patch)
    bezier_patch.set_transform(plt.matplotlib.transforms.Affine2D().translate(-1, -1))
    
    # 3. 复杂多边形组合
    polygon = patches.Polygon(
        [(3, 0), (3.5, 0.5), (3.2, 1), (2.8, 1), (2.5, 0.5)],
        closed=True,
        facecolor='lightgreen',
        edgecolor='darkgreen',
        linewidth=2,
        hatch='//'
    )
    ax.add_patch(polygon)
    
    ax.set_xlim(-1, 4)
    ax.set_ylim(-1, 2)
    ax.set_aspect('equal')
    ax.set_title('自定义形状补丁示例', fontsize=14, pad=20)
    ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

custom_shape_patches()

4. 动态更新与交互图表

Matplotlib 的动画功能可以创建动态图表,但性能优化和交互处理较复杂。

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

def animated_plot():
    """动态更新图表示例"""
    fig, ax = plt.subplots(figsize=(10, 6))
    
    # 初始化数据
    x = np.linspace(0, 4*np.pi, 200)
    line, = ax.plot(x, np.sin(x), 'b-', linewidth=2)
    
    ax.set_xlim(0, 4*np.pi)
    ax.set_ylim(-1.5, 1.5)
    ax.set_xlabel('时间')
    ax.set_ylabel('振幅')
    ax.set_title('正弦波动态演示', fontsize=14)
    ax.grid(True, alpha=0.3)
    
    def update(frame):
        """更新函数"""
        # 更新相位
        y = np.sin(x + frame * 0.1)
        line.set_ydata(y)
        
        # 更新标题显示当前帧
        ax.set_title(f'正弦波动态演示 - 帧: {frame}', fontsize=14)
        
        return line,
    
    # 创建动画
    ani = animation.FuncAnimation(fig, update, frames=100, 
                                  interval=50, blit=True)
    
    plt.tight_layout()
    plt.show()
    
    return ani

# 生成动态图表(实际运行需要交互环境)
# animated_plot()

三、技术难点与解决方案对比

图表类型 Matplotlib 实现难点 推荐替代方案 适用场景
桑基图 节点布局算法复杂,连接线处理困难 Pyecharts / Plotly 流量分析、能量流动、资源转移
水球图 液体波动效果难以模拟 Pyecharts 的 Liquid 组件 进度展示、百分比可视化
旭日图 环形分层布局计算复杂 Pyecharts / Plotly 层级数据、组织结构、文件目录
仪表盘 指针动画和渐变背景实现繁琐 Pyecharts / Plotly 监控面板、KPI展示
3D图表 视角控制和性能优化困难 Plotly / Mayavi 科学数据、三维模型
地理图表 地图数据加载和投影转换复杂 Pyecharts / Folium 地理数据、区域分布

四、最佳实践建议

  1. 库选择策略

    • 简单静态图表:优先使用 Matplotlib
    • 交互式图表:选择 Pyecharts 或 Plotly
    • 专业金融图表:使用 mplfinance
    • 地理信息图表:使用 Pyecharts 或 Folium
  2. 性能优化

    • 大数据集使用 rasterized=True 参数
    • 动态图表使用 blit=True 减少重绘
    • 3D图表降低网格密度提升渲染速度
  3. 代码结构优化

    # 良好的图表代码结构示例
    class ChartGenerator:
        def __init__(self, style='seaborn'):
            self.style = style
            plt.style.use(style)
        
        def create_chart(self, data, chart_type):
            """工厂方法创建图表"""
            if chart_type == 'heatmap':
                return self._create_heatmap(data)
            elif chart_type == 'sankey':
                return self._create_sankey(data)
            # ... 其他图表类型
        
        def _create_heatmap(self, data):
            """具体图表实现"""
            fig, ax = plt.subplots(figsize=(10, 8))
            # ... 具体实现
            return fig
    
  4. 输出格式控制

    # 高质量输出设置
    plt.rcParams['figure.dpi'] = 300  # 高分辨率
    plt.rcParams['savefig.bbox'] = 'tight'  # 紧凑布局
    plt.rcParams['font.family'] = 'DejaVu Sans'  # 字体设置
    
    # 保存多种格式
    fig.savefig('chart.png', dpi=300, transparent=True)
    fig.savefig('chart.pdf', format='pdf')
    fig.savefig('chart.svg', format='svg')
    

Matplotlib 作为基础绘图库,在简单图表和高度定制化场景中具有优势,但对于复杂交互式图表,建议结合 Pyecharts、Plotly 等专业库。

实际项目中,根据具体需求选择合适的工具组合,可以显著提升开发效率和可视化效果。


参考来源

更多推荐