Skip to content

Instantly share code, notes, and snippets.

@thudfactor
Last active May 23, 2020 18:46
Show Gist options
  • Save thudfactor/6611441 to your computer and use it in GitHub Desktop.
Save thudfactor/6611441 to your computer and use it in GitHub Desktop.
Drag from HTML, Drop to SVG

This demonstrates dragging from HTML to SVG elements, which can be tricky because of the differences between SVG and HTML DOMs. JQueryUI has a nice API for handling drag and drop, including defining the appearance of a dragged object and providing start, drag, and stop events. Unfortunately, JQueryUI's "drop" does not work at all on SVG elements inside the SVG tag, although it does appear to work on the SVG tag itself.

To get over this limitation, we create a custom Drag/Drop manager, and use the start, drag, and stop events for the dragged elements in conjunction with mouseover and mouseout events on the SVG elements to determine whether or not a "drop" has been dropped on a legal SVG element.

<!DOCTYPE html>
<html>
<head>
<title>D3 Drag Drop Example</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://codeorigin.jquery.com/jquery-1.10.2.min.js"></script>
<script src="http://codeorigin.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
<style>
body {
height: 100%;
text-align: center;
padding: 1rem;
}
.col {
display: inline-block;
width: 200px;
vertical-align: top;
}
ul {
margin: 0; padding: 0;
}
li {
margin: 0 0 .3rem 0;
padding: .3rem;
list-style: none;
background-color: #fff;
background-color: rgba(255,255,255,.8);
font: .8rem/1 arial, helvetica, sans-serif;
border: 1px #7f7f7f solid;
}
li.ui-draggable {
cursor: move;
}
li.ui-state-disabled {
cursor: not-allowed;
opacity: .5;
}
rect {
stroke: #7f7f7f;
stroke-width: 1
}
text {
font-family: arial, helvetica, sans-serif;
font-size: 20px;
fill: #fff;
}
.color1 { fill: #1f77b4; }
.color2 { fill: #2ca02c; }
.color3 { fill: #9467bd; }
</style>
</head>
<body>
<div class="col droptargets"></div>
<div class="col draggables"></div>
<script>
// --
// Setup: Creating the dataset and placing the data in the document.
// --
// Establish our dataset
var dwarfSet = {
Fairytale : ["Blick","Flick","Glick","Quee"],
Tolkien : ["Thorin","Gloin","Fili","Kili"],
Pratchett : ["Carrot","Goodmountain","Littlebottom"]
}
var types = d3.keys(dwarfSet);
var dwarves = d3.shuffle(d3.merge(d3.values(dwarfSet)));
// Droppable items on the right
var draggables = d3.select(".draggables").append("ul");
draggables.selectAll("li").data(dwarves).enter()
.append("li")
.text(function(d) { return d })
// Drop targets on the left
var canvas = d3.select(".droptargets").append("svg")
.attr("width",200)
.attr("height",400)
.attr("class","YlGn");
var dropTargets = canvas.selectAll("rect").data(types).enter().append("g")
dropTargets.append("rect")
.attr({
width: 180,
height: 100,
x: 10.5,
y: function(d,i) { return (i * 110) + .5},
rx: 20,
ry: 20,
class: function(d,i) { return "color" + (i+1) }
})
dropTargets.append("text")
.text(function (d) { return d })
.attr({
x: 25.5,
dy: 30,
y: function(d,i) { return (i * 110) + .5}
});
// ---
// Handle dragging from HTML to dropping on SVG
// ---
var DragDropManager = {
dragged: null,
droppable: null,
draggedMatchesTarget: function() {
if (!this.droppable) return false;
return (dwarfSet[this.droppable].indexOf(this.dragged) >= 0);
}
}
var body = d3.select("body");
// Register the associated element as a potential target on mouseOver
// D3 events call listeners with the current datum
// and index, so this is straightforward.
dropTargets.on('mouseover',function(d,i){
DragDropManager.droppable = d;
});
// Clear the target from the DragDropManager on mouseOut.
dropTargets.on('mouseOut',function(e){
DragDropManager.droppable = null;
});
// Set up jqueryUI's draggable on the list items
//
// Note that we have to move helper out of the way of the cursor
// using the "cursorAt" property; otherwise the cursor will be
// located over the helper and the SVG element's mouseover/mouseout
// events will not fire.
$(".draggables li").draggable({
revert: true,
revertDuration: 200,
cursorAt: { left: -2, top: -2 },
// Register what we're dragging with the drop manager
start: function (e) {
// Getting the datum from the standard event target requires more work.
DragDropManager.dragged = d3.select(e.target).datum();
},
// Set cursors based on matches, prepare for a drop
drag: function (e) {
matches = DragDropManager.draggedMatchesTarget();
body.style("cursor",function() {
return (matches) ? "copy" : "move";
});
// Eliminate the animation on revert for matches.
// We have to set the revert duration here instead of "stop"
// in order to have the change take effect.
$(e.target).draggable("option","revertDuration",(matches) ? 0 : 200)
},
// Handle the end state. For this example, disable correct drops
// then reset the standard cursor.
stop: function (e,ui) {
// Dropped on a non-matching target.
if (!DragDropManager.draggedMatchesTarget()) return;
$(e.target).draggable("disable");
$("body").css("cursor","");
}
});
</script>
</body>
</html>
@jpatel3
Copy link

jpatel3 commented Sep 26, 2014

thanks for the snippet. However, this doesn't work on the mobile devices, tried with touch events similar to mouse events, but no luck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment