单击向下箭头时显示特定节点的下拉列表

时间:2018-05-17 00:24:14

标签: javascript html d3.js svg

我想显示节点的名称以及像素材一样的下拉菜单中的编辑,删除等操作。我在每个叶节点旁边都包含了下拉图标,以便在单击时显示包含该特定节点名称的下拉列表并进行编辑,删除列表中的文本(如下拉列表功能)。单击该向下箭头图标时,我无法显示下拉列表。我该怎么做?

此代码显示叶节点名称

每一侧的向下箭头
node
  .append("foreignObject")
  .attr("width", "30")
  .attr("height", "30")
  .html(d => {
    if (d.parent && d.parent.parent) {
      return `<i class="dropdown-trigger material-icons" data-target=${d.data.name}>arrow_drop_down</i>
      <ul class="dropdown-content" id=${d.data.name}>
        <li>${d.data.name}</li>
        <li>delete</li>
      </ul>
                `;
    }
  })
  .attr("transform", d => {
    if (d.parent && d.parent.parent) {
      return pos === "left" ? "translate(-20,-5)" : "translate(10,-5)";
    }
  });

但下拉项目未显示。

&#13;
&#13;
var data = {
  "name": "root@gmail.com",
  "children": [{
    "topic_id": 31572,
    "name": "Person Name 1",
    "children": [{
        "topic_id": 31573,
        "name": "Branch 4.1"
      }, {
        "topic_id": 31574,
        "name": "Branch 4.2"
      }, {
        "topic_id": 31575,
        "name": "Branch 4.2"
      },
      {
        "topic_id": 31576,
        "name": "Branch 4.2"
      }, {
        "topic_id": 31577,
        "name": "Branch 4.2"
      },
      {
        "topic_id": 31578,
        "name": "Branch 4.2"
      }
    ]
  }, {
    "topic_id": 32572,
    "name": "Person name 2",
    "children": [{
        "topic_id": 33572,
        "name": "Branch 4.1"
      }, {
        "topic_id": 34572,
        "name": "Branch 4.2"
      }, {
        "topic_id": 35572,
        "name": "Branch 4.2"
      },
      {
        "topic_id": 36572,
        "name": "Branch 4.2"
      }, {
        "topic_id": 37572,
        "name": "Branch 4.2"
      },
      {
        "topic_id": 38572,
        "name": "Branch 4.2"
      }
    ]
  }, {
    "topic_id": 41572,
    "name": "Person Name 3",
    "children": [{
        "topic_id": 51572,
        "name": "Branch 4.1"
      }, {
        "topic_id": 61572,
        "name": "Branch 4.2"
      }, {
        "topic_id": 71572,
        "name": "Branch 4.2"
      },
      {
        "topic_id": 81572,
        "name": "Branch 4.2"
      }, {
        "topic_id": 91572,
        "name": "Branch 4.2"
      },
      {
        "topic_id": 92572,
        "name": "Branch 4.2"
      }
    ]
  }, {
    "name": "Person Name 4",
    "children": [{
        "name": "Branch 4.1"
      }, {
        "name": "Branch 4.2"
      }, {
        "name": "Branch 4.2"
      },
      {
        "name": "Branch 4.2"
      }, {
        "name": "Branch 4.2"
      },
      {
        "name": "Branch 4.2"
      }
    ]
  }]
};


const LAST_CHILDREN_WIDTH = 13;
let flagForChildren = false;
let groups = [];
data.children.forEach(d => {
  let a = [];
  if (d.children.length > 0) {
    flagForChildren = true;
  }
  for (let i = 0; i < d.children.length; i += 2) {
    let b = d.children.slice(i, i + 2);
    if (b[0] && b[1]) {
      a.push(Object.assign(b[0], {
        children: [b[1]]
      }));
    } else {
      let child = b[0];
      if (i >= 6) {
        child = Object.assign(child, {
          children: [{
            name: "..."
          }]
        });
      }
      a.push(child);
    }
  }
  d.children = a;
  groups.push(d);
});

data.children = groups;
let split_index = Math.round(data.children.length / 2);
let rectangleHeight = 45;
let leftData = {
  name: data.name,
  children: JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
let leftDataArray = [];
leftDataArray.push(leftData);

// Right data
let rightData = {
  name: data.name,
  children: JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
let right = d3.hierarchy(rightData);
let left = d3.hierarchy(leftData);
// Render both trees
drawTree(right, "right");
drawTree(left, "left");

// draw single tree
function drawTree(root, pos) {
  let SWITCH_CONST = 1;
  if (pos === "left") {
    SWITCH_CONST = -1;
  }
  const margin = {
      top: 20,
      right: 120,
      bottom: 20,
      left: 120
    },
    width = window.innerWidth - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  let svg = d3
    .select("svg")
    .attr("height", height + margin.top + margin.bottom)
    .attr("width", width + margin.right + margin.left)
    .attr('view-box', '0 0 ' + (width + margin.right) + ' ' + (height + margin.top + margin.bottom))
    .style("margin-top", "20px")
    .style("margin-left", "88px");

  const myTool = d3.select("body").append("div")
    .attr("class", "mytooltip")
    .style("opacity", "0")
    .style("display", "none");;


  // Shift the entire tree by half it's width
  let g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");

  let deductWidthValue = flagForChildren ? 0 : width * 0.33;
  // Create new default tree layout
  let tree = d3
    .tree()
    // Set the size
    // Remember the tree is rotated
    // so the height is used as the width
    // and the width as the height
    .size([height - 50, SWITCH_CONST * (width - deductWidthValue) / 2])
    .separation((a, b) => a.parent === b.parent ? 4 : 4.25);

  tree(root);

  let nodes = root.descendants();
  let links = root.links();
  // Set both root nodes to be dead center vertically
  nodes[0].x = height / 2;

  // Create links
  let link = g
    .selectAll(".link")
    .data(links)
    .enter();

  link
    .append("line")
    .attr("class", function(d) {
      if (d.target.depth === 2) {
        return 'link'
      } else {
        return 'hard--link'
      }
    })
    .attr("x1", function(d) {
      if (
        d.target.depth === 3
      ) {
        return 0;
      }
      return d.source.y + 100 / 2; //d.source.y + 100/2
    })
    .attr("x2", function(d) {
      if (
        d.target.depth === 3
      ) {
        return 0;
      } else if (d.target.depth === 2) {
        return d.target.y;
      }
      return d.target.y + 100 / 2; //d.target.y + 100/2;
    })
    .attr("y1", function(d) {
      if (
        d.target.depth === 3
      ) {
        return 0;
      }
      return d.source.x + 50 / 2;
    })
    .attr("y2", function(d) {
      if (
        d.target.depth === 3
      ) {
        return 0;
      } else if (d.target.depth === 2) {
        return d.target.x + LAST_CHILDREN_WIDTH / 2;
      }
      return d.target.x + 50 / 2;
    });

  //Rectangle width

  let node = g
    .selectAll(".node")
    .data(nodes)
    .enter()
    .append("g")
    .on("mouseover", function(d) {})
    .attr("class", function(d) {
      return "node" + (d.children ? " node--internal" : " node--leaf");
    })
    .attr("transform", function(d) {
      if (d.parent && d.parent.parent) { // this is the leaf node
        if (d.parent.parent.parent) {
          return (
            "translate(" +
            d.parent.y +
            "," +
            (d.x + LAST_CHILDREN_WIDTH + 15) +
            ")"
          );
        }
        return "translate(" + d.y + "," + d.x + ")";
      }
      return "translate(" + d.y + "," + d.x + ")";
    });

  // topic rect
  node
    .append("rect")
    .attr("height", (d, i) => d.parent && d.parent.parent ? 15 : rectangleHeight)
    .attr("width", (d, i) => d.parent && d.parent.parent ? 15 : rectangleWidth(d))
    .attr("rx", (d, i) => d.parent && d.parent.parent ? 5 : 5)
    .attr("ry", (d, i) => d.parent && d.parent.parent ? 5 : 5)

  // topic edges
  node.append('line')
    .attr('x1', d => {
      if (d.depth === 2) {
        return 10
      }
    })
    .attr('x2', d => {
      if (d.depth === 2) {
        return 10
      }
    })
    .attr('y1', d => {
      if (d.depth === 2) {
        if (d.children) {
          return 0;
        }
        return 40;
      }
    })
    .attr('y2', d => {
      if (d.depth === 2) {
        return 40
      }
    })
    .attr('class', 'hard--link')

  // topic names
  node
    .append("text")
    .attr("dy", function(d, i) {
      return d.parent && d.parent.parent ? 10 : rectangleHeight / 2;
    })
    .attr("dx", function(d, i) {
      if (!(d.parent && d.parent.parent)) {
        return 12;
      } else {
        return 20;
      }
    })
    .style("fill", function(d, i) {
      return d.parent && d.parent.parent ? "Black" : "White";
    })
    .text(function(d) {
      let name = d.data.topic_name || d.data.name;
      return name.length > 12 ? `${name.substring(0, 12)}...` : name;
    })
    .style("text-anchor", function(d) {
      if (d.parent && d.parent.parent) {
        return pos === "left" && "end"
      }
    })
    .style("font-size", "12")
    .attr("transform", function(d) {
      if (d.parent && d.parent.parent) {
        return pos === "left" ? "translate(-30,0)" : "translate(5,0)"
      }
    })
  node
    .append("foreignObject")
    .attr("width", "30")
    .attr("height", "30")
    .html(d => {
      if (d.parent && d.parent.parent) {
        return `<i class="dropdown-trigger material-icons" data-target=${d.data.name}>arrow_drop_down</i>
          <ul class="dropdown-content" id=${d.data.topic_id}>
            <li class="topic-name-btn">${d.data.name}</li>
            <li class="action" id="action-delete">delete</li>
          </ul>
        `;
      }
    })
    .attr("transform", d => {
      if (d.parent && d.parent.parent) {
        return pos === "left" ? "translate(-20,-5)" : "translate(10,-5)";
      }
    }).on('click', showDropdown)

  function showDropdown(el) {
    const selectedDOMId = document.getElementById(el.data.topic_id);
    const show = el.data.topic_id && selectedDOMId.classList.toggle("show");
    if (show !== undefined) {
      console.log('selectedDOMId', selectedDOMId);
      selectedDOMId.style.left = (d3.event.pageX - 113) + "px"
      selectedDOMId.style.left = (d3.event.pageY - 50) + "px"
    }
  }

}

function rectangleWidth(d) {
  const MIN_WIDTH = 50;
  const MAX_WIDTH = 100;
  let dynamicLength = 6;
  if (d.data.topic_name) {
    dynamicLength = d.data.topic_name.length;
  } else if (d.data.name) {
    dynamicLength = d.data.name.length;
  }
  dynamicLength = dynamicLength < 3 ? MIN_WIDTH : MAX_WIDTH;
  return dynamicLength;
}
&#13;
.show {
  display: block !important;
}

.dropdown-content {
  display: none;
  /* position: absolute; */
  background-color: #f1f1f1;
  padding: 0;
  margin-top: -25px;
  margin-left: 33px;
  min-width: 100px;
  overflow: auto;
  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
  z-index: 1;
}

.dropdown-content li {
  list-style: none;
}

.dropdown-trigger {
  cursor: pointer;
}

li.topic-name-btn {
  background: #159c11;
  color: #fff;
  height: 25px;
  vertical-align: middle;
  padding-top: 5px;
}
&#13;
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>

</head>

<body>
  <svg className='spider-graph-svg'>
      </svg>
</body>

</html>
&#13;
&#13;
&#13;

如何在不使用jquery的情况下显示单击向下箭头的特定节点中的下拉列表?

更新了代码但尚未正常工作

我现在可以显示下拉列表,但如果我单击其中一个主题,则会显示下拉列表,如果再次关闭该下拉列表,则单击另一个节点,将显示两个节点的下拉列表。如何在单击另一个节点时关闭其他先前的下拉列表,以便仅显示当前节点下拉列表?

1 个答案:

答案 0 :(得分:0)

您可以将一个HTML Select元素附加到foreignObject,并且可以使用D3数据连接来实现,例如

 var svg = d3.select("body").append("svg")
        .attr("width", 200)
        .attr("height", 100);
  
      var data = ["Node1"]
  
      var node = svg.selectAll(".node")
        .data(data)
        .enter()
        .append("g")
        .attr("transform", "translate(50,50)")
    
    node.append("circle")
        .attr("cx", 0)
        .attr("cy", 0)
        .attr("r", 10)
        .on("click", function(d){
  	         let div = d3.select(this.parentNode).select("div")
  	         let currentValue = div.style("display")
             let newValue = currentValue == "none" ? "block" : "none"
             div.style("display", newValue)
	     })

    var nodeSelect = node.append("foreignObject")
        .attr("width", 100)
        .attr("height", 100)
        .append("xhtml:body")
        .append("div")
            .append("select")
    
    nodeSelect.selectAll("option")
        .data(function(d){
              let options = [d,"delete","edit"]
          return options
            })
        .enter()
        .append("option")
        .attr("value", function(d){ return d })
        .text(function(d){ return d })
<!DOCTYPE html>
<html>
<head>
    <script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
</body>
</html>