前言
弦图是一种较为实用的可视化图,它可以展示出各部分之间的关系。通常用于生信领域,探求个物种之间的相互作用与关系,利用R语言中的工具包可轻松画出。
然而,对于脑的研究来说,R语言的弦图绘制工具包略有不足,比如大脑中存在多个脑区,而每个脑区又属于不同的网络,存在一个系列包含的关系,故R语言弦图无法满足这一要求。此外,python的弦图绘制工具包收费,对于学生党来说不太友好,故我写了一个绘制网络与节点的关系弦图。其代码包含两个步骤:1、外侧的圆环绘制(网络区域);2、内侧各节点连接曲线绘制。
原理及代码
首先构建你的数据,一个对称矩阵,这里的示例数据为一个69*69的连接矩阵。
接下来,需要创建一个类,规定内侧的连接曲线绘制方法,这里采用贝塞斯曲线,相较于直线或其他线来说,更加美观。
# 调用工具包
from matplotlib.font_manager import FontProperties
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
# 写一个贝塞斯曲线的类:
class BezierCurve:
def __init__(self, control_points): # 其包含了后续绘制图中所需的节点坐标
self.control_points = np.array(control_points)
def evaluate(self, t): # 在给定参数t下计算曲线上的点的坐标
n = len(self.control_points) - 1
return np.sum([self.control_points[i] * self.bernstein_poly(i, n, t) for i in range(n + 1)], axis=0)
def bernstein_poly(self, i, n, t): # 计算贝塞尔基函数的值
return np.math.comb(n, i) * (t ** i) * ((1 - t) ** (n - i))
下一步是对数据进行绘制,分别是内侧个节点的连接,以及外侧个节点的上层网络。由于连接有正有负,故分为值>0和值<0两步:
# 曲线绘制代码:
def draw_chord_plot(df): # 首先规定了基础的网络大小、色彩、字号等
plt.rcParams.update({'font.size': 30})
class_sizes = [12,15,13,10,5,6,8]
class_names = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
custom_colors = ['#008F7A','#0089BA', '#2C73D2', '#845EC2', '#FF6F91', '#FF9671', '#FFC75F']
custom_cmap = ListedColormap(custom_colors)
cmap = custom_cmap
colors = [cmap(i) for i in range(len(class_sizes))]
plt.figure(figsize=(20, 16))
plt.pie(class_sizes, labels=class_names, startangle=0, colors=colors, wedgeprops=dict(width=0.5, linewidth=15, edgecolor='white'), textprops={'fontsize': 30})
centre_circle = plt.Circle((0, 0),0.9, fc='white')
fig = plt.gcf()
fig.gca().add_artist(centre_circle)
sorted_values = df.values.flatten()
sorted_values.sort()
for i in range(69): # 绘制正连接曲线
for j in range(i + 1, 69):
value = df.iloc[i, j]
if value > 0:
angle_i = i * 360 / 69
angle_j = j * 360 / 69
x_i, y_i = 0.87 * np.cos(np.radians(angle_i)), 0.87 * np.sin(np.radians(angle_i))
x_j, y_j = 0.87 * np.cos(np.radians(angle_j)), 0.87 * np.sin(np.radians(angle_j))
# 使用自定义的贝塞尔曲线
bezier_curve = BezierCurve([(x_i, y_i), (0,0),(x_j, y_j)])
t_values = np.linspace(0, 1, 90)
curve_points = np.array([bezier_curve.evaluate(t) for t in t_values])
# 线条粗细和颜色
color = '#D86161'
# 线条粗细和颜色
max_line_width = 10
linewidths = max_line_width * np.concatenate((np.linspace(1, 0.1, 45), np.linspace(0.1, 1, 45)))
# 添加曲线
for t in range(1, len(curve_points)):
plt.plot([curve_points[t - 1, 0], curve_points[t, 0]],
[curve_points[t - 1, 1], curve_points[t, 1]],
color=color, linewidth=linewidths[t], alpha=1)
for i in range(69): # 绘制负连接曲线
for j in range(i + 1, 69):
value = df.iloc[i, j]
if value < 0:
angle_i = i * 360 / 69
angle_j = j * 360 / 69
x_i, y_i = 0.87 * np.cos(np.radians(angle_i)), 0.87 * np.sin(np.radians(angle_i))
x_j, y_j = 0.87 * np.cos(np.radians(angle_j)), 0.87 * np.sin(np.radians(angle_j))
# 使用自定义的贝塞尔曲线
bezier_curve = BezierCurve([(x_i, y_i), (0,0),(x_j, y_j)])
t_values = np.linspace(0, 1, 90)
curve_points = np.array([bezier_curve.evaluate(t) for t in t_values])
# 线条粗细和颜色
color = '#6D9AEF'
max_line_width = 10
linewidths = max_line_width * np.concatenate((np.linspace(1, 0.1, 45), np.linspace(0.1, 1, 45)))
for t in range(1, len(curve_points)):
plt.plot([curve_points[t - 1, 0], curve_points[t, 0]],
[curve_points[t - 1, 1], curve_points[t, 1]],
color=color, linewidth=linewidths[t], alpha=1)
# 外周圆环图例
class_names = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
legend_handles = [Line2D([0], [0], marker='o', color='w', markerfacecolor=c, markersize=30, label=l)
for c, l in zip(colors, class_names)]
plt.legend(handles=legend_handles, bbox_to_anchor=(0.95, 0.8), loc='upper left',frameon=False)
plt.axis('equal')
最后就是带入示例数据至以上代码:
df = pd.read_csv('示例文件')
draw_chord_plot(df)
plt.savefig('保存文件')
plt.show()
代码最终绘制成图如下: