jerosoler / Drawflow

Simple flow library 🖥️🖱️
https://jerosoler.github.io/Drawflow/
MIT License
4.78k stars 741 forks source link

How to group nodes #423

Closed Flyfot-Finesse closed 1 year ago

Flyfot-Finesse commented 2 years ago

Hello @jerosoler ,

Firstly great library!!

I am using this library on a project but we have a customized requirement to group Nodes in a wrapper and allow to link nodes from multiple groups to connect to each other. Something like on demo of another library link below: https://demo.jsplumbtoolkit.com/groups/

Can you please guide how can we achieve this with Drawflow?

Thanks & Regards, Ankit

jerosoler commented 2 years ago

Hi @apmkstudio

It's not possible.

The only thing I can think of is: An editor inside the node. This would be possible.

Duplicate:

Flyfot-Finesse commented 2 years ago

Hi @jerosoler ,

Thanks for your quick reply.

The editor inside the node doesn't support the goal we are trying to achieve. Any plan of adding this in the near future as I see this has been requested by many other users?

Or if you can guide how we can customize the library to achieve this?

Appreciate your help!

Regards, Ankit

jerosoler commented 2 years ago

I have been doing a test and it seems to work, without modifying the library. Listening to the events

https://user-images.githubusercontent.com/30957047/169275592-672745cf-3a48-4674-ac03-38a3451fc56f.mp4

Complet code example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow/dist/drawflow.min.css"/>
  <script src="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow/dist/drawflow.min.js"></script>
</head>
<div id="drawflow"></div>
  <style>
    #drawflow { 
      position: relative;
      text-align:initial;
      width: 100%;
      height: 800px;
      border: 1px solid red;
    }
    .drawflow svg {
        z-index: 1;
    }
    .drawflow .drawflow-node.GROUP {
        width: 500px;
        height: 500px;
        z-index: 0;
    }
    .drawflow .drawflow-node.GROUP.hover-drop {
        background: pink;
    }
</style>
<script>
    var id = document.getElementById("drawflow");
    const editor = new Drawflow(id);

    editor.start();    
    editor.addNode('aaa', 1, 1, 600, 200, 'aaa', {}, `aaa` );
    editor.addNode('bbb', 1, 1, 850, 200, 'bbb', {}, `bbb` );
    editor.addNode('ccc', 1, 1, 850, 370, 'ccc', {}, `ccc`);
    editor.addConnection(1, 2, 'output_1', 'input_1');
    editor.addConnection(2, 3, 'output_1', 'input_1');
    editor.addNode('GROUP', 0, 0, 1200, 100, 'GROUP', { elements: []},  `` );
    editor.addNode('GROUP', 0, 0, 0, 100, 'GROUP', { elements: []},  `` );

    let dragElementHover = null;
    let last_x = 0;
    let last_y = 0;
    editor.on("mouseMove", ({x,y}) => {
        if(editor.node_selected && editor.drag && editor.node_selected.classList[1] !== "GROUP") {
            const eles = document.elementsFromPoint(x,y);
            const ele = eles.filter( ele => ele.classList[1] === "GROUP");
            if(ele.length > 0) {
                dragElementHover = ele[0];
                dragElementHover.classList.add("hover-drop");
            } else {
                if(dragElementHover != null) {
                    dragElementHover.classList.remove("hover-drop");
                    dragElementHover = null;
                }
            }
        } else if(editor.node_selected && editor.drag && editor.node_selected.classList[1] == "GROUP") {
            const dragNode = editor.node_selected.id.slice(5);
            const dragNodeInfo = editor.getNodeFromId(dragNode);
            const elements = dragNodeInfo.data.elements;
            elements.forEach(eleN => {

                const node = document.getElementById(`node-${eleN}`);
                var xnew = (last_x - x) * editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom);
                var ynew = (last_y - y) * editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom);

                node.style.top = (node.offsetTop - ynew) + "px";
                node.style.left = (node.offsetLeft - xnew) + "px";

                editor.drawflow.drawflow[editor.module].data[eleN].pos_x = (node.offsetLeft - xnew);
                editor.drawflow.drawflow[editor.module].data[eleN].pos_y = (node.offsetTop - ynew);
                editor.updateConnectionNodes(`node-${eleN}`);

            });
        }
        last_x = x;
        last_y = y;
    })

    editor.on("nodeMoved", (id) => {
        const dragNode = id;
        if(dragElementHover !== null) {
            const dropNode = dragElementHover.id.slice(5);
            if(dragNode !== dropNode) {
                removeOfGroupNode(dragNode);
                dragElementHover.classList.remove("hover-drop");
                const dropNodeInfo = editor.getNodeFromId(dropNode);
                const dropNodeInfoData = dropNodeInfo.data;
                if(dropNodeInfoData.elements.indexOf(dragNode) === -1) {
                    dropNodeInfoData.elements.push(dragNode);
                    editor.updateNodeDataFromId(dropNode, dropNodeInfoData);
                }
            }
            dragElementHover = null;
        } else {
            removeOfGroupNode(dragNode);
        }
    })

    editor.on("nodeRemoved", (id) => {
        removeOfGroupNode(id);
    });

    function removeOfGroupNode(id) {
        Object.keys(editor.drawflow.drawflow[editor.module].data).forEach(ele => {
            if(editor.drawflow.drawflow[editor.module].data[ele].class === "GROUP") {
                const findIndex = editor.drawflow.drawflow[editor.module].data[ele].data.elements.indexOf(id);
                if(findIndex !== -1) {
                    editor.drawflow.drawflow[editor.module].data[ele].data.elements.splice(findIndex, 1);
                }
            }
       })
    }
</script>
</body>
</html>
Flyfot-Finesse commented 2 years ago

Hi @jerosoler ,

Thanks a lot for the sample code this works great for now.

Appreciate your support !!

Regards, Ankit

Flyfot-Finesse commented 2 years ago

Hello @jerosoler,

Can you help me with one more task please, I want the ability that the group node is height adjustable how can I do that can you please guide me? ie when I click on the group node I can have the option to increase the height of the node.

Thanks & Regards, Ankit

jerosoler commented 2 years ago

Use resize both, to modify the node.

Change css to:

 .drawflow .drawflow-node.GROUP {
        width: 500px;
        height: 300px;
        z-index: 0;
        resize: both;
        overflow: auto;
    }

The problem is that when an element is dragable it doesn't work and you have to do this trick.

And add javascript:

editor.on("clickEnd", (e) => {
        if(editor.node_selected) {
            const diffClick = 15;
            const height = editor.node_selected.getBoundingClientRect().height/editor.zoom - diffClick;
            const width = editor.node_selected.getBoundingClientRect().width/editor.zoom - diffClick;
            const xClick = e.offsetX;
            const yClick = e.offsetY;
            if(yClick >= height && xClick >= width) {
                editor.drag = false;
            } 
        }
    });

In this example, these changes will not be saved in the export, but you can save them in the node. For when I get the import back. Same as done in the element group.

Flyfot-Finesse commented 2 years ago

Thanks @jerosoler ,

Works fine, can you please give a sample of how to save height / width in the node? and when import again we can how the height of the node will be same as it was set before ?

Appreciate it thanks!!

Regards, Ankit

Flyfot-Finesse commented 2 years ago

Also is there any hook/ callback before the nodes load basically I want to do is group elements and show them as in kanban board so how to set X / Y axis dynamically on flow load?

jerosoler commented 2 years ago

Use event "import" to the detect load complete and move node.

View move node by javascript:

Flyfot-Finesse commented 2 years ago

Hi @jerosoler ,

I tried event import it doesn't work for me editor.on('import', function (id) { console.log("import done"); })

Basically I want to save the width and height of the group column and on load (import) the height should be same as before

Thanks, Ankit

jerosoler commented 2 years ago

Did you add it before the import or after? You have to go before.

Flyfot-Finesse commented 2 years ago

I tried after - let me try before and get back to you

Flyfot-Finesse commented 2 years ago

I tried before it triggers but cannot modify the content of node editor.on('import', function (data) { editor.drawflow.drawflow.Home.data[14].class = 'test'; console.log(editor.drawflow.drawflow.Home.data[14]) })

jerosoler commented 2 years ago

If you modify the class in the node, it is not updated in the view automatically.

Or you modify it before importing. Or you modify the node as well.

Flyfot-Finesse commented 2 years ago

Do you mean directly modifying the variable before import?

jerosoler commented 2 years ago

If you modify the variable before importing.

When importing, it loads the node correctly.

If not you will have to modify the node class by javascript. Since when updating the data is not reflected automatically.

Flyfot-Finesse commented 2 years ago

Hello @jerosoler ,

Thanks for the reply!

I tried before import also, but I am not able to save the custom width/height of the node can you please guide me on it how to set the width/height of the node on import and save it when drag-drop, thanks!

Basically what we would like is once the user stretch the width/height of the node (we save into the database the json) and again when the user comes to the page the node width/height is the same as the user set it previously.

Regards, Ankit

Flyfot-Finesse commented 2 years ago

Hello @jerosoler ,

Can you please guide me on the above as it's critical for the project that the width and height are saved properly.

Appreciate your support.

Thanks, Ankit

jerosoler commented 2 years ago

So you can get the height and width of element resized.

    let resizeElement = false;
    editor.on("clickEnd", (e) => {
        if(editor.node_selected) {
            const diffClick = 20;
            const height = editor.node_selected.getBoundingClientRect().height/editor.zoom - diffClick;
            const width = editor.node_selected.getBoundingClientRect().width/editor.zoom - diffClick;
            const xClick = e.offsetX;
            const yClick = e.offsetY;
            if(yClick >= height && xClick >= width) {
                resizeElement = true;
                editor.drag = false;
            }
        }
    });

    editor.on("mouseUp", (e) => { 
        if(resizeElement) {
            console.log(`Resize Node ${editor.node_selected.id} - Width: ${editor.node_selected.style.width} - Height: ${editor.node_selected.style.height}`)
            // editor.drawflow.drawflow[editor.module].data[editor.node_selected.id].width =  ...;
            resizeElement = false;
        }
    });
Flyfot-Finesse commented 2 years ago

Hello @jerosoler ,

Yes thanks for that but the issue is how I save it in the json so that this data can be used again to set height /width of the node on page reload ?

Thanks, Ankit

jerosoler commented 2 years ago

Uncomment this line.

            // editor.drawflow.drawflow[editor.module].data[editor.node_selected.id].width =  ...;

Create the line for the height.

Use the on "import" event method. To set width and height.

Flyfot-Finesse commented 2 years ago

Hello @jerosoler ,

Sorry but I am not able to modify the css class on import event can you please give an example how we can achieve it easily, thanks!

jerosoler commented 2 years ago

@apmkstudio view example:

Flyfot-Finesse commented 2 years ago

Hello @jerosoler,

I have followed the example code but I am not able to modify the node for example change class or style or any other node json parameter. See screenshot below

In console the first parameter only returns string "import" image

Code screenshot image

Thanks, Ankit

jerosoler commented 2 years ago

Event import only return one parameter.

you have to loop through the array and modify the class...

Flyfot-Finesse commented 2 years ago

Yes but that parameter is just a string so I cannot modify it.

jerosoler commented 2 years ago

You can loop through the same object that you passed in when importing. Or editor.drawflow. Or editor.export();

Flyfot-Finesse commented 2 years ago

Yes, I tried that too but unfortunately, it modifies the JSON object but not DOM even after updating using editor.updateConnectionNodes(node- + index) I want the dom class to update so I can do some changes infact I want height to update dynamically based on whats saved in node.

Flyfot-Finesse commented 2 years ago

Just for your information, I am not using VUE JS I am using simple javascript on the page. Technically in a Laravel blade file if this helps...

jerosoler commented 2 years ago

First of all does it save the height and width of the node? Where in?.

Second, editor.updateConnectionNodes updates connections, not nodes.

Understand what you are doing at each moment.

Flyfot-Finesse commented 2 years ago

I have added height and width to the node directly in json

"1": { "id": 1, "name": "welcome", "data": {}, "class": "welcome", "html": "\n <div>\n <div class=\"title-box\">👏 Welcome!!</div>\n <div class=\"box\">\n <p>Simple flow library <b>demo</b>\n <a href=\"https://github.com/jerosoler/Drawflow\" target=\"_blank\">Drawflow</a> by <b>Jero Soler</b></p><br>\n\n <p>Multiple input / outputs<br>\n Data sync nodes<br>\n Import / export<br>\n Modules support<br>\n Simple use<br>\n Type: Fixed or Edit<br>\n Events: view console<br>\n Pure Javascript<br>\n </p>\n <br>\n <p><b><u>Shortkeys:</u></b></p>\n <p>🎹 <b>Delete</b> for remove selected<br>\n 💠 Mouse Left Click == Move<br>\n ❌ Mouse Right == Delete Option<br>\n 🔍 Ctrl + Wheel == Zoom<br>\n 📱 Mobile support<br>\n ...</p>\n </div>\n </div>\n ", "typenode": false, "inputs": {}, "outputs": {}, "pos_x": 50, "pos_y": 50, "height": 333, },

But when the page loads that data is not accessible for me before dom updated. Is there a method to update node I guess its updateNodeDataFromId() but it doesn't allow to change the height via JS I want the Group node height to be adjustable and stay same on page load. Appreciate if you can help on it.

jerosoler commented 2 years ago

View:

    editor.start();
    const loadJson = {
    "drawflow": {
    "Home": {
    "data": { 
        "1": {
            "id": 1,
            "name": "welcome",
            "data": {},
            "class": "welcome",
            "html": "Test1",
            "typenode": false,
            "inputs": {},
            "outputs": {},
            "pos_x": 50,
            "pos_y": 50,
            "height": 333,
            "width": 200,
        },

        "2": {
            "id": 2,
            "name": "welcome",
            "data": {},
            "class": "welcome",
            "html": "Test2",
            "typenode": false,
            "inputs": {},
            "outputs": {},
            "pos_x": 200,
            "pos_y": 350,
            "height": 400,
            "width": 500,
        },
        }
        }
        }
    }
    editor.on("import", () => {
        Object.entries(editor.drawflow.drawflow['Home'].data).forEach(ele => {
            const id = ele[1].id;
            const width = ele[1].width;
            const height = ele[1].height;
            const node = editor.container.querySelector(`#node-${id}`);
            node.style.width = width + "px";
            node.style.height = height + "px";
        });

    });
    editor.import(loadJson);
Flyfot-Finesse commented 2 years ago

Thanks @jerosoler works fine!

brucesundberg commented 2 years ago

Hey @jerosoler, referencing your code example for creating group nodes, how would you additionally include reroute points within the group node, so that when the group node is repositioned, the reroute points are also repositioned? Thank you!

https://user-images.githubusercontent.com/5437674/185522622-19ff9624-3089-4245-86e3-5fcc98722216.mp4

jerosoler commented 2 years ago

Hi @brucesundberg

Try adding:

                node.style.left = (node.offsetLeft - xnew) + "px";

                //New Code
                const points = document.querySelectorAll(`.connection.node_out_node-${eleN} circle`);

                points.forEach(p => {
                    const px = p.getAttributeNS(null, 'cx') - xnew;
                    const py = p.getAttributeNS(null, 'cy') - ynew;
                    p.setAttributeNS(null, 'cx', px);
                    p.setAttributeNS(null, 'cy', py);

                    Object.keys(editor.drawflow.drawflow[editor.module].data[eleN].outputs).forEach(output => {
                        console.log(output);
                        editor.drawflow.drawflow[editor.module].data[eleN].outputs[output].connections.forEach(con => {
                            con.points.forEach(point => {
                                point.pos_x = px;
                                point.pos_y = py;
                            })
                        })
                    });
                });
                // End new Code

                editor.drawflow.drawflow[editor.module].data[eleN].pos_x = (node.offsetLeft - xnew);
brucesundberg commented 2 years ago

Thank you a ton, @jerosoler! Works perfectly!

krefects commented 2 years ago

Não funciona corretamente quando usado o "mini-map"; new DrawflowMinimap(idMinimap, editor, 0.1);

mauzzamkhan commented 11 months ago

Hey @jerosoler, referencing your code example for creating group nodes, how would you additionally include reroute points within the group node, so that when the group node is repositioned, the reroute points are also repositioned? Thank you!

reroute.anchor.mp4

hey please can you show the code how you have added multiple nodes in a group i want to group child nodes. without hovering on

rayrayraykk commented 7 months ago

Thanks for your great work. How to support nested group nodes?