Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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,

  • To zoom based on mouse pointer's position not centre of chart
  • Have a limit on minimum size my chart can go so it won't go invisible
  • Have a reset button

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>
like image 821
Mathematics Avatar asked Nov 29 '16 11:11

Mathematics


1 Answers

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>
like image 165
ConnorsFan Avatar answered Nov 05 '22 13:11

ConnorsFan