Skip to content

Instantly share code, notes, and snippets.

@tomkelly000
Last active December 20, 2015 09:48
Show Gist options
  • Save tomkelly000/6110163 to your computer and use it in GitHub Desktop.
Save tomkelly000/6110163 to your computer and use it in GitHub Desktop.
d3-dropdownmenu
/*-------------------------------------------------------------------*/
/* When the script is imported it extends the d3 library */
/* to include d3.element.dropdownmenu */
/*-------------------------------------------------------------------*/
(function() {
d3.element = {}; // initialize the namespace
var root; // make a closure around root
/*-------------------------------------------------------------------*/
/* Helper methods */
/* Local to this self-calling anonymous function */
/* Don't pollute global namespace */
/*-------------------------------------------------------------------*/
function styleUL(ul) {
ul
.style('position', 'absolute')
.style('list-style', 'none')
.style('padding', '0')
.style('left', '100%')
.style('top', '0%')
.style('display', 'none')
}
function styleLI(li) {
li
.style('position', 'relative')
.style('white-space', 'nowrap')
}
function setHandlersForLI(li) {
/* namespaced so d3 won't remove them later */
li.on('mouseover.d3-dropdownmenu', function() {
d3.select(this).select('ul')
.style('display', 'block');
})
li.on('mouseout.d3-dropdownmenu', function() {
d3.select(this).select('ul')
.style('display', 'none');
})
}
/*-------------------------------------------------------------------*/
function toLink(selection) {
/* tree traversal methods */
selection.root = function() {
return root;
}
selection.nodes = function() {
return this.selectAll('li').each(function() {
toNode(d3.select(this))
})
}
selection.links = function() {
return this.selectAll('ul').each(function() {
toLink(d3.select(this))
})
}
selection.childNodes = function() {
return d3.selectAll(this.childNodes)
.each(function() {
toNode(d3.select(this))
});
}
selection.firstChildNode = function() {
return toNode(d3.select(this.node().firstChild));
}
selection.lastChildNode = function() {
return toNode(d3.select(this.node().lastChild));
}
selection.parentNode = function() {
return toNode(d3.select(this.node().parentNode));
}
/* end of tree traversal methods */
/*---------------------------------------------------------------*/
selection.horizontal = function() {
d3.selectAll(this.node().childNodes)
.style('float', 'left')
.select('ul') // have to shift child list over
.style('left', '0%')
.style('top', '100%')
return toLink(d3.select(this))
};
return selection;
}
function toNode(selection) {
selection.add = function(data) {
var ul = this.select('ul');
if (ul.empty()) {
ul = this
.append('ul')
.call(styleUL);
}
// parses tree recursively
(function parseTree(selection, tree) {
for (var attrname in tree) {
selection.append('li').datum(attrname)
.html(function(d) { return d; })
.call(styleLI)
.call(setHandlersForLI)
// so as not to waste space on null terminators
.call(function(selection) {
if (tree[attrname]) {
selection.append('ul')
.call(styleUL)
.call(parseTree, tree[attrname]);
}
});
}
})(ul, data);
return this; // for method chaining
};
/* tree traversal methods */
selection.root = function() {
return root;
};
selection.nodes = function() {
return this.selectAll('li').each(function() {
toNode(d3.select(this))
});
};
selection.links = function() {
return this.selectAll('ul').each(function() {
toLink(d3.select(this))
});
};
selection.childNodes = function() {
var ul = this.select('ul').node();
if (ul) {
return d3.selectAll(ul.childNodes)
.each(function() {
toNode(d3.select(this));
});
} else {
return null;
}
};
selection.firstChildNode = function() {
var ul = this.select('ul').node();
if (ul) {
return toNode(d3.select(ul.firstChild));
} else {
return null;
}
};
selection.lastChildNode = function() {
var ul = this.select('ul').node();
if (ul) {
return toNode(d3.select(ul.lastChild));
} else {
return null;
}
};
selection.childLink = function() {
var ul = this.select('ul');
if (ul) {
return toLink(ul);
} else {
return null;
}
};
selection.parentLink = function() {
return toLink(d3.select(this.node().parentNode));
};
selection.parentNode = function() {
if (this === root) {
return null;
} else {
return toNode(d3.select(this.node().parentNode.parentNode));
}
}
selection.nextSiblingNode = function() {
return toNode(d3.select(this.node().nextSibling))
};
selection.previousSiblingNode = function() {
return toNode(d3.select(this.node().previousSibling));
};
selection.prevSiblingNode = selection.previousSiblingNode;
return selection;
}
/*-------------------------------------------------------------------*/
d3.element.dropdownmenu = function(container) { // returns a menu
root = toNode(d3.select(container));
root.show = function() {
this.select('ul')
.style('display', 'block') // make it visible
.style('left', 'auto').style('top', 'auto')
return this;
}
return root;
};
})();
<!doctype html>
<html>
<head>
<title>
d3-dropdownmenu
</title>
<!-- dependencies -->
<script src='http://d3js.org/d3.v3.min.js'></script>
<script src='d3-dropdownmenu.js'></script>
<script>
function applyStyling(root) {
root.nodes()
.style('border', '1px solid #ddd')
.style('padding', '4px 8px')
.style('background', '#eee')
.style('width', '100px')
.on('mouseenter', function() {
d3.select(this).transition() // 'this' is a DOM element
.style('background', '#ee3')
})
.on('mouseleave', function() {
d3.select(this).transition()
.style('background', '#eee')
})
root.childNodes() // make top level a little different
.style('background', '#ccc')
.style('width', '75px')
.on('mouseenter', function() {
d3.select(this).transition()
.style('background', '#cc3')
})
.on('mouseleave', function() {
d3.select(this).transition()
.style('background', '#ccc')
})
}
</script>
</head>
<body>
<div id='demo'></div>
<script>
d3.element.dropdownmenu('#demo')
.add({
'Option 1' : {
'0' : {
'a' : {
'i' : null,
},
},
},
'Option 2' : {
'0' : null,
'1' : null,
},
'Option 3' : {
'0' : null,
'1' : null,
'<span id="option3-2">2</span>' : null,
},
'Option 4' : {
'0' : {
'a' : null,
'b' : null,
'c' : null,
},
'1' : null,
'2' : null,
'3' : null,
},
'<a href="#">Test Link</a>' : {
'<a href="#">Child Link</a>' : null,
},
'Images' : {
'<img src="img1.jpg" width="100px" height="100px"/>' : null,
'<img src="img2.jpg" width="100px" height="100px"/>' : null,
'<img src="img3.jpg" width="100px" height="100px"/>' : null,
}
})
.show() // basic menu has been created
// example of tree traversal
.firstChildNode() // Option 1
.nextSiblingNode() // Option 2
.lastChildNode() // 1
.add({
'New Option 1' : null,
'New Option 2' : null,
})
.add({
'New Option 3' : null,
})
.lastChildNode()
.add({
'Option 2.1.3.A' : null,
'Option 2.1.3.B' : null,
})
.root() // get back to the root
// and use d3 methods as well
.call(function(root) {
root.select('#option3-2').style('color', 'blue')
})
.call(applyStyling) // defined in header
.call(function(root) {
root.childLink().horizontal() // make top level horizontal
})
.call(function(root) {
var option = root.firstChildNode();
for (var i = 0; option != null; option = option.firstChildNode()) {
option.style('color', '#0' + (i%10) + (i%10));
i+=3;
}
})
// center it
.call(function(root) {
root.style('position', 'relative')
.style('left', '50%')
.style('margin-left', -parseInt(root.childLink().style('width'))/2 + 'px');
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment