带子菜单的 D3 上下文菜单

D3 Context menu with sub-menu

我正在基于 javascript 带有回调函数的数组创建上下文菜单,但遇到两个问题

  1. 当鼠标移动到没有子节点的元素时无法隐藏子菜单(在删除部分)。

  2. 无法在适当的层次结构中显示子菜单(移动到同级部分后无法返回到子部分的子菜单)。

    我的代码:

d3.contextMenu = function (menu, openCallback) {

 // create the div element that will hold the context menu
 d3.selectAll('.d3-context-menu').data([1])
  .enter()
  .append('div')
  .attr('class', 'd3-context-menu');

 // close menu
 d3.select('body').on('click.d3-context-menu', function() {
  d3.select('.d3-context-menu').style('display', 'none');
 });

 // this gets executed when a contextmenu event occurs
 return function(data, index) { 
  var elm = this;

  d3.selectAll('.d3-context-menu').html('');
  var list = d3.selectAll('.d3-context-menu').append('ul');
         list.selectAll('li').data(menu).enter()
   .append('li')
   .html(function(d) {
    return d.title;
   })
   .on('click', function(d, i) {
    d.onMouseClick(elm, data, index);
   })
            .on('mouseover',function(d,i){
                d.onMouseOver(elm,data,index);
                if(d.chidernItems.length>0 )
                     {
                      d3.select(this).selectAll("ul").remove(); 
                      d3.select(this)
                        .append("ul")
                        .selectAll("li")
                           .data(d.chidernItems)
                            .enter().append("li")
                              .text(function(d) { return d.title; })
                         .on("mouseover", function(d,i){
                                d.onMouseOver(elm,data,index);
                            })
                         .on('click',  function(d, i) {
                                d.onMouseClick(elm, data, index);
                            })
                         .on('mouseout',function(d,i){
                            d3.select(this).remove(); 
                            });
                     }
                 else
                     return false;
              
               
            })
            .on('mouseout',function(d,i){
            if(d.chidernItems.length==0 )
                {
                   d3.select(this).selectAll("ul").remove(); 
                }
                                  
            });
        
          

  // the openCallback allows an action to fire before the menu is displayed
  // an example usage would be closing a tooltip
  if (openCallback) openCallback(data, index);

  // display context menu
  d3.select('.d3-context-menu')
   .style('left', (d3.event.pageX - 2) + 'px')
   .style('top', (d3.event.pageY - 2) + 'px')
   .style('display', 'block');

  d3.event.preventDefault();
 };
};

var menu = [
    {
      title: 'Create Child Section',
      // Exceute Action 
      onMouseClick: function(elm, d, i) {
        console.log('Create Child Section clicked!');
        console.log('The data for this circle is: ' + d);
      },
      onMouseOver: function(elm,d,i){
          console.log('Create Child mouseover  data = ' +d );
      },
      chidernItems: [
           {
              title: 'Vertical',
              // Exceute Action 
              onMouseClick: function(elm, d, i) {
                console.log('Vertical Create Child clicked!');
                console.log('The data for this circle is: ' + d);
              },
              onMouseOver: function(elm,d,i){
                  console.log('Vertical Create Child mouseover  data = ' +d );
              }
           },
           {
              title: 'Horizontal',
              // Exceute Action 
              onMouseClick: function(elm, d, i) {
                console.log('Horizontal Create Child clicked!');
                console.log('The data for this circle is: ' + d);
              },
              onMouseOver: function(elm,d,i){
                  console.log('Horizontal Create Child mouseover  data = ' +d );
              }
           }
      ]
    }, 
    {
      title: 'Create Sibling Section',
       // Exceute Action 
      onMouseClick: function(elm, d, i) {
        console.log('Create Sibling Section clicked!');
        console.log('The data for this circle is: ' + d);
      },
      onMouseOver: function(elm,d,i){
          console.log('Sibling Section mouseover  data = ' +d );
      },
         chidernItems: [
           {
              title: 'Vertical',
              // Exceute Action 
              onMouseClick: function(elm, d, i) {
                console.log('Sibling Vertical clicked!');
                console.log('The data for this circle is: ' + d);
              },
              onMouseOver: function(elm,d,i){
                  console.log('Sibling Vertical mouseover  data = ' +d );
              }
           },
            {
              title: 'Horizontal',
              // Exceute Action 
              onMouseClick: function(elm, d, i) {
                console.log('Horizontal clicked!');
                console.log('The data for this circle is: ' + d);
              },
              onMouseOver: function(elm,d,i){
                  console.log('Horizontal mouseover  data = ' +d );
              }
           }
      ]
        
    },
    {
      title: 'Delete Section',
        
       // Exceute Action 
      onMouseClick: function(elm, d, i) {
        console.log('Delete Section Section clicked!');
        console.log('The data for this circle is: ' + d);
      },
      onMouseOver: function(elm,d,i){
          console.log('Delete Section mouseover  data = ' +d );
      },
           chidernItems: []
    }]

    var data = [1];

    var g = d3.select('body').append('svg')
      .attr('width', 200)
      .attr('height', 400)
      .append('g');

    g.selectAll('circles')
      .data(data)
      .enter()
      .append('circle')
      .attr('r', 30)
      .attr('fill', 'steelblue')
      .attr('cx', function(d) {
        return 100;
      })
      .attr('cy', function(d) {
        return d * 100;
      })
      .on('contextmenu', d3.contextMenu(menu));
.d3-context-menu {
 position: absolute;
 display: none;
 background-color: #f2f2f2;
 border-radius: 4px;
 font-family: Arial, sans-serif;
 font-size: 14px;
 min-width: 150px;
 border: 1px solid #d4d4d4;
 z-index:1200;
}

.d3-context-menu ul {
 list-style-type: none;
 margin: 4px 0px;
 padding: 0px;
 cursor: default;
}

.d3-context-menu ul li {
 padding: 4px 16px;
}

.d3-context-menu ul li:hover {
 background-color: #4677f8;
 color: #fefefe;
}


.d3-context-menu ul li > ul {
    position: absolute; 
    background-color: #f2f2f2; 
    top: 0; 
    left: 175px;
    z-index: -1; 
} 

.d3-context-menu ul li > ul li:hover 
{ 
    background-color: #4677f8;
 color: #fefefe;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.0/d3.min.js"></script>

我在鼠标悬停功能中每次都在删除和创建子菜单,有没有更好的方法?

通过将 'mouseover''mouseout' 函数更改为 'mouseenter''mouseleave' 并在 mouseleave 函数中引入 d3.select(this).selectAll("ul").style('display', 'none'); 来解决问题。