Python Tree库绘制多叉树的用法介绍

Python Tree库绘制多叉树的用法介绍

Tree 库是一个 Python 的第三方库。这个库主要用于生成树和绘制树的图形。

一、安装Tree

pip install Tree

使用 Tree 库需要配合 PIL 库来实现绘图。

二、官方案例

先看一下 PyPI 里 Tree 库提供的 demo 。PyPI地址:https://pypi.org/project/Tree/

# coding=utf-8
from Tree.core import Tree
from math import radians as rad
from PIL import Image


def main():
    branches = ((.5, rad(-30)), (.6, rad(30)), (.4, rad(60)))
    tree = Tree(pos=(0, 0, 0, -500), branches=branches)
    tree.grow(10)
    tree.move_in_rectangle()
    im = Image.new("RGB", tree.get_size(), (239, 239, 239))
    tree.draw_on(im, (85, 25, 0, 128, 53, 21), (0, 62, 21), 10)
    im.show()


if __name__ == '__main__':
    main()

运行结果:

运行代码,生成了一棵三叉树,然后用 PIL 将这棵树展示成了一张图片。接下来介绍 Tree 库的用法。

三、Tree库介绍

Tree 库分为三个部分,core.py,utils.py和draw.py。

utils.py中实现了节点类Node和颜色转换函数。

draw.py中实现了绘制图形的类Drawer。

core.py中实现了生成树的类Tree和生成分支的函数。

三个部分是耦合的,使用时不需要直接调用utils.py和draw.py中的类和函数,直接使用core.py中的Tree类就行了。

from Tree.core import Tree

在 Tree 库中,多处使用到了Python标准库 math 和第三方库 PIL。不仅如此,当我们调用 Tree 库时,也需要借助 math 库和 PIL 库来生成树的图片。

math库的使用可以参考:https://blog.csdn.net/weixin_43790276/article/details/98476264

PIL库的使用可以参考:https://blog.csdn.net/weixin_43790276/article/details/108478270

四、Tree库的使用

实例化一个Tree类的对象,即可生成一棵树。在初始化一棵树时,有3个参数,pos,branches和sigma。

pos是一个长度为4的元组(列表也可以),分别表示树的起始点和结束点的横纵像素坐标(x0, y0, x1, y1),源码中会根据这两个坐标用勾股定理计算出树的长度(在树没有生长时只有树干),起始点和结束点的位置关系会决定树的生长方向,可以通过坐标的正负值来调整。如果传入的元组长度小于4会报索引越界(找不到足够的数据),如果元组长度大于4则取前4个值,多的数据无效。

branches是一个列表或元组,列表中有多少个值,树生长时就有多少个分支。每一个分支的参数也是一个列表或元组,参数中需要两个数据,第一个表示树枝相对于父枝干的长度变化系数(一般小于1,树枝比树干短),第二个表示树枝相对于父枝干的偏移角度,角度是弧度制(数字角度可以用math库中的radians转换)。在初始化一棵树时,branches如果不传值默认是None,这样源码中计算分支时会报错,如果branches传一个空列表,则使用PIL展示树时会因无法扩展而报错,所以必须传入非空的branches参数。

sigma是一个元组(列表也可以,不过会提示不符合PEP规范),元组中有两个值,第一个用于调整分支的长度,第二个用于调整分支的角度(乘math中的pi)。使用sigma参数不易控制预期效果,所以保持默认的(0, 0)即可,一般不传值。

grow(times=1): 用于使树生长,默认生长1次,即在树干的基础上生长一次,最后一次是树叶,其他的是枝干。虽然默认生长1次,但在后面调用draw_on()方法绘图时,会有除0报错,所以最小需要生长两次,传值应该大于等于2。

age属性表示树的年龄,树grow()了多少次,age就是多少。

move_in_rectangle(): 用于移动树的位置,使树的位置自适应画布(自动将图片移动到画布中心),是一个辅助绘图的方法。

get_size(): 用于获取树的尺寸,返回结果是一个元组,分别表示树的宽和高(width, height)。

使用PIL中的new()函数创建一块画布,用于绘图,有三个参数。第一个参数表示图片的模式,使用“RGB”(red,green,blue三原色的缩写,表示真彩色图像)即可。第二个参数表示画布的大小(按像素计算),因为树从树干生长后,尺寸会变化,所以使用get_size()动态获取当前树的尺寸。第三个参数表示画布的颜色,默认值为0,黑色画布,可以根据需要修改。

draw_on(canvas, stem_color, leaf_color, thickness, ages=None): 将树的结构绘制到画布上,需要4个参数。

canvas, 画布。传入使用PIL库new()出来的画布(也可以使用其他绘图的库)。

stem_color, 表示树干的颜色和枝干的颜色变换梯度。传入一个元组(RGB颜色可以用长度为3的元组表示),如果传入的元组长度为3,则所有枝干的颜色一样,没有渐变,如果传入的元组长度为6,则会根据枝干的年龄age来进行颜色渐变(参考draw.py中的_get_color(age)源码),元组的长度小于6且不为3会报索引越界,长度大于6则后面的数据无效。

leaf_color, 树叶的颜色。传入一个长度为3的元组,长度小于3会报传参错误,大于3则后面的数据无效,这里也可以传入一个16进制的颜色编码。

thickness, 树干的粗细。传入一个整数,值越大,树干越粗。

基本的方法和属性介绍完了,现在看一个简单的例子。

# coding=utf-8
from Tree.core import Tree
from math import radians
from PIL import Image


pos = [0, 0, 0, -300]
branches = [[0.58, radians(-45)], [0.58, radians(45)]]
tree = Tree(pos=pos, branches=branches)
tree.grow(5)
print('tree age is: ', tree.age)
tree.move_in_rectangle()
image = Image.new("RGB", tree.get_size(), 0)
tree.draw_on(image, (80, 20, 10, 120, 60, 30), '#003E15', 15)
image.show()

运行结果:

tree age is:  5

五、Tree库的其他方法介绍

print('树干长度:', tree.length)
print('树叶长度:', tree.get_branch_length())
print(tree.get_branch_length(age=1))
print(tree.get_rectangle())

运行结果:

树干长度: 300.0
树叶长度: 19.690703039999992
174.0
(0.0, 0.0, 626.4380061192694, 613.2190030596347)

length属性表示树干的长度。

get_branch_length(): 返回指定年龄的枝干长度,不指定年龄则返回树叶的长度。指定的年龄可以无限大(会根据变化系数推导结果)。

get_rectangle(): 树经过多次生长后,返回树占用的矩形坐标。

print('树的节点数:', tree.get_node_sum())
print(tree.get_node_sum(3))
# print(tree.nodes)
print(tree.get_nodes())
# print(tree.get_branches())
delta = (10, 10, 10, 10)
tree.move(delta)

运行结果:

树的节点数: 63
15
[[(313.2190030596347, 313.2190030596347)], [(190.18242313317546, 190.1824231331754), (436.255582986094, 190.1824231331754)], [(89.26242313317545, 190.18242313317535), (190.18242313317543, 89.26242313317539), (436.255582986094, 89.26242313317539), (537.175582986094, 190.1824231331754)], [(47.87291764591453, 231.5719286204362), (47.87291764591458, 148.79291764591443), (148.79291764591454, 47.87291764591453), (231.57192862043632, 47.87291764591453), (394.8660774988331, 47.87291764591453), (477.64508847335486, 47.87291764591453), (578.5650884733549, 148.79291764591449), (578.5650884733549, 231.57192862043632)], [(47.87291764591453, 265.5214166204362), (13.923429645914553, 231.5719286204362), (13.92342964591461, 148.79291764591443), (47.87291764591458, 114.84342964591445), (114.84342964591457, 47.87291764591453), (148.79291764591454, 13.923429645914553), (231.57192862043632, 13.923429645914553), (265.5214166204363, 47.87291764591453), (360.9165894988331, 47.87291764591453), (394.8660774988331, 13.923429645914553), (477.64508847335486, 13.923429645914553), (511.59457647335483, 47.87291764591453), (578.5650884733549, 114.84342964591451), (612.5145764733549, 148.79291764591449), (612.5145764733549, 231.57192862043632), (578.5650884733549, 265.5214166204363)], [(61.79634729182908, 279.44484626635074), (33.949487999999974, 279.44484626635074), (0.0, 245.49535826635076), (0.0, 217.64849897452166), (5.684341886080802e-14, 162.71634729182898), (5.684341886080802e-14, 134.86948799999988), (33.94948800000003, 100.91999999999985), (61.796347291829136, 100.91999999999985), (100.92000000000002, 61.79634729182908), (100.92000000000002, 33.949487999999974), (134.869488, 0.0), (162.7163472918291, 0.0), (217.64849897452174, 0.0), (245.49535826635088, 0.0), (279.44484626635085, 33.949487999999974), (279.44484626635085, 61.79634729182908), (346.99315985291855, 61.79634729182908), (346.99315985291855, 33.949487999999974), (380.9426478529185, 0.0), (408.7895071447477, 0.0), (463.7216588274403, 0.0), (491.5685181192694, 0.0), (525.5180061192693, 33.949487999999974), (525.5180061192693, 61.79634729182908), (564.6416588274403, 100.91999999999996), (592.4885181192694, 100.91999999999996), (626.4380061192694, 134.86948799999993), (626.4380061192694, 162.71634729182904), (626.4380061192694, 217.64849897452177), (626.4380061192694, 245.49535826635088), (592.4885181192694, 279.44484626635085), (564.6416588274403, 279.44484626635085)]]

get_node_sum(): 返回从树干到指定年龄的节点总数,不指定年龄则返回当前树的节点数。指定的年龄可以无限大(会根据分支数推导结果)。

nodes属性表示当前树中的所有节点对象,每个年龄的节点构成一个列表。

get_nodes(): 返回当前树中的所有节点坐标,每个年龄的节点构成一个列表。

get_branches(): 返回当前树中的所有枝干坐标,坐标的格式为(x0, y0, x1, y1),每个年龄的枝干构成一个列表。此方法与branches属性没有关系,branches的值是初始化时传入的参数。

move(delta): 移动树,传入一个delta参数,参数格式与pos相同(x0, y0, x1, y1),四个坐标值按delta的值进行平移。

六、Tree库的灵活使用

1. 画一棵好看一点的树

# coding=utf-8
from Tree.core import Tree
from math import radians
from PIL import Image


branches = [[0.7, radians(-50)], [0.45, radians(10)], [0.6, radians(30)]]
tree = Tree(pos=(0, 0, 0, -300), branches=branches)
tree.grow(9)
tree.move_in_rectangle()
image = Image.new("RGB", tree.get_size(), (250, 250, 250))
tree.draw_on(image, (80, 20, 10, 120, 60, 30), '#003E15', 12)
image.show()

运行结果:

2. 不受限于树,也可以绘制其他图形

# coding=utf-8
from Tree.core import Tree
from math import radians
from PIL import Image


tree = Tree(pos=(0, 0, 0, -100), branches=[[1.1, radians(30)]])
tree.grow(24)
tree.move_in_rectangle()
tree.move((100, 100, 100, 100))
size = tuple([s+200 for s in tree.get_size()])
image = Image.new("RGB", size, (250, 250, 250))
tree.draw_on(image, (255, 0, 0), (255, 0, 0), 15)
image.show()

运行结果:

树的生长和绘图很耗内存,树生长的次数较大的话,内存就不够了。

猜你喜欢

转载自blog.csdn.net/weixin_43790276/article/details/108561960