如何在打开draw.io/diagrams.net的XML文件时自动应用流式布局?
问题
我通过程序化生成draw.io XML文件,给ID为“1”的父元素设置了流式布局样式style="childLayout=flowLayout;flowOrientation=west;",但打开文件时所有元素都堆在x=0、y=0的位置,只有手动移动某个元素后,布局才会更新成预期的流程图。
我需要实现:
- 打开文件时自动应用流式布局
- 避免用户手动移动元素触发布局
- 避免生成XML时手动计算元素坐标
如果XML层面无法解决,是否有Python或JavaScript API可以本地模拟打开文件、触发布局并保存带明确坐标的新文件?
附原XML文件内容:
<?xml version="1.0" ?> <mxfile host="app.diagrams.net"> <diagram name="my flow chart"> <mxGraphModel dx="1000" dy="1000" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"> <root> <mxCell id="0"/> <mxCell id="1" style="childLayout=flowLayout;flowOrientation=west;" parent="0"/> <object id="bus1" label="bus1"> <mxCell style="shape=ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1"> <mxGeometry as="geometry" width="80" height="80"/> </mxCell> </object> <object id="bus2" label="bus2"> <mxCell style="shape=ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1"> <mxGeometry as="geometry" width="80" height="80"/> </mxCell> </object> <object id="load1" label="load1" bus="bus2"> <mxCell style="shape=rectangle;whiteSpace=wrap;html=1;" vertex="1" parent="1"> <mxGeometry as="geometry" width="80" height="40"/> </mxCell> </object> <mxCell id="load1_edge" source="bus2" target="load1" edge="1" parent="1"> <mxGeometry as="geometry"/> </mxCell> <object id="line1" label="line1" bus0="bus1" bus1="bus2"> <mxCell source="bus1" target="bus2" edge="1" parent="1"> <mxGeometry as="geometry"/> </mxCell> </object> <object id="gen1" label="gen1" bus="bus1"> <mxCell style="shape=rectangle;whiteSpace=wrap;html=1;" vertex="1" parent="1"> <mxGeometry x="0" y="0" as="geometry" width="80" height="40"/> </mxCell> </object> <mxCell id="gen1_edge" edge="1" source="gen1" target="bus1" parent="1"> <mxGeometry as="geometry"/> </mxCell> </root> </mxGraphModel> </diagram> </mxfile>
注:我已在draw.io的GitHub仓库提交了该功能请求。
解决方案
XML层面临时修复
目前draw.io的XML规范没有直接让布局自动生效的属性,但可以尝试以下两种修改:
- 移除固定坐标:你的
gen1元素明确设置了x="0" y="0",删除这两个属性,让所有子元素的几何属性仅保留宽高,不指定坐标。draw.io加载时可能会对无坐标元素自动应用父布局。
修改后的gen1片段:
<object id="gen1" label="gen1" bus="bus1"> <mxCell style="shape=rectangle;whiteSpace=wrap;html=1;" vertex="1" parent="1"> <mxGeometry as="geometry" width="80" height="40"/> </mxCell> </object>
- 添加父元素自适应标记:在父元素样式中增加
resizeParent=1;,让父元素根据子元素自动调整大小,可能触发布局计算:
<mxCell id="1" style="childLayout=flowLayout;flowOrientation=west;resizeParent=1;" parent="0"/>
替代方案:API自动处理
如果XML层面无法解决,可通过以下方式自动触发布局并保存:
JavaScript方案(基于draw.io桌面版)
draw.io桌面版支持命令行执行脚本,编写JS脚本加载文件、触发布局并保存:
- 确保安装draw.io桌面版
- 编写
applyLayout.js脚本:
const fs = require('fs'); const { app, BrowserWindow } = require('electron'); function applyLayout(inputPath, outputPath) { let win; app.on('ready', () => { win = new BrowserWindow({ show: false }); win.loadURL(`file://${inputPath}`); win.webContents.on('did-finish-load', async () => { // 触发布局计算 await win.webContents.executeJavaScript(` const graph = mxUtils.getCurrentGraph(); const parent = graph.getModel().getCell('1'); graph.layoutCells([parent], true); `); // 序列化并保存XML const xml = await win.webContents.executeJavaScript(` mxUtils.getCurrentGraph().getModel().serialize(); `); fs.writeFileSync(outputPath, xml); win.close(); app.quit(); }); }); } applyLayout(process.argv[2], process.argv[3]);
- 运行脚本:
draw.io --no-sandbox --app applyLayout.js input.drawio output.drawio
Python方案(基于Playwright模拟浏览器)
用Playwright模拟网页版draw.io操作,完成布局应用和导出:
- 安装依赖:
pip install playwright playwright install chromium
- 编写Python脚本:
from playwright.sync_api import sync_playwright import time def apply_drawio_layout(input_path, output_path): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto('https://app.diagrams.net/') # 等待页面加载完成 page.wait_for_selector('#mxFileInput', timeout=10000) # 上传目标文件 page.set_input_files('#mxFileInput', input_path) time.sleep(2) # 执行布局计算 page.evaluate(''' const graph = mxUtils.getCurrentGraph(); const parent = graph.getModel().getCell('1'); mxLayoutManager.getLayout(parent).execute(parent); ''') time.sleep(1) # 导出XML文件 page.click('[data-testid="export-button"]') page.click('[data-testid="export-xml"]') page.click('[data-testid="export-export"]') # 保存下载的文件 download = page.wait_for_download() download.save_as(output_path) browser.close() if __name__ == '__main__': apply_drawio_layout('input.drawio', 'output.drawio')
内容的提问来源于stack exchange,提问作者Stefan
相关产品推荐
相关产品推荐

