/* requirements: d3, FontAwesome usage example: var radialMenuItems = [ {"label": "A", "action":function(){return alert("hello menu A")}, "faIcon":"share-alt"}, {"label": "B", "action":function(){return alert("hello menu B")}, "faIcon":"line-chart"}, {"label": "C", "action":function(){return alert("hello menu C")}, "faIcon":"credit-card"}, {"label": "D", "action":function(){return alert("hello menu D")}, "faIcon":"arrows-alt"}, {"label": "E", "action":function(){return alert("hello menu E")}, "faIcon":"external-link"}, ] var radialMenuOptions = { "inner": 52, "outer": 66, "padding": 2, "fractionToCover": Math.PI*.75 } d3.selectAll(".node") .radialMenu(radialMenuItems, radialMenuOptions); */ d3.selection.prototype.radialMenu = function(items, options){ /* pass in items in the form: [ {label:"A", faIcon:"font-awesome-icon-name", action:function(){}}, {label:"B", faIcon:"font-awesome-icon-name", action:function(){}} ] pass in options in the form: {"inner":30, "outer": 50, "fractionToCover": 0.6} */ var fullCircle = Math.PI*2; function destroyMenu(){ d3.selectAll(".radial-menu-group").remove(); } //turn off the context menue for all svg elements d3.selectAll("svg").on("contextmenu", function(data, index) { d3.event.preventDefault(); }); var inner = (options.inner == undefined) ? 20 : options.inner; var outer = (options.outer == undefined) ? 40 : options.outer; var padding = (options.padding == undefined) ? fullCircle / 360 * 2 : fullCircle / 360 * options.padding; var startAt = (options.startAt == undefined) ? fullCircle * -0.10 : options.startAt; var fractionToCover = (options.fractionToCover == undefined) ? fullCircle * 0.33 : options.fractionToCover; var trigger = (options.trigger == undefined) ? "contextmenu" : options.trigger; var cursor = (options.cursor == undefined) ? "pointer" : options.cursor; var segmentSize = fractionToCover / items.length; this.on(trigger, function(event){ destroyMenu(); options = (options == undefined) ? {} : options; var menuArc = d3.arc() .startAngle(function(d,i) {return startAt + i * segmentSize + padding; }) .endAngle(function(d,i) { return startAt + (i+1) * segmentSize - padding; }) .innerRadius(inner) .outerRadius(outer) var parent = d3.select(this.parentNode); parent.on("mouseleave", destroyMenu); var menus = parent.selectAll(".radial-menu-group") .data(items).enter(); //this transparent circle catches other clicks menus.append("circle") .attr("class","radial-menu-group") .attr("r", outer * 1.5) .style("opacity",0); menus.append("path") .attr("class","radial-menu-group") .attr("d", menuArc) .style("cursor", cursor) .on("click",function(d){d.action();}) var labels = menus.append("g") .attr("class","radial-menu-group") .attr("transform", function(d, i){ var angle = (startAt + (i+0.5)* segmentSize)*180/Math.PI -90; return "rotate("+ angle +") translate("+((inner + outer)/2)+") " }) .style("cursor", cursor) .on("click",function(d){d.action();}); labels.append("svg:foreignObject") .attr("class", function(d){return "radial-menu-group fa fa-" + d.faIcon}) .attr("transform", "rotate(90)") .attr("x",-5) .attr("y",-5) .attr("width", 50) .attr("height", 50) .html(""); labels.append("text") .attr("class", "radial-menu-group") .attr("dx", outer - (inner + outer)/2 + 5) .attr("dy", 5) .text(function(d){return d.label;}) }); }