structviz 0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
structviz/models.py ADDED
@@ -0,0 +1,82 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Optional, Literal
3
+
4
+ class LayoutConfig(BaseModel):
5
+ max_columns: int = 0 # 每行最多 block 数,0=不限制
6
+ nodesep: float = 0.3 # 同行节点水平间距 (英寸)
7
+ ranksep: float = 0.6 # 行间垂直间距 (英寸)
8
+ block_pad: float = 0.15 # block 内左右内边距 (英寸)
9
+ row_gap: float = 1.0 # block 内换行间距 (minlen 倍数)
10
+ deputy_gap: int = 40 # 中心领导框内 主任/副主任 水平间距
11
+ wrap_width: int = 12 # 部门名称自动换行宽度 (字符数),0=不换行
12
+ dept_gap: int = 6 # 板块内部门节点间距
13
+ dept_width: int = 150 # 部门节点宽度
14
+
15
+ class Meta(BaseModel):
16
+ type: Literal["equity", "org"]
17
+ title: str = "Chart"
18
+ theme: str = "corporate"
19
+ layout: Optional[LayoutConfig] = None
20
+ papersize: Optional[str] = None # 纸张预设: a4/a3/letter 等
21
+ dpi: Optional[int] = None # 输出 DPI
22
+
23
+ # ---------- 股权图模型 ----------
24
+ class EquityNode(BaseModel):
25
+ id: str
26
+ label: str
27
+ shape: str = "box"
28
+ style: str = ""
29
+ fillcolor: str = ""
30
+ color: str = ""
31
+ fontcolor: str = ""
32
+
33
+ class EquityEdge(BaseModel):
34
+ from_id: str = Field(alias="from")
35
+ to: str
36
+ label: str = ""
37
+ color: str = ""
38
+ penwidth: float = 1.0
39
+ style: str = "solid"
40
+
41
+ class EquityChart(BaseModel):
42
+ meta: Meta
43
+ nodes: List[EquityNode]
44
+ edges: List[EquityEdge]
45
+
46
+ # ---------- 组织架构图模型 ----------
47
+ class OrgNode(BaseModel):
48
+ id: str
49
+ title: str
50
+ name: str = ""
51
+ role: str = ""
52
+ style: Literal["default", "external", "large"] = "default"
53
+ reports_to: Optional[str] = None
54
+ is_cluster_head: bool = False
55
+
56
+ BlockPosition = Literal["left", "right", "bottom"]
57
+
58
+ class OrgBlock(BaseModel):
59
+ id: str
60
+ position: BlockPosition = "bottom"
61
+ label: str = ""
62
+ nodes: List[OrgNode] = []
63
+ max_columns: Optional[int] = None # 块内每行节点数
64
+ edge_from: Optional[str] = None # 箭头到块边框 (lhead)
65
+ block_align: Literal["left", "center"] = "left"
66
+ row_gap: Optional[float] = None # 块内换行间距 (覆盖全局)
67
+
68
+ class OrgGroup(BaseModel):
69
+ level: int
70
+ label: str
71
+ nodes: List[OrgNode] = [] # 本层主要节点
72
+ right_nodes: List[OrgNode] = [] # 本层右侧对齐节点 (独立)
73
+ blocks: List[OrgBlock] = [] # 统一块: position 控制位置
74
+ max_columns: Optional[int] = None # 本层 blocks 每行最多数
75
+ max_right_columns: Optional[int] = None
76
+ gap: Optional[float] = None # 本层后垂直间距
77
+ offset: Optional[float] = None # 本层水平偏移 (英寸)
78
+ nodesep: Optional[float] = None # 本层节点水平间距 (覆盖全局)
79
+
80
+ class OrgChart(BaseModel):
81
+ meta: Meta
82
+ groups: List[OrgGroup]
structviz/render.py ADDED
@@ -0,0 +1,20 @@
1
+ import graphviz
2
+
3
+ def render_dot(dot_source: str, output_path: str, formats: list):
4
+ """
5
+ 使用 graphviz 库将 DOT 源码渲染为多种格式。
6
+ output_path 不含后缀,自动根据格式添加。
7
+ """
8
+ dot_file = output_path + ".dot"
9
+ with open(dot_file, 'w', encoding='utf-8') as f:
10
+ f.write(dot_source)
11
+
12
+ for fmt in formats:
13
+ if fmt == "dot":
14
+ continue
15
+ try:
16
+ src = graphviz.Source(dot_source)
17
+ src.format = fmt
18
+ src.render(output_path, cleanup=True)
19
+ except Exception as e:
20
+ print(f"Error rendering {fmt}: {e}")
structviz/themes.py ADDED
@@ -0,0 +1,99 @@
1
+ import re
2
+
3
+ # 纸张尺寸预设 (单位: 英寸,Graphviz size 属性要求英寸)
4
+ # 宽高比: A系列 ≈ 1:√2, Letter/Legal 略宽
5
+ PAPER_SIZES = {
6
+ "a4": {"size": "8.27,11.69", "ratio": "fill", "label": "A4 (210×297mm)"},
7
+ "a3": {"size": "11.69,16.54", "ratio": "fill", "label": "A3 (297×420mm)"},
8
+ "a2": {"size": "16.54,23.39", "ratio": "fill", "label": "A2 (420×594mm)"},
9
+ "a1": {"size": "23.39,33.11", "ratio": "fill", "label": "A1 (594×841mm)"},
10
+ "a0": {"size": "33.11,46.81", "ratio": "fill", "label": "A0 (841×1189mm)"},
11
+ "letter": {"size": "8.5,11", "ratio": "fill", "label": "Letter (8.5×11in)"},
12
+ "legal": {"size": "8.5,14", "ratio": "fill", "label": "Legal (8.5×14in)"},
13
+ "tabloid": {"size": "11,17", "ratio": "fill", "label": "Tabloid (11×17in)"},
14
+ }
15
+
16
+ def parse_paper_size(value: str) -> dict | None:
17
+ """解析纸张尺寸:预设名称或自定义 "宽,高[mm|in]",默认单位 mm"""
18
+ if not value:
19
+ return None
20
+ if value in PAPER_SIZES:
21
+ return PAPER_SIZES[value]
22
+ m = re.match(r'^(\d+\.?\d*)\s*,\s*(\d+\.?\d*)\s*(mm|in)?$', value, re.IGNORECASE)
23
+ if not m:
24
+ return None
25
+ w, h, unit = float(m.group(1)), float(m.group(2)), (m.group(3) or 'mm').lower()
26
+ if unit == 'in':
27
+ return {"size": f"{w},{h}", "ratio": "fill", "label": f"Custom ({w}×{h}in)"}
28
+ else:
29
+ return {"size": f"{w/25.4:.2f},{h/25.4:.2f}", "ratio": "fill", "label": f"Custom ({w}×{h}mm)"}
30
+
31
+ THEMES = {
32
+ "corporate": {
33
+ "node_default": {
34
+ "shape": "plaintext",
35
+ "fontname": "Helvetica,Arial,sans-serif",
36
+ "fontsize": "11"
37
+ },
38
+ "card_header_bg": "#2C3E50",
39
+ "card_header_font": "white",
40
+ "card_body_bg": "#FFFFFF",
41
+ "card_border_color": "#34495E",
42
+ "external_header_bg": "#E67E22",
43
+ "external_border": "#E67E22",
44
+ "edge_color": "#7F8C8D",
45
+ "edge_penwidth": 1.2,
46
+ "ranksep": 0.6,
47
+ "nodesep": 0.3,
48
+ "cluster_bg": "#F4F6F7",
49
+ "dept_fillcolor": "#EBF5FB",
50
+ "dept_border": "#2980B9",
51
+ },
52
+ "clean": {
53
+ "node_default": {
54
+ "shape": "box",
55
+ "style": "rounded,filled",
56
+ "fillcolor": "#F8F9FA",
57
+ "color": "#ADB5BD",
58
+ "fontname": "Helvetica",
59
+ "fontsize": "11"
60
+ },
61
+ "card_header_bg": "#6C757D",
62
+ "card_header_font": "white",
63
+ "card_body_bg": "#F8F9FA",
64
+ "card_border_color": "#ADB5BD",
65
+ "external_header_bg": "#FD7E14",
66
+ "external_border": "#FD7E14",
67
+ "edge_color": "#6C757D",
68
+ "edge_penwidth": 1.0,
69
+ "ranksep": 0.6,
70
+ "nodesep": 0.3,
71
+ "cluster_bg": "#FFFFFF",
72
+ "dept_fillcolor": "#F8F9FA",
73
+ "dept_border": "#ADB5BD",
74
+ },
75
+ "dark": {
76
+ "node_default": {
77
+ "shape": "box",
78
+ "style": "rounded,filled",
79
+ "fillcolor": "#343A40",
80
+ "color": "#495057",
81
+ "fontcolor": "white",
82
+ "fontname": "Helvetica",
83
+ "fontsize": "11"
84
+ },
85
+ "card_header_bg": "#212529",
86
+ "card_header_font": "#F8F9FA",
87
+ "card_body_bg": "#343A40",
88
+ "card_border_color": "#495057",
89
+ "external_header_bg": "#D6336C",
90
+ "external_border": "#D6336C",
91
+ "edge_color": "#ADB5BD",
92
+ "edge_penwidth": 1.2,
93
+ "ranksep": 0.6,
94
+ "nodesep": 0.3,
95
+ "cluster_bg": "#343A40",
96
+ "dept_fillcolor": "#343A40",
97
+ "dept_border": "#495057",
98
+ }
99
+ }
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: structviz
3
+ Version: 0.2
4
+ Summary: 声明式股权图与组织架构图生成工具 — YAML 驱动,D2/Graphviz/Excalidraw 三引擎
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pyyaml>=6.0
9
+ Requires-Dist: pydantic>=2.0
10
+ Requires-Dist: graphviz>=0.20
11
+ Requires-Dist: click>=8.0
12
+ Requires-Dist: jinja2>=3.0
13
+
14
+ # StructViz
15
+
16
+ 声明式股权图与组织架构图生成工具。YAML 定义结构,一键输出 PNG / PDF / SVG / HTML / Excalidraw 图表。
17
+
18
+ 双引擎:**D2**(直角线 SVG) + **Graphviz DOT**(自动布局) + **Excalidraw**(精确定位)。
19
+
20
+ ## 安装
21
+
22
+ ```bash
23
+ # D2 引擎
24
+ sudo pacman -S d2 # Arch
25
+ # 或从 https://github.com/terrastruct/d2/releases 下载
26
+
27
+ # DOT 引擎
28
+ sudo pacman -S graphviz # Arch
29
+ sudo apt install graphviz # Debian
30
+
31
+ # Python 包
32
+ pip install structviz
33
+ ```
34
+
35
+ ## 快速开始
36
+
37
+ ```bash
38
+ # D2 引擎(直角线 SVG)
39
+ structviz examples/org_siku.yaml -t org -f d2 -o output/
40
+
41
+ # DOT 引擎
42
+ structviz examples/org_bingong.yaml -t org -f png,pdf -p a4 -o output/
43
+
44
+ # Excalidraw 引擎
45
+ structviz examples/org_bingong.yaml -t org -f excalidraw -o output/
46
+
47
+ # 股权图
48
+ structviz examples/equity_sample.yaml -t equity -f png -o output/
49
+ ```
50
+
51
+ ## CLI
52
+
53
+ ```
54
+ structviz YAML_FILE [OPTIONS]
55
+
56
+ Options:
57
+ -t, --type [equity|org] 图表类型 (必填)
58
+ -f, --formats TEXT 输出格式: png,pdf,svg,html,excalidraw,d2
59
+ -o, --output TEXT 输出目录
60
+ -p, --papersize TEXT 纸张: a4|a3|letter 或自定义 "W,H[mm|in]"
61
+ -d, --dpi INTEGER 输出 DPI
62
+ ```
63
+
64
+ ## YAML 结构
65
+
66
+ ### 组织架构图 (org)
67
+
68
+ ```yaml
69
+ meta:
70
+ type: org
71
+ title: "组织架构"
72
+ theme: corporate
73
+ papersize: a4
74
+ dpi: 150
75
+ layout: # 全局布局参数
76
+ max_columns: 3 # 底族每行最多数
77
+ nodesep: 0.3 # 同行节点间距 (英寸)
78
+ ranksep: 0.6 # 行间距 (英寸)
79
+ block_pad: 0.15 # block 内边距
80
+ row_gap: 1 # 换行间距 (minlen)
81
+ # D2 专用
82
+ deputy_gap: 40 # 中心领导框内间距
83
+ wrap_width: 12 # 部门名自动换行宽度(0=不换)
84
+ dept_gap: 6 # 板块内部门间距
85
+ dept_width: 150 # 部门节点宽度
86
+
87
+ groups:
88
+ - level: 0
89
+ label: "权力机构"
90
+ gap: 0.8 # 到下层间距
91
+ nodes:
92
+ - id: 股东大会
93
+ title: "股东大会"
94
+ style: large # large | default | external
95
+
96
+ - level: 1
97
+ label: "决策层"
98
+ blocks:
99
+ - id: 专业委员会
100
+ position: right # right=同行右侧 | bottom=下一行 | left=同行左侧
101
+ max_columns: 3 # 块内每行节点数
102
+ block_align: left # left | center
103
+ edge_from: 董事会 # 箭头到块边框
104
+ nodes:
105
+ - id: 战略委员会
106
+ title: "战略委员会"
107
+
108
+ - level: 2
109
+ gap: 1.5
110
+ nodesep: 3 # 本层节点间距 (覆盖全局)
111
+ nodes:
112
+ - id: 业务运营
113
+ title: "业务运营"
114
+ style: large
115
+ reports_to: 总经理 # 自动连线
116
+
117
+ - level: 3
118
+ blocks:
119
+ - id: 业务运营板块
120
+ position: bottom
121
+ max_columns: 2
122
+ edge_from: 业务运营
123
+ nodes:
124
+ - id: 债贷业务部
125
+ title: "债贷业务部"
126
+ ```
127
+
128
+ ### OrgNode 字段
129
+
130
+ | 字段 | 说明 |
131
+ |------|------|
132
+ | `id` | 唯一标识 |
133
+ | `title` | 显示标题,`\n` 换行 |
134
+ | `name` | 副标题 (可选) |
135
+ | `role` | 小字职务 (可选) |
136
+ | `style` | `large`(居中加粗) / `default` / `external`(橙色) |
137
+ | `reports_to` | 上级节点 id |
138
+ | `is_cluster_head` | 板块标题 (仅 block 内) |
139
+
140
+ ### OrgGroup 布局参数
141
+
142
+ | 字段 | 说明 |
143
+ |------|------|
144
+ | `gap` | 到下层垂直间距 |
145
+ | `offset` | 水平偏移 (英寸) |
146
+ | `nodesep` | 本层节点间距 (覆盖全局) |
147
+ | `max_columns` | blocks 每行数 (覆盖全局) |
148
+ | `max_right_columns` | right_nodes 每行数 |
149
+
150
+ ### OrgBlock 字段
151
+
152
+ | 字段 | 说明 |
153
+ |------|------|
154
+ | `position` | `right` / `bottom` / `left` |
155
+ | `max_columns` | 块内每行节点数 |
156
+ | `row_gap` | 块内换行间距 (minlen) |
157
+ | `block_align` | `left` / `center` |
158
+ | `edge_from` | 箭头源节点 id |
159
+
160
+ ## 纸张尺寸
161
+
162
+ | 预设 | 尺寸 |
163
+ |------|------|
164
+ | a4 | 210×297mm |
165
+ | a3 | 297×420mm |
166
+ | letter | 8.5×11in |
167
+ | 自定义 | `"200,260"` (mm) 或 `"8.5,11in"` |
168
+
169
+ ## 输出文件
170
+
171
+ | 引擎 | 格式 | 文件 |
172
+ |------|------|------|
173
+ | D2 | d2 | `name.d2` (源码) + `name_d2.svg` |
174
+ | DOT | png, pdf, svg | `name.png`, `name.svg` |
175
+ | DOT | html | `name.html` |
176
+ | Excalidraw | excalidraw | `name.excalidraw` + `name_ex.png` |
177
+
178
+ ## 项目结构
179
+
180
+ ```
181
+ structviz/
182
+ ├── pyproject.toml
183
+ ├── README.md
184
+ ├── examples/
185
+ │ ├── equity_sample.yaml
186
+ │ ├── org_siku.yaml
187
+ │ └── org_bingong.yaml
188
+ └── structviz/
189
+ ├── models.py # Pydantic 模型
190
+ ├── themes.py # 主题 + 纸张
191
+ ├── cli.py # CLI 入口
192
+ ├── render.py # DOT 渲染
193
+ ├── d2_render.py # D2 渲染 + 直角后处理
194
+ ├── html_wrapper.py # HTML 包装
195
+ ├── excalidraw_render.py # Excalidraw → PNG
196
+ └── generators/
197
+ ├── equity.py # 股权图 DOT
198
+ ├── org.py # 组织架构 DOT
199
+ ├── d2.py # 组织架构 D2
200
+ └── excalidraw.py # 组织架构 Excalidraw
201
+ ```
202
+
203
+ ## 许可
204
+
205
+ MIT
@@ -0,0 +1,18 @@
1
+ structviz/__init__.py,sha256=Lgf9keBX7aG_JEFt6UYSyiFKcm-BYGtmk9ohpQhYpyk,20
2
+ structviz/cli.py,sha256=3X3DOSTDOmkB5nny312c29QAgsBCMXZ9B9RycXtzzXs,4024
3
+ structviz/d2_render.py,sha256=TDehGa-V2JUWG4e_Ibjf-ETsx9qyeEm3sDmQxGZxwJI,5623
4
+ structviz/excalidraw_render.py,sha256=B9-uCuJT-2KdQB-68l1vkZCwdXRKzC1XNs10VpKCGQk,7546
5
+ structviz/html_wrapper.py,sha256=9BgpzwugxHnHPYwfhiYwiijHgS0hhqpmxkIrhY4ot1o,871
6
+ structviz/models.py,sha256=uqOuvSZ7Y2Mc7gBQhh0RMZ_IUrewsacda__rRTF3c1k,2920
7
+ structviz/render.py,sha256=3Janxo1aVPnNqu6SiUSgLQhuyvDMdAfQQVWUNNGyf6s,626
8
+ structviz/themes.py,sha256=iE5qy83Zmph4xezu-4btDlJ9Nn9r3_S7B2Yg8YKJxpo,3580
9
+ structviz/generators/__init__.py,sha256=A0mGW-y57TdjTXYWpbwCesq0nlik3wE4byHTKA49xmo,154
10
+ structviz/generators/d2.py,sha256=tGTc31Xf-TubGCFjsMAxCPlcgBYkfdBvDZvfGdutsvQ,7506
11
+ structviz/generators/equity.py,sha256=8TPYCHzGiyPUeIaogNqtqQz_WleY4QceXdendM9W1EQ,1766
12
+ structviz/generators/excalidraw.py,sha256=Z0YTWUzVpdL1Snyny1sKlcwUp6CJ15XkvW4ek54U8Tc,12603
13
+ structviz/generators/org.py,sha256=yCSyDJKVEUM8wVWraJXlhTEgHelHtBbokHLV7EjOcIw,14550
14
+ structviz-0.2.dist-info/METADATA,sha256=bGRlnHt_jvwCqGxolIn-NULqjSVs_ngCOkR1O7B2sQI,5581
15
+ structviz-0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
16
+ structviz-0.2.dist-info/entry_points.txt,sha256=zBH-_kOAemCA9u3GGLXUdEVEKILyg0tQFeQWBCGhjuI,49
17
+ structviz-0.2.dist-info/top_level.txt,sha256=_mn-wwrd47HGVONU2Qg5lktCRhAzGccUsOhB8eeWQ94,10
18
+ structviz-0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ structviz = structviz.cli:main
@@ -0,0 +1 @@
1
+ structviz