Skip to content

Instantly share code, notes, and snippets.

@archetana
Created July 6, 2018 12:52
Show Gist options
  • Save archetana/b11d1a3712c2761f2c45cafd2bcb9b50 to your computer and use it in GitHub Desktop.
Save archetana/b11d1a3712c2761f2c45cafd2bcb9b50 to your computer and use it in GitHub Desktop.
Pan and Zoom in jsPlumb Community Edition with Dagre and jQueryUI Draggable
<div class="container">
<div class="panzoom">
<div class="diagram">
<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 class="hint">
<h4>Hint</h4>
<ul>
<li>Drag and drop empty space to pan</li>
<li>Nodes are also draggable</li>
<li>Mouse wheel to pan up/down</li>
<li>Shift+Mouse wheel to pan left/right</li>
<li>Ctrl+Mouse wheel to zoom in/out</li>
</ul>
<h4>Used</h4>
<ul>
<li><a href="https://jsplumbtoolkit.com/community/doc/home.html">jsPlumb Community Edition</a></li>
<li><a href="https://github.com/timmywil/jquery.panzoom">jQuery Panzoom plugin</a><br>My <a href="https://github.com/YuriGor/jquery.panzoom/tree/ignoreChildrensEvents">branch</a> used, to avoid children's events conflict, see discussion <a href="https://github.com/timmywil/jquery.panzoom/issues/299">here</a> for details.</li>
<li><a href="https://github.com/cpettitt/dagre">Dagre for initial auto-layout</a></li>
<li><a href="https://jqueryui.com/draggable/">jQueryUI Draggable for dragging nodes</a></li>
</ul>
<a target="_blank" href="http://yurigor.com/pan-zoom-jsplumb-dagrejs-jqueryui-draggable/">See my blog for details</a>
</div>
</div>
</div>

Pan and Zoom in jsPlumb Community Edition with Dagre and jQueryUI Draggable

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.

A Pen by archetana on CodePen.

License.

var minScale = 0.4;
var maxScale = 2;
var incScale = 0.1;
var plumb = null;
var $container = $(".container");
$diagram = $container.find(".diagram");
var $panzoom = null;
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" },
];
jsPlumb.ready(function() {
plumb = jsPlumb.getInstance({
PaintStyle: { strokeWidth: 1 },
Anchors: [["Left","Right","Bottom"], ["Top","Bottom"]],
Container: $diagram,
});
_.each(links,function(link){
plumb.connect({
source:link.from,
target:link.to,
connector: [ "Flowchart",
{
cornerRadius: 3,
stub:16
}
],
endpoints:["Blank","Blank"],
overlays:[["Arrow",{location:1,width:10, length:10}]],
});
});
var dg = new dagre.graphlib.Graph();
dg.setGraph({nodesep:30,ranksep:30,marginx:50,marginy:50});
dg.setDefaultEdgeLabel(function() { return {}; });
$container.find(".item").each(
function(idx, node) {
var $n = $(node);
var box = {
width : Math.round($n.outerWidth()),
height : Math.round($n.outerHeight())
};
dg.setNode($n.attr('id'), box);
}
);
plumb.getAllConnections()
.forEach(function(edge) {dg.setEdge(edge.source.id,edge.target.id);});
dagre.layout(dg);
var graphInfo = dg.graph();
dg.nodes().forEach(
function(n) {
var node = dg.node(n);
var top = Math.round(node.y-node.height/2)+'px';
var left = Math.round(node.x-node.width/2)+'px';
$('#' + n).css({left:left,top:top});
});
plumb.repaintEverything();
_.defer(function(){
$panzoom = $container.find('.panzoom').panzoom({
minScale: minScale,
maxScale: maxScale,
increment: incScale,
cursor: "",
ignoreChildrensEvents:true,
}).on("panzoomstart",function(e,pz,ev){
$panzoom.css("cursor","move");
})
.on("panzoomend",function(e,pz){
$panzoom.css("cursor","");
});
$panzoom.parent()
.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 : e.originalEvent.deltaY > 0;
$panzoom.panzoom('zoom', zoomOut, {
animate: true,
exponential: false,
});
}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,
});
}
})
.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);
})
.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","");
});
});
var currentScale = 1;
$container.find(".diagram .item").draggable({
start: function(e){
var pz = $container.find(".panzoom");
currentScale = pz.panzoom("getMatrix")[0];
$(this).css("cursor","move");
pz.panzoom("disable");
},
drag:function(e,ui){
ui.position.left = ui.position.left/currentScale;
ui.position.top = ui.position.top/currentScale;
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");
}
});
});
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsPlumb/2.0.7/jsPlumb.min.js"></script>
<script src="https://cdn.rawgit.com/cpettitt/dagre/e66c29b8/dist/dagre.min.js"></script>
<script src="https://cdn.rawgit.com/YuriGor/jquery.panzoom/ignoreChildrensEvents/dist/jquery.panzoom.min.js"></script>
$EntityTypeColor: #00BFFF;
.container {
background-color: black;
color: $EntityTypeColor;
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
}
.panzoom {
position: absolute;
width:500px;
height:250px;
background-color: rgba(blue,.1);
left: 50px;
top: 20px;
overflow: visible;
border: 5px dotted rgba(#CCC,.1);
}
.diagram {
position: absolute;
}
.item {
cursor: pointer;
position: absolute;
overflow: hidden;
white-space:nowrap;
border: 1px solid darken($EntityTypeColor,30%);
padding: 8px;
border-radius: 3px;
background-color: rgba($EntityTypeColor, .2);
&:hover {
border: 1px solid $EntityTypeColor;
}
}
.jsplumb-connector path { stroke:$EntityTypeColor;
}
.hint {
position: absolute;
color: grey;
left:100%;
top: -24px;
white-space:nowrap;
font-size:14px;
h4 {
margin-left:15px;
}
a {
margin-left:25px;
color: red;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
li a {
margin-left:0;
color: $EntityTypeColor;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment