Skip to content

Instantly share code, notes, and snippets.

@tlfrd
Last active August 3, 2017 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tlfrd/cc7a8aa688217dc1d502e7a92943950e to your computer and use it in GitHub Desktop.
Save tlfrd/cc7a8aa688217dc1d502e7a92943950e to your computer and use it in GitHub Desktop.
Draggable Connected Dot Plot
license: mit

Learning how to use d3-drag for an interactive "Make a guess" connected dot plot. Dragging is limited to the x-axis and the ends of the line cannot cross over (i.e. their positions cannot be switched). Clicking reset returns the dots to their original positions.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.dot {
fill: white;
stroke: black;
}
.active {
stroke: red;
}
body {
font-family: monospace;
}
a {
position: absolute;
top: 20px;
left: 20px;
}
</style>
</head>
<body>
<a href="#" class="reset-btn">Reset</a>
<script>
var margin = {top: 0, right: 0, bottom: 0, left: 0};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var radius = 20,
strokewidth = 5;
var te = d3.easeCubic;
var dots = [
{
type: "min",
colour: "white",
x: width / 4,
y: height / 2
},
{
type: "median",
colour: "grey",
x: width / 2,
y: height / 2
},
{
type: "max",
colour: "black",
x: width * 3 / 4,
y: height / 2
}];
var dotsoriginal = JSON.parse(JSON.stringify(dots));
var dragbehaviour = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
var labels = svg.selectAll("text")
.data(dots)
.enter().append("text")
.attr("x", function(d) { return d.x })
.attr("y", function(d) {
return height / 2 + (d.type == "min" || d.type == "max" ? 60 : -50)
})
.attr("text-anchor", "middle")
.text(function(d) { return "(" + d.x + ", " + d.y + ")" });
var lineGenerator = d3.line()
.x(function(d) { return d.x + (d.type == "min" ? radius : -radius)})
.y(function(d) { return d.y });
var pathString = function() { return lineGenerator(dots) };
var line = svg.append("path")
.attr("class", "line")
.attr("d", pathString)
.attr("stroke", "black")
.style("stroke-width", strokewidth);
var circles = svg.selectAll("circle")
.data(dots)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.style("fill", function(d) { return d.colour })
.style("stroke-width", strokewidth)
.call(dragbehaviour);
function dragstarted(d) {
d3.select(this)
.classed("active", true);
}
function dragged(d) {
var minX, maxX;
if (d.type == "min") {
minX = radius + strokewidth;
maxX = dots[1].x - (radius * 2) - strokewidth;
} else if (d.type == "median") {
minX = dots[0].x + (radius * 2) + strokewidth;
maxX = dots[2].x - (radius * 2) - strokewidth;
} else {
minX = dots[1].x + (radius * 2) + strokewidth;
maxX = width - radius - strokewidth;
}
d3.select(this)
.attr("cx", d.x = Math.max(minX, Math.min(maxX, d3.event.x)));
labels
.attr("x", function(d) { return d.x })
.text(function(d) { return "(" + d.x + ", " + d.y + ")" });
line.attr("d", pathString);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
d3.select(".reset-btn")
.on("click", function(d) {
d3.event.preventDefault();
dots = dotsoriginal;
dotsoriginal = JSON.parse(JSON.stringify(dots));
line.transition()
.duration(500)
.ease(te)
.attr("d", pathString);
circles.data(dots).transition()
.duration(500)
.ease(te)
.attr("cx", function(d) { return d.x; });
labels.data(dots).transition()
.duration(500)
.ease(te)
.attr("x", function(d) { return d.x; })
.text(function(d) { return "(" + d.x + ", " + d.y + ")" });
});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment