Mathematics
Mathematics

Reputation: 7628

Zooming and Panning in JS/JQuery like we can do it using SVG

I am creating an org chart with thousands of nodes but created a sample example here,

https://jsfiddle.net/jy6j87g0/2/

As you can see zooming and panning is working but I would like to make it work like one here,

http://live.yworks.com/demobrowser/index.html#Organization-Charts

To be specific

I am trying to make my fiddle,

I am struggling to find where to start, should I look further into CSS transformation or use d3.js instead and create everything from scratch.

Link to library - https://github.com/dabeng/OrgChart

'use strict';

(function($) {

  $(function() {

    var datascource = {
      'name': 'Lao Lao',
      'title': 'general manager',
      'children': [{
        'name': 'Bo Miao',
        'title': 'department manager'
      }, {
        'name': 'Su Miao',
        'title': 'department manager',
        'children': [{
          'name': 'Tie Hua',
          'title': 'senior engineer'
        }, {
          'name': 'Hei Hei',
          'title': 'senior engineer',
          'children': [{
            'name': 'Pang Pang',
            'title': 'engineer'
          }, {
            'name': 'Xiang Xiang',
            'title': 'UE engineer'
          }]
        }]
      }, {
        'name': 'Yu Jie',
        'title': 'department manager'
      }, {
        'name': 'Yu Li',
        'title': 'department manager'
      }, {
        'name': 'Hong Miao',
        'title': 'department manager'
      }, {
        'name': 'Yu Wei',
        'title': 'department manager'
      }, {
        'name': 'Chun Miao',
        'title': 'department manager'
      }, {
        'name': 'Yu Tie',
        'title': 'department manager'
      }]
    };

    $('#chart-container').orgchart({
        'data': datascource,
        'nodeContent': 'title',
        'pan': true,
        'zoom': true
      })
      .on('touchmove', function(event) {
        event.preventDefault();
      });

  });

})(jQuery);
<link href="https://cdn.rawgit.com/FortAwesome/Font-Awesome/master/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://dabeng.github.io/OrgChart/css/style.css" rel="stylesheet"/>
<link href="https://dabeng.github.io/OrgChart/css/jquery.orgchart.css" rel="stylesheet"/>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="https://dabeng.github.io/OrgChart/js/jquery.orgchart.js"></script>
<div id="chart-container">
</div>

Upvotes: 8

Views: 1778

Answers (1)

Martin Parenteau
Martin Parenteau

Reputation: 73741

The code snippet below fulfills the 3 requirements set out in your question:

  1. The zoom is centered on the mouse position
  2. The scaling cannot go below MIN_ZOOM = 0.25
  3. The Reset button restores the original position and scaling

The transform matrix and the mouse position observed in the mousemove event handler are stored in module variables, and used later in the wheel event handler.

In my tests, the wheel event was always triggered after the scaling had been processed by OrgChart. If this processing order is not the same in your case, or not stable, you can wrap the code of the wheel event handler in a setTimeout(fn, 0) construct to make sure that the scaling has already been performed by OrgChart:

.on("wheel", function (event) {
    setTimeout(function () {
        // Process the event after OrgChart
        (put the code here)
    }, 0);
}

'use strict';

(function ($) {

    $(function () {

        var datascource = {
            'name': 'Lao Lao',
            'title': 'general manager',
            'children': [{
                'name': 'Bo Miao',
                'title': 'department manager'
            }, {
                'name': 'Su Miao',
                'title': 'department manager',
                'children': [{
                    'name': 'Tie Hua',
                    'title': 'senior engineer'
                }, {
                    'name': 'Hei Hei',
                    'title': 'senior engineer',
                    'children': [{
                        'name': 'Pang Pang',
                        'title': 'engineer'
                    }, {
                        'name': 'Xiang Xiang',
                        'title': 'UE engineer'
                    }]
                }]
            }, {
                'name': 'Yu Jie',
                'title': 'department manager'
            }, {
                'name': 'Yu Li',
                'title': 'department manager'
            }, {
                'name': 'Hong Miao',
                'title': 'department manager'
            }, {
                'name': 'Yu Wei',
                'title': 'department manager'
            }, {
                'name': 'Chun Miao',
                'title': 'department manager'
            }, {
                'name': 'Yu Tie',
                'title': 'department manager'
            }]
        };

        var MIN_ZOOM = 0.25; // Mimimum value for scaling
        var defaultMatrix = [1, 0, 0, 1, 0, 0]; // Chart at normal scaling and position
        var prevMatrix = defaultMatrix;
        var prevPosition = { x: 0, y: 0 };

        var parseNumber = function (str) {
            return parseFloat(str.replace(/[^\d\.\-]/g, ""));
        }

        var getTransformMatrix = function () {
            var transform = $(".orgchart").css("transform");
            if (transform !== 'none') {
                var tokens = transform.split(",");
                var matrix = [];
                for (var i = 0; i < tokens.length; i++) {
                    matrix.push(parseNumber(tokens[i]));
                }
                return matrix;
            } else {
                return null;
            }
        };

        var setTransformMatrix = function (matrix) {
            $(".orgchart").css("transform", "matrix(" + matrix.join(",") + ")");
            prevMatrix = matrix;
        };

        var getMousePosition = function (event) {
            var rect = $(".orgchart")[0].getBoundingClientRect();
            return {
                x: event.clientX - rect.left - rect.width / 2,
                y: event.clientY - rect.top - rect.height / 2
            }
        };

        $("#btnReset").click(function () {
            setTransformMatrix(defaultMatrix);
        });

        $("#chart-container").orgchart({
            'data': datascource,
            'nodeContent': 'title',
            'pan': true,
            'zoom': true
        }).on("touchmove", function (event) {
            event.preventDefault();
        }).on("mousemove", function (event) {
            // Remember transform matrix and mouse position
            prevMatrix = getTransformMatrix() || prevMatrix;
            prevPosition = getMousePosition(event);
        }).on("wheel", function (event) {
            // In my tests, this event is triggered after processing has been done by OrgChart
            // If not the case, the following code can be wrapped in setTimeout(fn, 0) call
            var $this = $(this);
            var matrix = getTransformMatrix();
            if (matrix) {
                var $orgchart = $(".orgchart");

                // Prevent scaling below minimum zoom
                matrix[0] = Math.max(matrix[0], MIN_ZOOM);
                matrix[3] = Math.max(matrix[3], MIN_ZOOM);

                var position = getMousePosition(event);

                // Calculate expected mouse position with new scaling
                // corresponding to previous mouse position with old scaling
                var expectedPosition = {
                    x: prevPosition.x * matrix[0] / prevMatrix[0],
                    y: prevPosition.y * matrix[3] / prevMatrix[3]
                };

                // Translate chart position to match the expected position
                matrix[4] += position.x - expectedPosition.x;
                matrix[5] += position.y - expectedPosition.y;

                setTransformMatrix(matrix);
                prevPosition = expectedPosition;
            }
        });
    });

})(jQuery);
#btnReset
{
    position: absolute;
    left: 16px;
    top: 16px;
    z-index: 1000;
}
<link href="https://cdn.rawgit.com/FortAwesome/Font-Awesome/master/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://dabeng.github.io/OrgChart/css/style.css" rel="stylesheet" />
<link href="https://dabeng.github.io/OrgChart/css/jquery.orgchart.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="https://dabeng.github.io/OrgChart/js/jquery.orgchart.js"></script>
<button id="btnReset">Reset</button>
<div id="chart-container">
</div>

Upvotes: 3

Related Questions