由于要绘制企业之间的股权关系图,所以在网上查阅了很多可视化的资料,主要是参考以下这篇https://blog.csdn.net/stevenyu1986/article/details/102992395
但这篇在属性方面的介绍比较少,且部分语句的逻辑有问题,这里再拓展一下
问题背景:有三个股权层级的企业,分别用ABC表示,需要用图形展示相互之间的股权投资关系。这里首先我们要先准备好股权矩阵,可以用dataframe保存,然后用dataframe.values方法直接获取多维数组,注意持股比例一列需为文本格式(str),因为绘图时将作为连接线edge的label参数。
代码实现如下:
from graphviz import Digraph
from graphviz import Source
#输入股权层级矩阵
Layer=[['A公司', 1],
['B1公司', 2],
['B2公司', 2],
['B3公司', 2],
['C1公司', 3],
['C2公司', 3],
['C3公司', 3],
['C4公司', 3],
['C5公司', 3]]
TopLayer=max(Layer)[1]#获取最高股权层级
#输入直接持股矩阵,各列分别为序号,母公司、子公司和直接持股比例,注意直接持股比例为str型
EquityMat=[
[1,'A公司','B1公司','30.00%'],
[2,'A公司','B2公司','40.00%'],
[3,'A公司','B3公司','50.00%'],
[4,'B1公司','C1公司','10.00%'],
[5,'B1公司','C2公司','20.00%'],
[6,'B1公司','C3公司','30.00%'],
[7,'B2公司','C3公司','40.00%'],
[8,'B2公司','C4公司','50.00%'],
[9,'B3公司','C4公司','60.00%'],
[10,'B3公司','C5公司','50.00%'],
[11,'C4公司','C5公司','60.00%'],
[12,'C5公司','B1公司','70.00%']
]
#建立digraph
emap=Digraph(name='公司股权关系图')
#根据股权层级建立node,有多少公司就有多少node;遍历股权层级,同一股权层级为一个subgraph,node的属性一致
for n in range(TopLayer+1):
with emap.subgraph() as layer_n:#name='cluster'+str(n)
for i in range(len(Layer)):
if Layer[i][1]==n:
layer_n.attr(rank='same')
layer_n.node(name=Layer[i][0],color='blue',shape='box',fontname='Microsoft YaHei')
#根据股权关系矩阵建立连接线
for num in range(len(EquityMat)):
emap.edge(EquityMat[num][1],EquityMat[num][2],label=EquityMat[num][3],color='red',fontname='Microsoft Yahei')
emap.render('emap.gv',view=False)#将绘图保存为emap.gv,不需要打开
Source.from_file('emap.gv') #直接在jupyter notebook中查看

下面进行各种拓展:
1、将各层级用框包围起来,并设置背景色,此时必须在subgraph的命名中用cluster
for n in range(TopLayer+1):
with emap.subgraph(name='cluster'+str(n)) as layer_n:
if n==1:
layer_n.attr(bgcolor='pink',label='母公司',fontcolor='violet',fontname='Microsoft Yahei')
elif n==2:
layer_n.attr(bgcolor='skyblue',label='子公司',fontcolor='violet',fontname='Microsoft Yahei')
elif n==3:
layer_n.attr(bgcolor='orange',label='孙公司',fontcolor='violet',fontname='Microsoft Yahei')
for key in dic:
if Layer[i][1]==n:
layer_n.attr(rank='same')
layer_n.node(name=Layer[i][0],color='blue',shape='box',fontname='Microsoft YaHei')

2、规定edge在作图时不区分层级(the edge is not used in ranking the nodes),加入constraint=false
for num in range(len(EquityMat)):
emap.edge(EquityMat[num][1],EquityMat[num][2],label=EquityMat[num][3],color='red',fontname='Microsoft Yahei',constraint='false')

3、修改splines的线型,如果想用直线折现,就在digraph中的graph_attr设置splines=orth,但是当node过多时,极容易报错

(1)splines的默认设置
即splines=true,效果是尽量为直线,偶尔有曲线,但不会穿过node,而如果设置为false型,就是全部为直线,直接穿过node,这两种显示效果不好,一般不用。默认情况下,splines=true
(2)compound型
emap=Digraph(name='公司股权关系图',graph_attr={'splines':'compound'})

(3)ortho型
可以看到运行时有警告,即edge的标签不能显示在正确的位置,右侧那个50%是B3->C5,但离线已经很远了,建议在edge语句中使用xlabel而不是label
emap=Digraph(name='公司股权关系图',graph_attr={'splines':'ortho'})

使用xlabel避免label离线太远
for num in range(len(EquityMat)):
emap.edge(EquityMat[num][1],EquityMat[num][2],xlabel=EquityMat[num][3],color='red',fontname='Microsoft Yahei')

问题是上面的输出过于紧凑,解决办法是在建立digraph时就制定图纸的长宽比,比如我们设置为黄金比例0.618,并在edge语句中加入fontsize='10'
#建立digraph
emap=Digraph(name='公司股权关系图',graph_attr={'splines':'ortho','ratio':'0.618'})

4、修改图纸的方向
emap=Digraph(name='公司股权关系图',graph_attr={'splines':'true','ratio':'0.618','rankdir':'LR'})
或者
emap=Digraph(name='公司股权关系图',graph_attr={'splines':'true','ratio':'0.618'})
emap.attr(rankdir='LR')
5、将所有子公司的node设置为浅蓝色
#如果这里没有指定fillcolor,那么就用边框的color作为fill的颜色
for i in range(len(Layer)):
if Layer[i][1]==n:#Layer[i][1]是层级数
layer_n.attr(rank='same')
#Layer[i][0]是公司名称
layer_n.node(name=Layer[i][0],color='blue',shape='box',fontname='Microsoft YaHei')
if Layer[i][0].startswith('B'):
layer_n.node_attr.update(fillcolor='skyblue',style='filled')

6、将所有指向C3公司的线改为绿色
方法:1:在建立edge前先判断被指向node是不是C3公司
#根据股权关系矩阵建立连接线
for num in range(len(EquityMat)):
if EquityMat[num][2]=='C3公司':
emap.edge(EquityMat[num][1],EquityMat[num][2],label=EquityMat[num][3],color='green',fontname='Microsoft Yahei',fontsize='10')
else:
emap.edge(EquityMat[num][1],EquityMat[num][2],label=EquityMat[num][3],color='red',fontname='Microsoft Yahei',fontsize='10')
方法2:为每一条edge建立subgraph,先统一设置为蓝色,再更新
#根据股权关系矩阵建立连接线
for num in range(len(EquityMat)):
with emap.subgraph(edge_attr={'color':'red'}) as v:#通过建立子图更改某些线的属性
v.edge(EquityMat[num][1],EquityMat[num][2],label=EquityMat[num][3],fontname='Microsoft Yahei',fontsize='10')
if EquityMat[num][2]=='C3公司':
v.edge_attr.update(color='green')

网友评论