w-flowchart 1.0.0 → 1.0.2
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.
- package/README.md +5 -1
- package/dist/w-flowchart.umd.js +2 -2
- package/dist/w-flowchart.umd.js.map +1 -1
- package/docs/WFlowchart.mjs.html +169 -0
- package/docs/global.html +657 -0
- package/docs/index.html +2 -2
- package/package.json +52 -49
- package/src/WFlowchart.mjs +97 -0
- package/src/p1/README.md +2 -2
- package/src/p1/gen.mjs +54 -20
- package/src/p10/README.md +6 -2
- package/src/p10/gen.mjs +235 -15
- package/src/p2/README.md +1 -1
- package/src/p2/gen.mjs +52 -7
- package/src/p3/README.md +1 -1
- package/src/p3/gen.mjs +24 -2
- package/src/p4/README.md +1 -1
- package/src/p4/gen.mjs +36 -10
- package/src/p5/README.md +1 -1
- package/src/p5/gen.mjs +45 -18
- package/src/p6/README.md +1 -1
- package/src/p6/gen.mjs +108 -5
- package/src/p7/README.md +1 -1
- package/src/p7/gen.mjs +42 -3
- package/src/p8/README.md +1 -1
- package/src/p8/gen.mjs +41 -2
- package/src/p9/README.md +1 -1
- package/src/p9/gen.mjs +14 -7
- package/src/p9/page9.html +13 -5
- package/test/cases-diamond.mjs +221 -0
- package/test/cases.mjs +17 -1
- package/test/data//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.json +43 -0
- package/test/data//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.json +132 -0
- package/test/data//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.json +27 -0
- package/test/data//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.json +111 -0
- package/test/data//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.json +117 -0
- package/test/data//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.json +203 -0
- package/test/data//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.json +156 -0
- package/test/data//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.json +46 -0
- package/test/data//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.json +137 -0
- package/test/data//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.json +28 -0
- package/test/data//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.json +134 -0
- package/test/data//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.json +178 -0
- package/test/figures.mjs +3 -2
- package/test/lib.mjs +3 -1
- package/test/p1.test.mjs +5 -0
- package/test/p10.test.mjs +54 -2
- package/test/p2.test.mjs +5 -0
- package/test/p3.test.mjs +5 -0
- package/test/p4.test.mjs +5 -0
- package/test/p5.test.mjs +6 -0
- package/test/p6.test.mjs +5 -0
- package/test/p7.test.mjs +5 -0
- package/test/p8.test.mjs +5 -0
- package/test/p9.test.mjs +5 -0
- package/test/pics/p1/cycle.png +0 -0
- package/test/pics/p1/deep-nest.png +0 -0
- package/test/pics/p1/diamond.png +0 -0
- package/test/pics/p1/edges-mix.png +0 -0
- package/test/pics/p1/english.png +0 -0
- package/test/pics/p1/flat-tb.png +0 -0
- package/test/pics/p1/fold-tall.png +0 -0
- package/test/pics/p1/fold-wide.png +0 -0
- package/test/pics/p1/group-edge.png +0 -0
- package/test/pics/p1/items-node.png +0 -0
- package/test/pics/p1/long-label.png +0 -0
- package/test/pics/p1/lr-flat.png +0 -0
- package/test/pics/p1/nested-group.png +0 -0
- package/test/pics/p1/no-edges.png +0 -0
- package/test/pics/p1/title-overflow.png +0 -0
- package/test/pics/p1/valign.png +0 -0
- package/test/pics/p1//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p1//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p1//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p1//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p1//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p1//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p1//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p1//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p1//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p1//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p1//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p1//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p10/cycle.png +0 -0
- package/test/pics/p10/cycle.svg +1 -0
- package/test/pics/p10/deep-nest.svg +1 -0
- package/test/pics/p10/dia-1in-1out.png +0 -0
- package/test/pics/p10/dia-1in-1out.svg +1 -0
- package/test/pics/p10/dia-1in-2out.png +0 -0
- package/test/pics/p10/dia-1in-2out.svg +1 -0
- package/test/pics/p10/dia-1in.png +0 -0
- package/test/pics/p10/dia-1in.svg +1 -0
- package/test/pics/p10/dia-1out.png +0 -0
- package/test/pics/p10/dia-1out.svg +1 -0
- package/test/pics/p10/dia-2in-1out.png +0 -0
- package/test/pics/p10/dia-2in-1out.svg +1 -0
- package/test/pics/p10/dia-2in-2out-dd.png +0 -0
- package/test/pics/p10/dia-2in-2out-dd.svg +1 -0
- package/test/pics/p10/dia-2in-2out-ss.png +0 -0
- package/test/pics/p10/dia-2in-2out-ss.svg +1 -0
- package/test/pics/p10/dia-2in.png +0 -0
- package/test/pics/p10/dia-2in.svg +1 -0
- package/test/pics/p10/dia-2out.png +0 -0
- package/test/pics/p10/dia-2out.svg +1 -0
- package/test/pics/p10/diamond.png +0 -0
- package/test/pics/p10/diamond.svg +1 -0
- package/test/pics/p10/edges-mix.png +0 -0
- package/test/pics/p10/edges-mix.svg +1 -0
- package/test/pics/p10/english.png +0 -0
- package/test/pics/p10/english.svg +1 -0
- package/test/pics/p10/flat-tb.svg +1 -0
- package/test/pics/p10/fold-tall.svg +1 -0
- package/test/pics/p10/fold-wide.svg +1 -0
- package/test/pics/p10/group-edge.png +0 -0
- package/test/pics/p10/group-edge.svg +1 -0
- package/test/pics/p10/items-node.svg +1 -0
- package/test/pics/p10/long-label.svg +1 -0
- package/test/pics/p10/lr-flat.svg +1 -0
- package/test/pics/p10/nested-group.png +0 -0
- package/test/pics/p10/nested-group.svg +1 -0
- package/test/pics/p10/no-edges.svg +1 -0
- package/test/pics/p10/title-overflow.svg +1 -0
- package/test/pics/p10/valign.svg +1 -0
- package/test/pics/p10//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p10//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.svg +1 -0
- package/test/pics/p10//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p10//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.svg +1 -0
- package/test/pics/p10//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p10//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.svg +1 -0
- package/test/pics/p10//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p10//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.svg +1 -0
- package/test/pics/p10//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p10//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.svg +1 -0
- package/test/pics/p10//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p10//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.svg +1 -0
- package/test/pics/p10//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p10//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.svg +1 -0
- package/test/pics/p10//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p10//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.svg +1 -0
- package/test/pics/p10//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p10//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.svg +1 -0
- package/test/pics/p10//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p10//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.svg +1 -0
- package/test/pics/p10//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p10//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.svg +1 -0
- package/test/pics/p10//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p10//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.svg +1 -0
- package/test/pics/p2/cycle.png +0 -0
- package/test/pics/p2/deep-nest.png +0 -0
- package/test/pics/p2/diamond.png +0 -0
- package/test/pics/p2/edges-mix.png +0 -0
- package/test/pics/p2/english.png +0 -0
- package/test/pics/p2/flat-tb.png +0 -0
- package/test/pics/p2/fold-tall.png +0 -0
- package/test/pics/p2/fold-wide.png +0 -0
- package/test/pics/p2/group-edge.png +0 -0
- package/test/pics/p2/items-node.png +0 -0
- package/test/pics/p2/long-label.png +0 -0
- package/test/pics/p2/lr-flat.png +0 -0
- package/test/pics/p2/nested-group.png +0 -0
- package/test/pics/p2/no-edges.png +0 -0
- package/test/pics/p2/title-overflow.png +0 -0
- package/test/pics/p2/valign.png +0 -0
- package/test/pics/p2//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p2//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p2//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p2//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p2//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p2//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p2//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p2//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p2//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p2//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p2//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p2//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p3/cycle.png +0 -0
- package/test/pics/p3/deep-nest.png +0 -0
- package/test/pics/p3/diamond.png +0 -0
- package/test/pics/p3/edges-mix.png +0 -0
- package/test/pics/p3/english.png +0 -0
- package/test/pics/p3/flat-tb.png +0 -0
- package/test/pics/p3/fold-tall.png +0 -0
- package/test/pics/p3/fold-wide.png +0 -0
- package/test/pics/p3/group-edge.png +0 -0
- package/test/pics/p3/items-node.png +0 -0
- package/test/pics/p3/long-label.png +0 -0
- package/test/pics/p3/lr-flat.png +0 -0
- package/test/pics/p3/nested-group.png +0 -0
- package/test/pics/p3/no-edges.png +0 -0
- package/test/pics/p3/title-overflow.png +0 -0
- package/test/pics/p3/valign.png +0 -0
- package/test/pics/p3//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p3//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p3//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p3//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p3//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p3//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p3//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p3//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p3//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p3//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p3//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p3//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p4/cycle.png +0 -0
- package/test/pics/p4/deep-nest.png +0 -0
- package/test/pics/p4/diamond.png +0 -0
- package/test/pics/p4/edges-mix.png +0 -0
- package/test/pics/p4/english.png +0 -0
- package/test/pics/p4/flat-tb.png +0 -0
- package/test/pics/p4/fold-tall.png +0 -0
- package/test/pics/p4/fold-wide.png +0 -0
- package/test/pics/p4/group-edge.png +0 -0
- package/test/pics/p4/items-node.png +0 -0
- package/test/pics/p4/long-label.png +0 -0
- package/test/pics/p4/lr-flat.png +0 -0
- package/test/pics/p4/nested-group.png +0 -0
- package/test/pics/p4/no-edges.png +0 -0
- package/test/pics/p4/title-overflow.png +0 -0
- package/test/pics/p4/valign.png +0 -0
- package/test/pics/p4//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p4//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p4//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p4//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p4//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p4//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p4//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p4//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p4//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p4//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p4//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p4//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p5/cycle.png +0 -0
- package/test/pics/p5/deep-nest.png +0 -0
- package/test/pics/p5/diamond.png +0 -0
- package/test/pics/p5/edges-mix.png +0 -0
- package/test/pics/p5/english.png +0 -0
- package/test/pics/p5/flat-tb.png +0 -0
- package/test/pics/p5/fold-tall.png +0 -0
- package/test/pics/p5/fold-wide.png +0 -0
- package/test/pics/p5/group-edge.png +0 -0
- package/test/pics/p5/items-node.png +0 -0
- package/test/pics/p5/long-label.png +0 -0
- package/test/pics/p5/lr-flat.png +0 -0
- package/test/pics/p5/nested-group.png +0 -0
- package/test/pics/p5/no-edges.png +0 -0
- package/test/pics/p5/title-overflow.png +0 -0
- package/test/pics/p5/valign.png +0 -0
- package/test/pics/p5//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p5//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p5//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p5//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p5//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p5//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p5//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p5//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p5//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p5//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p5//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p5//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p6/cycle.png +0 -0
- package/test/pics/p6/deep-nest.png +0 -0
- package/test/pics/p6/diamond.png +0 -0
- package/test/pics/p6/edges-mix.png +0 -0
- package/test/pics/p6/english.png +0 -0
- package/test/pics/p6/flat-tb.png +0 -0
- package/test/pics/p6/fold-tall.png +0 -0
- package/test/pics/p6/fold-wide.png +0 -0
- package/test/pics/p6/group-edge.png +0 -0
- package/test/pics/p6/items-node.png +0 -0
- package/test/pics/p6/long-label.png +0 -0
- package/test/pics/p6/lr-flat.png +0 -0
- package/test/pics/p6/nested-group.png +0 -0
- package/test/pics/p6/no-edges.png +0 -0
- package/test/pics/p6/title-overflow.png +0 -0
- package/test/pics/p6/valign.png +0 -0
- package/test/pics/p6//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p6//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p6//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p6//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p6//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p6//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.alt.png +0 -0
- package/test/pics/p6//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.new.png +0 -0
- package/test/pics/p6//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p6//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p6//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p6//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p6//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p6//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p6//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p7/cycle.png +0 -0
- package/test/pics/p7/deep-nest.png +0 -0
- package/test/pics/p7/diamond.png +0 -0
- package/test/pics/p7/edges-mix.png +0 -0
- package/test/pics/p7/english.png +0 -0
- package/test/pics/p7/flat-tb.png +0 -0
- package/test/pics/p7/fold-tall.png +0 -0
- package/test/pics/p7/fold-wide.png +0 -0
- package/test/pics/p7/group-edge.png +0 -0
- package/test/pics/p7/items-node.png +0 -0
- package/test/pics/p7/long-label.png +0 -0
- package/test/pics/p7/lr-flat.png +0 -0
- package/test/pics/p7/nested-group.png +0 -0
- package/test/pics/p7/no-edges.png +0 -0
- package/test/pics/p7/title-overflow.png +0 -0
- package/test/pics/p7/valign.png +0 -0
- package/test/pics/p7//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p7//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p7//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p7//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p7//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p7//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p7//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p7//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p7//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p7//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p7//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p7//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p8/cycle.png +0 -0
- package/test/pics/p8/deep-nest.png +0 -0
- package/test/pics/p8/diamond.png +0 -0
- package/test/pics/p8/edges-mix.png +0 -0
- package/test/pics/p8/english.png +0 -0
- package/test/pics/p8/flat-tb.png +0 -0
- package/test/pics/p8/fold-tall.png +0 -0
- package/test/pics/p8/fold-wide.png +0 -0
- package/test/pics/p8/group-edge.png +0 -0
- package/test/pics/p8/items-node.png +0 -0
- package/test/pics/p8/long-label.png +0 -0
- package/test/pics/p8/lr-flat.png +0 -0
- package/test/pics/p8/nested-group.png +0 -0
- package/test/pics/p8/no-edges.png +0 -0
- package/test/pics/p8/title-overflow.png +0 -0
- package/test/pics/p8/valign.png +0 -0
- package/test/pics/p8//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p8//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p8//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p8//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p8//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p8//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p8//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p8//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p8//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p8//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p8//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p8//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p9/cycle.png +0 -0
- package/test/pics/p9/deep-nest.png +0 -0
- package/test/pics/p9/diamond.png +0 -0
- package/test/pics/p9/edges-mix.png +0 -0
- package/test/pics/p9/english.png +0 -0
- package/test/pics/p9/flat-tb.png +0 -0
- package/test/pics/p9/fold-tall.png +0 -0
- package/test/pics/p9/fold-wide.png +0 -0
- package/test/pics/p9/group-edge.png +0 -0
- package/test/pics/p9/items-node.png +0 -0
- package/test/pics/p9/long-label.png +0 -0
- package/test/pics/p9/lr-flat.png +0 -0
- package/test/pics/p9/nested-group.png +0 -0
- package/test/pics/p9/no-edges.png +0 -0
- package/test/pics/p9/title-overflow.png +0 -0
- package/test/pics/p9/valign.png +0 -0
- package/test/pics/p9//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p9//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p9//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p9//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics/p9//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics/p9//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p9//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p9//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p9//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
- package/test/pics/p9//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics/p9//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics/p9//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/snapshot.mjs +67 -0
- package/test/wflowchart.test.mjs +57 -11
- package/toolg/gDistRollup.mjs +3 -0
- package/test/data//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.json +0 -92
- package/test/data//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.json +0 -112
- package/test/data//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.json +0 -116
- package/test/data//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.json +0 -251
- package/test/data//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.json +0 -126
- package/test/data//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.json +0 -133
- package/test/data//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.json +0 -102
- package/test/data//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.json +0 -117
- package/test/data//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.json +0 -162
- package/test/pics/cycle.png +0 -0
- package/test/pics/cycle.svg +0 -1
- package/test/pics/deep-nest.svg +0 -1
- package/test/pics/diamond.png +0 -0
- package/test/pics/diamond.svg +0 -1
- package/test/pics/edges-mix.png +0 -0
- package/test/pics/edges-mix.svg +0 -1
- package/test/pics/english.png +0 -0
- package/test/pics/english.svg +0 -1
- package/test/pics/flat-tb.svg +0 -1
- package/test/pics/fold-tall.svg +0 -1
- package/test/pics/fold-wide.svg +0 -1
- package/test/pics/group-edge.png +0 -0
- package/test/pics/group-edge.svg +0 -1
- package/test/pics/items-node.svg +0 -1
- package/test/pics/long-label.svg +0 -1
- package/test/pics/lr-flat.svg +0 -1
- package/test/pics/nested-group.png +0 -0
- package/test/pics/nested-group.svg +0 -1
- package/test/pics/no-edges.svg +0 -1
- package/test/pics/title-overflow.svg +0 -1
- package/test/pics/valign.svg +0 -1
- package/test/pics//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.svg +0 -1
- package/test/pics//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.png +0 -0
- package/test/pics//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.svg +0 -1
- package/test/pics//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.svg +0 -1
- package/test/pics//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.svg +0 -1
- package/test/pics//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.svg +0 -1
- package/test/pics//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.svg +0 -1
- package/test/pics//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.svg +0 -1
- package/test/pics//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.svg +0 -1
- package/test/pics//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.svg +0 -1
- /package/test/pics/{deep-nest.png → p10/deep-nest.png} +0 -0
- /package/test/pics/{flat-tb.png → p10/flat-tb.png} +0 -0
- /package/test/pics/{fold-tall.png → p10/fold-tall.png} +0 -0
- /package/test/pics/{fold-wide.png → p10/fold-wide.png} +0 -0
- /package/test/pics/{items-node.png → p10/items-node.png} +0 -0
- /package/test/pics/{long-label.png → p10/long-label.png} +0 -0
- /package/test/pics/{lr-flat.png → p10/lr-flat.png} +0 -0
- /package/test/pics/{no-edges.png → p10/no-edges.png} +0 -0
- /package/test/pics/{title-overflow.png → p10/title-overflow.png} +0 -0
- /package/test/pics/{valign.png → p10/valign.png} +0 -0
package/src/p10/gen.mjs
CHANGED
|
@@ -54,7 +54,91 @@ const esc = s => String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>
|
|
|
54
54
|
const mix = (a,b,t) => { const pa=[1,3,5].map(i=>parseInt(a.slice(i,i+2),16)), pb=[1,3,5].map(i=>parseInt(b.slice(i,i+2),16)); return '#'+pa.map((x,i)=>Math.round(x*(1-t)+pb[i]*t).toString(16).padStart(2,'0')).join('') }
|
|
55
55
|
// 確保「起點後第一段」與「終點前最後一段」之直線長度 >= MIN(轉折不貼端點, 維持箭頭指向性)。
|
|
56
56
|
// 做法: 末段過短時, 沿趨近軸向把「最後轉角」與「其前一轉角」一起後移(兩者同移=保正交); 需 >=4 點才不動端點。
|
|
57
|
-
|
|
57
|
+
// ── 菱形端點貼齊(ELK 以外接矩形算端點, 落在矩形邊上非頂點處會與菱形斜邊「空接」) ──
|
|
58
|
+
// 規則(共進共出, 每側獨立; 詳 p10/README):
|
|
59
|
+
// R1 共出: 該側所有「出」一律由該側「頂點」出發, 多條共用首段後於各自高度 90 度轉出(分岔)。
|
|
60
|
+
// R2 共進: 該側「無出」→ 全部匯入「頂點」(各自橫移至中軸, 共用末段, 箭頭重合);
|
|
61
|
+
// 該側「有出」→ 依「半側」匯入該半側「斜邊中點」(頂點讓給出, 進出不同點)。
|
|
62
|
+
// 正交線交叉允許存在(正交路由常態), 一致性優先。
|
|
63
|
+
|
|
64
|
+
// 判定端點落在外接矩形哪一側; 回 { ax, sg, v }(離開軸/正負/該側頂點), 不在邊上回 null
|
|
65
|
+
function diamondSide(p, box){
|
|
66
|
+
const cx = box.x + box.w/2, cy = box.y + box.h/2, EPS = 1.5
|
|
67
|
+
if (Math.abs(p.y - (box.y + box.h)) < EPS) return { ax: 'y', sg: 1, v: { x: cx, y: box.y + box.h } }
|
|
68
|
+
if (Math.abs(p.y - box.y) < EPS) return { ax: 'y', sg: -1, v: { x: cx, y: box.y } }
|
|
69
|
+
if (Math.abs(p.x - box.x) < EPS) return { ax: 'x', sg: -1, v: { x: box.x, y: cy } }
|
|
70
|
+
if (Math.abs(p.x - (box.x + box.w)) < EPS) return { ax: 'x', sg: 1, v: { x: box.x + box.w, y: cy } }
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 該側某「半側」之斜邊中點(混合側的進匯點); half=+1 取座標較大半側, -1 反之
|
|
75
|
+
function diamondSlantMid(box, side, half){
|
|
76
|
+
const cx = box.x + box.w/2, cy = box.y + box.h/2, hw = box.w/2, hh = box.h/2
|
|
77
|
+
if (side.ax === 'y') return { x: cx + half * hw / 2, y: cy + side.sg * hh / 2 }
|
|
78
|
+
return { x: cx + side.sg * hw / 2, y: cy + half * hh / 2 }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// R1 共出: 出邊起點改自「頂點」出發
|
|
82
|
+
function routeOutFromVertex(pts, side){
|
|
83
|
+
const p0 = pts[0], p1 = pts[1]
|
|
84
|
+
const EPS = 1.5, J = 18
|
|
85
|
+
const v = side.v
|
|
86
|
+
const off = side.ax === 'y' ? Math.abs(p0.x - v.x) : Math.abs(p0.y - v.y)
|
|
87
|
+
if (off < EPS) return // 已在頂點(置中), 不動
|
|
88
|
+
const alongAxis = side.ax === 'y' ? Math.abs(p1.x - p0.x) < EPS : Math.abs(p1.y - p0.y) < EPS
|
|
89
|
+
if (!alongAxis) { pts.unshift(v); return } // 原首段即沿側邊橫走: 自頂點先橫走接回
|
|
90
|
+
if (pts.length > 2) {
|
|
91
|
+
// 有後續轉折: 頂點 → 沿軸行至原首個轉折高度 → 橫向接回(單一 L 型; 轉折可貼近頂點)
|
|
92
|
+
const q1 = side.ax === 'y' ? { x: v.x, y: p1.y } : { x: p1.x, y: v.y }
|
|
93
|
+
pts.splice(0, 1, v, q1)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
// 無轉折之直線邊: 於頂點外側作雙直角接回各自路線(距離取 J 與可用長度之小者)
|
|
97
|
+
const segLen = side.ax === 'y' ? (p1.y - p0.y) * side.sg : (p1.x - p0.x) * side.sg
|
|
98
|
+
const j = Math.min(J, Math.max(4, segLen / 2))
|
|
99
|
+
const q1 = side.ax === 'y' ? { x: v.x, y: v.y + side.sg * j } : { x: v.x + side.sg * j, y: v.y }
|
|
100
|
+
const q2 = side.ax === 'y' ? { x: p0.x, y: v.y + side.sg * j } : { x: v.x + side.sg * j, y: p0.y }
|
|
101
|
+
pts.splice(0, 1, v, q1, q2)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// R2 決策: 進匯點 = 該側無出 → 頂點; 有出 → 依端點所在半側之斜邊中點
|
|
105
|
+
function diamondInAnchor(box, side, hasOutOnSide, pE){
|
|
106
|
+
if(!hasOutOnSide) return { x: side.v.x, y: side.v.y }
|
|
107
|
+
const half = (side.ax === 'y' ? (pE.x - (box.x + box.w/2)) : (pE.y - (box.y + box.h/2))) >= 0 ? 1 : -1
|
|
108
|
+
return diamondSlantMid(box, side, half)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// R2 共進: 進邊終點改匯入 anchor(頂點或半側斜邊中點)
|
|
112
|
+
// 橫向差距小且末段前為可伸縮橫向段 → 整段末端直線「平移」對齊 anchor 軸線(零多餘轉折);
|
|
113
|
+
// 否則於側外 J 處橫移 bus 再沿軸進入 anchor。
|
|
114
|
+
function routeInToAnchor(pts, side, anchor){
|
|
115
|
+
const pE = pts[pts.length - 1], pP = pts[pts.length - 2]
|
|
116
|
+
const EPS = 1.5, J = 18, SLIDE = 40 // SLIDE: 可平移之最大橫向差距(過大恐撞鄰近元素, 改走 bus)
|
|
117
|
+
const alongAxis = side.ax === 'y' ? Math.abs(pP.x - pE.x) < EPS : Math.abs(pP.y - pE.y) < EPS
|
|
118
|
+
if (!alongAxis) return // 末段非沿進入軸(罕見), 保留原路由
|
|
119
|
+
const lateral = side.ax === 'y' ? Math.abs(pE.x - anchor.x) : Math.abs(pE.y - anchor.y)
|
|
120
|
+
if (lateral < EPS) {
|
|
121
|
+
pts[pts.length - 1] = { x: anchor.x, y: anchor.y } // 已在 anchor 軸線上: 直接沿軸進入
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
if (lateral <= SLIDE && pts.length >= 3) {
|
|
125
|
+
const pP2 = pts[pts.length - 3]
|
|
126
|
+
const prevHorizontal = side.ax === 'y' ? Math.abs(pP2.y - pP.y) < EPS : Math.abs(pP2.x - pP.x) < EPS
|
|
127
|
+
if (prevHorizontal) {
|
|
128
|
+
if (side.ax === 'y') pP.x = anchor.x; else pP.y = anchor.y
|
|
129
|
+
pts[pts.length - 1] = { x: anchor.x, y: anchor.y }
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const Lv = side.ax === 'y' ? side.v.y + side.sg * J : side.v.x + side.sg * J // 側外橫移層
|
|
134
|
+
const segOK = side.ax === 'y' ? (pP.y - Lv) * side.sg > 2 : (pP.x - Lv) * side.sg > 2 // 原末段須延伸到橫移層之外
|
|
135
|
+
if (!segOK) { pts[pts.length - 1] = { x: anchor.x, y: anchor.y }; return } // 空間不足: 直接斜線前最短接入(極罕見)
|
|
136
|
+
const b1 = side.ax === 'y' ? { x: pE.x, y: Lv } : { x: Lv, y: pE.y }
|
|
137
|
+
const b2 = side.ax === 'y' ? { x: anchor.x, y: Lv } : { x: Lv, y: anchor.y }
|
|
138
|
+
pts.splice(pts.length - 1, 1, b1, b2, { x: anchor.x, y: anchor.y })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function ensureStub(pts, MIN, skipStart){
|
|
58
142
|
function fixEnd(p){
|
|
59
143
|
const n=p.length; if(n<4) return
|
|
60
144
|
const C=p[n-1], B=p[n-2]
|
|
@@ -64,11 +148,16 @@ function ensureStub(pts, MIN){
|
|
|
64
148
|
p[n-2]={x:B.x-ux*need, y:B.y-uy*need}
|
|
65
149
|
p[n-3]={x:p[n-3].x-ux*need, y:p[n-3].y-uy*need}
|
|
66
150
|
}
|
|
151
|
+
// 終點端(箭頭側)一律套最低門檻——含菱形貼齊後之路徑(bus 支線本給滿 J; 平移/保留支線之轉角平移不破壞正交)
|
|
67
152
|
fixEnd(pts)
|
|
68
|
-
|
|
153
|
+
// 起點端僅於「菱形頂點分岔」時跳過(分岔轉折貼近頂點屬預期; 後移轉角會與其後路徑段脫勾扭出斜線)
|
|
154
|
+
if(!skipStart){ pts.reverse(); fixEnd(pts); pts.reverse() }
|
|
69
155
|
return pts
|
|
70
156
|
}
|
|
71
157
|
|
|
158
|
+
// 供測試直接驗證菱形端點規則之純函式(不影響渲染)
|
|
159
|
+
window.__dia = { diamondSide, diamondSlantMid, diamondInAnchor, routeOutFromVertex, routeInToAnchor }
|
|
160
|
+
|
|
72
161
|
window.renderFig = async function(spec){
|
|
73
162
|
try {
|
|
74
163
|
const dir = spec.dir==='LR' ? 'RIGHT' : 'DOWN'
|
|
@@ -92,7 +181,7 @@ window.renderFig = async function(spec){
|
|
|
92
181
|
// 例外: 含循環(如流程之退回邊)的圖, 位置提示會干擾分層使入口節點偏離頂端 → 根層不加提示(容器內仍保序)。
|
|
93
182
|
const ORDER_OPTS = { 'elk.layered.crossingMinimization.semiInteractive':'true' }
|
|
94
183
|
const hasCycle = (() => { const adj={}; spec.edges.forEach(e=>{ (adj[e.from]=adj[e.from]||[]).push(e.to) }); const st={}; const dfs=(u)=>{ st[u]=1; for(const v of (adj[u]||[])){ if(st[v]===1) return true; if(!st[v] && dfs(v)) return true } st[u]=2; return false }; return Object.keys(adj).some(u=>!st[u]&&dfs(u)) })()
|
|
95
|
-
const gOpts = { 'elk.padding':'[top=10,left=14,bottom=14,right=14]', 'elk.nodeLabels.placement':'[H_CENTER, V_TOP, INSIDE]', 'elk.algorithm':'layered', 'elk.direction':dir, 'elk.edgeRouting':'ORTHOGONAL', 'elk.hierarchyHandling':'INCLUDE_CHILDREN', 'elk.spacing.nodeNode':'28', 'elk.layered.spacing.nodeNodeBetweenLayers':'30', 'elk.spacing.edgeEdge':'22', 'elk.layered.spacing.edgeEdgeBetweenLayers':'24', ...ORDER_OPTS }
|
|
184
|
+
const gOpts = { 'elk.layered.nodePlacement.bk.fixedAlignment':'BALANCED', 'elk.padding':'[top=10,left=14,bottom=14,right=14]', 'elk.nodeLabels.placement':'[H_CENTER, V_TOP, INSIDE]', 'elk.algorithm':'layered', 'elk.direction':dir, 'elk.edgeRouting':'ORTHOGONAL', 'elk.hierarchyHandling':'INCLUDE_CHILDREN', 'elk.spacing.nodeNode':'28', 'elk.layered.spacing.nodeNodeBetweenLayers':'30', 'elk.spacing.edgeEdge':'22', 'elk.layered.spacing.edgeEdgeBetweenLayers':'24', ...ORDER_OPTS }
|
|
96
185
|
function elkEdge(e){ const lab = e.label ? [{ text:e.label, width: measure(e.label,EFS,300).w, height:18 }] : []; return { id:e.id, sources:[e.from], targets:[e.to], labels:lab } }
|
|
97
186
|
function build(parentId, padMap, wrapAR){
|
|
98
187
|
const children=[], edges=[]
|
|
@@ -108,7 +197,7 @@ window.renderFig = async function(spec){
|
|
|
108
197
|
spec.edges.filter(e=>e.container===parentId).forEach(e=>{ edges.push(elkEdge(e)) })
|
|
109
198
|
return { children, edges }
|
|
110
199
|
}
|
|
111
|
-
const rootOpts = { 'elk.algorithm':'layered', 'elk.direction':dir, 'elk.edgeRouting':'ORTHOGONAL', 'elk.hierarchyHandling':'INCLUDE_CHILDREN', 'elk.spacing.nodeNode':'30', 'elk.layered.spacing.nodeNodeBetweenLayers':'34', 'elk.spacing.edgeNode':'24', 'elk.spacing.edgeEdge':'22', 'elk.layered.spacing.edgeEdgeBetweenLayers':'24', 'elk.layered.spacing.edgeNodeBetweenLayers':'26', ...(hasCycle ? { 'elk.layered.cycleBreaking.strategy':'MODEL_ORDER' } : ORDER_OPTS) }
|
|
200
|
+
const rootOpts = { 'elk.layered.nodePlacement.bk.fixedAlignment':'BALANCED', 'elk.algorithm':'layered', 'elk.direction':dir, 'elk.edgeRouting':'ORTHOGONAL', 'elk.hierarchyHandling':'INCLUDE_CHILDREN', 'elk.spacing.nodeNode':'30', 'elk.layered.spacing.nodeNodeBetweenLayers':'34', 'elk.spacing.edgeNode':'24', 'elk.spacing.edgeEdge':'22', 'elk.layered.spacing.edgeEdgeBetweenLayers':'24', 'elk.layered.spacing.edgeNodeBetweenLayers':'26', ...(hasCycle ? { 'elk.layered.cycleBreaking.strategy':'MODEL_ORDER' } : ORDER_OPTS) }
|
|
112
201
|
const elk = new ELK()
|
|
113
202
|
const mkGraph = (pm, war) => { const rb = build(null, pm, war); return { id:'root', layoutOptions:rootOpts, children:rb.children, edges:rb.edges } }
|
|
114
203
|
let res = await elk.layout(mkGraph({}, {}))
|
|
@@ -151,14 +240,121 @@ window.renderFig = async function(spec){
|
|
|
151
240
|
groupsOut.sort((a,b)=>a.depth-b.depth).forEach(o=>{
|
|
152
241
|
svg+='<rect x="'+(o.x)+'" y="'+(o.y)+'" width="'+o.w+'" height="'+o.h+'" rx="8" fill="'+o.g.fill+'" fill-opacity="0.38" stroke="'+o.g.stroke+'" stroke-width="2"/>'
|
|
153
242
|
const tw=o.lbl?o.lbl.width+12:o.w, th=o.lbl?o.lbl.height+6:24, tx=o.x+Math.max(0,(o.w-tw)/2), ty=o.y+(o.lbl?o.lbl.y:5)
|
|
154
|
-
svg+='<foreignObject x="'+tx+'" y="'+ty+'" width="'+tw+'" height="'+th+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="text-align:center;font-weight:bold;font-size:'+GFS+'px;color:'+o.g.stroke+';line-height:1.3">'+esc(o.g.label)+'</div></foreignObject>'
|
|
243
|
+
svg+='<foreignObject x="'+tx+'" y="'+ty+'" width="'+tw+'" height="'+th+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="text-align:center;font-weight:bold;font-size:'+GFS+'px;color:'+o.g.stroke+';line-height:1.3;overflow-wrap:break-word;word-break:normal">'+esc(o.g.label)+'</div></foreignObject>'
|
|
155
244
|
})
|
|
156
245
|
// 2) 邊(在節點之下、容器之上)
|
|
246
|
+
const nodeBoxById={}; nodesOut.forEach(o=>{ nodeBoxById[o.n.id]={ x:o.x, y:o.y, w:o.w, h:o.h, diamond:!!o.n.diamond } })
|
|
247
|
+
const specEdgeById={}; spec.edges.forEach(se=>{ specEdgeById[se.id]=se })
|
|
248
|
+
// 2a) 蒐集各 section 絕對路徑點 + 統計各菱形「各側」進出數(側=上/下/左/右, key: nodeId|軸|正負)。
|
|
249
|
+
// 規則: 純出側→全部由頂點分岔; 純入側→各自貼斜邊; 混合側→全部貼斜邊
|
|
250
|
+
// (混合側若連出走頂點+橫向接回, 橫向段必與夾在中間的連入直線交叉; 全貼斜邊則零共用點、零新增交叉)
|
|
251
|
+
const drawList=[], sideMix={}
|
|
157
252
|
edgesOut.forEach(o=>{
|
|
158
|
-
const e=o.e
|
|
159
|
-
|
|
253
|
+
const e=o.e, se=specEdgeById[e.id]
|
|
254
|
+
const srcBox=se&&nodeBoxById[se.from], tgtBox=se&&nodeBoxById[se.to]
|
|
255
|
+
const secs=(e.sections||[]).map(sec=>{
|
|
160
256
|
const pts=[sec.startPoint].concat(sec.bendPoints||[]).concat([sec.endPoint]).map(p=>({x:o.ox+p.x, y:o.oy+p.y}))
|
|
161
|
-
|
|
257
|
+
const item={ pts }
|
|
258
|
+
if(srcBox&&srcBox.diamond){ item.srcSide=diamondSide(pts[0], srcBox); if(item.srcSide){ const k=se.from+'|'+item.srcSide.ax+item.srcSide.sg; (sideMix[k]=sideMix[k]||{in:0,out:0}).out++ } }
|
|
259
|
+
if(tgtBox&&tgtBox.diamond){ item.tgtSide=diamondSide(pts[pts.length-1], tgtBox); if(item.tgtSide){ const k=se.to+'|'+item.tgtSide.ax+item.tgtSide.sg; (sideMix[k]=sideMix[k]||{in:0,out:0}).in++ } }
|
|
260
|
+
return item
|
|
261
|
+
})
|
|
262
|
+
drawList.push({ o, e, se, srcBox, tgtBox, secs })
|
|
263
|
+
})
|
|
264
|
+
// 2a-2) 通用路徑化簡: ELK 階層邊(INCLUDE_CHILDREN)常見「先繞反方向再折返」之 S/U 形繞行,
|
|
265
|
+
// 反覆對「三段式窗」(平行-垂直-平行)嘗試收斂成兩段, 僅在碰撞偵測全過才接受:
|
|
266
|
+
// 不撞節點框/容器標題帶/邊標籤、不與他邊平行段貼齊併線; 端點段軸向與方向不可翻轉(維持節點進出方向,
|
|
267
|
+
// 化簡先於菱形貼齊故不干擾貼齊規則); 本邊標籤若因化簡脫離路徑則整段還原(標籤座標由 ELK 依原路徑決定)。
|
|
268
|
+
const obstacles=[]
|
|
269
|
+
nodesOut.forEach(o=>{ obstacles.push({ x:o.x-8, y:o.y-8, w:o.w+16, h:o.h+16 }) })
|
|
270
|
+
groupsOut.forEach(o=>{ if(!o.lbl) return; const tw=o.lbl.width+12, th=o.lbl.height+6, tx=o.x+Math.max(0,(o.w-tw)/2), ty=o.y+o.lbl.y; obstacles.push({ x:tx-4, y:ty-4, w:tw+8, h:th+8 }) })
|
|
271
|
+
const labelBoxes=[]
|
|
272
|
+
drawList.forEach(dd=>{ (dd.e.labels||[]).forEach(l=>{ if(!l.text) return; labelBoxes.push({ x:dd.o.ox+(l.x||0)-2, y:dd.o.oy+(l.y||0)-2, w:(l.width||40)+10, h:(l.height||18)+8 }) }) })
|
|
273
|
+
function segHitsBox(a,b,box){ // 軸對齊線段 vs 矩形(線段必水平或垂直)
|
|
274
|
+
const x1=Math.min(a.x,b.x), x2=Math.max(a.x,b.x), y1=Math.min(a.y,b.y), y2=Math.max(a.y,b.y)
|
|
275
|
+
return x2>box.x && x1<box.x+box.w && y2>box.y && y1<box.y+box.h
|
|
276
|
+
}
|
|
277
|
+
function segOverlapsOther(a,b,selfItem){ // 與他邊平行段貼齊重疊(側距<6 且共線範圍>10)→ 視覺併線, 拒絕
|
|
278
|
+
const horiz = Math.abs(a.y-b.y)<0.5
|
|
279
|
+
for(const dd of drawList) for(const it of dd.secs){ if(it===selfItem) continue
|
|
280
|
+
const q=it.pts
|
|
281
|
+
for(let k=0;k+1<q.length;k++){ const c=q[k], d=q[k+1]
|
|
282
|
+
if(horiz && Math.abs(c.y-d.y)<0.5 && Math.abs(c.y-a.y)<6){ const lo=Math.max(Math.min(a.x,b.x),Math.min(c.x,d.x)), hi=Math.min(Math.max(a.x,b.x),Math.max(c.x,d.x)); if(hi-lo>10) return true }
|
|
283
|
+
if(!horiz && Math.abs(c.x-d.x)<0.5 && Math.abs(c.x-a.x)<6){ const lo=Math.max(Math.min(a.y,b.y),Math.min(c.y,d.y)), hi=Math.min(Math.max(a.y,b.y),Math.max(c.y,d.y)); if(hi-lo>10) return true }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
// 標籤一律為硬障礙(含本邊自己的): ELK 邊標籤是「先佔位、路徑繞標籤走」, 原路的 jog 即標籤讓位,
|
|
289
|
+
// 拉直會從自家標籤正中穿過(且「脫離才還原」守門對「穿過=距離變近」無感), 故不可豁免自家標籤
|
|
290
|
+
function segClear(a,b,selfItem){
|
|
291
|
+
for(const ob of obstacles) if(segHitsBox(a,b,ob)) return false
|
|
292
|
+
for(const lb of labelBoxes) if(segHitsBox(a,b,lb)) return false
|
|
293
|
+
return !segOverlapsOther(a,b,selfItem)
|
|
294
|
+
}
|
|
295
|
+
function mergeColl(p){ // 去重合點 + 併共線(含同線折返, 折返僅由化簡產生)
|
|
296
|
+
for(let i=p.length-2;i>=0;i--) if(Math.abs(p[i].x-p[i+1].x)<0.5 && Math.abs(p[i].y-p[i+1].y)<0.5) p.splice(i,1)
|
|
297
|
+
for(let i=p.length-2;i>=1;i--){ const A=p[i-1],B=p[i],C=p[i+1]
|
|
298
|
+
if((Math.abs(A.x-B.x)<0.5 && Math.abs(B.x-C.x)<0.5) || (Math.abs(A.y-B.y)<0.5 && Math.abs(B.y-C.y)<0.5)) p.splice(i,1) }
|
|
299
|
+
}
|
|
300
|
+
function distToPoly(pt,p){ let d=Infinity
|
|
301
|
+
for(let i=0;i+1<p.length;i++){ const a=p[i], b=p[i+1]
|
|
302
|
+
const len2=Math.max(1,(b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y))
|
|
303
|
+
const t=Math.max(0,Math.min(1, ((pt.x-a.x)*(b.x-a.x)+(pt.y-a.y)*(b.y-a.y))/len2 ))
|
|
304
|
+
const qx=a.x+t*(b.x-a.x), qy=a.y+t*(b.y-a.y); d=Math.min(d, Math.hypot(pt.x-qx, pt.y-qy)) }
|
|
305
|
+
return d
|
|
306
|
+
}
|
|
307
|
+
function simplifyOrtho(item, labels){
|
|
308
|
+
const p=item.pts
|
|
309
|
+
const before=p.map(q=>({x:q.x,y:q.y}))
|
|
310
|
+
mergeColl(p)
|
|
311
|
+
for(let pass=0; pass<20; pass++){
|
|
312
|
+
let changed=false
|
|
313
|
+
for(let i=0;i+3<p.length;i++){
|
|
314
|
+
const isStart=(i===0), isEnd=(i+3===p.length-1)
|
|
315
|
+
if(isStart && isEnd) continue // 4 點路徑已極簡, 兩端軸向無法同時保持
|
|
316
|
+
const A=p[i], B=p[i+1], C=p[i+2], D=p[i+3]
|
|
317
|
+
const s1h=Math.abs(A.y-B.y)<0.5
|
|
318
|
+
// 取代點取窗之一角: 一般保持首段軸向; 末端窗改保持末段軸向(箭頭進入方向不可變)
|
|
319
|
+
const P = isEnd ? (s1h ? {x:A.x,y:D.y} : {x:D.x,y:A.y}) : (s1h ? {x:D.x,y:A.y} : {x:A.x,y:D.y})
|
|
320
|
+
if(isStart){ const sg0=s1h?Math.sign(B.x-A.x):Math.sign(B.y-A.y), sgN=s1h?Math.sign(P.x-A.x):Math.sign(P.y-A.y); if(sgN!==sg0) continue }
|
|
321
|
+
if(isEnd){ const s3h=Math.abs(C.y-D.y)<0.5, sg0=s3h?Math.sign(D.x-C.x):Math.sign(D.y-C.y), sgN=s3h?Math.sign(D.x-P.x):Math.sign(D.y-P.y); if(sgN!==sg0) continue }
|
|
322
|
+
if(!segClear(A,P,item) || !segClear(P,D,item)) continue
|
|
323
|
+
p.splice(i+1, 2, P)
|
|
324
|
+
mergeColl(p)
|
|
325
|
+
changed=true
|
|
326
|
+
break
|
|
327
|
+
}
|
|
328
|
+
if(!changed) break
|
|
329
|
+
}
|
|
330
|
+
// 標籤守門: 化簡若令本邊任一標籤脫離路徑(距離顯著變大), 整段還原
|
|
331
|
+
if(labels.length && p.length !== before.length){
|
|
332
|
+
for(const l of labels){
|
|
333
|
+
if(distToPoly(l,p) > distToPoly(l,before)+2){ p.length=0; before.forEach(q=>p.push(q)); return }
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
drawList.forEach(dd=>{
|
|
338
|
+
const labs=(dd.e.labels||[]).filter(l=>l.text).map(l=>({ x:dd.o.ox+(l.x||0)+(l.width||40)/2, y:dd.o.oy+(l.y||0)+(l.height||18)/2 }))
|
|
339
|
+
dd.secs.forEach(item=>{ if(item.pts.length>=5) simplifyOrtho(item, labs) })
|
|
340
|
+
})
|
|
341
|
+
// 2b) 依側別規則貼齊並繪製
|
|
342
|
+
const procSegsById={} // 實際繪製之路徑點(含菱形貼齊與 ensureStub), 供 geom 輸出一致
|
|
343
|
+
drawList.forEach(dd=>{
|
|
344
|
+
const o=dd.o, e=dd.e
|
|
345
|
+
dd.secs.forEach(item=>{
|
|
346
|
+
const pts=item.pts
|
|
347
|
+
if(item.srcSide && pts.length >= 2) routeOutFromVertex(pts, item.srcSide) // R1: 出永遠自該側頂點
|
|
348
|
+
if(item.tgtSide && pts.length >= 2){
|
|
349
|
+
// R2: 純進側匯「頂點」; 混合側依「半側」匯「斜邊中點」(進出不同點)
|
|
350
|
+
const m=sideMix[dd.se.to+'|'+item.tgtSide.ax+item.tgtSide.sg]
|
|
351
|
+
const anchor=diamondInAnchor(dd.tgtBox, item.tgtSide, !!(m && m.out > 0), pts[pts.length-1])
|
|
352
|
+
routeInToAnchor(pts, item.tgtSide, anchor)
|
|
353
|
+
}
|
|
354
|
+
// 分岔/匯入重排後清除共線折返: 頂點分岔之「接回原路第一轉折層」若跨過下一轉點, 會多走再折回疊出短勾, 併共線即塌縮為直接連線
|
|
355
|
+
if(item.srcSide || item.tgtSide) mergeColl(pts)
|
|
356
|
+
ensureStub(pts, 18, !!item.srcSide)
|
|
357
|
+
;(procSegsById[e.id]=procSegsById[e.id]||[]).push(pts)
|
|
162
358
|
let d='M '+pts[0].x+' '+pts[0].y; for(let i=1;i<pts.length;i++) d+=' L '+pts[i].x+' '+pts[i].y
|
|
163
359
|
const dash = (e.id && spec.edges.find(x=>x.id===e.id) && spec.edges.find(x=>x.id===e.id).dashed) ? ' stroke-dasharray="6 4"' : ''
|
|
164
360
|
svg+='<path d="'+d+'" fill="none" stroke="'+EDGE_LINE+'" stroke-width="2"'+dash+'/>'
|
|
@@ -172,7 +368,8 @@ window.renderFig = async function(spec){
|
|
|
172
368
|
;(e.labels||[]).forEach(l=>{ if(!l.text) return; const lx=o.ox+(l.x||0), ly=o.oy+(l.y||0), lw=l.width||40, lh=l.height||18
|
|
173
369
|
svg+='<foreignObject x="'+lx+'" y="'+ly+'" width="'+(lw+6)+'" height="'+(lh+4)+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="font-size:'+EFS+'px;color:'+EDGE_TEXT+';text-shadow:0 0 3px '+HALO+',0 0 3px '+HALO+',0 0 3px '+HALO+',0 0 3px '+HALO+';white-space:nowrap">'+esc(l.text)+'</div></foreignObject>' })
|
|
174
370
|
})
|
|
175
|
-
// 3) 節點(最上)
|
|
371
|
+
// 3) 節點(最上)。各文字 div 之斷行 CSS(overflow-wrap:break-word + word-break:normal)須與 measure()/measureItems() 一致,
|
|
372
|
+
// 否則長英文識別字量測時折行、渲染時不折 → 單行溢出 foreignObject 被裁切
|
|
176
373
|
nodesOut.forEach(o=>{
|
|
177
374
|
const n=o.n
|
|
178
375
|
if(n.items && n.items.length){ // 標題+項目框: 依序分層(框背景→標題背景→框線→分隔線→標題→items), 使框線置頂不被標題背景遮蔽、線寬完整
|
|
@@ -182,14 +379,14 @@ window.renderFig = async function(spec){
|
|
|
182
379
|
svg+='<path d="M '+(o.x+r)+' '+o.y+' H '+(o.x+o.w-r)+' A '+r+' '+r+' 0 0 1 '+(o.x+o.w)+' '+(o.y+r)+' V '+sep+' H '+o.x+' V '+(o.y+r)+' A '+r+' '+r+' 0 0 1 '+(o.x+r)+' '+o.y+' Z" fill="'+mix(n.fill,n.stroke,0.16)+'"/>' // 2 標題背景(上圓角下平)
|
|
183
380
|
svg+='<rect x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+o.h+'" rx="'+r+'" fill="none" stroke="'+n.stroke+'" stroke-width="1.6"/>' // 3 框線(頂層)
|
|
184
381
|
svg+='<line x1="'+o.x+'" y1="'+sep+'" x2="'+(o.x+o.w)+'" y2="'+sep+'" stroke="'+n.stroke+'" stroke-width="1"/>' // 4 分隔線
|
|
185
|
-
svg+='<foreignObject x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+headH+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-weight:bold;font-size:'+FS+'px;color:'+n.font+'">'+esc(n.title||n.label)+'</div></foreignObject>' // 5 標題文字
|
|
382
|
+
svg+='<foreignObject x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+headH+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-weight:bold;font-size:'+FS+'px;color:'+n.font+'"><div style="width:100%;min-width:0;overflow-wrap:break-word;word-break:normal">'+esc(n.title||n.label)+'</div></div></foreignObject>' // 5 標題文字
|
|
186
383
|
svg+='<foreignObject x="'+o.x+'" y="'+sep+'" width="'+o.w+'" height="'+(o.h-headH)+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="font-size:'+(FS-1)+'px;line-height:1.55;padding:6px 9px 4px;text-align:left;color:'+n.font+'">'+lis+'</div></foreignObject>' // 6 items文字
|
|
187
384
|
} else if(n.diamond){ const cx=o.x+o.w/2, cy=o.y+o.h/2
|
|
188
385
|
svg+='<polygon points="'+cx+','+o.y+' '+(o.x+o.w)+','+cy+' '+cx+','+(o.y+o.h)+' '+o.x+','+cy+'" fill="'+n.fill+'" stroke="'+n.stroke+'" stroke-width="1.8"/>'
|
|
189
|
-
svg+='<foreignObject x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+o.h+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:'+FS+'px;color:'+n.font+';line-height:1.4;padding:0 4px;box-sizing:border-box">'+esc(n.label)+'</div></foreignObject>'
|
|
386
|
+
svg+='<foreignObject x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+o.h+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:'+FS+'px;color:'+n.font+';line-height:1.4;padding:0 4px;box-sizing:border-box"><div style="width:100%;min-width:0;overflow-wrap:break-word;word-break:normal">'+esc(n.label)+'</div></div></foreignObject>'
|
|
190
387
|
} else {
|
|
191
388
|
svg+='<rect x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+o.h+'" rx="7" fill="'+n.fill+'" stroke="'+n.stroke+'" stroke-width="1.6"/>'
|
|
192
|
-
svg+='<foreignObject x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+o.h+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:'+FS+'px;color:'+n.font+';line-height:1.4;padding:0 4px;box-sizing:border-box">'+esc(n.label)+'</div></foreignObject>'
|
|
389
|
+
svg+='<foreignObject x="'+o.x+'" y="'+o.y+'" width="'+o.w+'" height="'+o.h+'"><div xmlns="http://www.w3.org/1999/xhtml" class="lbl" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:'+FS+'px;color:'+n.font+';line-height:1.4;padding:0 4px;box-sizing:border-box"><div style="width:100%;min-width:0;overflow-wrap:break-word;word-break:normal">'+esc(n.label)+'</div></div></foreignObject>'
|
|
193
390
|
}
|
|
194
391
|
})
|
|
195
392
|
|
|
@@ -200,7 +397,7 @@ window.renderFig = async function(spec){
|
|
|
200
397
|
W:W+PAD*2, H:H+PAD*2,
|
|
201
398
|
nodes: nodesOut.map(o=>({ id:o.n.id, x:o.x, y:o.y, w:o.w, h:o.h, parent:o.n.parent||null, kind:(o.n.items&&o.n.items.length)?'items':(o.n.diamond?'diamond':'box'), headH:(size[o.n.id]&&size[o.n.id].headH)||null })),
|
|
202
399
|
groups: groupsOut.map(o=>({ id:o.g.id, x:o.x, y:o.y, w:o.w, h:o.h, depth:o.depth, parent:o.g.parent||null, labelW:(o.lbl&&o.lbl.width)||0 })),
|
|
203
|
-
edges: edgesOut.map(o=>({ id:o.e.id, segs:
|
|
400
|
+
edges: edgesOut.map(o=>({ id:o.e.id, segs: procSegsById[o.e.id]||[] }))
|
|
204
401
|
}
|
|
205
402
|
return { ok:true, w:W+PAD*2, h:H+PAD*2, geom }
|
|
206
403
|
} catch(e){ return { ok:false, err:String(e&&(e.stack||e.message)||e).slice(0,400) } }
|
|
@@ -208,8 +405,8 @@ window.renderFig = async function(spec){
|
|
|
208
405
|
window.__ready = true
|
|
209
406
|
</script></body></html>`
|
|
210
407
|
|
|
211
|
-
//
|
|
212
|
-
|
|
408
|
+
// 渲染單張至就緒頁面(renderFig 完成 + 字型就緒); caller 負責 close browser
|
|
409
|
+
async function renderFigPage(data) {
|
|
213
410
|
const browser = await chromium.launch()
|
|
214
411
|
try {
|
|
215
412
|
const page = await browser.newPage({ deviceScaleFactor: 2 })
|
|
@@ -220,9 +417,32 @@ export async function genPng(data, opt = {}) {
|
|
|
220
417
|
if (!res.ok) throw new Error('p10 render failed: ' + res.err)
|
|
221
418
|
await page.evaluate(() => document.fonts && document.fonts.ready)
|
|
222
419
|
await page.waitForTimeout(150)
|
|
420
|
+
return { browser, page }
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
await browser.close()
|
|
424
|
+
throw err
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 單張渲染 → PNG Buffer(供 WFlowchart 統一入口呼叫; 自帶瀏覽器生命週期)
|
|
429
|
+
export async function genPng(data, opt = {}) {
|
|
430
|
+
const { browser, page } = await renderFigPage(data)
|
|
431
|
+
try {
|
|
223
432
|
return await page.locator('#stage svg').screenshot()
|
|
224
433
|
}
|
|
225
434
|
finally {
|
|
226
435
|
await browser.close()
|
|
227
436
|
}
|
|
228
437
|
}
|
|
438
|
+
|
|
439
|
+
// 單張渲染 → SVG 字串(本產線自組之 SVG; 正規化為   使其為合法 standalone SVG)
|
|
440
|
+
export async function genSvg(data, opt = {}) {
|
|
441
|
+
const { browser, page } = await renderFigPage(data)
|
|
442
|
+
try {
|
|
443
|
+
return (await page.evaluate(() => document.getElementById('stage').innerHTML)).replace(/ /g, ' ').trim()
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
await browser.close()
|
|
447
|
+
}
|
|
448
|
+
}
|
package/src/p2/README.md
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
### 對外介面
|
|
54
54
|
|
|
55
|
-
-
|
|
55
|
+
- 產線對外介面為 `genPng(data, opt)` 與 `genSvg(data, opt)`,`data` 為正規化繪圖數據 `{ dir, nodes, edges }`;`genPng` 回傳 `Promise<Buffer>`(PNG),`genSvg` 回傳 standalone SVG 字串(引擎原生輸出,另將頁面 `<style>` 之視覺修正 CSS 內嵌進 SVG 根元素使其脫離頁面仍忠實、` ` 正規化為 ` `);兩者皆自帶瀏覽器生命週期(`chromium.launch()` → 渲染 → `browser.close()`),呼叫端不需自行管理 Playwright。
|
|
56
56
|
- 統一由 `src/WFlowchart.mjs` 依 `mode === 'p2'` 呼叫,不提供批次流程或落地寫檔行為,是否寫檔、寫至何處由呼叫端決定。
|
|
57
57
|
|
|
58
58
|
## 自動化機制
|
package/src/p2/gen.mjs
CHANGED
|
@@ -37,6 +37,20 @@ export function translate(data) {
|
|
|
37
37
|
const dir = data.dir || 'TB'
|
|
38
38
|
const innerDir = dir === 'TB' ? 'LR' : 'TB'
|
|
39
39
|
|
|
40
|
+
// mermaid 保留字(end/click/style/class/subgraph/direction 等)不可作節點 id → 一律加前綴消歧
|
|
41
|
+
// 僅影響 DSL 內部識別(顯示文字仍用 label); 不同原 id 淨化後若同名, 以序號區隔避免節點被合併
|
|
42
|
+
const used = new Set()
|
|
43
|
+
const qidMap = {}
|
|
44
|
+
const qid = (id) => {
|
|
45
|
+
if (qidMap[id]) return qidMap[id]
|
|
46
|
+
const base = 'n_' + String(id).replace(/[^A-Za-z0-9_]/g, '_')
|
|
47
|
+
let q = base, k = 2
|
|
48
|
+
while (used.has(q)) q = base + '_' + (k++)
|
|
49
|
+
used.add(q)
|
|
50
|
+
qidMap[id] = q
|
|
51
|
+
return q
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
// 建立 group → children 映射(children 依 nodes 原序)
|
|
41
55
|
const childrenOf = {} // groupId → [nodeId]
|
|
42
56
|
const nodeById = {}
|
|
@@ -57,7 +71,7 @@ export function translate(data) {
|
|
|
57
71
|
function renderNode(nd, indent) {
|
|
58
72
|
const sp = ' '.repeat(indent)
|
|
59
73
|
if (isGroupCls(nd.cls)) {
|
|
60
|
-
lines.push(`${sp}subgraph ${nd.id}["${nd.label}"]`)
|
|
74
|
+
lines.push(`${sp}subgraph ${qid(nd.id)}["${nd.label}"]`)
|
|
61
75
|
lines.push(`${sp} direction ${innerDir}`)
|
|
62
76
|
const kids = childrenOf[nd.id] || []
|
|
63
77
|
for (const cid of kids) renderNode(nodeById[cid], indent + 1)
|
|
@@ -66,7 +80,7 @@ export function translate(data) {
|
|
|
66
80
|
const shape = nd.cls === 'diamond'
|
|
67
81
|
? `{${nd.label}}`
|
|
68
82
|
: `["${nd.label}"]`
|
|
69
|
-
lines.push(`${sp}${nd.id}${shape}`)
|
|
83
|
+
lines.push(`${sp}${qid(nd.id)}${shape}`)
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
86
|
|
|
@@ -77,7 +91,7 @@ export function translate(data) {
|
|
|
77
91
|
for (const ed of data.edges) {
|
|
78
92
|
const arrow = ed.kind === 'dashed' ? '-.->' : '-->'
|
|
79
93
|
const lbl = ed.label ? `|"${ed.label}"|` : ''
|
|
80
|
-
lines.push(` ${ed.from} ${arrow}${lbl} ${ed.to}`)
|
|
94
|
+
lines.push(` ${qid(ed.from)} ${arrow}${lbl} ${qid(ed.to)}`)
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
// classDef: 蒐集所有出現的 cls, 從 palette 取色
|
|
@@ -96,7 +110,7 @@ export function translate(data) {
|
|
|
96
110
|
lines.push(` classDef ${cls} fill:${fillColor},stroke:${strokeColor},stroke-width:${sw},color:${textColor};`)
|
|
97
111
|
}
|
|
98
112
|
for (const [cls, ids] of Object.entries(clsByNodes)) {
|
|
99
|
-
lines.push(` class ${ids.join(',')} ${cls};`)
|
|
113
|
+
lines.push(` class ${ids.map(qid).join(',')} ${cls};`)
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
return lines.join('\n')
|
|
@@ -187,10 +201,9 @@ try {
|
|
|
187
201
|
} catch(e){ window.__setupErr = (e&&(e.message||e.stack))||String(e) }
|
|
188
202
|
</script></body></html>`
|
|
189
203
|
|
|
190
|
-
//
|
|
204
|
+
// 渲染單張至就緒頁面(renderFig 完成); caller 負責 close browser
|
|
191
205
|
// data: 正規化繪圖數據 { dir, nodes, edges }(caller 已先做 label 衍生)
|
|
192
|
-
|
|
193
|
-
export async function genPng(data, opt = {}) {
|
|
206
|
+
async function renderFigPage(data) {
|
|
194
207
|
const code = FM + translate(data)
|
|
195
208
|
const browser = await chromium.launch()
|
|
196
209
|
try {
|
|
@@ -203,8 +216,40 @@ export async function genPng(data, opt = {}) {
|
|
|
203
216
|
const r = await page.evaluate(([c, idx]) => window.renderFig(c, idx), [code, 0])
|
|
204
217
|
if (!r.ok) throw new Error(r.err)
|
|
205
218
|
await page.waitForTimeout(250)
|
|
219
|
+
return { browser, page }
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
await browser.close()
|
|
223
|
+
throw err
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 單張渲染 → PNG Buffer
|
|
228
|
+
export async function genPng(data, opt = {}) {
|
|
229
|
+
const { browser, page } = await renderFigPage(data)
|
|
230
|
+
try {
|
|
206
231
|
return await page.locator('#box svg').screenshot()
|
|
207
232
|
} finally {
|
|
208
233
|
await browser.close()
|
|
209
234
|
}
|
|
210
235
|
}
|
|
236
|
+
|
|
237
|
+
// 單張渲染 → SVG 字串(#box 內後處理完成之 SVG; 正規化為   使其為合法 standalone SVG)
|
|
238
|
+
// 頁面 <head><style> 之視覺修正 CSS(邊標籤透明底/光暈/字型覆蓋)序列化後會遺失 → 內嵌進 SVG 根使其 standalone
|
|
239
|
+
export async function genSvg(data, opt = {}) {
|
|
240
|
+
const { browser, page } = await renderFigPage(data)
|
|
241
|
+
try {
|
|
242
|
+
return (await page.evaluate(() => {
|
|
243
|
+
const el = document.querySelector('#box svg')
|
|
244
|
+
const css = Array.from(document.querySelectorAll('head style')).map(s => s.textContent).join('\n')
|
|
245
|
+
const st = document.createElementNS('http://www.w3.org/2000/svg', 'style')
|
|
246
|
+
st.textContent = css
|
|
247
|
+
el.insertBefore(st, el.firstChild)
|
|
248
|
+
const out = document.getElementById('box').innerHTML
|
|
249
|
+
st.remove()
|
|
250
|
+
return out
|
|
251
|
+
})).replace(/ /g, ' ').trim()
|
|
252
|
+
} finally {
|
|
253
|
+
await browser.close()
|
|
254
|
+
}
|
|
255
|
+
}
|
package/src/p3/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- **渲染方式**:於頁面注入 `window.renderAndDetect(src)` 函式,呼叫 `window.nomnoml.renderSvg(src)` 將 DSL 字串渲染為 SVG,並將結果寫入 `#box` div 的 innerHTML。
|
|
7
7
|
- **截圖目標**:以 Playwright locator `#box svg` 精確擷取 SVG 元素本身,排除頁面邊距干擾。
|
|
8
8
|
- **解析度**:啟動 `browser.newPage({ deviceScaleFactor: 2 })`,輸出為 2× 實體像素的高解析度 PNG。
|
|
9
|
-
- **呼叫方式**:`genPng(data, opt)` 為單張渲染函式,接受正規化繪圖數據 `{ dir, nodes, edges }`,內部自建 browser/page 並於結束時關閉,回傳 PNG 的 Node Buffer(不落地檔案),由 `src/WFlowchart.mjs` 統一調用(`mode: 'p3'
|
|
9
|
+
- **呼叫方式**:`genPng(data, opt)` 為單張渲染函式,接受正規化繪圖數據 `{ dir, nodes, edges }`,內部自建 browser/page 並於結束時關閉,回傳 PNG 的 Node Buffer(不落地檔案),由 `src/WFlowchart.mjs` 統一調用(`mode: 'p3'`);同結構之 `genSvg(data, opt)` 回傳 `#box` 內引擎原生輸出之 SVG 字串(` ` 正規化為 ` `)。
|
|
10
10
|
|
|
11
11
|
## 產製原理(資料驅動)
|
|
12
12
|
|
package/src/p3/gen.mjs
CHANGED
|
@@ -168,7 +168,7 @@ async function autofix(page, src) {
|
|
|
168
168
|
// 單張渲染: 供其他模組 import 呼叫。輸入單份正規化繪圖數據(結構同 FIGURES[n].data,
|
|
169
169
|
// caller 已先做 label 衍生), 內部自建 browser/page(避免與批次流程共用 state), 沿用批次流程
|
|
170
170
|
// 同一套 deviceScaleFactor、等待邏輯、碰撞偵測自動調 spacing, 回傳 PNG 之 Node Buffer(不落地檔案)。
|
|
171
|
-
|
|
171
|
+
async function renderFigPage(data) {
|
|
172
172
|
const browser = await chromium.launch()
|
|
173
173
|
try {
|
|
174
174
|
const page = await browser.newPage({ deviceScaleFactor: 2 })
|
|
@@ -177,12 +177,34 @@ export async function genPng(data, opt = {}) {
|
|
|
177
177
|
const src = translate(data)
|
|
178
178
|
const fx = await autofix(page, src)
|
|
179
179
|
if (!fx.ok) throw new Error(fx.err)
|
|
180
|
-
//
|
|
180
|
+
// 以最佳間距重繪
|
|
181
181
|
await page.evaluate((x) => window.renderAndDetect(x), '#spacing: ' + fx.spacing + '\n' + src)
|
|
182
182
|
await page.evaluate(() => document.fonts && document.fonts.ready)
|
|
183
183
|
await page.waitForTimeout(180)
|
|
184
|
+
return { browser, page }
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
await browser.close()
|
|
188
|
+
throw err
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 單張渲染 → PNG Buffer
|
|
193
|
+
export async function genPng(data, opt = {}) {
|
|
194
|
+
const { browser, page } = await renderFigPage(data)
|
|
195
|
+
try {
|
|
184
196
|
return await page.locator('#box svg').screenshot()
|
|
185
197
|
} finally {
|
|
186
198
|
await browser.close()
|
|
187
199
|
}
|
|
188
200
|
}
|
|
201
|
+
|
|
202
|
+
// 單張渲染 → SVG 字串(#box 內後處理完成之 SVG; 正規化為   使其為合法 standalone SVG)
|
|
203
|
+
export async function genSvg(data, opt = {}) {
|
|
204
|
+
const { browser, page } = await renderFigPage(data)
|
|
205
|
+
try {
|
|
206
|
+
return (await page.evaluate(() => document.getElementById('box').innerHTML)).replace(/ /g, ' ').trim()
|
|
207
|
+
} finally {
|
|
208
|
+
await browser.close()
|
|
209
|
+
}
|
|
210
|
+
}
|
package/src/p4/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- **繪圖庫**:Cytoscape.js(`cytoscape/dist/cytoscape.min.js`)搭配 dagre 排版引擎(`dagre/dist/dagre.min.js`)與橋接外掛(`cytoscape-dagre/dist/cytoscape-dagre.js`),三者由本機 `node_modules` 讀出後內聯注入頁面 `<script>`(`common/pkg.mjs` 的 `pkgScript()`),非透過 CDN,斷網環境亦可渲染。
|
|
6
6
|
- **渲染環境**:由 Playwright `chromium.launch()` 開啟無頭瀏覽器,`page.setContent()` 注入含三段內聯 script 的靜態 HTML,頁面掛載一個 `1400×1400px` 的 `<div id="cy">` 作為 Cytoscape 容器。
|
|
7
7
|
- **截圖與匯出**:排版完成後呼叫 `cy.png({ full:true, scale:2, bg:'#ffffff', output:'base64' })` 取得 base64 字串,存入全域變數 `window.__png`,再由 `page.evaluate()` 讀回 Node 端,以 `Buffer.from(b64, 'base64')` 轉為 PNG Buffer 回傳。`full:true` 使輸出範圍自動裁切至實際元素範圍,`scale:2` 使最終 PNG 解析度為實際佈局尺寸的 2 倍;`deviceScaleFactor` 設為 `1`(由 `browser.newPage({ deviceScaleFactor:1 })` 指定),縮放完全由 `cy.png` 的 `scale:2` 控制。
|
|
8
|
-
- **呼叫方式**:`genPng(data, opt)` 為單張渲染函式,接受正規化繪圖數據 `{ dir, nodes, edges }`,內部自建 browser/page 並於結束時關閉,回傳 PNG 的 Node Buffer(不落地檔案),由 `src/WFlowchart.mjs` 統一調用(`mode: 'p4'
|
|
8
|
+
- **呼叫方式**:`genPng(data, opt)` 為單張渲染函式,接受正規化繪圖數據 `{ dir, nodes, edges }`,內部自建 browser/page 並於結束時關閉,回傳 PNG 的 Node Buffer(不落地檔案),由 `src/WFlowchart.mjs` 統一調用(`mode: 'p4'`);同結構之 `genSvg(data, opt)` 經 `cytoscape-svg` 外掛將同一份佈局/樣式匯出為 SVG 字串(canvas 引擎重繪為 SVG,經像素級忠實度驗證)。
|
|
9
9
|
|
|
10
10
|
## 產製原理(資料驅動)
|
|
11
11
|
|
package/src/p4/gen.mjs
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
import { chromium } from 'playwright'
|
|
5
5
|
import { pkgScript } from '../common/pkg.mjs'
|
|
6
6
|
|
|
7
|
-
// cytoscape + dagre + cytoscape-dagre 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
|
|
7
|
+
// cytoscape + dagre + cytoscape-dagre + cytoscape-svg 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
|
|
8
8
|
const CYTOSCAPE_JS = pkgScript('cytoscape/dist/cytoscape.min.js')
|
|
9
9
|
const DAGRE_JS = pkgScript('dagre/dist/dagre.min.js')
|
|
10
10
|
const CYTOSCAPE_DAGRE_JS = pkgScript('cytoscape-dagre/dist/cytoscape-dagre.js')
|
|
11
|
+
const CYTOSCAPE_SVG_JS = pkgScript('cytoscape-svg/cytoscape-svg.js')
|
|
11
12
|
|
|
12
13
|
// 正規化數據 → cytoscape elements(節點帶 parent=group; 邊 dashed→classes)
|
|
13
14
|
function toEls(data) {
|
|
@@ -41,18 +42,26 @@ const html = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
|
|
|
41
42
|
<script>${CYTOSCAPE_JS}</script>
|
|
42
43
|
<script>${DAGRE_JS}</script>
|
|
43
44
|
<script>${CYTOSCAPE_DAGRE_JS}</script>
|
|
45
|
+
<script>${CYTOSCAPE_SVG_JS}</script>
|
|
44
46
|
</head><body style="margin:0;background:#fff">
|
|
45
47
|
<div id="cy" style="width:1400px;height:1400px;background:#fff"></div>
|
|
46
48
|
<script>
|
|
47
49
|
if (window.cytoscapeDagre) { try { cytoscape.use(window.cytoscapeDagre) } catch(e){} }
|
|
48
|
-
window.
|
|
50
|
+
if (window.cytoscapeSvg) { try { cytoscape.use(window.cytoscapeSvg) } catch(e){} }
|
|
51
|
+
// mode: 'png'(預設) 或 'svg' — 版面/元素/樣式建置流程共用, 僅末端輸出格式不同(genPng 行為不變)
|
|
52
|
+
window.renderFig = function(els, styleJson, rankDir, mode){ return new Promise(function(resolve){
|
|
49
53
|
try {
|
|
50
54
|
var cy = cytoscape({ container: document.getElementById('cy'), elements: els, style: JSON.parse(styleJson) })
|
|
51
55
|
els.forEach(function(x){ if (x.classes && x.data && x.data.id) cy.getElementById(x.data.id).addClass(x.classes) })
|
|
52
56
|
var l = cy.layout({ name:'dagre', rankDir:rankDir, nodeSep:42, rankSep:55, edgeSep:18 })
|
|
53
57
|
l.promiseOn('layoutstop').then(function(){
|
|
54
58
|
setTimeout(function(){
|
|
55
|
-
try {
|
|
59
|
+
try {
|
|
60
|
+
var bb=cy.elements().boundingBox()
|
|
61
|
+
if (mode === 'svg') { window.__svg = cy.svg({ full:true, scale:2, bg:'#ffffff' }) }
|
|
62
|
+
else { window.__png = cy.png({ full:true, scale:2, bg:'#ffffff', output:'base64' }) }
|
|
63
|
+
resolve({ ok:true, w:Math.round(bb.w), h:Math.round(bb.h) })
|
|
64
|
+
}
|
|
56
65
|
catch(e){ resolve({ ok:false, err:String(e.message||e) }) }
|
|
57
66
|
}, 250)
|
|
58
67
|
})
|
|
@@ -62,21 +71,38 @@ window.renderFig = function(els, styleJson, rankDir){ return new Promise(functio
|
|
|
62
71
|
window.__ready = true
|
|
63
72
|
</script></body></html>`
|
|
64
73
|
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
export async function genPng(data, opt = {}) {
|
|
74
|
+
// 共用渲染流程 — 啟動 headless 頁面, 建置 cytoscape + dagre 版面, 回傳指定 mode 之末端輸出(base64 png 或 svg 字串)
|
|
75
|
+
// 供 genPng/genSvg 共用(genPng 行為不變: mode 傳 'png' 與原本 3 參數呼叫等價)
|
|
76
|
+
async function renderPage(data, mode) {
|
|
69
77
|
const browser = await chromium.launch()
|
|
70
78
|
try {
|
|
71
79
|
const page = await browser.newPage({ deviceScaleFactor: 1 })
|
|
72
80
|
await page.setContent(html, { waitUntil: 'load' })
|
|
73
81
|
await page.waitForFunction(() => window.__ready, { timeout: 60000 }).catch(() => {})
|
|
74
82
|
await page.evaluate(() => document.fonts && document.fonts.ready)
|
|
75
|
-
const res = await page.evaluate(([els, st, rd]) => window.renderFig(els, st, rd), [toEls(data), style, data.dir])
|
|
83
|
+
const res = await page.evaluate(([els, st, rd, md]) => window.renderFig(els, st, rd, md), [toEls(data), style, data.dir, mode])
|
|
76
84
|
if (!res.ok) throw new Error(res.err)
|
|
77
|
-
|
|
78
|
-
return Buffer.from(b64, 'base64')
|
|
85
|
+
return await page.evaluate((m) => (m === 'svg' ? window.__svg : window.__png), mode)
|
|
79
86
|
} finally {
|
|
80
87
|
await browser.close()
|
|
81
88
|
}
|
|
82
89
|
}
|
|
90
|
+
|
|
91
|
+
// 單張渲染 — 供其他模組 import 使用
|
|
92
|
+
// data: 正規化繪圖數據 { dir, nodes, edges }(caller 已先做 label 衍生)
|
|
93
|
+
// 回傳: PNG 圖片的 Node Buffer
|
|
94
|
+
export async function genPng(data, opt = {}) {
|
|
95
|
+
const b64 = await renderPage(data, 'png')
|
|
96
|
+
return Buffer.from(b64, 'base64')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 單張渲染 — 供其他模組 import 使用(cytoscape-svg 外掛匯出, 與 genPng 共用相同版面/樣式/scale)
|
|
100
|
+
// data: 正規化繪圖數據 { dir, nodes, edges }(caller 已先做 label 衍生)
|
|
101
|
+
// 回傳: SVG 字串
|
|
102
|
+
export async function genSvg(data, opt = {}) {
|
|
103
|
+
let svg = await renderPage(data, 'svg')
|
|
104
|
+
svg = svg.replace(/ /g, ' ')
|
|
105
|
+
if (!/\sxmlns=/.test(svg)) svg = svg.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"')
|
|
106
|
+
if (!/font-family/.test(svg)) svg = svg.replace('>', '><style>text{font-family:\'Microsoft JhengHei\',sans-serif}</style>')
|
|
107
|
+
return svg
|
|
108
|
+
}
|
package/src/p5/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- **解析度處理**:不使用固定 `deviceScaleFactor`,改為通用公式依 SVG `viewBox` 寬動態推算 `density`(見自動化機制節),讓輸出像素寬落在 1600–2200 px 區間。
|
|
15
15
|
- **字型問題**:本專案所用之 D2 傳入自訂 font buffer 會穩定回傳 `"invalid JSON input"`(實測),故不傳字型給 D2,改在產生的 SVG 插入 `<style>text{font-family:"Microsoft JhengHei","Microsoft YaHei",sans-serif !important;}</style>` 讓 librsvg 以系統安裝的 Microsoft JhengHei 描繪中文。D2 引擎本身已內建 CJK 全形寬度量測,故即使長中文標籤,box 寬度也不溢出。
|
|
16
16
|
- **邊標籤光暈**:D2 對有標籤的邊會用 `mask` + 黑色 `rect` 遮斷連線偽造白底效果,且標籤文字疊在連線正上方;`injectEdgeLabelHalo(svg)` 移除 mask 內黑色 rect 讓連線完整穿過標籤區,並注入 CSS 為 `.text-italic` 加 `paint-order:stroke` + 白色描邊,使標籤文字在線段上仍清晰可讀。
|
|
17
|
-
- **介面**:`genPng(data, opt)` 為單張渲染函式,回傳值為 PNG Buffer(不寫檔),由 `src/WFlowchart.mjs` 統一調用;`data` 為正規化繪圖數據 `{ dir, nodes, edges }`(caller 已完成 label
|
|
17
|
+
- **介面**:`genPng(data, opt)` 為單張渲染函式,回傳值為 PNG Buffer(不寫檔),由 `src/WFlowchart.mjs` 統一調用;`data` 為正規化繪圖數據 `{ dir, nodes, edges }`(caller 已完成 label 衍生);同結構之 `genSvg(data, opt)` 回傳 D2 引擎原生輸出之 SVG 字串(已套用邊標籤光暈與字型注入之後處理)。
|
|
18
18
|
|
|
19
19
|
## 產製原理(資料驅動)
|
|
20
20
|
|