Search code examples
d3.jssvgdragpan

Pan during dragging in D3JS


what I want to achieve is ability to pan the entire SVG "image" during the dragging operation.

now im playing wit this example: https://bl.ocks.org/mbostock/6123708 as I found it most suitable for me (most because it's handle the drag "correctly", most other examples just center the dot during drag operation I will drag more complex object so to me this feature that I increment the position not change it is more suitable) but don't really know how I could handle this feature of pan during drag

  1. user start dragging the circle usin LeftMouseButton
  2. holding this button press the RightMouseButton (an then the magic starts happening)
  3. the whole SVG start to move (pan) and so the dragged element (the dragging is still working and updating position so the mouse position over the dragging element don't change)
  4. user release the RightMouseButton the SVG stops panning and only the drag is still working
  5. user release the LeftMouseButton and the drag elements update it's position

As far I know that I must:

  • handle the pan only for RightMouseButton - SOLVED
  • handle the Drag only for LeftMouseButton - in development :)
  • allow Pan start when i'm in drag "mode" - this is most the question
  • don't allow to trigger context menu when release RightMenuButton - SOLVED

Sorry for english mistakes it's not my native one, fell free to ask any questions that could solve this issue :)

Update

As for now I end with souch a code for drag:

  var drag = d3.behavior.drag()
  .on("dragstart", function () {

    //d3.event.sourceEvent.stopPropagation();
    d3.select(this).classed("dragging", true);

  }).on("drag", function () {

    var $this = d3.select(this);
    var t = d3.transform($this.attr("transform"));
    t.translate[0] += d3.event.dx;
    t.translate[1] += d3.event.dy;

    $this.attr("transform", "translate(" + t.translate + ")");

  }).on("dragend", function () {

    console.log("dragend");
    d3.select(this).classed("dragging", false);

  });

And souch for zoom:

var zoom = d3.behavior.zoom()
            .scaleExtent([0.1, 2])
            .on("zoomstart", function() {

                if (d3.event.sourceEvent.buttons != 2) {
                    savedTranslation = zoom.translate();
                } else {
                    savedTranslation = null;
                }

            }).on("zoom", function() {

                var translate = d3.event.translate;

                if (d3.event.sourceEvent.buttons != 2) {
                    zoom.translate(savedTranslation);
                    translate = savedTranslation;
                }

                svgContainer.attr("transform", "translate(" + translate + ")scale(" + d3.event.scale + ")");

            }).on("zoomend", function() {

                if (savedTranslation) {
                    zoom.translate(savedTranslation);
                    savedTranslation = null;
                }

            });

The usage of the zoom + prevent context menu popup (also block the double click to zoom as it will be use to popup edit popup modal)

var svg = d3.select("#svg-workspace")
            .call(zoom)
            .on("dblclick.zoom", null)
            .on('contextmenu',function () {
                d3.event.preventDefault();
                return false;
            });

And the attach od D3.JS

d3.select($foreignObject.get(0))
    .call(drag);

The $foreignObject is jQuery grabbed object

And stil the problem is to start panning when dragging.


Solution

  • This is probably really hard to achive in D3JS so I end up with writing some JS code. Really helpfull was this page: http://www.petercollingridge.co.uk/interactive-svg-components/pan-and-zoom-control

        var transMatrix = [1,0,0,1,0,0];
    
    var $svg = $("#svg-workspace");
    
    mapMatrix = $("#svg-workspace-group")[0];
    
    function pan(dx, dy)
    {
      transMatrix[4] += dx;
      transMatrix[5] += dy;
    
      newMatrix = "matrix(" +  transMatrix.join(' ') + ")";
      mapMatrix.setAttributeNS(null, "transform", newMatrix);
    }
    
        function zoom(scale, mousex, mousey)
        {
            for (var i=0; i < transMatrix.length; i++)
            {
                transMatrix[i] *= scale;
            }
    
            transMatrix[4] += (1-scale) * mousex;
            transMatrix[5] += (1-scale) * mousey;
    
            newMatrix = "matrix(" +  transMatrix.join(' ') + ")";
            mapMatrix.setAttributeNS(null, "transform", newMatrix);
    }
    
    $svg.contextmenu(function () {return false;});
    
    var drag = false;
    var distanceX = 0;
    var distanceY = 0;
    
    var initX = 0;
    var initY = 0;
    
    $svg.on("mousedown", function(evt) {
      if(evt.originalEvent.which == 1) {
        drag = true;
        distanceX = 0;
        distanceY = 0;
      }
    });
    
    $svg.on("mouseup", function(evt) {
      if (evt.originalEvent.which == 1) {
        drag = false;
        initX = parseInt($("#dragtest").attr("cx"));
        initY = parseInt($("#dragtest").attr("cy"));
      }
    });
    
    $svg.on("mousemove", function(evt) {
      if( (evt.originalEvent.buttons & 2) == 2) {
        pan(evt.originalEvent.movementX, evt.originalEvent.movementY);
      } else if(drag) {
        distanceX += evt.originalEvent.movementX;
        distanceY += evt.originalEvent.movementY;
    
        $("#dragtest").attr("cx", initX + distanceX / transMatrix[0]);
        $("#dragtest").attr("cy", initY + distanceY / transMatrix[0]);
      }
    });
    
    $svg.on("mousewheel", function(evt) {
      if(evt.originalEvent.deltaY > 0) {
        zoom(0.9, evt.originalEvent.offsetX, evt.originalEvent.offsetY);
      } else if (evt.originalEvent.deltaY < 0) {
        zoom(1.1, evt.originalEvent.offsetX, evt.originalEvent.offsetY);
      }
    });
    

    The corresponding HTML code:

    <svg id="svg-workspace" xmlns="http://www.w3.org/2000/svg" width="900" height="600" viewBox="0 0 900 600" style="display: block; margin: 0 auto; border: 1px solid #ddd;">
      <g id="svg-workspace-group" transform="translate(0,0)scale(1)">
        <circle cx="0" cy="0" r="5" stroke="black" stroke-width="1" fill="red" />
        <circle cx="100" cy="-100" r="5" stroke="black" stroke-width="1" fill="red" />
        <circle cx="100" cy="100" r="5" stroke="black" stroke-width="1" fill="red" />
        <circle cx="-100" cy="100" r="5" stroke="black" stroke-width="1" fill="red" />
        <circle id="dragtest" cx="-100" cy="-100" r="5" stroke="black" stroke-width="1" fill="red" />
      </g>
    </svg>
    

    I found only one issue with this code Dragging is not working properly with zooming, but yea I could live with that ;)