基于Graphviz绘制《红楼梦》关系图

Graphviz是一款开源图形可视化软件。图形可视化(Graph visualization)是一种将图形的结构信息表示为网络图的方法。它在网络、生物信息学、软件工程、数据库和网页设计、机器学习以及其他技术领域的可视化界面中有着重要的应用。

安装Graphviz的方法如下:

  • Linux系统:
sudo apt install graphviz

或者

sudo dnf install graphviz
  • Windows系统:

采用官网提供的安装包安装,或者采用如下pip安装

pip install graphviz

成功安装graphviz后,可利用其中的Digraph库生成有向图。使用graphviz的基本操作见文档,这里介绍几个可以修改的图形属性:

  • 布局引擎(Layout Engines):本代码中为ENGINENAME,是图形可视化的算法名称。dot 将有向图里的每个节点分层,画出的图像有较为严格的层次。fdp(Force-Directed Placement) 为力导向布局,画出的图像有类似放射发散性的布局。circo 为环形布局。
  • 布局方向(Rankdir):本代码中为RANKDIR,其中TB代表Top to Bottom,LR代表Left to Right。
  • 节点形状:本代码中为SHAPE
  • 字体:本代码中为FONTNAME
  • 输出格式:本代码中为FORMAT
  • 红楼梦家族:本代码中为FAMILYNAME

具体代码如下,其中图形属性部分包含可以修改的参数。红楼梦家族成员关系信息用列表存储,generateRelations函数将列表转化为树。

from graphviz import Digraph

#图形属性
FAMILYNAME = 'all'    # 'all', 'jia', 'wang', 'xue', 'shi'
ENGINENAME = 'dot'    # 'dot', 'circo', 'fdp', 'neato', 'osage', 'patchwork', 'sfdp', 'twopi'
RANKDIR = 'LR'        # 'LR', 'RL', 'TB', 'BT' 
SHAPE = 'box'         # 'box', 'ellipse', 'diamond', ...
FONTNAME = 'FangSong' # 'FangSong', 'SimSun', 'KaiTi', ...
FORMAT = 'pdf'        # 'pdf', 'jpeg', 'png', ...

#家族成员关系(用列表表示树)
RELATION_JIA = [u"贾太公", [[u"贾代儒之父", [[u"贾代儒", [[u"贾瑞父", [u"贾瑞"]]]]]], [u"贾演", [[u"贾代化", [u"贾敷", [u"贾敬", [[u"贾珍", [u"贾蓉", u"贾蔷(侄子)"]], u"贾惜春"]]]]]], [u"贾源", [[u"贾代善", [[u"贾赦", [[u"贾琏", [u"巧姐"]], u"贾琮", u"贾迎春"]], [u"贾政", [[u"贾珠", [u"贾兰"]], u"贾元春", u"贾宝玉", u"贾探春", u"贾环"]], [u"贾敏", [u"林黛玉"]]]]]]]]
RELATION_WANG = [u"王公", [[u"王夫人之父", [[u"熙凤父", [u"王熙凤", u"王仁"]], u"王子腾", u"王夫人", u"薛姨妈"]]]]
RELATION_XUE = [u"薛公", [[u"宝钗祖父", [[u"宝琴父", [u"薛蝌", u"薛宝琴"]], [u"宝钗父", [u"薛蟠", u"薛宝钗"]]]]]]
RELATION_SHI = [u"史公", [[u"湘云祖父", [[u"湘云父", [u"史湘云"]], u"史鼐", u"史鼎"]], u"贾母"]]
RELATION_ALL = [u"四大家族", [RELATION_JIA, RELATION_WANG, RELATION_XUE, RELATION_SHI]]

#添加图形元素和关系
def generateRelations(graph_lst : list, current_relation : list) -> None:
    current_person = current_relation[0]
    for next_relation in current_relation[1]:
        isEnd = type(next_relation) != type([])
        next_person = next_relation if isEnd else next_relation[0]
        graph_lst[0].node(next_person, fontname=FONTNAME, shape=SHAPE)
        graph_lst[0].edge(current_person, next_person)
        if not isEnd:
            generateRelations(graph_lst, next_relation)

#获取家族列表
def getFamily(name : str) -> list:
    match name.lower():
        case 'all':
            return RELATION_ALL
        case 'jia':
            return RELATION_JIA
        case 'wang':
            return RELATION_WANG
        case 'xue':
            return RELATION_XUE
        case 'shi':
            return RELATION_SHI
    assert False


#创建有向图
graph = Digraph(engine=ENGINENAME, encoding='utf-8')
graph.attr(rankdir=RANKDIR)
#创建第一个顶点
graph.node(getFamily(FAMILYNAME)[0], fontname=FONTNAME, shape=SHAPE)
#添加节点和边
generateRelations([graph], getFamily(FAMILYNAME))
#生成图像
graph.render(f'relationship_{FAMILYNAME}_{ENGINENAME}_{RANKDIR}_{FONTNAME}', format=FORMAT, view=True)

运行代码后,会在代码存放的目录下生成指定格式的文件。

采用dot布局算法生成的四大家族关系图如下

采用fdp布局算法生成的四大家族关系图如下

采用circo布局算法生成的王家关系图如下

其他的布局算法可以自己尝试一下,graphviz的功能还是很强大的。

结语:最近在温习一些图论相关的知识时偶然发现了这个功能强大的Graphviz库,又想起高中时自己画过的《红楼梦》关系图,遂尝试了一番,敲了敲代码,弄出了文章中的几个图像。不过阅读《红楼梦》不应死记这些名字,有时候就算不给出某个人物的名字,其说话方式也能让大部分读者知晓其身份。Graphviz的功能远不止这些,有兴趣可以看下官网的介绍,希望你能有些收获。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注