You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Cairo与Python实现树形表格的PDF可视化输出?

使用Python + Cairo绘制层级树形结构到PDF

基于你提供的代码框架,我补全了完整的实现逻辑,包括节点布局计算、Cairo绘图渲染和PDF输出功能,下面是可直接运行的代码:

import uuid
import cairo

def sanitize_id(id):
    return id.strip().replace(" ", "")

(_ADD, _DELETE, _INSERT) = range(3)
(_ROOT, _DEPTH, _WIDTH) = range(3)

class Node:
    def __init__(self, name, identifier=None, expanded=True):
        self.__identifier = (str(uuid.uuid1()) if identifier is None else sanitize_id(str(identifier)))
        self.name = name
        self.expanded = expanded
        self.__bounds = [0, 0, 0, 0]  # 存储节点位置:x, y, width, height
        self.__children = []
        self.__parent = None

    @property
    def identifier(self):
        return self.__identifier

    @property
    def children(self):
        return self.__children

    @property
    def parent(self):
        return self.__parent

    @property
    def bounds(self):
        return self.__bounds

    @bounds.setter
    def bounds(self, value):
        self.__bounds = value

    def add_child(self, child):
        if child not in self.__children:
            child.__parent = self
            self.__children.append(child)

    def insert_child(self, index, child):
        if index < 0 or index > len(self.__children):
            raise IndexError("Index out of range")
        child.__parent = self
        self.__children.insert(index, child)

    def remove_child(self, child):
        if child in self.__children:
            child.__parent = None
            self.__children.remove(child)

def calculate_tree_layout(root, spacing=50, node_width=150, node_height=50):
    """深度优先遍历计算每个节点的位置,确保层级布局合理"""
    def _calc_depth(node, depth):
        node.depth = depth
        total_height = node_height
        for child in node.children:
            _, child_h = _calc_depth(child, depth + 1)
            total_height += child_h + spacing
        return node_width, total_height

    # 先计算整个树的总尺寸
    total_w, total_h = _calc_depth(root, 0)

    # 为每个节点分配坐标
    def _assign_pos(node, x, y):
        node.bounds = [x, y, node_width, node_height]
        current_y = y + node_height + spacing
        for child in node.children:
            # 子节点向右偏移,形成层级缩进
            child_x = x + spacing * 2
            _assign_pos(child, child_x, current_y)
            current_y += child.bounds[3] + spacing

    _assign_pos(root, spacing, spacing)
    return total_w + spacing * 3, total_h + spacing * 2

def draw_tree_to_pdf(root, pdf_path, spacing=50, node_width=150, node_height=50):
    """将树形结构渲染到指定PDF文件"""
    # 先计算布局尺寸
    surface_w, surface_h = calculate_tree_layout(root, spacing, node_width, node_height)

    # 创建Cairo PDF渲染表面
    surface = cairo.PDFSurface(pdf_path, surface_w, surface_h)
    ctx = cairo.Context(surface)

    # 设置全局样式:字体、颜色
    ctx.set_source_rgb(0.1, 0.1, 0.1)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
    ctx.set_font_size(14)

    def _draw_node(node):
        x, y, w, h = node.bounds
        # 绘制节点背景矩形
        ctx.rectangle(x, y, w, h)
        ctx.set_source_rgb(0.9, 0.9, 0.9)
        ctx.fill_preserve()
        ctx.set_source_rgb(0.1, 0.1, 0.1)
        ctx.set_line_width(1)
        ctx.stroke()

        # 居中绘制节点名称
        text_ext = ctx.text_extents(node.name)
        text_x = x + (w - text_ext[2]) / 2
        text_y = y + (h + text_ext[3]) / 2
        ctx.move_to(text_x, text_y)
        ctx.show_text(node.name)

        # 绘制到子节点的连接线
        for child in node.children:
            cx, cy, _, _ = child.bounds
            # 父节点底部中点
            parent_end_x = x + w/2
            parent_end_y = y + h
            # 子节点顶部中点
            child_start_x = cx + w/2
            child_start_y = cy

            # 绘制折线连接线
            ctx.move_to(parent_end_x, parent_end_y)
            ctx.line_to(parent_end_x, parent_end_y + spacing/2)
            ctx.line_to(child_start_x, child_start_y - spacing/2)
            ctx.line_to(child_start_x, child_start_y)
            ctx.set_line_width(1)
            ctx.stroke()

            # 递归绘制子节点
            _draw_node(child)

    # 从根节点开始绘制整个树
    _draw_node(root)
    surface.finish()

# 测试示例:创建树形结构并生成PDF
if __name__ == "__main__":
    # 构建示例树
    root = Node("公司总部")
    dept1 = Node("技术部")
    dept1.add_child(Node("前端组"))
    dept1.add_child(Node("后端组"))
    dept2 = Node("市场部")
    dept2.add_child(Node("品牌推广组"))
    dept2.add_child(Node("销售组"))
    root.add_child(dept1)
    root.add_child(dept2)

    # 生成PDF
    draw_tree_to_pdf(root, "company_org_chart.pdf")
    print("组织结构图PDF已生成:company_org_chart.pdf")

功能说明

  • Node类:完整封装了树形节点的属性和操作,包括父子关系管理、位置边界存储,满足层级结构的基本需求。
  • 布局计算:通过深度优先遍历先统计整个树的高度,再为每个节点分配坐标,确保层级之间有清晰的缩进和间距。
  • Cairo渲染:负责绘制节点的矩形背景、居中文字,以及父子节点间的连接线,生成的PDF结构清晰、样式统一。
  • 自定义参数:你可以调整spacing(间距)、node_width/node_height(节点尺寸)来适配不同的树形结构需求。

前置准备

确保已经安装了pycairo库:

pip install pycairo

内容的提问来源于stack exchange,提问作者andrew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:04:20