一、简单来说,它们是什么?

1. 弦图:关系的"朋友圈"

  弦图是一种展示实体间相互关系的圆形网络图,所有节点均匀分布在圆周上,节点之间的连接用弧形曲线表示,曲线宽度编码关系强度。
  想象一下班级里的同学关系:把所有同学的名字写在一个圆圈上,如果张三和李四是好朋友,就在他们之间画一条弧线,关系越好,弧线越粗,这样一眼就能看出谁和谁关系好,谁是最受欢迎的人。弦图像一张关系网,告诉我们:“谁和谁有关系?关系有多铁?”

2. 桑基图:物品的"流动轨迹"

  桑基图以爱尔兰工程师Matthew Sankey命名,专门用于展示能量、物料或资金的流动过程。它强调流量守恒原则(流入=流出+损耗),通过变宽的"河流"状连接显示流量变化。
  想象一下快递的配送过程:左边是发货仓库(北京、上海、广州),中间是快递中心,右边是收货地址(家庭、公司、学校),线的粗细表示包裹数量,线从哪来到哪去看得清清楚楚,桑基图像一条河流,告诉我们:“东西从哪里来?到哪里去?路上有没有丢失?”

3. 什么情况下用哪个?

(1)用弦图,当你关心:
❓ “这个圈子里谁和谁关系好?”
❓ “谁是这个圈子的核心人物?”
❓ “整体关系网是什么样子?”
❓ “谁的人脉最广?”

适合场景:

分析社交网络(微信好友、微博关注)

查看合作关系(公司部门间、作者合著)

研究产品关联(买了A的人也常买B)

展示城市间的交通(不考虑方向)

(2)用桑基图,当你关心:
❓ “钱/货/人从哪里来,到哪里去?”
❓ “过程中损失了多少?”
❓ “主要流通路径是什么?”
❓ “转化率怎么样?”

适合场景:

跟踪资金流向(公司预算、个人开支)

分析用户转化(网站访问→注册→购买)

管理供应链(原料→生产→配送→销售)

优化能源使用(发电→输电→用电→损耗)

二、弦图实例

1. 绘制流程

计算节点角度

n = 6  # 
angles = np.linspace(0, 2 * np.pi, n, endpoint=False)
# 结果:array([0.        , 1.04719755, 2.0943951 , 3.14159265, 4.1887902 , 5.23598776])
# 对应角度:0°, 60°, 120°, 180°, 240°, 300°

转换到直角坐标系

radius = 1.0
node_x = radius * np.cos(angles)  # x坐标
node_y = radius * np.sin(angles)  # y坐标

分配节点颜色

colors = plt.cm.tab20(np.arange(n) / n)

绘制节点

ax.scatter(node_x, node_y, s=500, c=colors, edgecolor='black', alpha=0.8, zorder=5)
# s=500: 节点大小
# zorder=5: 确保节点在最上层显示

添加标签

label_radius = radius * 1.15  # 标签在半径1.15倍处
label_x = label_radius * np.cos(angles[i])
label_y = label_radius * np.sin(angles[i])
# 根据象限确定对齐方式
ha = 'left' if label_x > 0 else 'right'    # 水平对齐
va = 'bottom' if label_y > 0 else 'top'    # 垂直对齐

遍历所有关系对

max_value = matrix.max()  # 找到矩阵中最大值(示例中为8for i in range(n):      # 源节点索引
    for j in range(n):  # 目标节点索引
        if i != j and matrix[i, j] > 0:  # 排除自身和零关系
            # 计算线宽(归一化到1-6范围)
            linewidth = 1 + 5 * (matrix[i, j] / max_value)
            # 示例:matrix[0,1]=8 → linewidth=6
            #        matrix[0,4]=0 → 不绘制

计算贝塞尔曲线控制点

# 起点和终点
x1, y1 = node_x[i], node_y[i]  # 源节点坐标
x2, y2 = node_x[j], node_y[j]  # 目标节点坐标
# 计算中点
mx, my = (x1 + x2) / 2, (y1 + y2) / 2
# 计算连接线向量
dx, dy = x2 - x1, y2 - y1
# 计算垂直向量(使曲线向外凸出)
perp_x, perp_y = -dy, dx  # 旋转90度
# 归一化垂直向量
length = np.sqrt(perp_x**2 + perp_y**2)
if length > 0:
    perp_x, perp_y = perp_x / length, perp_y / length
   
    # 控制点(向外偏移0.3单位)
    control_x = mx + perp_x * 0.3
    control_y = my + perp_y * 0.3

生成贝塞尔曲线点集

# 生成50个均匀分布的参数t值
t = np.linspace(0, 1, 50)
# 二次贝塞尔曲线公式
curve_x = (1 - t)**2 * x1 + 2*(1 - t)*t * control_x + t**2 * x2
curve_y = (1 - t)**2 * y1 + 2*(1 - t)*t * control_y + t**2 * y2

绘制曲线

ax.plot(curve_x, curve_y,
        linewidth=linewidth,    # 线宽表示关系强度
        color=colors[i],        # 使用源节点颜色
        alpha=0.4,              # 半透明
        solid_capstyle='round') # 线端为圆形

完整代码如下:

import matplotlib.pyplot as plt
import numpy as np
import warnings
warnings.filterwarnings('ignore')
# 设置中文字体(Windows系统)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
def simple_chord_diagram(matrix, labels):
    fig, ax = plt.subplots(figsize=(10, 10))
    n = len(labels)
    angles = np.linspace(0, 2 * np.pi, n, endpoint=False)
    radius = 1.0
    node_x = radius * np.cos(angles)
    node_y = radius * np.sin(angles)
    colors = plt.cm.tab20(np.arange(n) / n)
    ax.scatter(node_x, node_y, s=500, c=colors, edgecolor='black', alpha=0.8, zorder=5)
    for i, (x, y, label) in enumerate(zip(node_x, node_y, labels)):
        label_radius = radius * 1.15
        label_x = label_radius * np.cos(angles[i])
        label_y = label_radius * np.sin(angles[i])
        ha = 'left' if label_x > 0 else 'right'
        va = 'bottom' if label_y > 0 else 'top'
        ax.text(label_x, label_y, label,
                ha=ha, va=va, fontsize=11, fontweight='bold',
                bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.8))
    max_value = matrix.max()
    for i in range(n):
        for j in range(n):
            if i != j and matrix[i, j] > 0:
                # 线宽基于数值
                linewidth = 1 + 5 * (matrix[i, j] / max_value)
                x1, y1 = node_x[i], node_y[i]
                x2, y2 = node_x[j], node_y[j]
                mx, my = (x1 + x2) / 2, (y1 + y2) / 2
                dx, dy = x2 - x1, y2 - y1
                perp_x, perp_y = -dy, dx
                length = np.sqrt(perp_x ** 2 + perp_y ** 2)
                if length > 0:
                    perp_x, perp_y = perp_x / length, perp_y / length
                    control_x = mx + perp_x * 0.3
                    control_y = my + perp_y * 0.3
                    t = np.linspace(0, 1, 50)
                    curve_x = (1 - t) ** 2 * x1 + 2 * (1 - t) * t * control_x + t ** 2 * x2
                    curve_y = (1 - t) ** 2 * y1 + 2 * (1 - t) * t * control_y + t ** 2 * y2
                    ax.plot(curve_x, curve_y,
                            linewidth=linewidth,
                            color=colors[i],
                            alpha=0.4,
                            solid_capstyle='round')
                    mid_idx = len(t) // 2
                    arrow_x = curve_x[mid_idx]
                    arrow_y = curve_y[mid_idx]
                    if mid_idx > 0 and mid_idx < len(t) - 1:
                        dx_dir = curve_x[mid_idx + 1] - curve_x[mid_idx - 1]
                        dy_dir = curve_y[mid_idx + 1] - curve_y[mid_idx - 1]
                        ax.arrow(arrow_x, arrow_y,
                                 dx_dir * 0.1, dy_dir * 0.1,
                                 head_width=0.03, head_length=0.05,
                                 fc=colors[i], ec=colors[i],
                                 alpha=0.7)
    ax.set_xlim(-1.3, 1.3)
    ax.set_ylim(-1.3, 1.3)
    ax.set_aspect('equal')
    ax.axis('off')
    plt.title('弦图示例:社交网络关系强度', fontsize=16, fontweight='bold', pad=20)
    ax.text(0, -1.25,
            f"线宽表示关系强度 | 共有{n}个用户",
            ha='center', fontsize=10,
            bbox=dict(boxstyle='round', facecolor='lightgray', alpha=0.5))

    return fig, ax
social_network = np.array([
    [0, 8, 5, 3, 0, 2],  # 张三的联系
    [7, 0, 4, 6, 1, 0],  # 李四的联系
    [6, 5, 0, 2, 3, 1],  # 王五的联系
    [4, 7, 3, 0, 2, 5],  # 赵六的联系
    [1, 2, 4, 3, 0, 6],  # 钱七的联系
    [3, 1, 2, 6, 7, 0]  # 孙八的联系
])
users = ['张三', '李四', '王五', '赵六', '钱七', '孙八']
fig, ax = simple_chord_diagram(social_network, users)
plt.tight_layout()
plt.show()

在这里插入图片描述

观察 说明
李四→赵六 的线最粗 李四与赵六互动最频繁,关系最强。
钱七 的线普遍细 钱七与其他人互动较少,可能性格内向。
孙八→钱七 的线很粗 孙八主动与钱七频繁互动,是钱七的主要联系人。
张三→李四 双向都粗 张三与李四互相高频互动,是“强双向关系”。

三、桑基图实例

  桑基图绘制流程:先计算所有节点的总流量再确定每个节点的垂直位置(按流量比例分配)再 绘制节点(矩形)然后绘制连接线(带状多边形)最后添加标签和装饰。
matplotlib.sankey.Sankey 模块,只能画“单排/单侧”的桑基条,不能画“左右/多阶段”的完整桑基图,本实例选择用 Plotly模块绘制。

1. 数据基础

桑基图需要三个核心数据维度:
(1)来源节点(From)
(2)目标节点(To)
(3)流量值(Value)
在代码中体现为:

flow_data = [
    ['北京仓', '华北转运', 3000],  # 从北京仓到华北转运,3000['华北转运', '家庭', 1200],    # 从华北转运到家庭,1200件   # ...]

2. 守恒原则

桑基图遵循质量守恒定律:
流入节点的总流量 = 流出节点的总流量
代码通过 node_totals 字典追踪每个节点的总流量

3. 核心部分

(1)计算每个节点的垂直位置

node_positions = {}
for node in warehouse_nodes:
    total = node_totals.get(node, 0)
    height = total * 0.0002  # 根据流量计算高度
    node_positions[node] = {
        'x': stages['发货仓库'],  # 固定水平位置
        'y_start': y_pos,        # 起始Y坐标
        'y_end': y_pos + height, # 结束Y坐标
        'y_center': y_pos + height / 2,  # 中心点(关键!)
    }
    y_pos += height + 0.03  # 累加位置,避免重叠

(2)流线绘制(贝塞尔曲线+多边形填充)

1. 计算贝塞尔曲线控制点
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
control_x = cx
control_y = cy + bend_factor  # 弯曲因子
# 2. 二次贝塞尔曲线公式
curve_x = (1 - t)**2 * x1 + 2*(1 - t)*t * control_x + t**2 * x2
curve_y = (1 - t)**2 * y1 + 2*(1 - t)*t * control_y + t**2 * y2
# 3. 创建带状多边形(使线有宽度)
dx = np.gradient(curve_x, t)  # 计算导数
dy = np.gradient(curve_y, t)
norm = np.sqrt(dx**2 + dy**2)
nx = -dy / (norm + 1e-8) * width / 2000  # 法线向量
ny = dx / (norm + 1e-8) * width / 2000
# 4. 填充多边形形成带宽度的线
poly_x = np.concatenate([upper_x, lower_x[::-1]])
poly_y = np.concatenate([upper_y, lower_y[::-1]])
ax.fill(poly_x, poly_y, ...)

(3)流量比例可视化(宽度映射)

base_width = flow * 0.005  # 放大系数
min_width = 1.0
max_width = 25.0
width = min(max(base_width, min_width), max_width)  # 限制范围,流量越大 → 线宽越大,形成直观对比。

完整代码如下:

import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']  # 中文
plt.rcParams['axes.unicode_minus'] = False
def create_custom_sankey():
    """创建自定义桑基图(不使用Sankey模块)"""
    fig, ax = plt.subplots(figsize=(14, 9))
    ax.set_title('快递包裹静态桑基图:从哪里来?到哪里去?', fontsize=18, fontweight='bold', pad=25)
    # 颜色方案
    colors = {
        '北京仓': '#2ca02c',  # 绿色
        '上海仓': '#ff7f0e',  # 橙色
        '广州仓': '#d62728',  # 红色
        '华北转运': '#8c564b',  # 棕色
        '华东转运': '#e377c2',  # 粉色
        '华南转运': '#7f7f7f',  # 灰色
        '家庭': '#1f77b4',  # 蓝色
        '公司': '#9467bd',  # 紫色
        '学校': '#bcbd22'  # 橄榄色
    }
    # 定义三个阶段的位置
    stages = {
        '发货仓库': 0.1,
        '转运中心': 0.5,
        '收货地址': 0.9
    }
    # 定义节点和流量数据 - 调整流量值以增加差异
    flow_data = [
        # 第一阶段:仓库 -> 转运中心
        ['北京仓', '华北转运', 3000],  # 增加
        ['上海仓', '华东转运', 4000],  # 增加
        ['广州仓', '华南转运', 3200],  # 增加
        # 第二阶段:转运中心 -> 收货地址
        ['华北转运', '家庭', 1200],  # 家庭
        ['华北转运', '公司', 1400],  # 公司最多
        ['华北转运', '学校', 400],  # 学校最少
        ['华东转运', '家庭', 1800],  # 家庭最多
        ['华东转运', '公司', 1500],  # 公司
        ['华东转运', '学校', 700],  # 学校
        ['华南转运', '家庭', 900],  # 家庭
        ['华南转运', '公司', 1600],  # 公司最多
        ['华南转运', '学校', 700]  # 学校
    ]
    # 1. 计算所有节点的总流量(用于确定节点大小)
    node_totals = {}
    for src, tgt, flow in flow_data:
        node_totals[src] = node_totals.get(src, 0) + flow
        node_totals[tgt] = node_totals.get(tgt, 0) + flow
    # 2. 计算每个阶段节点的Y位置
    node_positions = {}
    # 发货仓库节点
    warehouse_nodes = ['北京仓', '上海仓', '广州仓']
    y_pos = 0
    for node in warehouse_nodes:
        total = node_totals.get(node, 0)
        height = total * 0.0002  # 增加高度缩放
        node_positions[node] = {
            'x': stages['发货仓库'],
            'y_start': y_pos,
            'y_end': y_pos + height,
            'y_center': y_pos + height / 2,
            'total': total,
            'stage': '发货仓库'
        }
        y_pos += height + 0.03  # 增加间距
    # 转运中心节点
    transport_nodes = ['华北转运', '华东转运', '华南转运']
    y_pos = 0
    for node in transport_nodes:
        total = node_totals.get(node, 0)
        height = total * 0.0002
        node_positions[node] = {
            'x': stages['转运中心'],
            'y_start': y_pos,
            'y_end': y_pos + height,
            'y_center': y_pos + height / 2,
            'total': total,
            'stage': '转运中心'
        }
        y_pos += height + 0.03
    # 收货地址节点
    address_nodes = ['家庭', '公司', '学校']
    y_pos = 0
    for node in address_nodes:
        total = node_totals.get(node, 0)
        height = total * 0.0002
        node_positions[node] = {
            'x': stages['收货地址'],
            'y_start': y_pos,
            'y_end': y_pos + height,
            'y_center': y_pos + height / 2,
            'total': total,
            'stage': '收货地址'
        }
        y_pos += height + 0.03
    # 3. 绘制连接线 - 重点改进:显著增加线宽差异
    for src, tgt, flow in flow_data:
        if src in node_positions and tgt in node_positions:
            src_pos = node_positions[src]
            tgt_pos = node_positions[tgt]
            base_width = flow * 0.005  # 增大5倍,显著增强差异
            # 确保线宽有明显区分
            min_width = 1.0  # 最小线宽
            max_width = 25.0  # 最大线宽(增大)
            width = min(max(base_width, min_width), max_width)
            # 起点和终点
            x1, y1 = src_pos['x'] + 0.02, src_pos['y_center']
            x2, y2 = tgt_pos['x'] - 0.02, tgt_pos['y_center']
            # 创建贝塞尔曲线
            t = np.linspace(0, 1, 50)
            # 控制点使曲线平滑
            cx = (x1 + x2) / 2
            cy = (y1 + y2) / 2
            # 弯曲因子
            bend_factor = 0.04
            # 计算二次贝塞尔曲线
            control_x = cx
            control_y = cy + bend_factor
            curve_x = (1 - t) ** 2 * x1 + 2 * (1 - t) * t * control_x + t ** 2 * x2
            curve_y = (1 - t) ** 2 * y1 + 2 * (1 - t) * t * control_y + t ** 2 * y2
            # 创建带宽度的带状多边形
            if width > 0:
                # 计算法线向量
                dx = np.gradient(curve_x, t)
                dy = np.gradient(curve_y, t)
                norm = np.sqrt(dx ** 2 + dy ** 2)
                # 归一化法向量
                nx = -dy / (norm + 1e-8) * width / 2000
                ny = dx / (norm + 1e-8) * width / 2000
                # 创建带状多边形的边界
                upper_x = curve_x + nx
                upper_y = curve_y + ny
                lower_x = curve_x - nx
                lower_y = curve_y - ny
                # 填充多边形
                poly_x = np.concatenate([upper_x, lower_x[::-1]])
                poly_y = np.concatenate([upper_y, lower_y[::-1]])

                ax.fill(poly_x, poly_y,
                        color=colors.get(src, '#cccccc'),
                        alpha=0.75,
                        edgecolor='black',
                        linewidth=0.5,
                        zorder=1)
            # 添加流量标签
            if flow > 300:  # 降低阈值
                mid_idx = len(t) // 2
                # 根据流量设置标签样式
                if flow > 2000:
                    label_bg = '#FFD700'  # 金色
                    label_size = 11
                elif flow > 1000:
                    label_bg = '#FFFACD'
                    label_size = 10
                else:
                    label_bg = 'white'
                    label_size = 9
                ax.text(curve_x[mid_idx], curve_y[mid_idx],
                        f'{flow}',
                        ha='center', va='center',
                        fontsize=label_size,
                        fontweight='bold',
                        bbox=dict(boxstyle='round,pad=0.4',
                                  facecolor=label_bg,
                                  edgecolor='darkgray',
                                  alpha=0.95,
                                  linewidth=1.5))
    # 4. 绘制节点
    for node, pos in node_positions.items():
        # 绘制矩形节点
        rect_x = pos['x'] - 0.02
        rect_width = 0.04
        rect_y = pos['y_start']
        rect_height = pos['y_end'] - pos['y_start']
        ax.fill([rect_x, rect_x + rect_width, rect_x + rect_width, rect_x],
                [rect_y, rect_y, rect_y + rect_height, rect_y + rect_height],
                color=colors.get(node, '#cccccc'),
                edgecolor='black',
                linewidth=2.5,
                alpha=0.9,
                zorder=3)
        # 添加节点标签
        label_x = pos['x'] + 0.045 if pos['x'] < 0.5 else pos['x'] - 0.045
        ha = 'left' if pos['x'] < 0.5 else 'right'
        ax.text(label_x, pos['y_center'],
                f"{node}\n{pos['total']}件",
                ha=ha, va='center',
                fontsize=10,
                fontweight='bold',
                bbox=dict(boxstyle='round,pad=0.4',
                          facecolor='white',
                          edgecolor='gray',
                          alpha=0.95,
                          linewidth=1.5))
    # 5. 添加阶段标签
    for stage_name, x_pos in stages.items():
        ax.text(x_pos, -0.06, stage_name,
                ha='center', va='top',
                fontsize=14,
                fontweight='bold',
                bbox=dict(boxstyle='round,pad=0.4',
                          facecolor='#E3F2FD',
                          edgecolor='#1E88E5',
                          alpha=0.9,
                          linewidth=2))
    # 6. 添加箭头表示流向
    arrow_x = [0.15, 0.5, 0.85]
    arrow_y = -0.12
    for i in range(len(arrow_x) - 1):
        ax.annotate('',
                    xy=(arrow_x[i + 1], arrow_y),
                    xytext=(arrow_x[i], arrow_y),
                    arrowprops=dict(arrowstyle='->',
                                    color='#333333',
                                    lw=4,
                                    shrinkA=5,
                                    shrinkB=5))
    # 7. 设置图形属性
    ax.set_xlim(0, 1)
    max_y = max(pos['y_end'] for pos in node_positions.values())
    ax.set_ylim(-0.25, max_y + 0.15)
    ax.set_aspect('auto')
    ax.axis('off')
    # 8. 添加统计信息
    total_packages = sum(flow for _, _, flow in flow_data)
    # 计算各收货地址的包裹数量
    home_packages = sum(flow for _, tgt, flow in flow_data if tgt == '家庭')
    company_packages = sum(flow for _, tgt, flow in flow_data if tgt == '公司')
    school_packages = sum(flow for _, tgt, flow in flow_data if tgt == '学校')
    stats_text = f"📦 总计: {total_packages} 件包裹\n"
    stats_text += f"🏠 家庭: {home_packages} 件 ({home_packages / total_packages * 100:.1f}%)\n"
    stats_text += f"🏢 公司: {company_packages} 件 ({company_packages / total_packages * 100:.1f}%)\n"
    stats_text += f"🏫 学校: {school_packages} 件 ({school_packages / total_packages * 100:.1f}%)"
    ax.text(0.02, 0.98, stats_text,
            transform=ax.transAxes,
            fontsize=11,
            fontweight='bold',
            verticalalignment='top',
            bbox=dict(boxstyle='round',
                      facecolor='#E8F5E9',
                      edgecolor='#4CAF50',
                      alpha=0.9,
                      linewidth=2))
    # 9. 添加线宽示例图
    example_y = 0.75
    line_examples = [
        (4000, "超粗线:极大量包裹", '#FF4500'),
        (1800, "粗线:大量包裹", '#1E90FF'),
        (900, "中等线:一般包裹", '#32CD32'),
        (400, "细线:少量包裹", '#888888')
    ]
    for flow, label, color in line_examples:
        # 计算示例线宽
        example_width = min(max(flow * 0.005, 1.0), 25.0)
        # 绘制示例线
        ax.plot([0.02, 0.08], [example_y, example_y],
                color=color,
                linewidth=example_width,
                alpha=0.9,
                transform=ax.transAxes,
                solid_capstyle='round')
        ax.text(0.09, example_y, f"{label} ({flow}件)",
                transform=ax.transAxes,
                fontsize=9,
                va='center',
                fontweight='bold')
        example_y -= 0.04
    # 10. 添加图例说明
    legend_text = "🎨 颜色说明:\n"
    legend_text += "🟢 绿色:北京仓\n"
    legend_text += "🟠 橙色:上海仓\n"
    legend_text += "🔴 红色:广州仓\n"
    legend_text += "🟤 棕色:华北转运\n"
    legend_text += "💗 粉色:华东转运\n"
    legend_text += "⚪ 灰色:华南转运\n"
    legend_text += "🔵 蓝色:家庭地址\n"
    legend_text += "🟣 紫色:公司地址\n"
    legend_text += "🟡 黄色:学校地址"
    ax.text(0.98, 0.98, legend_text,
            transform=ax.transAxes,
            fontsize=10,
            verticalalignment='top',
            horizontalalignment='right',
            bbox=dict(boxstyle='round',
                      facecolor='#F3E5F5',
                      edgecolor='#9C27B0',
                      alpha=0.9,
                      linewidth=2))
    plt.tight_layout()
    return fig, ax
# 创建自定义桑基图
fig, ax = create_custom_sankey()
plt.show()

在这里插入图片描述

观察 说明
最粗的橙色河流 上海仓 → 华东转运 → 家庭,4000 件,是整个系统的“主动脉”。
家庭收货总量最多 三条蓝条相加 ≈ 3900 件,占比 19 %,是第一大收货场景。
学校最边缘 三条黄色条最细,总计仅 1800 件(8.8 %),需求最小。
广州仓最清闲 红色条最短,出库仅 3200 件,远低于上海、北京。
无包裹消失 仓库出库总量 = 转运中心过站总量 = 收货总量,无丢失

四、弦图与桑基图的对比

对比维度 弦图(Chord) 桑基图(Sankey)
核心用途 看“谁和谁有关系 + 关系强弱” 看“从哪来到哪去 + 流量大小”
数据类型 对称方阵(张三→李四 = 李四→张三?) 三列清单(源-目标-流量)
布局 节点均匀分布在圆周 节点排成三竖列(左-中-右)
河流形状 圆弧弦,无固定方向 贝塞尔带,从左到右
箭头含义 箭头仅提示“主动方”,可双向 箭头即物流方向,单向
一眼结论 谁是最活跃的人、谁被孤立 哪条路径最忙、有无丢包
适用场景 社交网络、贸易互补、角色关系 快递、能源、资金流、生产流程

总结

  弦图和桑基图都是可视化复杂关系的强大工具,但服务目的不同:弦图像一个“朋友圈”,展示谁和谁关系密切(如社交网络中的互动频率),节点分布在圆周上,弧线宽度表示关系强度;桑基图则像一条“河流”,追踪物品、资金或能量的流动路径(如快递从仓库到家庭的配送过程),强调流向和流量守恒,线条宽度代表流量大小。简单来说,弦图回答“谁和谁关系好”,桑基图回答“东西从哪里来到哪里去”。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐