如何使用 Cairo 和 Python 在 PDF 中打印树 table 可视化?

How to print tree table visualization in PDF use Cairo and Python?

追问:

Tree plotting in Python

The author's web site

我想使用 Cairo 和 Python 在 PDF 中可视化树 table(层次结构)。

我修改了代码如下:

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.__bpointer = None
    self.__fpointer = []

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

    @property
    def bpointer(self):
    return self.__bpointer

    @bpointer.setter
    def bpointer(self, value):
    if value is not None:
        self.__bpointer = sanitize_id(value)

    @property
    def fpointer(self):
    return self.__fpointer

    def update_fpointer(self, identifier, mode=_ADD):
    if mode is _ADD:
        self.__fpointer.append(sanitize_id(identifier))
    elif mode is _DELETE:
        self.__fpointer.remove(sanitize_id(identifier))
    elif mode is _INSERT:
        self.__fpointer = [sanitize_id(identifier)]


class Tree(object):

    def __init__(self, cr):
    self._context = cr
    self._colx = 50.0
    self._coly = 50.0
    self.textW = 128.0
    self.textH = 20.0
    self.nodes = []

    def get_index(self, position):
    for index, node in enumerate(self.nodes):
        if node.identifier == position:
            break
    return index

    def create_node(self, name, identifier=None, parent=None):

    node = Node(name, identifier)
    self.nodes.append(node)
    self.__update_fpointer(parent, node.identifier, _ADD)
    node.bpointer = parent
    return node

    def ShowText(self, x, y, st):

    self._context.move_to(x, y)
    self._context.show_text(st)
    self._context.stroke()

    def ShowRectText(self, x, y, w, h, st):
    self.ShowText(x, y, st)
    self._context.rectangle(x - 5, y - self.textH, w, h)
    self._context.stroke()

    def show(self, position, level=_ROOT):
    queue = self[position].fpointer
    h = self.textH*self.__len__()

    if level == _ROOT:
        s1 = "{0} [{1}]".format(self[position].name,
                                self[position].identifier)

        self.ShowRectText(self._colx, self._coly, self.textW, h, s1)
        self._coly = self._coly + self.textH

    else:
        s2 = "{0} [{1}]".format(self[position].name, self[position].identifier)
        self._colx = self._colx + self.textW * level
        self.ShowRectText(self._colx, self._coly, self.textW, h, s2)
        self._coly = self._coly + self.textH
        self._colx = self._colx - self.textW * level
        self._context.stroke()

    if self[position].expanded:
        level += 1
        for element in queue:
            self.show(element, level)  # recursive call

    def expand_tree(self, position, mode=_DEPTH):
    # Python generator. Loosly based on an algorithm from 'Essential LISP' by
    # John R. Anderson, Albert T. Corbett, and Brian J. Reiser, page 239-241
    yield position
    queue = self[position].fpointer
    while queue:
        yield queue[0]
        expansion = self[queue[0]].fpointer
        if mode is _DEPTH:
            queue = expansion + queue[1:]  # depth-first
        elif mode is _WIDTH:
            queue = queue[1:] + expansion  # width-first

    def is_branch(self, position):
    return self[position].fpointer

    def __update_fpointer(self, position, identifier, mode):
    if position is None:
        return
    else:
        self[position].update_fpointer(identifier, mode)

    def __update_bpointer(self, position, identifier):
    self[position].bpointer = identifier

    def __getitem__(self, key):
    return self.nodes[self.get_index(key)]

    def __setitem__(self, key, item):
    self.nodes[self.get_index(key)] = item

    def __len__(self):
    return len(self.nodes)

    def __contains__(self, identifier):
    return [node.identifier for node in self.nodes
            if node.identifier is identifier]


if __name__ == "__main__":
    surface = cairo.PDFSurface("cairo_tree_table_show.pdf", 1000, 800)
    context = cairo.Context(surface)
    tree = Tree(context)

    tree.create_node("Harry", "harry")  # root node
    tree.create_node("Jane", "jane", parent="harry")
    tree.create_node("Bill", "bill", parent="harry")
    tree.create_node("Joe", "joe", parent="jane")
    tree.create_node("Diane", "diane", parent="jane")
    tree.create_node("George", "george", parent="diane")
    tree.create_node("Mary", "mary", parent="diane")
    tree.create_node("Jill", "jill", parent="george")
    tree.create_node("Carol", "carol", parent="jill")
    tree.create_node("Grace", "grace", parent="bill")
    tree.create_node("Mark", "mark", parent="jane")

    tree.show("harry")

它给了我这个:

但我想要:

如果我能在循环中得到树叶的层数,我设置矩形高度=“(这个层的叶子)*textH”,绘制table。

我添加了一些函数(get_leaf_nodes)来获取所有叶子并计算当前网格的高度。

reference this question 来自阿尔瓦罗·富恩特斯

网格高度是对的now.I不得不说Stack Overflow是great.Always给我想要的

结果是:

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.__bpointer = None
        self.__fpointer = []

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

    @property
    def bpointer(self):
        return self.__bpointer

    @bpointer.setter
    def bpointer(self, value):
        if value is not None:
            self.__bpointer = sanitize_id(value)

    @property
    def fpointer(self):
        return self.__fpointer

    def update_fpointer(self, identifier, mode=_ADD):
        if mode is _ADD:
            self.__fpointer.append(sanitize_id(identifier))
        elif mode is _DELETE:
            self.__fpointer.remove(sanitize_id(identifier))
        elif mode is _INSERT:
            self.__fpointer = [sanitize_id(identifier)]


class Tree(object):

    def __init__(self, cr):
        self._context = cr
        self._colx = 50.0
        self._coly = 50.0
        self.textW = 128.0
        self.textH = 20.0
        self.leafs = []
        self.nodes = []

    def get_leaf_nodes(self, position):
        """get all leafs"""
        self.leafs = []
        self._collect_leaf_nodes(position)
        return self.leafs

    def _collect_leaf_nodes(self, position):
        queue = self[position].fpointer
        if queue == []:
            self.leafs.append(self[position])
        else:
            for n in queue:
                self._collect_leaf_nodes(n)

    def get_index(self, position):
        for index, node in enumerate(self.nodes):
            if node.identifier == position:
                break
        return index

    def create_node(self, name, identifier=None, parent=None):

        node = Node(name, identifier)
        self.nodes.append(node)
        self.__update_fpointer(parent, node.identifier, _ADD)
        node.bpointer = parent
        return node

    def ShowText(self, x, y, st):

        self._context.move_to(x, y)
        self._context.show_text(st)
        self._context.stroke()

    def ShowRectText(self, x, y, w, h, st):
        self.ShowText(x, y, st)
        self._context.rectangle(x - 5, y - 0.8 * self.textH, w, h)
        self._context.stroke()

    def show(self, position, level=_ROOT):
        queue = self[position].fpointer
        # get all the children
        h = self.textH * len(self.get_leaf_nodes(position))
        if level == _ROOT:
            s1 = "{0} [{1}]".format(self[position].name,
                                self[position].identifier)
            self.ShowRectText(self._colx, self._coly, self.textW, h, s1)

        else:
            s2 = "{0} [{1}]".format(self[position].name, self[position].identifier)
            self._colx = self._colx + self.textW * level

            self.ShowRectText(self._colx, self._coly, self.textW, h, s2)
            if queue==[]:
                self._coly = self._coly + self.textH
            self._colx = self._colx - self.textW * level

        if self[position].expanded:
            level += 1
            for element in queue:
                self.show(element, level)  # recursive call

    def expand_tree(self, position, mode=_DEPTH):
    # Python generator. Loosly based on an algorithm from 'Essential LISP' by
    # John R. Anderson, Albert T. Corbett, and Brian J. Reiser, page 239-241
    # http://www.quesucede.com/page/show/id/python-3-tree-implementation
        yield position
        queue = self[position].fpointer
        while queue:
            yield queue[0]
            expansion = self[queue[0]].fpointer
            if mode is _DEPTH:
                queue = expansion + queue[1:]  # depth-first
            elif mode is _WIDTH:
                queue = queue[1:] + expansion  # width-first

    def is_branch(self, position):
        return self[position].fpointer

    def __update_fpointer(self, position, identifier, mode):
        if position is None:
            return
        else:
            self[position].update_fpointer(identifier, mode)

    def __update_bpointer(self, position, identifier):
        self[position].bpointer = identifier

    def __getitem__(self, key):
        return self.nodes[self.get_index(key)]

    def __setitem__(self, key, item):
        self.nodes[self.get_index(key)] = item

    def __len__(self):
        return len(self.nodes)

    def __contains__(self, identifier):
        return [node.identifier for node in self.nodes
                if node.identifier is identifier]


if __name__ == "__main__":
    surface = cairo.PDFSurface("cairo_tree_table_show.pdf", 1000, 800)
    context = cairo.Context(surface)
    tree = Tree(context)

    tree.create_node("Harry", "harry")  # root node
    tree.create_node("Jane", "jane", parent="harry")
    tree.create_node("Bill", "bill", parent="harry")
    tree.create_node("Joe", "joe", parent="jane")
    tree.create_node("Diane", "diane", parent="jane")
    tree.create_node("George", "george", parent="diane")
    tree.create_node("Mary", "mary", parent="diane")
    tree.create_node("Jill", "jill", parent="george")
    tree.create_node("Carol", "carol", parent="jill")
    tree.create_node("Grace", "grace", parent="bill")
    tree.create_node("Mark", "mark", parent="jane")
    tree.show("harry")