Skip to content

Instantly share code, notes, and snippets.

@tophtucker
Last active August 29, 2015 14:19
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 tophtucker/2e72c41ffb8e7ecab8d9 to your computer and use it in GitHub Desktop.
Save tophtucker/2e72c41ffb8e7ecab8d9 to your computer and use it in GitHub Desktop.
Arrow connector helper

It is conceivable that one might want a setup whereby someone can arrange a Web Page such that certain things point to certain other things without writing any javascript or a ton of custom css or whatever. This is a solution.

1. HTML

In the data-arrow-target attribute of an HTML element, provide a CSS selector. If the selector matches more than one element, lines will be drawn from the element to all the matching elements.

E.g.: <div id="one" data-arrow-target="#two"></div>

2. JavaScript

arrowConnector() returns a render function.

// Gets your renderer
var connect = arrowConnector();

// Renders for the first time
connect();

// Render on resize
d3.select(window).on("resize", connect);

3. CSS

Arrows get a class .arrow-connector. Do as you will with it.

The end.

FAQ

  • But there are no arrowheads! Correct. Yet!
  • That's a useless closure. Correct. For now!
function arrowConnector() {
var svg, arrows;
function render() {
if(d3.select(".arrow-connector-container").empty()) {
svg = d3.select("body").append("svg")
.attr("xmlns", "http://www.w3.org/2000/svg")
.classed("arrow-connector-container", true)
.style("position", "absolute")
.style("top", "0")
.style("left", "0")
.style("width", "100%")
.style("height", "100%")
.style("pointer-events", "none");
} else {
svg = d3.select(".arrow-connector-container");
}
arrows = svg.selectAll("line")
.data(getTargets());
arrows.enter()
.append("line")
.classed("arrow-connector", true);
arrows
.attr("x1", function(d) { return d[0].x })
.attr("y1", function(d) { return d[0].y })
.attr("x2", function(d) { return d[1].x })
.attr("y2", function(d) { return d[1].y });
}
function getTargets() {
var targets = [];
d3.selectAll("[data-arrow-target]")
.each(function(d,i) {
fromCorners = edgesToCorners(this);
d3.selectAll(this.dataset.arrowTarget).each(function(dd,ii) {
var toCorners = edgesToCorners(this);
// check all possible combinations of eligible endpoints for the shortest distance
var fromClosest, toClosest, distance;
fromCorners.forEach(function(from) {
toCorners.forEach(function(to) {
if(distance == null || hypotenuse( to.x-from.x, to.y-from.y ) < distance) {
distance = hypotenuse( to.x-from.x, to.y-from.y );
fromClosest = from;
toClosest = to;
}
});
});
targets.push([fromClosest,toClosest]);
});
});
return targets;
}
// gets from the sides of a bounding rect (left, right, top, bottom)
// to its corners (topleft, topright, bottomleft, bottomright)
function edgesToCorners(element) {
var corners = [];
["left","right"].forEach(function(i) { ["top","bottom"].forEach(function(j) { corners.push({"x":i,"y":j}); }); });
return corners.map(function(corner) {
return {
"x": element.getBoundingClientRect()[corner.x] + window.pageXOffset,
"y": element.getBoundingClientRect()[corner.y] + window.pageYOffset
};
});
}
// this seems good to have
function hypotenuse(a, b) {
return Math.sqrt( Math.pow(a,2) + Math.pow(b,2) );
}
return render;
}
<html>
<head>
<style>
.arrow-connector {
stroke: black;
}
#one, #two {
position: absolute;
width: 100px;
height: 100px;
}
#one {
top: 50px;
left: 50px;
background: #f0f;
}
#two {
top: 250px;
left: 500px;
background: #0f0;
}
</style>
</head>
<body>
<div id="one" data-arrow-target="#two"></div>
<div id="two"></div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="arrowConnector.js"></script>
<script type="text/javascript">
var connect = arrowConnector();
connect();
d3.select(window).on("resize", connect);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment