UI options to change density
This commit is contained in:
@@ -31,8 +31,11 @@ undo/redo possible with https://github.com/bmad4ever/ComfyUI-Bmad-DirtyUndoRedo
|
||||
|
||||
recommend: remove reroute nodes so the algorithms work better as it figures out the ranks/depth, after applied layout u can re-add reroute nodes for any wires partially hidden by nodes
|
||||
|
||||
TODO:
|
||||
- add UI options to change density
|
||||
**TODO**:
|
||||
- [x] refresh after apply layout
|
||||
- [x] add UI options to change density
|
||||
- [ ] better UI than pop-up for options to change density
|
||||
- [ ] option to select layout strategy (see docs for each algo)
|
||||
|
||||
## example
|
||||
using [noisy latent composition example](https://comfyanonymous.github.io/ComfyUI_examples/noisy_latent_composition/)
|
||||
|
||||
+57
-28
@@ -6,6 +6,7 @@ import "./dagre.min.js";
|
||||
import "./elk.bundled.min.js";
|
||||
// idk how to do named import with those
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
"name": "doof.autoNodesLayout",
|
||||
setup() { // Add canvas menu options
|
||||
@@ -15,9 +16,10 @@ app.registerExtension({
|
||||
options.push(myRightClickMenu); // def below
|
||||
return options;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const myRightClickMenu = {
|
||||
"content": "📍 auto nodes layout",
|
||||
"has_submenu": true,
|
||||
@@ -34,44 +36,53 @@ const myRightClickMenu = {
|
||||
{
|
||||
"content": "ELK.js ‘layered’ layout",
|
||||
"callback": elkLayeredLayout // def below
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// 2 values to control layout density (string for max compability with window.prompt)
|
||||
var ranksep = "200"; // spacing (px) between ranks/depths/columns
|
||||
var nodesep = "150"; // spacing (px) between nodes in same rank/depth/column
|
||||
|
||||
|
||||
/**
|
||||
* arrange nodes using Dagre layout
|
||||
* @see https://github.com/dagrejs/dagre
|
||||
* @todo add options to change density
|
||||
* @todo better UI than pop-up
|
||||
* @returns {undefined} Nothing is returned.
|
||||
*/
|
||||
function dagreLayout() {
|
||||
detectRerouteNode(); // def below
|
||||
popupInput(); // def below
|
||||
|
||||
// setup dagre
|
||||
const daG = new dagre.graphlib
|
||||
.Graph()
|
||||
.Graph({ "compound": false })
|
||||
.setGraph({
|
||||
"rankdir": "LR", // left to right
|
||||
"ranker": "network-simplex", // values: "network-simplex", "tight-tree", "longest-path"
|
||||
"nodesep": 100, // spacing in same column, can be changed
|
||||
"ranksep": 200, // spacing between columns, can be changed
|
||||
});
|
||||
"ranker": "network-simplex",
|
||||
// values: "network-simplex", "tight-tree", "longest-path"
|
||||
"ranksep": parseFloat(ranksep),
|
||||
"nodesep": parseFloat(nodesep),
|
||||
})
|
||||
.setDefaultNodeLabel(() => ({}))
|
||||
.setDefaultEdgeLabel(() => ({}));
|
||||
|
||||
// convert litegraph to dagre
|
||||
app.graph._nodes.forEach((n) => daG.setNode(
|
||||
n.id.toString(),
|
||||
{
|
||||
"label": n.type,
|
||||
"width": n.size[0],
|
||||
"height": n.size[1]
|
||||
"height": n.size[1],
|
||||
}
|
||||
));
|
||||
app.graph.links.forEach((e) => daG.setEdge(
|
||||
e.origin_id.toString(),
|
||||
e.target_id.toString(),
|
||||
{ "label": e.type }
|
||||
e.target_id.toString()
|
||||
));
|
||||
|
||||
// apply layout algorithm
|
||||
dagre.layout(daG);
|
||||
|
||||
// retrieve nodes position
|
||||
@@ -85,43 +96,53 @@ function dagreLayout() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* arrange nodes using ELK ‘layered’ layout
|
||||
* @see https://github.com/kieler/elkjs
|
||||
* @todo add options to change density
|
||||
* @todo better UI than pop-up
|
||||
* @returns {undefined} Nothing is returned.
|
||||
*/
|
||||
function elkLayeredLayout() {
|
||||
detectRerouteNode(); // def below
|
||||
popupInput(); // def below
|
||||
|
||||
// convert litegraph to elk
|
||||
const myElkNodes = app.graph._nodes.map((n) => ({
|
||||
"id": n.id,
|
||||
"width": n.size[0],
|
||||
"height": n.size[1]
|
||||
"height": n.size[1],
|
||||
}));
|
||||
const myElkEdges = app.graph.links.filter(Boolean).map((e) => ({
|
||||
"id": e.id,
|
||||
"sources": [ e.origin_id ],
|
||||
"targets": [ e.target_id ]
|
||||
"targets": [ e.target_id ],
|
||||
}));
|
||||
|
||||
// setup ELK
|
||||
const myElkGraph = {
|
||||
"id": "root",
|
||||
"children": myElkNodes,
|
||||
"edges": myElkEdges,
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered"
|
||||
}
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "RIGHT",
|
||||
"elk.layered.layering.strategy": "NETWORK_SIMPLEX",
|
||||
// values: "NETWORK_SIMPLEX", "LONGEST_PATH", "COFFMAN_GRAHAM"
|
||||
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
|
||||
// values: "NETWORK_SIMPLEX", "BRANDES_KOEPF", "LINEAR_SEGMENTS"
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": ranksep,
|
||||
"elk.spacing.nodeNode": nodesep,
|
||||
},
|
||||
}
|
||||
|
||||
// setup ELK & retrieve nodes position
|
||||
// apply layout algorithm
|
||||
const elk = new ELK()
|
||||
.layout(myElkGraph)
|
||||
.then((val) => {
|
||||
.then((val) => { // retrieve nodes position
|
||||
for (const nodeLaidOut of val.children) {
|
||||
const n = app.graph.getNodeById(nodeLaidOut.id);
|
||||
n.pos[0] = nodeLaidOut.x * 2; // add more spacing
|
||||
n.pos[1] = nodeLaidOut.y * 2;
|
||||
n.pos[0] = nodeLaidOut.x;
|
||||
n.pos[1] = nodeLaidOut.y;
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
@@ -130,19 +151,27 @@ function elkLayeredLayout() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ask for user input to control layout density then
|
||||
* make a alert in case there’s any reroute node
|
||||
* @returns {undefined} Nothing is returned.
|
||||
*/
|
||||
function detectRerouteNode() {
|
||||
function popupInput() {
|
||||
// change global value in a session
|
||||
ranksep = window.prompt("Enter spacing (px) between ranks/depths/columns", ranksep);
|
||||
nodesep = window.prompt("Enter spacing (px) between nodes in same rank/depth/column", nodesep);
|
||||
|
||||
// make a alert in case there’s any reroute node
|
||||
for (const n of app.graph._nodes) {
|
||||
if (n.constructor.name === "RerouteNode") {
|
||||
if (n.type === "Reroute") {
|
||||
window.alert(
|
||||
"Layout algorithms work best without Reroute nodes!\n"
|
||||
"Layout algorithms work better without Reroute nodes!\n"
|
||||
+ "better remove reroute before auto-layout then re-add after"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user