See the Pen Pan and Zoom in jsPlumb Community Edition with Dagre and jQueryUI Draggable by Yuri Gor (@yurigor) on CodePen.0
Chart with draggable HTML elements as nodes, connected by jsPlumb library.
“Pan&Zoom” feature missing in Comunity Edition implemented by using “jQuery Panzoom” plugin.
Nodes dragging implemented by jQueryUI Draggable, to compensate scale distortion.
Dagre layout library used for demonstration.
I use static predefined html in this example:
<!-- .container - just part of your page,
where you want to render diagram -->
<div class="container">
<!-- .panzoom - wrapper div, panzoom plugin will transform it.
Use it for worksheet element styling -->
<div class="panzoom">
<!-- .diagram - wrapper div to be used by jsPlumb.
It will have zero height, so no visual CSS works here. -->
<div class="diagram">
<!-- .item - diagram nodes, must have unique id's
to be able connect them by jsPlumb. -->
<div id="i0" class="item">Root</div>
<div id="i1" class="item">Child 1</div>
<div id="i11" class="item">Child 1.1</div>
<div id="i12" class="item">Child 1.2</div>
<div id="i2" class="item">Child 2</div>
<div id="i21" class="item">Child 2.1</div>
<div id="i3" class="item">Child 3</div>
</div>
</div>
</div>
Links between nodes declared in js array:
var links = [
{ from: "i0", to: "i1" },
{ from: "i1", to: "i11" },
{ from: "i1", to: "i12" },
{ from: "i0", to: "i2" },
{ from: "i2", to: "i21" },
{ from: "i0", to: "i3" },
];
Initializing of panzoom:
$panzoom = $container.find('.panzoom').panzoom({
minScale: minScale,//0.4
maxScale: maxScale,//2
increment: incScale,//0.1
cursor: "",/*empty string prevents panzoom
from changing cursor styles defined in your css.*/
}).on("panzoomstart",function(e,pz,ev){
$panzoom.css("cursor","move");//set "move" cursor on start only
})
.on("panzoomend",function(e,pz){
$panzoom.css("cursor","");//restore cursor
});
Mouse wheel support and pan while drag begins outside diagram:
$panzoom.parent()
.on('mousewheel.focal', function( e ) {
//if Control pressed then zoom
if(e.ctrlKey||e.originalEvent.ctrlKey)
{
e.preventDefault();
var delta = e.delta || e.originalEvent.wheelDelta;
var zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
$panzoom.panzoom('zoom', zoomOut, {
animate: true,
exponential: false,
});
}else{//else pan (touchpad and Shift key works)
e.preventDefault();
var deltaY = e.deltaY || e.originalEvent.wheelDeltaY || (-e.originalEvent.deltaY);
var deltaX = e.deltaX || e.originalEvent.wheelDeltaX || (-e.originalEvent.deltaX);
$panzoom.panzoom("pan",deltaX/2,deltaY/2,{
animate: true,
relative: true,
});
}
})
//on start store initial offsets and mouse coord
.on("mousedown touchstart",function(ev){
var matrix = $container.find(".panzoom").panzoom("getMatrix");
var offsetX = matrix[4];
var offsetY = matrix[5];
var dragstart = {x:ev.pageX,y:ev.pageY,dx:offsetX,dy:offsetY};
$(ev.target).css("cursor","move");
$(this).data('dragstart', dragstart);
})
//calculate mouse offset from starting pos and apply it to panzoom matrix
.on("mousemove touchmove", function(ev){
var dragstart = $(this).data('dragstart');
if(dragstart)
{
var deltaX = dragstart.x-ev.pageX;
var deltaY = dragstart.y-ev.pageY;
var matrix = $container.find(".panzoom").panzoom("getMatrix");
matrix[4] = parseInt(dragstart.dx)-deltaX;
matrix[5] = parseInt(dragstart.dy)-deltaY;
$container.find(".panzoom").panzoom("setMatrix",matrix);
}
})
.on("mouseup touchend touchcancel", function(ev){
$(this).data('dragstart',null);
$(ev.target).css("cursor","");
});
});
Make nodes draggable by jQueryUI/draggable:
var currentScale = 1;
$container.find(".diagram .item").draggable({
start: function(e){
var pz = $container.find(".panzoom");
//save current scale factor to consider it later
currentScale = pz.panzoom("getMatrix")[0];
$(this).css("cursor","move");
//disable panzoom, to avoid panning while dragging node
pz.panzoom("disable");
},
drag:function(e,ui){
/*compensate current scale while dragging,
else pointer and node will have different speeds*/
ui.position.left = ui.position.left/currentScale;
ui.position.top = ui.position.top/currentScale;
//it's possible to have not connected nodes, so let's check it.
if($(this).hasClass("jsplumb-connected"))
{
plumb.repaint($(this).attr('id'),ui.position);
}
},
stop: function(e,ui){
var nodeId = $(this).attr('id');
if($(this).hasClass("jsplumb-connected"))
{
plumb.repaint(nodeId,ui.position);
}
$(this).css("cursor","");
$container.find(".panzoom").panzoom("enable");
}
});
Good job!
So useful
hi,this is really good job you have done。
actually i have used jsplumb in React, SO, how can i use this in my react code?
thanks for what you have done
Hey Yuri,
Was wondering, is the issue with child nodes not being movable still a problem natively in JqueryUI?
Hi Daniel, looks like no.
But it’s not a problem of Jquery, it’s a panzoom issue
In this demo I use my own fork
https://github.com/YuriGor/jquery.panzoom/tree/ignoreChildrensEvents
Look this discussion for details
https://github.com/timmywil/jquery.panzoom/issues/299
I have pretty much replicated your code in my own project for this, and I used your forked repo, but I still cant seem to click on child elements of my panzoom container. All clicks in the panzoom container are read as clicks on the panzoom container.
Oh my god I hadn’t committed the changes in my code editor. It works! You’re a freaking genius!
Thanks for the code, nice work 🙂
I had one issue when using Shift+Mouse wheel to pan left/right. After adding the following code it works 🙂
.on(‘mousewheel.focal’, function( e ) {
if(e.ctrlKey||e.originalEvent.ctrlKey)
{
e.preventDefault();
var delta = e.delta || e.originalEvent.wheelDelta;
var zoomOut = delta ? delta 0;
$panzoom.panzoom(‘zoom’, zoomOut, {
animate: true,
exponential: false,
});
}else if(e.shiftKey||e.originalEvent.shiftKey){
e.preventDefault();
var deltaY = e.deltaY || e.originalEvent.wheelDeltaY || (-e.originalEvent.deltaY);
$panzoom.panzoom(“pan”,deltaY/2,0,{
animate: true,
relative: true,
});
}else{
e.preventDefault();
var deltaY = e.deltaY || e.originalEvent.wheelDeltaY || (-e.originalEvent.deltaY);
var deltaX = e.deltaX || e.originalEvent.wheelDeltaX || (-e.originalEvent.deltaX);
$panzoom.panzoom(“pan”,deltaX/2,deltaY/2,{
animate: true,
relative: true,
});
}
})