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.
Created
July 6, 2018 12:52
-
-
Save archetana/b11d1a3712c2761f2c45cafd2bcb9b50 to your computer and use it in GitHub Desktop.
Pan and Zoom in jsPlumb Community Edition with Dagre and jQueryUI Draggable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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