Skip to content Skip to sidebar Skip to footer

Getting The Node Details In The Context Menu In D3

I am trying to get the node details (id attribute) when it is right clicked and the contextmenu function is called. I am able to get the node object using var self = d3.select(this

Solution 1:

You can pass the datum as a parameter of the function called on the contextmenu event:

.on('contextmenu', function(d) { ... }

which allows you to get the id within the function:

console.log(d.id);

.node {
  fill: #000;
}

.cursor {
  fill: green;
  stroke: brown;
  pointer-events: none;
}


.node text {
  pointer-events: none;
  font: 10px sans-serif;
}

path.link {
  fill: none;
  stroke: #666;
  stroke-width: 1.5px;
}



.link {
  fill: none;
  stroke: #666;
  stroke-width: 1.5px;
}

#licensing {
  fill: green;
}

.link.licensing {
  stroke: green;
}

.link.resolved {
  stroke-dasharray: 0,2 1;
}

circle {
  fill: green;
  stroke: red;
  stroke-width: 1.5px;
}

text {
  font: 10px sans-serif;
  pointer-events: none;
  text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
    <script>
    var width = 500,    height = 300;
    var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];

 
 
    var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];


      function reset() {
        }

 
function contextMenu() {
    var height,
        width, 
        margin = 0.1, // fraction of width
        items = [], 
        rescale = false, 
        style = {
            'rect': {
                'mouseout': {
                    'fill': 'rgb(244,244,244)', 
                    'stroke': 'white', 
                    'stroke-width': '1px'
                }, 
                'mouseover': {
                    'fill': 'rgb(200,200,200)'
                }
            }, 
            'text': {
                'fill': 'steelblue', 
                'font-size': '13'
            }
        }; 
    
    function menu(x, y) {
        d3.select('.context-menu').remove();
        scaleItems();

        // Draw the menu
        d3.select('svg')
            .append('g').attr('class', 'context-menu')
            .selectAll('tmp')
            .data(items).enter()
            .append('g').attr('class', 'menu-entry')
            .style({'cursor': 'pointer'})
            .on('mouseover', function(){ 
                d3.select(this).select('rect').style(style.rect.mouseover) })
            .on('mouseout', function(){ 
                d3.select(this).select('rect').style(style.rect.mouseout) });
        
        d3.selectAll('.menu-entry')
            .append('rect')
            .attr('x', x)
            .attr('y', function(d, i){ return y + (i * height); })
            .attr('width', width)
            .attr('height', height)
            .style(style.rect.mouseout);
        
        d3.selectAll('.menu-entry')
            .append('text')
            .text(function(d){ return d; })
            .attr('x', x)
            .attr('y', function(d, i){ return y + (i * height); })
            .attr('dy', height - margin / 2)
            .attr('dx', margin)
            .style(style.text);

        // Other interactions
        d3.select('body')
            .on('click', function() {
                d3.select('.context-menu').remove();
            });

    }
    
    menu.items = function(e) {
        if (!arguments.length) return items;
        for (i in arguments) items.push(arguments[i]);
        rescale = true;
        return menu;
    }

    // Automatically set width, height, and margin;
    function scaleItems() {
        if (rescale) {
            d3.select('svg').selectAll('tmp')
                .data(items).enter()
                .append('text')
                .text(function(d){ return d; })
                .style(style.text)
                .attr('x', -1000)
                .attr('y', -1000)
                .attr('class', 'tmp');
            var z = d3.selectAll('.tmp')[0]
                      .map(function(x){ return x.getBBox(); });
            width = d3.max(z.map(function(x){ return x.width; }));
            margin = margin * width;
            width =  width + 2 * margin;
            height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
            
            // cleanup
            d3.selectAll('.tmp').remove();
            rescale = false;
        }
    }

    return menu;
}


var width = 400,
  height = 200,
    radius = 8;


var map = {}
nodes.forEach(function(d,i){
  map[d.id] = i;
})

links.forEach(function(d) {
  d.source = map[d.source];
  d.target = map[d.target];
})

    var force = d3.layout.force()
    .nodes(d3.values(nodes))
    .links(links)
    .size([width, height])
    .linkDistance(50)
    .charge(-200)
    .on("tick", tick)
    .start();

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);


// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
    .data(["suit", "licensing", "resolved"])
  .enter().append("marker")
    .attr("id", function(d) { return d; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
  .append("path")
    .attr("d", "M0,-5L10,0L0,5");

var path = svg.append("g").selectAll("path")
    .data(force.links())
  .enter().append("path")
    .attr("class", function(d) { return "link " + d.type; })
    .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });

var menu = contextMenu().items('first item', 'second option', 'whatever, man');

var circle = svg.append("g").selectAll("circle")
    .data(force.nodes())
  .enter().append("circle")
    .attr("r", 6)
    .call(force.drag)
    .on('contextmenu', function(d){ 
        d3.event.preventDefault();
        
        var self = d3.select(this);
        var n1=(self[0])[0];
        console.log(d.id);
        
        menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
    });

var text = svg.append("g").selectAll("text")
    .data(force.nodes())
  .enter().append("text")
    .attr("x", 8)
    .attr("y", ".31em")
    .text(function(d) { return d.id; });

var node = svg.selectAll(".node"),
  link = svg.selectAll(".link");

  
function mousedownNode(d, i) {
  nodes.splice(i, 1);
  links = links.filter(function(l) {
    return l.source !== d && l.target !== d;
  });
  d3.event.stopPropagation();

  refresh();
}
  
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
  path.attr("d", linkArc);
  circle.attr("transform", transform);
  text.attr("transform", transform);
}

function linkArc(d) {
  var dx = d.target.x - d.source.x,
      dy = d.target.y - d.source.y,
      dr = Math.sqrt(dx * dx + dy * dy);
  return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}

function transform(d) {
  return "translate(" + d.x + "," + d.y + ")";
}



    </script>

Solution 2:

In addition: For all the ones that want to know how to be able to show node details (for example the node name) in the context menu itself - this is my solution.

  • The node details can be taken from the "data" element

  • The placeholder can be taken from the "d" element

  • The desired information to show in the context menu has to be written into the "text" attribute

    if(d.title == 'ConfigMenuPlaceholder'){ text = 'Config: '+data.name; }

These lines should be written at the following position:

                function createNestedMenu(parent, root, depth = 0) {
                var resolve = function (value) {
                    return utils.toFactory(value).call(root, data, index);
                };

                parent.selectAll('li')
                .data(function (d) {
                        var baseData = depth === 0 ? menuItems : d.children;
                        return resolve(baseData);
                    })
                    .enter()
                    .append('li')
                    .each(function (d) {
                        var elm = this;
                        // get value of each data
                        var isDivider = !!resolve(d.divider);
                        var isDisabled = !!resolve(d.disabled);
                        var hasChildren = !!resolve(d.children);
                        var hasAction = !!d.action;
                        var text = isDivider ? '<hr>' : resolve(d.title);

                        if(d.title == 'ConfigMenuPlaceholder'){
                            text = 'Config: '+data.name;
                        }

                        var listItem = d3.select(this)
                            .classed('is-divider', isDivider)
                            .classed('is-disabled', isDisabled)
                            .classed('is-header', !hasChildren && !hasAction)
                            .classed('is-parent', hasChildren)
                            .html(text)
                            .on('click', function () {
                                // do nothing if disabled or no action
                                if (isDisabled || !hasAction) return;
                                d.action(elm, data, index);
                                //d.action.call(root, data, index);
                                closeMenu();
                            });

                        if (hasChildren) {
                            // create children(`next parent`) and call recursive
                            var children = listItem.append('ul').classed('is-children', true);
                            createNestedMenu(children, root, ++depth)
                        }
                    });
            }

Post a Comment for "Getting The Node Details In The Context Menu In D3"