Generate Visio-style software engineering diagrams via draw.io Desktop. Use when user asks for: Visio风格图 / 数据库ER图 / 实体关系图 / 类图 / 架构图 / draw.io图 / 软件工程图 / 数据库设计图 / 系统部署图 / 数据流图 / DFD / 程序流程图 / N-S盒图 / Nassi-Shneiderman / 盒图 / UML图 / UML用例图 / UML状态图 / UML时序图 / 用例图 / 状态图 / 时序图 / 活动图 / 泳道图 / 跨职能流程图 / 软件工程图规范. Requires draw.io Desktop installed at C:\Program Files\draw.io\draw.io.
Resources
3Install
npx skillscat add getfunwindz/visio-style-diagram Install via the SkillsCat registry.
Visio-Style Diagram Generator
Generate Visio-style software engineering diagrams via draw.io Desktop.
Outputs .drawio (editable XML) + .png (max 1200px wide).
Skill files:
SKILL.md— this file (skill instruction)README.md— full Chinese documentation with API referenceexamples/er_diagram.py— complete runnable ER diagram generating script (5 diagrams)
All generated files go into a diagram/ subfolder for centralized management.
软件工程规范约束 (Software Engineering Standards)
1. 数据流图 (DFD) 规范
| 元素 | 符号 | 含义 | 约束 |
|---|---|---|---|
| 外部实体 | 矩形 | 系统外的人/组织/系统 | 必须位于图的边缘 |
| 加工 | 圆角矩形/椭圆 | 数据处理 | 必须有编号,有输入/输出流 |
| 数据流 | 带箭头直线 | 数据流动方向 | 必须有名称 |
| 数据存储 | 双横线/开口矩形 | 数据存储 | 必须通过数据流与加工连接 |
2. N-S盒图 规范
| 结构 | 表示 |
|---|---|
| 顺序 | 从上到下排列的矩形块 |
| IF-THEN-ELSE | 条件+左下THEN+右下ELSE |
| CASE | 条件+N个分支列 |
| WHILE-DO | 顶部条件+循环体框 |
| DO-UNTIL | 循环体框+底部条件 |
3. 程序流程图 符号
| 符号 | 用途 |
|---|---|
| 圆角矩形 | 起止框 |
| 矩形 | 处理框 |
| 菱形 | 判断框 |
| 平行四边形 | 数据I/O |
| 箭头 | 控制流 |
4. UML用例图 元素
| 元素 | 含义 |
|---|---|
| Actor 火柴人 | 角色 |
| Use Case 椭圆 | 功能单元 |
| System Boundary 矩形 | 系统范围 |
| 关联/泛化/include/extend | 关系连线 |
5. UML状态图 元素
初始状态(实心圆) → 状态(圆角矩形) → 转换(箭头[guard]) → 选择(菱形) → 最终状态(牛眼)
6. UML时序图 元素
角色/对象 → 生命线(虚线) → 激活条(窄矩形) → 同步/异步/返回消息
7. E-R图 规范
| 元素 | 符号 |
|---|---|
| 实体 | 矩形 |
| 属性 | 椭圆 |
| 关系 | 菱形(1:1 / 1:N / M:N) |
| 主键 | 加粗/下划线 |
Visual Specification
Canvas: #EBEBEB | Entity: #FFFFFF + #DCDCDC border | Header: #C2CFF2
Field: #444444 9px | PK: bold | Separator: #E8E8E8
Connector: 1pt #999999 orthogonal | Cardinality: #555555 bold
Label: italic, centered, white bgCJK Entity Height Formula
HEADER_H=28; ROW_H=20; ENTITY_W=190; COL_GAP=60; ROW_GAP=70
def calc_h(fields): return HEADER_H + len(fields) * ROW_H + 4Collision-Free Routing Rules
- Pre-compute Y positions with column_y(heights, gap)
- Use explicit port positions (exitX/Y, entryX/Y)
- Obstacle-aware: 2-bend(right/left) → 3-bend(aisle) → 4-bend(detour)
- Row-aligned column grid
- validate() before export
See examples/ files for full API usage and diagram generators.
Example Implementation — EntityLayout Engine
import os, subprocess
import xml.sax.saxutils as xesc
OUT = r'output_directory\diagram'
DRAWIO = r'C:\Program Files\draw.io\draw.io'
CID = [1]
def nid(): CID[0] += 1; return str(CID[0])
def esc(s): return xesc.escape(str(s), {'"': '"'})
HEADER_H=28; ROW_H=20; ENTITY_W=190
def calc_h(fields): return HEADER_H + len(fields) * ROW_H + 4
def entity_html(title, fields, pk):
rows = [
'<tr><td style="background-color:#C2CFF2;text-align:center;font-weight:bold;font-size:11px;padding:4px;" colspan="2">'
+ esc(title) + '</td></tr>']
for fn in fields:
b = '<b>' + esc(fn) + '</b>' if fn in pk else esc(fn)
rows.append('<tr><td style="border-top:1px solid #E8E8E8;font-size:9px;padding:3px 6px;" colspan="2">' + b + '</td></tr>')
return '<table>' + ''.join(rows) + '</table>'
class EntityLayout:
def __init__(self):
self.entities=[]; self.cells=[]; self._map={}
def add_entity(self, x, y, name, fields, pk_set=None, w=ENTITY_W):
pk=pk_set or set(); h=calc_h(fields); eid=nid()
html=entity_html(name, fields, pk)
self.cells.append(
'<mxCell id="'+eid+'" value="'+esc(html)+'" '
'style="whiteSpace=wrap;html=1;rounded=0;fillColor=#FFFFFF;'
'strokeColor=#DCDCDC;fontColor=#444444;fontSize=9;" vertex="1" parent="1">')
self.cells.append(
' <mxGeometry x="'+str(x)+'" y="'+str(y)+'" '
'width="'+str(w)+'" height="'+str(h)+'" as="geometry"/>')
self.cells.append(' </mxCell>')
rect=(x,y,w,h)
self.entities.append((eid,name,rect))
self._map[eid]=rect
return eid
def add_connector(self, src, dst, label='', c1='', c2='', sx=1, sy=0.5, tx=0, ty=0.5):
sr=self._map[src]; dr=self._map[dst]
ex=sr[0]+sx*sr[2]; ey=sr[1]+sy*sr[3]
nx=dr[0]+tx*dr[2]; ny=dr[1]+ty*dr[3]
ob=[r for e,_,r in self.entities if e not in (src,dst)]
wpts=self._route(ex,ey,nx,ny,ob)
eid=nid(); lbl=esc(label) if label else ''
style=('edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;'
'strokeColor=#999999;strokeWidth=1;fontSize=10;'
'fontColor=#444444;fontStyle=2;'
'labelBackgroundColor=#FFFFFF;endArrow=classic;endFill=1;'
'exitX={};exitY={};entryX={};entryY={};').format(sx,sy,tx,ty)
self.cells.append(
'<mxCell id="'+eid+'" value="'+lbl+'" style="'+style
+'" edge="1" parent="1" source="'+src+'" target="'+dst+'">')
if wpts:
pts=''.join(' <mxPoint x="{}" y="{}"/>\n'.format(x,y) for x,y in wpts)
self.cells.append(' <mxGeometry relative="1" as="geometry">\n'+pts+' </mxGeometry>')
else:
self.cells.append(' <mxGeometry relative="1" as="geometry"/>')
self.cells.append(' </mxCell>')
# Cardinality labels at waypoint midpoint
if c1 or c2:
if wpts: mi=len(wpts)//2; mx_pt,my_pt=wpts[mi]
else: mx_pt,my_pt=(ex+nx)/2,(ey+ny)/2
for txt,xoff in [(c1,-30),(c2,30)]:
if not txt: continue
ci=nid()
self.cells.append(
'<mxCell id="'+ci+'" value="'+esc(txt)+'" '
'style="text;html=1;fontSize=10;fontStyle=1;fontColor=#555555;'
'align=center;verticalAlign=middle;" vertex="1" connectable="0" parent="1">')
self.cells.append(
' <mxGeometry x="'+str(mx_pt+xoff-10)+'" y="'+str(my_pt-10)+'" '
'width="20" height="20" as="geometry"/>')
self.cells.append(' </mxCell>')
@staticmethod
def _line_hits(x1,y1,x2,y2,r):
rx,ry,rw,rh=r; eps=1 # 1px epsilon prevents boundary false-positives
if x1==x2:
if not(rx-eps<=x1<=rx+rw+eps): return False
return not(max(y1,y2)<=ry-eps or min(y1,y2)>=ry+rh+eps)
if y1==y2:
if not(ry-eps<=y1<=ry+rh+eps): return False
return not(max(x1,x2)<=rx-eps or min(x1,x2)>=rx+rw+eps)
return False
@staticmethod
def _path_ok(pts,ob):
for i in range(len(pts)-1):
for r in ob:
if EntityLayout._line_hits(pts[i][0],pts[i][1],pts[i+1][0],pts[i+1][1],r): return False
return True
def _route(self,ex,ey,nx,ny,ob):
if not ob: return []
ro=max(r[0]+r[2] for r in ob)+80
lo=min(r[0] for r in ob)-80
to=min(r[1] for r in ob)-60
bo=max(r[1]+r[3] for r in ob)+60
for my in [ny,ey,(ey+ny)/2,to+30,bo-30]:
p=[(ex,ey),(ro,my),(nx,ny)]
if self._path_ok(p,ob): return [(ro,my)]
for my in [ny,ey,(ey+ny)/2,to+30,bo-30]:
p=[(ex,ey),(lo,my),(nx,ny)]
if self._path_ok(p,ob): return [(lo,my)]
for my in [to,bo,(to+bo)/2]:
for mx in [ex,nx,ro]:
p=[(ex,ey),(ex,my),(mx,my),(nx,ny)]
if self._path_ok(p,ob): return [(ex,my),(mx,my)]
for my in [to,bo,(to+bo)/2]:
for mx in [ex,nx,lo]:
p=[(ex,ey),(ex,my),(mx,my),(nx,ny)]
if self._path_ok(p,ob): return [(ex,my),(mx,my)]
p=[(ex,ey),(ex,to),(ro,to),(ro,ny),(nx,ny)]
if self._path_ok(p,ob): return [(ex,to),(ro,to),(ro,ny)]
p=[(ex,ey),(ex,bo),(ro,bo),(ro,ny),(nx,ny)]
if self._path_ok(p,ob): return [(ex,bo),(ro,bo),(ro,ny)]
p=[(ex,ey),(ex,to),(lo,to),(lo,ny),(nx,ny)]
if self._path_ok(p,ob): return [(ex,to),(lo,to),(lo,ny)]
p=[(ex,ey),(ex,bo),(lo,bo),(lo,ny),(nx,ny)]
if self._path_ok(p,ob): return [(ex,bo),(lo,bo),(lo,ny)]
return [(ro,ey),(ro,ny)]
def validate(self):
issues=[]
for i,(_,n1,r1) in enumerate(self.entities):
for j,(_,n2,r2) in enumerate(self.entities):
if i>=j: continue
x1,y1,w1,h1=r1; x2,y2,w2,h2=r2
if not(x1+w1<=x2 or x1>=x2+w2 or y1+h1<=y2 or y1>=y2+h2):
issues.append(f' OVERLAP: "{n1}" <-> "{n2}"')
return issues
def to_xml(self):
return '<mxfile host="Electron" modified="2026-06-15T00:00:00.000Z" agent="Mozilla/5.0" version="30.0.4"><diagram name="d1"><mxGraphModel pageWidth="2400" pageHeight="1800"><root><mxCell id="0"/><mxCell id="1" parent="0"/>'+'\n'.join(self.cells)+'</root></mxGraphModel></diagram></mxfile>'
def export(self, subdir, name):
d=os.path.join(OUT,subdir); os.makedirs(d,exist_ok=True)
src=os.path.join(d,name+'.drawio')
with open(src,'w',encoding='utf-8') as f: f.write(self.to_xml())
dst=src.replace('.drawio','.png')
subprocess.run([DRAWIO,'--export','--format','png','--width','1200','--border','16','--output',dst,src],capture_output=True,timeout=30)
return dst