javascriptreactjszoomingpanning

How can I use react-zoom-pan-pinch to allow users to view all of a chart that changes size based on user input?


I've designed and built an interactive Org Chart that allows users to view business organizations in a hierarchical format. By default only the first row below the root node of the chart is visible, but users can click to expand the chart further, thereby changing the size of the chart. Users can also drag and drop nodes to simulate a reorganization of the business.

I'm currently using react-zoom-pan-pinch to allow users to zoom and pan the chart. It works very well when the org chart has not been expanded too much, but becomes problematic at larger chart scales.

The problem is that the organizations being represented by the chart are very broad in comparison to their depth, meaning a fully expanded chart is a horizontal rectangle, not a square. react-zoom-pan-pinch will only allow me to zoom out to the maximum vertical extent of the chart, meaning users can't view a fully expanded organization without scrolling from side to side. This is not an acceptable behavior.

This is for work, so I cannot post code without violating numerous agreements. Instead I have linked to the react-zoom-pan-pinch documentation and will go over what I have tried changing.

The first place I looked was the TransformWrapper Props section of the documentation.

There I found the inititalScale, minScale, and maxScale props.

I can set the initialScale prop to a value of less than 1, and obtain something close to the result I want at first. Setting it to 0.5 results in the chart being zoomed out further than normally possible, but when I zoom in to a value of 1 I am unable to zoom back out. This was expected, as the minScale prop was still set to 1.

Having checked that the props indeed work, I went ahead and set minScale to 0.5, assuming I would be able to zoom back out to the initial view seen when initialScale is set to 0.5. This seemed like it should work, but it did not. Even with the minScale prop set to 0.5, I am unable to zoom back out after zooming in to a value of 1. This is very strange to me, as the acceptance of 0.5 as the initialScale prop and subsequent rendering of the chart indicates that values below 1 are acceptable.

I am now messing around with the rest of the props listed in the documentation, but have yet to achieve the desired result (infinite zoomout).

I believe the root of the issue is that react-zoom-pan-pinch is meant for images, not things that change size and aspect ratio, but it is a good package and I would prefer to keep using it.

What are the settings I should be using to allow infinite zoom out?


Solution

  • I discovered the answer to my own question. It turns out the minScale, maxScale, and other props were not being passed to the component properly. Once they are passed properly the package works very well. Here's my explanation/fix

    The documentation suggests doing this:

    
    import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
    
    class Example extends Component {
      render() {
        return (
          <TransformWrapper
            initialScale={1}
            minScale={0.5}
            maxScale={7}
            initialPositionX={200}
            initialPositionY={100}
          >
            {({ zoomIn, zoomOut, resetTransform, ...rest }) => (
              <React.Fragment>
                <div className="tools">
                  <button onClick={() => zoomIn()}>+</button>
                  <button onClick={() => zoomOut()}>-</button>
                  <button onClick={() => resetTransform()}>x</button>
                </div>
                <TransformComponent>
                  <img src="image.jpg" alt="test" />
                  <div>Example text</div>
                </TransformComponent>
              </React.Fragment>
            )}
          </TransformWrapper>
        );
      }
    }
    

    The above doesn't work, and the minScale and maxScale props aren't passed to the component. If you open the React dev tools in your browser and go to TransformWrapper, you'll see the default values of 1 for minScale and 8 for maxScale, not the values you entered in your code.

    You can solve the problem by creating an object:

      const transformOptions = {
        initialScale: 1,
        minScale: 0.5,
        maxScale: 2
      }
    

    Then setting an options prop inside the TransformWrapper component equal to the object, like so:

    
    import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
    
    class Example extends Component {
      render() {
        return (
          <TransformWrapper
            initialScale={1}
            options={transformOptions}
            initialPositionX={200}
            initialPositionY={100}
          >
            {({ zoomIn, zoomOut, resetTransform, ...rest }) => (
              <React.Fragment>
                <div className="tools">
                  <button onClick={() => zoomIn()}>+</button>
                  <button onClick={() => zoomOut()}>-</button>
                  <button onClick={() => resetTransform()}>x</button>
                </div>
                <TransformComponent>
                  <img src="image.jpg" alt="test" />
                  <div>Example text</div>
                </TransformComponent>
              </React.Fragment>
            )}
          </TransformWrapper>
        );
      }
    }
    

    The same thing applies to pan, wheel, and zoom options. They don't work if set directly in the component as suggested by the documentation, but do work if you create objects like I did above.