Skip to content

Instantly share code, notes, and snippets.

@RMKD
Created October 24, 2016 02:56
Show Gist options
  • Save RMKD/7a99f89b2b7a73a33c298c6635fae926 to your computer and use it in GitHub Desktop.
Save RMKD/7a99f89b2b7a73a33c298c6635fae926 to your computer and use it in GitHub Desktop.

Revisions

  1. RMKD created this gist Oct 24, 2016.
    111 changes: 111 additions & 0 deletions d3-radial-menu.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,111 @@
    /*
    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("<i></i>");

    labels.append("text")
    .attr("class", "radial-menu-group")
    .attr("dx", outer - (inner + outer)/2 + 5)
    .attr("dy", 5)
    .text(function(d){return d.label;})
    });
    }