Skip to content

Instantly share code, notes, and snippets.



Last active Aug 29, 2015
What would you like to do?
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.


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

// Render on resize"resize", connect);

3. CSS

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

The end.


  • But there are no arrowheads! Correct. Yet!
  • That's a useless closure. Correct. For now!
function arrowConnector() {
var svg, arrows;
function render() {
if(".arrow-connector-container").empty()) {
svg ="body").append("svg")
.attr("xmlns", "")
.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 =".arrow-connector-container");
arrows = svg.selectAll("line")
.classed("arrow-connector", true);
.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 = [];
.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;
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 {
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;
.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;
<div id="one" data-arrow-target="#two"></div>
<div id="two"></div>
<script src="" charset="utf-8"></script>
<script src="arrowConnector.js"></script>
<script type="text/javascript">
var connect = arrowConnector();
connect();"resize", connect);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment