CHORD DIAGRAM
绘图 · 第 33 / 36 章
60 字约 1 分钟
基础弦图

基础弦图
Python33 行
import matplotlib.pyplot as plt
import pandas as pd
from pycirclize import Circos
COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"
matrix = [
[0, 10, 5, 8, 3],
[10, 0, 6, 4, 7],
[5, 6, 0, 9, 2],
[8, 4, 9, 0, 5],
[3, 7, 2, 5, 0],
]
labels = list("ABCDE")
df = pd.DataFrame(matrix, index=labels, columns=labels)
# 关键:cmap 需要 {名称: 颜色} 的字典
name2color = {name: color for name, color in zip(labels, COLORS)}
circos = Circos.chord_diagram(
df,
space=5,
cmap=name2color, # ← 字典,不是列表
label_kws=dict(size=13, color=COLORS[3], fontweight="bold"),
link_kws=dict(ec="white", lw=0.8, alpha=0.4, direction=1),
)
fig = circos.plotfig()
fig.suptitle("Chord Diagram — Basic", fontsize=15, color=COLORS[3], y=0.98)
plt.show()贸易流向弦图

贸易流向弦图
Python66 行
import matplotlib.pyplot as plt
from pycirclize import Circos
import numpy as np
COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"
countries = ["USA", "China", "Germany", "Japan", "Brazil"]
matrix = np.array([
[0, 50, 30, 25, 15],
[45, 0, 35, 20, 18],
[28, 32, 0, 15, 12],
[22, 18, 14, 0, 10],
[12, 15, 10, 8, 0],
])
# 每个扇区的大小 = 该国家所有连线的总和
# 注意:为了对称,扇区大小要包含出口 + 进口
sector_sizes = {}
for i, c in enumerate(countries):
out_sum = matrix[i, :].sum()
in_sum = matrix[:, i].sum()
sector_sizes[c] = out_sum + in_sum
circos = Circos(sectors=sector_sizes, space=4)
color_map = {name: COLORS[i] for i, name in enumerate(countries)}
# 画弧 + 标签
for sector in circos.sectors:
track = sector.add_track((70, 100))
track.axis(fc=color_map[sector.name], alpha=0.85, ec="white", lw=1.5)
sector.text(sector.name, r=115, size=12,
color=COLORS[3], fontweight="bold")
# 为每个扇区内的每个连线分配位置
# 用一个字典记录每个扇区当前已经用掉的长度
used = {c: 0 for c in countries}
for i, src in enumerate(countries):
for j, tgt in enumerate(countries):
if i == j or matrix[i, j] == 0:
continue
# 源扇区中,这条线段的起始与结束位置(相对扇区大小)
src_start = used[src]
src_end = src_start + matrix[i, j]
used[src] = src_end
# 目标扇区中,对应线段的起始与结束位置
# 注意:目标扇区中是 matrix[i, j] 这条线在 tgt 扇区里占的份额
tgt_start = used[tgt]
tgt_end = tgt_start + matrix[i, j]
used[tgt] = tgt_end
circos.link(
(src, src_start, src_end),
(tgt, tgt_start, tgt_end),
color=color_map[src],
alpha=0.4,
)
fig = circos.plotfig()
fig.suptitle("Trade Flow Chord Diagram",
fontsize=15, color=COLORS[3], y=0.98)
plt.show()分组弦图(多类别)

分组弦图(多类别)
Python81 行
import matplotlib.pyplot as plt
from pycirclize import Circos
import numpy as np
COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"
# 分组:产品类 / 服务类
groups = {
"Product": ["P-A", "P-B", "P-C"],
"Service": ["S-A", "S-B", "S-C"],
}
group_colors = {
"Product": COLORS[0],
"Service": COLORS[1],
}
matrix = np.array([
[0, 15, 10, 8, 5, 3],
[15, 0, 12, 6, 4, 2],
[10, 12, 0, 5, 7, 4],
[8, 6, 5, 0, 10, 8],
[5, 4, 7, 10, 0, 9],
[3, 2, 4, 8, 9, 0],
])
all_labels = [l for g in groups.values() for l in g]
label_to_group = {l: g for g, ls in groups.items() for l in ls}
label_to_idx = {l: i for i, l in enumerate(all_labels)}
# ① 每个扇区大小 = 该实体的所有连线总和(出 + 入)
sector_sizes = {}
for i, name in enumerate(all_labels):
out_sum = matrix[i, :].sum()
in_sum = matrix[:, i].sum()
sector_sizes[name] = out_sum + in_sum
circos = Circos(sectors=sector_sizes, space=3)
# ② 画弧 + 标签
for sector in circos.sectors:
g = label_to_group[sector.name]
c = group_colors[g]
track = sector.add_track((70, 100))
track.axis(fc=c, alpha=0.85, ec="white", lw=1.5)
sector.text(sector.name, r=112, size=10,
color=COLORS[3], fontweight="bold")
# ③ 累积分配每条弦在扇区中的位置
used = {l: 0 for l in all_labels}
for i, src in enumerate(all_labels):
for j, tgt in enumerate(all_labels):
if i >= j or matrix[i, j] == 0:
continue
# 源扇区段落
src_start = used[src]
src_end = src_start + matrix[i, j]
used[src] = src_end
# 目标扇区段落
tgt_start = used[tgt]
tgt_end = tgt_start + matrix[i, j]
used[tgt] = tgt_end
# 按源所在组着色
c = group_colors[label_to_group[src]]
circos.link(
(src, src_start, src_end),
(tgt, tgt_start, tgt_end),
color=c,
alpha=0.35,
)
fig = circos.plotfig()
fig.suptitle("Grouped Chord Diagram",
fontsize=15, color=COLORS[3], y=0.98)
plt.show()带弦颜色渐变的弦图

带弦颜色渐变的弦图
Python73 行
import matplotlib.pyplot as plt
from matplotlib.colors import to_rgb, to_hex
from pycirclize import Circos
import numpy as np
COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"
labels = ["A", "B", "C", "D", "E"]
matrix = np.array([
[0, 20, 12, 8, 5],
[18, 0, 15, 10, 6],
[10, 14, 0, 12, 8],
[7, 9, 11, 0, 10],
[4, 5, 7, 9, 0],
])
# ① 每个扇区大小 = 该实体的出入总和
sector_sizes = {}
for i, name in enumerate(labels):
sector_sizes[name] = matrix[i, :].sum() + matrix[:, i].sum()
circos = Circos(sectors=sector_sizes, space=5)
# ② 画弧 + 标签
for sector in circos.sectors:
idx = labels.index(sector.name)
track = sector.add_track((72, 100))
track.axis(fc=COLORS[idx], alpha=0.9, ec="white", lw=1.5)
sector.text(sector.name, r=112, size=14,
color=COLORS[3], fontweight="bold")
# ③ 工具:混合两个 hex 颜色
def blend_hex(c1, c2):
r1, g1, b1 = to_rgb(c1)
r2, g2, b2 = to_rgb(c2)
return to_hex(((r1 + r2) / 2, (g1 + g2) / 2, (b1 + b2) / 2))
# ④ 累积分配每条弦在扇区中的位置
used = {l: 0 for l in labels}
for i, src in enumerate(labels):
for j, tgt in enumerate(labels):
if i >= j or matrix[i, j] == 0:
continue
# 非对称矩阵:用两个方向的平均值作为弦宽
w = (matrix[i, j] + matrix[j, i]) / 2
# 源扇区段落
src_start = used[src]
src_end = src_start + w
used[src] = src_end
# 目标扇区段落
tgt_start = used[tgt]
tgt_end = tgt_start + w
used[tgt] = tgt_end
c_hex = blend_hex(COLORS[i], COLORS[j])
circos.link(
(src, src_start, src_end),
(tgt, tgt_start, tgt_end),
color=c_hex,
alpha=0.45,
)
fig = circos.plotfig()
fig.suptitle("Chord Diagram — Blended Colors",
fontsize=15, color=COLORS[3], y=0.98)
plt.show()