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

Smooth zooming and moveing to a bounding box in svg #219

Open
Timo-Weike opened this issue Nov 7, 2020 · 10 comments
Open

Smooth zooming and moveing to a bounding box in svg #219

Timo-Weike opened this issue Nov 7, 2020 · 10 comments

Comments

@Timo-Weike
Copy link
Contributor

I have a svg graphic and upon an event I want to move and zoom smoothly to a specific bounding box.

Lets say we have this circle

<circle id="circle" r="100" cy="200" cx="200" style="..." />

upon an event I want to move and zoom the scene to the bounding box

top = 90
left = 90
right = 310
bottom = 310

but the functions smoothZoomAbs and smoothMoveTo seam ill fit since they do not operate together and smoothZoomAbs does zoom according to screen coordinates.

How could I accomplish this?

@anvaka
Copy link
Owner

anvaka commented Nov 10, 2020

Try showRectangle: https://github.com/anvaka/panzoom/blob/master/index.js#L170-L176 - does it work?

@Timo-Weike
Copy link
Contributor Author

showRectangle works generally as expected, but...

the zooming only happens after the next zoom/pan interaction with the canvas. I think the problem is the missing call to makeDirty like in zoomByRatio

makeDirty();

So I would suggest adding

triggerEvent('pan');
triggerEvent('zoom');
makeDirty();

to the end of showRectangle.

Also for my use case I would need something like smoothShowRectangle

An implementation could be

function smoothShowRectangle(rect) {
  // getCurrentRect gets the rect which represents the current viewBox of the svg
  var currentRect = getCurrentRect();
  var from ={
    top: currentRect.top,
    right: currentRect.right,
    bottom: currentRect.bottom,
    left: currentRect.left
  };
  var to = {
    top: rect.top,
    right: rect.right,
    bottom: rect.bottom,
    left: rect.left
  };
  
  smoothScroll.cancel();
  cancelShowRectangleAnimation();

  showRectangleAnimation = animate(from, to, {
    step: function (r) {
      showRectangle(r);
    },
    done: triggerZoomEnd
  });
}

I just don't know how getCurrentRect could be implemented.

@Timo-Weike
Copy link
Contributor Author

Timo-Weike commented Nov 14, 2020

I think I have found a good implementation for smoothShowRectangle

function smoothShowRectangle(rect, duration = undefined) {
  cancelAllAnimations();

  var to = rect
  var from = transformToClientRect(transform);

  // default duration is 600ms
  var dur = 600;
  if (typeof duration === 'function') {
    // let consumer calculate a duration based on the new and current transform
    dur = duration(from, to);
  }

  var p = new Promise((resolve, _) => {
    showRectangleAnimation = animate(from, to, {
      duration: dur,
      step: function (nextTransform) {
        showRectangle(nextTransform);
      },
      done: () => {
        triggerZoomEnd();
        triggerPanEnd();
        resolve(true);
      }
    });
  })

  return p;
}

where transformToClientRect is

function transformToClientRect(transform) {
  var clientRect = owner.getBoundingClientRect();
  var size = transformToScreen(clientRect.width, clientRect.height);

  var w = size.x / transform.scale;
  var h = size.y / transform.scale;
  var l = transform.x / -transform.scale;
  var t = transform.y / -transform.scale;

  return {
    top: t,
    left: l,
    bottom: t + h,
    right: l + w,
  };
}

the argument duration is a consumer given function that can be used to calculate a duration for the animation based on the new and old rect.
For example I tested this function

(from, to) => {
  var distance = Math.sqrt(
    Math.pow(from.top - to.top, 2)
    + Math.pow(from.right - to.right, 2)
    + Math.pow(from.bottom - to.bottom, 2)
    + Math.pow(from.left - to.left, 2)
  );

  var exp_diff = Math.exp(distance / 1000);
  var sigmoid = (exp_diff * 1000) / (exp_diff + 1);

  return sigmoid;
}

it used the euclidien distance to determine the "distance" between the two rects and then used a scaled sigmoid function to cap the duration at 1 second.

I also added a Promise as a return value so that the consumer can act after the animation is over

The corisponding typescript binding is

smoothShowRectangle: (rect: ClientRect, duration: (from:ClientRect, to:ClientRect) => number) => Promise<boolean>;

It might also be worth considering to add a promise to the other smooth function.

Should I fork or create a branch, implement it and create a pull request for the changes?

@Timo-Weike
Copy link
Contributor Author

It would maybe also worth considering to make transformToClientRect available to the consumer, so that one could save the current rect and use it later to call smoothShowRect again to restore a previous zoom/pan state

@attilam
Copy link

attilam commented Nov 25, 2020

I also need to zoom to a specific rect, and came here because makeDirty is missing from showRectangle, but @Timo-Weike your solution for smooth movement seems what I'd actually need. :) Could you publish it?

@Timo-Weike
Copy link
Contributor Author

Timo-Weike commented Nov 26, 2020

@attilam I will try to commit my changes into a fork or new branch this weekend. But I can't promise it.

But besides the call to cancelAllAnimations(); the version of smoothShowRectangle in my third post should be a drop-in into the index.js file with minor changes in the api object and the index.d.ts file.

@attilam
Copy link

attilam commented Nov 26, 2020

Thanks!

I take it cancelAllAnimations calls cancelZoomAnimation() and some other functions that look like it to cancel all types of animations? I'll check it out.

@Timo-Weike
Copy link
Contributor Author

@attilam I implemented the function in a fork https://github.com/Timo-Weike/panzoom and @anvaka I also made a pull-request for my changes #221

@attilam
Copy link

attilam commented Dec 2, 2020

@Timo-Weike Thank you!!

@Aerobraking
Copy link

Can we exepect that the #221 will be merged into the master branch? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants