Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zoom in/out #22

Open
taf2 opened this issue Dec 16, 2019 · 30 comments
Open

Zoom in/out #22

taf2 opened this issue Dec 16, 2019 · 30 comments
Assignees
Labels
enhancement New feature or request question Further information is requested

Comments

@taf2
Copy link

taf2 commented Dec 16, 2019

It would be great and very useful if for larger diagrams we could zoom in/out. This would make it easier to visualize larger flows.

@alyssaxuu alyssaxuu self-assigned this Dec 16, 2019
@alyssaxuu alyssaxuu added the enhancement New feature or request label Dec 16, 2019
@alyssaxuu
Copy link
Owner

That could definitely be a good idea, I'll look into it.

@yellow1912
Copy link

In addition to zooming in and out, perhaps this is more useful: module. I find this concept really useful on Botstar.com which has a comprehensive UI to build bot flows. The module allow you to group multiple steps and hide them by default.
image

@yellow1912
Copy link

NVM, I found #29 which proposes the idea of a wrapper outside of flowy to keep things simple. I think that's quite amazing to be honest. It would be nice if we do have the api to addBlock and removeBlock exposed, that would help alot.

@alyssaxuu
Copy link
Owner

NVM, I found #29 which proposes the idea of a wrapper outside of flowy to keep things simple. I think that's quite amazing to be honest. It would be nice if we do have the api to addBlock and removeBlock exposed, that would help alot.

What would "addBlock" being exposed look like? There is currently onSnap which gets triggered when a block is able to be attached to another block, which lets you to either prevent or allow the attachment. Just added a method for the block removal.

@yellow1912
Copy link

I think right now all the "actions" require interaction from the user (drag and drop). There is an import method but it would affect the whole tree. Perhaps we could extend the import and allow specifying the root of the import, would that work?

@alyssaxuu
Copy link
Owner

I think right now all the "actions" require interaction from the user (drag and drop). There is an import method but it would affect the whole tree. Perhaps we could extend the import and allow specifying the root of the import, would that work?

I see, so you suggest a way to import blocks under a certain parent? I can definitely make it so that blocks can be added programmatically (and not just individual blocks, also trees), but the problem is, how would that work? Blocks can be 100% custom - so would it be a method that accepts some HTML, an array of blocks, and the ID of the desired parent? Not sure what would be the most intuitive here.

@yellow1912
Copy link

I see what you mean, can it work the same way with the import to make things consistent?

{
	"html": "",
	"blockarr": [],
	"blocks": [
		{
			"id": 1,
			"parent": 0,
			"data": [
				{
				"name": "blockid",
				"value": "1"
				}
			],
			"attr": [
				{
				"id": "block-id",
				"class": "block-class"
				}
			]
		}
	]
}

Regarding the HTML, I think we should avoid touching any HTML if possible and only deal with json data. There could be a render callback that accept a block data then return a rendered HTML string perhaps?

@pilootchoum
Copy link

Hello,
I was thinking about implementing this feature as well,
Do you think using the scale property on all dom element on the canvas, and updating the blocks array would be enough?
I mean scaling with the same ratio the blocks? As it did not succeed so far.
If you have a direction, that would be great ;)

@alyssaxuu alyssaxuu added the question Further information is requested label Apr 24, 2020
@alyssaxuu
Copy link
Owner

Hello,
I was thinking about implementing this feature as well,
Do you think using the scale property on all dom element on the canvas, and updating the blocks array would be enough?
I mean scaling with the same ratio the blocks? As it did not succeed so far.
If you have a direction, that would be great ;)

I assume you mean using transform: scale()? It's certainly possible, I would just need to make sure this doesn't cause any conflicts with the block positioning/dimensions.

I haven't been prioritizing this feature simply because I see a potential problem - if the canvas area is scaled up/down, how would it look/feel like dragging and dropping a differently sized block there (e.g. in the demo, from the left panel)? I'm just not sure if it would feel smooth or intuitive.

Screen Shot 2020-04-24 at 8 22 19 AM

Here's an image of what I mean. It might just feel weird, but I'm open to hearing your thoughts.

@pilootchoum
Copy link

Sure I thought about this too, maybe, I would have forced the user then to reset the zoom before dropping a new element on the canvas, but that would be out of Flowy charge I mean.

Its just that after adding a few blocks, it seems to me that it becomes quite hard to see properly the canvas and maybe rearrange the blocks if needed.

But one can also just use the browser zoom feature if needed. Anyway that feature would be awesome I think ;)

@alyssaxuu
Copy link
Owner

Sure I thought about this too, maybe, I would have forced the user then to reset the zoom before dropping a new element on the canvas, but that would be out of Flowy charge I mean.

Its just that after adding a few blocks, it seems to me that it becomes quite hard to see properly the canvas and maybe rearrange the blocks if needed.

But one can also just use the browser zoom feature if needed. Anyway that feature would be awesome I think ;)

Hmm in that case the developer could simply use transform: scale() on the canvas themselves, since either way the block snapping / dragging functionality would be off when resizing. So I don't see it being useful as a method, in that sense. Like I said, unless someone thinks of a better way to go about it - maybe it could work when resized, just that when the block is dropped into the flowchart it would resize to match automatically?

@pilootchoum
Copy link

Yes that would be the best I think,
Once dropped the block should resize automaticlly to the new scale.

@pilootchoum
Copy link

Hello @alyssaxuu,

I tried to implement it myself doing the following :

on zoom in I am changing the scale of every childNode of the canvas except the arrows :
document.getElementById("canvas").childNodes.forEach((node) => { if (node.className.indexOf("arrowblock") < 0) { node.style.transform = "scale(" + this.currentScale + ")"; } });

Then in flowy, I am updating the height and witdth of every block :

flowy.scale = function (scale) { blocks.forEach(function (block) { block.width = block.width * scale; block.height = block.height * scale; }); if (blocks.length > 1) { rearrangeMe(); } };

I thought that the rearrangeMe function would update then the childWidth properties, and redraw the arrows to fit the new size of the element, however it does not seem to work properly.

Do you have any hint on how to do that in a better way?

@alyssaxuu
Copy link
Owner

Hello @alyssaxuu,

I tried to implement it myself doing the following :

on zoom in I am changing the scale of every childNode of the canvas except the arrows :
document.getElementById("canvas").childNodes.forEach((node) => { if (node.className.indexOf("arrowblock") < 0) { node.style.transform = "scale(" + this.currentScale + ")"; } });

Then in flowy, I am updating the height and witdth of every block :

flowy.scale = function (scale) { blocks.forEach(function (block) { block.width = block.width * scale; block.height = block.height * scale; }); if (blocks.length > 1) { rearrangeMe(); } };

I thought that the rearrangeMe function would update then the childWidth properties, and redraw the arrows to fit the new size of the element, however it does not seem to work properly.

Do you have any hint on how to do that in a better way?

I believe the best way to go about it would be to use transform scale to resize the canvas' contents, and then immediately after replace all the array properties of the blocks (I see you updated height and width, but there's also the X and Y properties that are important which you're missing. Childwidth is used to store how much space all the children elements one next to the other (including spacing in between) takes in order to then calculate how much offset a new block being dragged in should have. I suppose if you changed all those properties and ran the rearrangeMe function it should work (haven't tested it though).

@cdebattista
Copy link

Someone have a solution ?
screencast-crm-preprod fboard app-2020 10 19-10_20_38

One solution works but arrows are !@{"#

        let _this = this;
        let ratio = this.scale / this.previousScale;
        let canvas = document.getElementById("canvas");
        //console.log(ratio);

        canvas.childNodes.forEach(function(node){

            let nodeX = parseFloat(window.getComputedStyle(node).left);
            let nodeY = parseFloat(window.getComputedStyle(node).top);

            let style = window.getComputedStyle(node);
            let matrix = new WebKitCSSMatrix(style.transform);

            let previousTx = (- matrix.m41 * _this.previousScale);
            let previousTy = (- matrix.m42 * _this.previousScale);

            let previousDx = (nodeX * _this.previousScale);
            let previousDy = (nodeY * _this.previousScale);

            let newTx = ((previousTx * ratio + previousDx * (ratio - 1)) / _this.scale);
            let newTy = ((previousTy * ratio + previousDy * (ratio - 1)) / _this.scale);

            let newX = nodeX - newTx;
            let newY = nodeY - newTy;

            //node.style.transform = "scale(" + _this.scale + ")";

            //node.style.transform = "scale(" + _this.scale + ") translate(" + newTx + "px, " + newTy + "px)";
            //node.style.transformOrigin = "0 0";


            if (node.className.indexOf("arrowblock") < 0) {
                let blockProperties = {
                    id: parseInt(node.querySelector(".blockid").value),
                    x: ((node.getBoundingClientRect().left + window.scrollX) + (parseInt(window.getComputedStyle(node).width) / 2) + canvas.scrollLeft) * _this.scale,
                    y: ((node.getBoundingClientRect().top + window.scrollY) + (parseInt(window.getComputedStyle(node).height) / 2) + canvas.scrollTop) * _this.scale,
                    width: (parseInt(window.getComputedStyle(node).width)) * _this.scale,
                    height: (parseInt(window.getComputedStyle(node).height)) * _this.scale
                }
                let blocStyles = {
                    top : ((node.getBoundingClientRect().top + window.scrollY) - (canvas.getBoundingClientRect().top + window.scrollY) + canvas.scrollTop) * _this.scale,
                    left : ((node.getBoundingClientRect().left + window.scrollX) - (canvas.getBoundingClientRect().left + window.scrollX) + canvas.scrollLeft) * _this.scale
                }
                console.log(blockProperties, blocStyles);
                node.style.left = blocStyles.left + 'px';
                node.style.top = blocStyles.top + 'px';
                //node.style.transform = "scale(" + _this.scale + ")";
                node.style.transform = "scale(" + _this.scale + ") translate(" + newTx + "px, " + newTy + "px)";
                //node.style.transformOrigin = "0 0";
                //node.style.left = newX + 'px';
                //node.style.top = newY + 'px';
                flowy.transformBlocks(blockProperties.id, 'x', blockProperties.x);
                flowy.transformBlocks(blockProperties.id, 'y', blockProperties.y);
                flowy.transformBlocks(blockProperties.id, 'width', blockProperties.width);
                flowy.transformBlocks(blockProperties.id, 'height', blockProperties.height);

            }else{
                node.style.transform = "scale(" + _this.scale + ")";
            }

            /*console.log(nodeX, nodeY);
            console.log(previousTx, previousTy);
            console.log(previousDx, previousDy);
            console.log(newTx, newTy);
            console.log(newX, newY);*/

        });
        //flowy.scale(this.scale);
        console.log(flowy.output());

I tried a CSS trick and it works but now have to changes the x,y,width,height,childwitdh and the code above don't work

        canvas.style.transform = "scale(" + this.scale + ")";
        canvas.style.width = "calc(" + 100 / this.scale  + "% - 0px)"
        canvas.style.height = "calc(" + 100 / this.scale  + "% - 0px)"
        canvas.style.top = (100 - (100 / this.scale)) / 2 + "%"
        canvas.style.left = (100 - (100 / this.scale)) / 2 + "%"

@cdebattista
Copy link

cdebattista commented Oct 19, 2020

Finally got it ! next step panning.
screencast-crm-preprod fboard app-2020 10 19-15_36_04

var zoom = 1;
...
...
flowy.zoom = function(value = 0, restore = false){
	zoom += value;
	if(zoom > 1 || zoom < 0.1 || restore){
		zoom = 1;
	}
	canvas_div.style.zoom = zoom;
	canvas_div.childNodes.forEach(function(node){
		if (node.className.indexOf("arrowblock") < 0) {
			let id = parseInt(node.querySelector(".blockid").value);
			let block = blocks.filter(a => a.id == id)[0];
			block.x = ((node.getBoundingClientRect().left + window.scrollX) + (parseInt(window.getComputedStyle(node).width) / 2) + canvas.scrollLeft);
			block.y = ((node.getBoundingClientRect().top + window.scrollY) + (parseInt(window.getComputedStyle(node).height) / 2) + canvas.scrollTop);
		}
	});
}
...
...
var xpos = ((drag.getBoundingClientRect().left + window.scrollX) + (parseInt(window.getComputedStyle(drag).width) / 2) + canvas_div.scrollLeft) / zoom;
var ypos = ((drag.getBoundingClientRect().top + window.scrollY) + canvas_div.scrollTop) / zoom
zoom: function(){

        document.querySelector('.zoom-in').addEventListener('click', () => {
            flowy.zoom(+0.1);
        });
        document.querySelector('.zoom-out').addEventListener('click', () => {
            flowy.zoom(-0.1);
        });
        document.querySelector('.zoom-restore').addEventListener('click', () => {
            flowy.zoom(null, true);
        });
    },

@ebihimself
Copy link

@cdebattista in the approach you mentioned, when the tree becomes big and wide the indicator will not attach to the target node and will distance far away from the node you want. As zoom increases the distance will be increased more. Do you have any idea about that?

In my opinion, the indicator's left property should be tied to the canvas left property as well as so.

@robhoward
Copy link

So I had planned to implement this as well, we came almost to the same implementation. My UX is a little different. I've also been going through and refactoring the code into more functions.

Still a few issues with scroll position for large workflows.

image

image

@ravikoppula
Copy link

Hi Rob Howard

I am using flowy.js script and at the moment I failed to implement the ZOOM IN & OUT feature.

I really don't have idea how to implement. I gone through the Git comments but it does help me out any luck.

Can any one please share the piece of logic with.

Thank you so much in advance.

@robhoward
Copy link

Sure:

  1. I have a global var:
    var zoom = 100;

  2. Some Zoom functions:

     // ┌────────────────────────────────────────────────────────────────────
     // │ Zooms the display out making the blocks smaller
     // └────────────────────────────────────────────────────────────────────
     flowy.zoomOut = function () {
         let scale_factor = 5;
         zoom = zoom - scale_factor;
    
         flowy.setZoom();
         flowy.draw();
     }
    
     // ┌────────────────────────────────────────────────────────────────────
     // │ Zooms the display in making the blocks larger
     // └────────────────────────────────────────────────────────────────────
     flowy.zoomIn = function () {
         let scale_factor = 5;
         zoom = zoom + scale_factor;
    
         flowy.setZoom();
         flowy.draw();
     }
    
     // ┌────────────────────────────────────────────────────────────────────
     // │ Sets the current zoom
     // └────────────────────────────────────────────────────────────────────
     flowy.setZoom = function () {
         flowy.getZoom();
         canvas_div.style.zoom = `${zoom}%`;
     }
    
     // ┌────────────────────────────────────────────────────────────────────
     // │ Gets the current zoom
     // └────────────────────────────────────────────────────────────────────
     flowy.getZoom = function () {
    
         if (zoom > 100) {
             zoom = 100;
         }
    
         if (zoom < 40) {
             zoom = 40;
         }
    
         return zoom;
     }
    
  3. In flowy.moveBlock I recalculate the mouse position:

         if (zoom < 1) {
             mouse_x = mouse_x * (zoom / 100);
             mouse_y = mouse_y * (zoom / 100);
         }
    

Also in moveBlock (but refactored into another function:

        let z = zoom / 100;

        // Get the center point position of the dragged block and offset with zoom
        var xpos = ((drag.getBoundingClientRect().left + window.scrollX) + (parseInt(window.getComputedStyle(drag).width) / 2) + canvas_div.scrollLeft) / z;
        var ypos = ((drag.getBoundingClientRect().top + window.scrollY) + canvas_div.scrollTop) / z;

Then in my UX I have this wired up with the plus/minus keys and the UX in screen shot above. Hope that helps!

@robhoward
Copy link

You might want to instead look at #105. Scroll to the bottom and @cdebattista posted a solid update. I found it this morning and have been converting my zoom behavior to use his.

@ravikoppula
Copy link

Hi Rob Howard

Thank you so much for the response.

Can you please assist me how can I use the user interface part +/- as shown in the above image.

Do you attached the code? I cant see any out come after I apply the code you had attached.

Please assist me.

@robhoward
Copy link

robhoward commented Apr 5, 2021 via email

@ravikoppula
Copy link

Thank you for the response.

Let me explain to you the way I added your code in my project.

step 1. Downloaded the code from the Git repo https://github.com/alyssaxuu/flowy
step 2: In the flowy.min.js file I had added the below strip
var zoom = 100; Global variable.
// ┌────────────────────────────────────────────────────────────────────
// │ Zooms the display out making the blocks smaller
// └────────────────────────────────────────────────────────────────────
flowy.zoomOut = function () {
let scale_factor = 5;
zoom = zoom - scale_factor;

               flowy.setZoom();
               flowy.draw();
           }
          
           // ┌────────────────────────────────────────────────────────────────────
           // │ Zooms the display in making the blocks larger
           // └────────────────────────────────────────────────────────────────────
           flowy.zoomIn = function () {
               let scale_factor = 5;
               zoom = zoom + scale_factor;
          
               flowy.setZoom();
               flowy.draw();
           }
          
           // ┌────────────────────────────────────────────────────────────────────
           // │ Sets the current zoom
           // └────────────────────────────────────────────────────────────────────
           flowy.setZoom = function () {
               flowy.getZoom();
               canvas_div.style.zoom = `${zoom}%`;
           }
          
           // ┌────────────────────────────────────────────────────────────────────
           // │ Gets the current zoom
           // └────────────────────────────────────────────────────────────────────
           flowy.getZoom = function () {
          
               if (zoom > 100) {
                   zoom = 100;
               }
          
               if (zoom < 40) {
                   zoom = 40;
               }
          
               return zoom;
           }

  In the **flowy.moveBlock** 
  
       if (zoom < 1) {
           mouse_x = mouse_x * (zoom / 100);
           mouse_y = mouse_y * (zoom / 100);
       }

@NOTE: I have No Idea about the below code ( getBoundingClientRect() is returning an error method name not found ) but I have used the same method name many times in the flowy.min.js file. Indeed I added this code in the flowy.moveBlock
let z = zoom / 100;
// Get the center point position of the dragged block and offset with zoom
var xpos = ((drag.getBoundingClientRect().left + window.scrollX) + (parseInt(window.getComputedStyle(drag).width) / 2)

  • canvas_div.scrollLeft) / z;
    var ypos = ((drag.getBoundingClientRect().top + window.scrollY) + canvas_div.scrollTop) / z;

Step 3: In the index.html page inside the canvas div, I added the below code.

Zoom 100% |

Step4: Based on Id triggered the Zoom IN/ Out functions from the main.js.

These are the steps I followed to implement but still not working.

Can you please advice on this. I really want to implement this feature and release in the production.

If possible can you please hang-up on the skype call or any alternative channel for this fix to be done.

I really apricated your time for replying me back.

Have a great day :)

@robhoward
Copy link

robhoward commented Apr 6, 2021 via email

@sabatale
Copy link

Rob has its own flowy.draw() function so you'd have to build an equivalent to make it work.

You should be able to make cdebattista's code work fairly easily though.

@robhoward
Copy link

Yes, that's correct. I ended up doing a massive merge over the last few months of my changes with @cdebattista's code. I just finished it last month.

@DrewWeth
Copy link

@robhoward I know it's a year later, but if you have it working could you link an example repo or fork? I would love to use this and it seems non-trivial from above!

@robhoward
Copy link

robhoward commented Mar 13, 2023 via email

@belthaZornv
Copy link

Would be nice to see a PR of a zoom done tbh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests