705 lines
21 KiB
Python
705 lines
21 KiB
Python
import re
|
||
from typing import Optional
|
||
|
||
import svgwrite
|
||
from svgwrite.container import Group
|
||
from svgwrite.path import Path
|
||
from svgwrite.shapes import Rect
|
||
from svgwrite.text import Text
|
||
|
||
from paste.util import ustr
|
||
|
||
|
||
class TextRect(Group):
|
||
"""
|
||
可显示文本的矩形。
|
||
"""
|
||
|
||
def __init__(self, text, insert, text_extra: dict = None, rect_extra: dict = None, **extra):
|
||
# 父类初始化
|
||
super().__init__(**extra)
|
||
|
||
self.text = text
|
||
"""
|
||
要显示的文本内容。
|
||
"""
|
||
|
||
self.extra = extra
|
||
"""
|
||
组合扩展信息。
|
||
"""
|
||
|
||
self.rectExtra = rect_extra if rect_extra is not None else {}
|
||
"""
|
||
外框扩展信息。
|
||
"""
|
||
|
||
self.textExtra = text_extra if text_extra is not None else {}
|
||
"""
|
||
文本扩展信息。
|
||
"""
|
||
|
||
self.rectInsert = insert
|
||
"""
|
||
整体位置参数,即外框的位置参数。
|
||
"""
|
||
|
||
# 初始化文本尺寸
|
||
_fs = self.font_size
|
||
|
||
self.textInsert = self.text_pos
|
||
"""
|
||
文本位置参数。
|
||
"""
|
||
|
||
# 文本初始化
|
||
self.textElement = Text(self.text, insert=self.textInsert, **self.textExtra)
|
||
# 矩形初始化
|
||
self.rectElement = Rect(insert=self.rectInsert, size=self.rect_size, **self.rectExtra)
|
||
|
||
# 加入元素
|
||
self.add(self.rectElement)
|
||
self.add(self.textElement)
|
||
|
||
@property
|
||
def font_size(self):
|
||
"""
|
||
从样式中识别字体大小,单位用像素,缺省 14px。
|
||
|
||
:return: 字体大小
|
||
"""
|
||
_font_size = self.textExtra.get('font-size', self.extra.get('font-size', f"{14}px"))
|
||
self.textExtra['font-size'] = _font_size
|
||
_size = re.sub(r'\D', '', _font_size.strip())
|
||
return int(_size)
|
||
|
||
@property
|
||
def text_width(self):
|
||
"""
|
||
文本宽度(近似)。
|
||
"""
|
||
total = len(self.text)
|
||
q_count = ustr.str_q_count(self.text)
|
||
return q_count * self.font_size + (total - q_count) * self.font_size * 0.5
|
||
|
||
@property
|
||
def text_height(self):
|
||
"""
|
||
文本高度(近似)。
|
||
"""
|
||
return self.font_size * 1.2
|
||
|
||
@property
|
||
def rect_width(self):
|
||
"""
|
||
外框宽度。
|
||
"""
|
||
return self.text_width + self.font_size * 1.5
|
||
|
||
@property
|
||
def rect_height(self):
|
||
"""
|
||
外框高度。
|
||
"""
|
||
return self.text_height * 1.9
|
||
|
||
@property
|
||
def rect_size(self):
|
||
"""
|
||
外框尺寸。
|
||
"""
|
||
return self.rect_width, self.rect_height
|
||
|
||
@property
|
||
def text_pos(self):
|
||
"""
|
||
文本位置。
|
||
"""
|
||
return \
|
||
self.rectInsert[0] + (self.rect_width - self.text_width) * 0.5, \
|
||
self.rectInsert[1] + self.text_height * 1.25
|
||
|
||
def reposition(self, position: tuple):
|
||
"""
|
||
重新定位。
|
||
|
||
:param position: 位置坐标
|
||
"""
|
||
self.rectInsert = position
|
||
self.rectElement.attribs['x'] = self.rectInsert[0]
|
||
self.rectElement.attribs['y'] = self.rectInsert[1]
|
||
|
||
self.textInsert = self.text_pos
|
||
self.textElement.attribs['x'] = self.textInsert[0]
|
||
self.textElement.attribs['y'] = self.textInsert[1]
|
||
|
||
def point_bottom(self):
|
||
"""
|
||
底部点。
|
||
"""
|
||
return self.rectInsert[0] + self.rect_width / 2, self.rectInsert[1] + self.rect_size[1]
|
||
|
||
def point_top(self):
|
||
"""
|
||
顶部点。
|
||
"""
|
||
return self.rectInsert[0] + self.rect_width / 2, self.rectInsert[1]
|
||
|
||
def point_left(self):
|
||
"""
|
||
左侧点。
|
||
"""
|
||
return self.rectInsert[0], self.rectInsert[1] + self.rect_height / 2
|
||
|
||
def point_right(self):
|
||
"""
|
||
右侧点。
|
||
"""
|
||
return self.rectInsert[0] + self.rect_width, self.rectInsert[1] + self.rect_height / 2
|
||
|
||
@classmethod
|
||
def horizontal_path(cls, start: tuple, end: tuple, **extra):
|
||
"""
|
||
生成水平方向连接线。
|
||
|
||
:param start: 起点坐标
|
||
:param end: 终点坐标
|
||
:param extra: 扩展参数
|
||
:return: 路径对象
|
||
"""
|
||
_p_control = [
|
||
(start[0] + end[0]) * 0.5,
|
||
start[1]
|
||
]
|
||
|
||
_p_center = [
|
||
(start[0] + end[0]) * 0.5,
|
||
(start[1] + end[1]) * 0.5
|
||
]
|
||
|
||
_path = Path(**extra)
|
||
|
||
_path.push(['M', start])
|
||
_path.push(['Q', _p_control + _p_center])
|
||
_path.push(['T', end])
|
||
|
||
return _path
|
||
|
||
@classmethod
|
||
def vertical_path(cls, start: tuple, end: tuple, **extra):
|
||
"""
|
||
生成垂直方向连接线。
|
||
|
||
:param start: 起点坐标
|
||
:param end: 终点坐标
|
||
:param extra: 扩展参数
|
||
:return: 路径对象
|
||
"""
|
||
_p_control = [
|
||
start[0],
|
||
(start[1] + end[1]) * 0.5
|
||
]
|
||
|
||
_p_center = [
|
||
(start[0] + end[0]) * 0.5,
|
||
(start[1] + end[1]) * 0.5
|
||
]
|
||
|
||
_path = Path(**extra)
|
||
|
||
_path.push(['M', start])
|
||
_path.push(['Q', _p_control + _p_center])
|
||
_path.push(['T', end])
|
||
|
||
return _path
|
||
|
||
def choose_point(self, sibling: 'TextRect'):
|
||
"""
|
||
选择与目标文本框的连线点。
|
||
|
||
返回起点(tuple)在自生文本框上,终点(tuple)在目标文本框上。
|
||
|
||
:param sibling: 目标文本框
|
||
:return: 起点、终点、是否水平连线
|
||
"""
|
||
_start = self.point_bottom()
|
||
_end = sibling.point_top()
|
||
_is_horizontal = True
|
||
|
||
if self.point_bottom()[1] > sibling.point_top()[1]:
|
||
if self.point_right()[0] < sibling.point_left()[0]:
|
||
_start = self.point_right()
|
||
_end = sibling.point_left()
|
||
_is_horizontal = False
|
||
elif self.point_left()[0] > sibling.point_right()[0]:
|
||
_start = self.point_left()
|
||
_end = sibling.point_right()
|
||
_is_horizontal = False
|
||
else:
|
||
_start = self.point_top()
|
||
_end = sibling.point_bottom()
|
||
_is_horizontal = True
|
||
elif self.point_top()[1] < sibling.point_bottom()[1]:
|
||
if self.point_right()[0] < sibling.point_left()[0]:
|
||
_start = self.point_right()
|
||
_end = sibling.point_left()
|
||
_is_horizontal = False
|
||
elif self.point_left()[0] > sibling.point_right()[0]:
|
||
_start = self.point_left()
|
||
_end = sibling.point_right()
|
||
_is_horizontal = False
|
||
else:
|
||
_start = self.point_bottom()
|
||
_end = sibling.point_top()
|
||
_is_horizontal = True
|
||
else:
|
||
if self.point_right()[0] < sibling.point_left()[0]:
|
||
_start = self.point_right()
|
||
_end = sibling.point_left()
|
||
_is_horizontal = False
|
||
elif self.point_left()[0] > sibling.point_right()[0]:
|
||
_start = self.point_left()
|
||
_end = sibling.point_right()
|
||
_is_horizontal = False
|
||
else:
|
||
_start = self.point_bottom()
|
||
_end = sibling.point_top()
|
||
_is_horizontal = True
|
||
|
||
return _start, _end, _is_horizontal
|
||
|
||
def connect(self, sibling: 'TextRect', **extra):
|
||
"""
|
||
取得连接路径。
|
||
|
||
:param sibling: 目标文本框
|
||
:param extra: 连线扩展参数
|
||
:return: 连接路径
|
||
"""
|
||
_start, _end, _is_horizontal = self.choose_point(sibling)
|
||
if _is_horizontal:
|
||
return self.vertical_path(_start, _end, **extra)
|
||
else:
|
||
return self.horizontal_path(_start, _end, **extra)
|
||
|
||
|
||
class RelationGraph:
|
||
"""
|
||
SVG 关系图。
|
||
|
||
根据 title 名称和 row_list 列表数据输出 svg 格式的关系图谱。
|
||
"""
|
||
|
||
def __init__(self, filename: str = 'noname.svg'):
|
||
self.filename = filename
|
||
|
||
self.width = 800
|
||
"""
|
||
画布宽度。
|
||
"""
|
||
self.height = 600
|
||
"""
|
||
画布高度。
|
||
"""
|
||
self.vhSpace = 170
|
||
"""
|
||
内容垂直浮动空间。
|
||
"""
|
||
self.lrSpace = 100
|
||
"""
|
||
内容左右留白空间。
|
||
"""
|
||
|
||
self.titleTextExtra = {
|
||
'font-size': '16px', 'fill': 'rgb(255, 255, 255)'
|
||
}
|
||
"""
|
||
标题文本样式。
|
||
"""
|
||
|
||
self.titleRectExtra = {
|
||
'rx': 10, 'ry': 10, 'fill': 'rgb(233, 72, 41)', 'fill-opacity': 1, 'stroke': 'rgb(233, 72, 41)'
|
||
}
|
||
"""
|
||
标题外框样式
|
||
"""
|
||
|
||
self.textExtra = {
|
||
'font-size': '14px', 'fill': 'rgb(255, 255, 255)'
|
||
}
|
||
"""
|
||
普通文本样式。
|
||
"""
|
||
|
||
self.rectExtra = {
|
||
'rx': 10, 'ry': 10, 'fill': 'rgb(65, 130, 164)', 'fill-opacity': 1, 'stroke': 'rgb(65, 130, 164)'
|
||
}
|
||
"""
|
||
普通文本外框样式。
|
||
"""
|
||
|
||
self.pathExtra = {
|
||
'fill': 'none', 'stroke': 'rgb(65, 130, 164)'
|
||
}
|
||
"""
|
||
连线样式。
|
||
"""
|
||
|
||
self.drawing = svgwrite.Drawing(filename=self.filename)
|
||
"""
|
||
主绘图对象。
|
||
"""
|
||
|
||
self.attribs = self.drawing.attribs
|
||
"""
|
||
图像样式。
|
||
"""
|
||
|
||
self.save = self.drawing.save
|
||
"""
|
||
保存文件方法。
|
||
"""
|
||
|
||
self.attribs.update({
|
||
'width': self.width, 'height': self.height
|
||
})
|
||
|
||
self.titleTr: Optional[TextRect] = None
|
||
"""
|
||
标题文本框对象。
|
||
"""
|
||
|
||
def draw(self, title: str, row_list: list[dict]):
|
||
"""
|
||
绘制图形。
|
||
|
||
:param row_list: 数据对象列表,必须包含 unit_name, unit_uscid, enterprise_id 三个字段
|
||
:param title: 标题文本
|
||
:return: 自身对象
|
||
"""
|
||
# 重定设图像参数
|
||
self.attribs.update(self.attribs)
|
||
|
||
# 创建标题文本框
|
||
self.titleTr = TextRect(
|
||
text=title, insert=(0, 0), text_extra=self.titleTextExtra, rect_extra=self.titleRectExtra, **{
|
||
'debug': False
|
||
}
|
||
)
|
||
self.titleTr.reposition((
|
||
(self.width - self.titleTr.rect_width) * 0.5, (self.height - self.titleTr.rect_height) * 0.5 - 20
|
||
))
|
||
self.drawing.add(self.titleTr)
|
||
|
||
_tr_list: list[TextRect] = []
|
||
for _i, _row in enumerate(row_list):
|
||
# 遍历数据,初始创建所有的文本框,得到文本框尺寸信息
|
||
# 同时保留所有需要输出的数据
|
||
_text = f"{_row['short_name']} ({_row['count']})"
|
||
_tr = TextRect(
|
||
text=_text, insert=(0, 0), rect_extra=self.rectExtra, **self.textExtra, **{
|
||
'debug': False,
|
||
'data-name': _row['unit_name'],
|
||
'data-uscid': _row['unit_uscid'],
|
||
'data-enterprise-id': _row['enterprise_id'],
|
||
}
|
||
)
|
||
_tr_list.append(_tr)
|
||
|
||
_harf = int(len(_tr_list) / 2) if int(len(_tr_list) % 2) == 0 else int(len(_tr_list) / 2) + 1
|
||
_top_list = []
|
||
_lft_list = _tr_list[:_harf]
|
||
_rit_list = _tr_list[_harf:]
|
||
_btm_list = []
|
||
|
||
if len(_tr_list) >= 12:
|
||
_top_list = _lft_list[:2]
|
||
_lft_list = _lft_list[2:]
|
||
if len(_tr_list) >= 14:
|
||
_btm_list = _rit_list[-2:]
|
||
_rit_list = _rit_list[:-2]
|
||
|
||
# 遍历所有顶部文本框,重新定位
|
||
for _i, _tr in enumerate(_top_list):
|
||
if _i == 0:
|
||
_position = (
|
||
self.titleTr.point_top()[0] - _tr.rect_width - 15,
|
||
self.titleTr.point_top()[1] - self.vhSpace - _tr.rect_height - 15
|
||
)
|
||
else:
|
||
_position = (
|
||
self.titleTr.point_top()[0] + 15,
|
||
self.titleTr.point_top()[1] - self.vhSpace - _tr.rect_height - 15
|
||
)
|
||
_tr.reposition(_position)
|
||
|
||
# 遍历所有底部文本框,重新定位
|
||
for _i, _tr in enumerate(_btm_list):
|
||
if _i == 0:
|
||
_position = (
|
||
self.titleTr.point_bottom()[0] - _tr.rect_width - 15,
|
||
self.titleTr.point_bottom()[1] + self.vhSpace + _tr.rect_height + 15
|
||
)
|
||
else:
|
||
_position = (
|
||
self.titleTr.point_bottom()[0] + 15,
|
||
self.titleTr.point_bottom()[1] + self.vhSpace + _tr.rect_height + 15
|
||
)
|
||
_tr.reposition(_position)
|
||
|
||
_top = self.titleTr.point_top()[1] - self.vhSpace
|
||
# 遍历所有左则文本框,重新定位
|
||
for _tr in _lft_list:
|
||
_w = _tr.rect_width
|
||
_h = _tr.rect_height
|
||
_space = self.titleTr.point_bottom()[1] - self.titleTr.point_top()[1] + self.vhSpace * 2 + _h
|
||
|
||
_margin = 0
|
||
if len(_lft_list) > 1:
|
||
_margin = (_space - len(_lft_list) * _h) / (len(_lft_list) - 1)
|
||
|
||
_left = self.titleTr.point_left()[0] - _w - self.lrSpace
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
if _tr.point_left()[0] < 20:
|
||
_left = 20
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
_top += _h + _margin
|
||
|
||
_top = self.titleTr.point_top()[1] - self.vhSpace
|
||
# 遍历所有右侧文本框,重新定位
|
||
for _tr in _rit_list:
|
||
_w = _tr.rect_width
|
||
_h = _tr.rect_height
|
||
_space = self.titleTr.point_bottom()[1] - self.titleTr.point_top()[1] + self.vhSpace * 2 + _h
|
||
|
||
_margin = 0
|
||
if len(_rit_list) > 1:
|
||
_margin = (_space - len(_rit_list) * _h) / (len(_rit_list) - 1)
|
||
|
||
_left = self.titleTr.point_right()[0] + self.lrSpace
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
if _tr.point_right()[0] > self.width - 20:
|
||
_left = self.width - _tr.rect_width - 20
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
|
||
_top += _h + _margin
|
||
|
||
for _tr in _tr_list:
|
||
self.drawing.add(self.titleTr.connect(_tr, **self.pathExtra))
|
||
|
||
for _tr in _tr_list:
|
||
self.drawing.add(_tr)
|
||
|
||
|
||
class EnterpriseGraph:
|
||
"""
|
||
SVG 企业汇总信息图。
|
||
|
||
根据 title 名称和 row_list 列表数据输出 svg 格式的关系图谱。
|
||
"""
|
||
|
||
def __init__(self, filename: str = 'noname.svg'):
|
||
self.filename = filename
|
||
|
||
self.width = 800
|
||
"""
|
||
画布宽度。
|
||
"""
|
||
self.height = 300
|
||
"""
|
||
画布高度。
|
||
"""
|
||
self.vhSpace = 50
|
||
"""
|
||
内容垂直浮动空间。
|
||
"""
|
||
self.lrSpace = 100
|
||
"""
|
||
内容左右留白空间。
|
||
"""
|
||
|
||
self.titleTextExtra = {
|
||
'font-size': '16px', 'fill': 'rgb(255, 255, 255)'
|
||
}
|
||
"""
|
||
标题文本样式。
|
||
"""
|
||
|
||
self.titleRectExtra = {
|
||
'rx': 10, 'ry': 10, 'fill': 'rgb(233, 72, 41)', 'fill-opacity': 1, 'stroke': 'rgb(233, 72, 41)'
|
||
}
|
||
"""
|
||
标题外框样式
|
||
"""
|
||
|
||
self.textExtra = {
|
||
'font-size': '14px', 'fill': 'rgb(255, 255, 255)'
|
||
}
|
||
"""
|
||
普通文本样式。
|
||
"""
|
||
|
||
self.rectExtra = {
|
||
'rx': 10, 'ry': 10, 'fill': 'rgb(65, 130, 164)', 'fill-opacity': 1, 'stroke': 'rgb(65, 130, 164)'
|
||
}
|
||
"""
|
||
普通文本外框样式。
|
||
"""
|
||
|
||
self.pathExtra = {
|
||
'fill': 'none', 'stroke': 'rgb(65, 130, 164)'
|
||
}
|
||
"""
|
||
连线样式。
|
||
"""
|
||
|
||
self.drawing = svgwrite.Drawing(filename=self.filename)
|
||
"""
|
||
主绘图对象。
|
||
"""
|
||
|
||
self.attribs = self.drawing.attribs
|
||
"""
|
||
图像样式。
|
||
"""
|
||
|
||
self.save = self.drawing.save
|
||
"""
|
||
保存文件方法。
|
||
"""
|
||
|
||
self.attribs.update({
|
||
'width': self.width, 'height': self.height
|
||
})
|
||
|
||
self.titleTr: Optional[TextRect] = None
|
||
"""
|
||
标题文本框对象。
|
||
"""
|
||
|
||
def draw(self, title: str, data_item: dict):
|
||
"""
|
||
绘制图形。
|
||
|
||
:param data_item: 数据项字典,中文名称:数据值
|
||
:param title: 标题文本
|
||
:return: 自身对象
|
||
"""
|
||
# 重定设图像参数
|
||
self.attribs.update(self.attribs)
|
||
|
||
# 创建标题文本框
|
||
self.titleTr = TextRect(
|
||
text=title, insert=(0, 0), text_extra=self.titleTextExtra, rect_extra=self.titleRectExtra, **{
|
||
'debug': False
|
||
}
|
||
)
|
||
self.titleTr.reposition((
|
||
(self.width - self.titleTr.rect_width) * 0.5, (self.height - self.titleTr.rect_height) * 0.5 - 20
|
||
))
|
||
self.drawing.add(self.titleTr)
|
||
|
||
_tr_list: list[TextRect] = []
|
||
for _key, _val in data_item.items():
|
||
# 遍历数据,初始创建所有的文本框,得到文本框尺寸信息
|
||
# 同时保留所有需要输出的数据
|
||
_text = f"{_key}:{_val}"
|
||
_tr = TextRect(
|
||
text=_text, insert=(0, 0), rect_extra=self.rectExtra, **self.textExtra, **{
|
||
'debug': False,
|
||
}
|
||
)
|
||
_tr_list.append(_tr)
|
||
|
||
_harf = int(len(_tr_list) / 2) if int(len(_tr_list) % 2) == 0 else int(len(_tr_list) / 2) + 1
|
||
_top_list = []
|
||
_lft_list = _tr_list[:_harf]
|
||
_rit_list = _tr_list[_harf:]
|
||
_btm_list = []
|
||
|
||
if len(_tr_list) >= 12:
|
||
_top_list = _lft_list[:2]
|
||
_lft_list = _lft_list[2:]
|
||
if len(_tr_list) >= 14:
|
||
_btm_list = _rit_list[-2:]
|
||
_rit_list = _rit_list[:-2]
|
||
|
||
# 遍历所有顶部文本框,重新定位
|
||
for _key, _tr in enumerate(_top_list):
|
||
if _key == 0:
|
||
_position = (
|
||
self.titleTr.point_top()[0] - _tr.rect_width - 15,
|
||
self.titleTr.point_top()[1] - self.vhSpace - _tr.rect_height - 15
|
||
)
|
||
else:
|
||
_position = (
|
||
self.titleTr.point_top()[0] + 15,
|
||
self.titleTr.point_top()[1] - self.vhSpace - _tr.rect_height - 15
|
||
)
|
||
_tr.reposition(_position)
|
||
|
||
# 遍历所有底部文本框,重新定位
|
||
for _key, _tr in enumerate(_btm_list):
|
||
if _key == 0:
|
||
_position = (
|
||
self.titleTr.point_bottom()[0] - _tr.rect_width - 15,
|
||
self.titleTr.point_bottom()[1] + self.vhSpace + _tr.rect_height + 15
|
||
)
|
||
else:
|
||
_position = (
|
||
self.titleTr.point_bottom()[0] + 15,
|
||
self.titleTr.point_bottom()[1] + self.vhSpace + _tr.rect_height + 15
|
||
)
|
||
_tr.reposition(_position)
|
||
|
||
_top = self.titleTr.point_top()[1] - self.vhSpace
|
||
# 遍历所有左则文本框,重新定位
|
||
for _tr in _lft_list:
|
||
_w = _tr.rect_width
|
||
_h = _tr.rect_height
|
||
_space = self.titleTr.point_bottom()[1] - self.titleTr.point_top()[1] + self.vhSpace * 2 + _h
|
||
|
||
_margin = 0
|
||
if len(_lft_list) > 1:
|
||
_margin = (_space - len(_lft_list) * _h) / (len(_lft_list) - 1)
|
||
|
||
_left = self.titleTr.point_left()[0] - _w - self.lrSpace
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
if _tr.point_left()[0] < 20:
|
||
_left = 20
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
_top += _h + _margin
|
||
|
||
_top = self.titleTr.point_top()[1] - self.vhSpace
|
||
# 遍历所有右侧文本框,重新定位
|
||
for _tr in _rit_list:
|
||
_w = _tr.rect_width
|
||
_h = _tr.rect_height
|
||
_space = self.titleTr.point_bottom()[1] - self.titleTr.point_top()[1] + self.vhSpace * 2 + _h
|
||
|
||
_margin = 0
|
||
if len(_rit_list) > 1:
|
||
_margin = (_space - len(_rit_list) * _h) / (len(_rit_list) - 1)
|
||
|
||
_left = self.titleTr.point_right()[0] + self.lrSpace
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
if _tr.point_right()[0] > self.width - 20:
|
||
_left = self.width - _tr.rect_width - 20
|
||
_position = (_left, _top)
|
||
_tr.reposition(_position)
|
||
|
||
_top += _h + _margin
|
||
|
||
for _tr in _tr_list:
|
||
self.drawing.add(self.titleTr.connect(_tr, **self.pathExtra))
|
||
|
||
for _tr in _tr_list:
|
||
self.drawing.add(_tr)
|