javascript - 悬停在节点上时显示节点内的复选框

标签 javascript html d3.js svg

我想在悬停在节点上时显示复选框,以便在单击该复选框时,该节点的名称应显示在蜘蛛图的顶部,如果未选中,则该名称应该消失。

我使用鼠标悬停事件来显示复选框,但不起作用

.on("mouseover", function(d) {
          d3.select(this).transition()
            .duration(200)
            .style('cursor', 'pointer')
            .html('<input type="checkbox" name="name" class="checkbox" />')
        })

完整代码和演示

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <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>
  <script>
    var data = {
      "name": "root@gmail.com",
      "children": [{
        "name": "Person Name 1",
        "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"
          }
        ]
      }, {
        "name": "Person name 2",
        "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"
          }
        ]
      }, {
        "name": "Person Name 3",
        "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"
          }
        ]
      }, {
        "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) {
          d3.select(this).transition()
            .duration(200)
            .style('cursor', 'pointer')
            .html('<input type="checkbox" name="name" class="checkbox" />')
        })
        .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)"
          }
        })
    }

    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;
    }
  </script>
</body>

</html>

最佳答案

由于 input 元素不是 svg 元素,您不能像 html 元素那样简单地插入一个复选框并使其工作。我们必须使用 foreignObject 并在其中附加 input

一旦你创建了那些小的 rects,我们需要附加一个包含我们的复选框的 foreignObject 并像这样隐藏它们:

// 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)

node.append('foreignObject').attr('width', '40')
    .attr('height', '40').append('xhtml:input')
    .attr('type', 'checkbox').style('display', 'none')
    // An on click function for the checkboxes
    .on("click",function(d){
        console.log(d)
    });

当用户将鼠标悬停在 rects 上时,我们可以简单地取消隐藏复选框,如果未选中则再次隐藏它们(否则继续显示直到用户取消选中):

.on("mouseover", function(d) {
  d3.select(this).select('input').style('display', '');
})
.on('mouseout', function(d) {
  if (!d3.select(this).select('input').property('checked')) {
    d3.select(this).select('input').style('display', 'none');
  }
});

完整代码如下:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <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>
  <script>
    var prevElem;
    var data = {
      "name": "root@gmail.com",
      "children": [{
        "name": "Person Name 1",
        "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"
          }
        ]
      }, {
        "name": "Person name 2",
        "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"
          }
        ]
      }, {
        "name": "Person Name 3",
        "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"
          }
        ]
      }, {
        "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) {
          d3.select(this).select('input').style('display', '')
        })

        .on('mouseout', function(d) {
          if (!d3.select(this).select('input').property('checked')) {
            d3.select(this).select('input').style('display', 'none')
          }
        })
        .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)


      // Create foreign object checkboxes and keep them hidden
      node.append('foreignObject').attr('width', '40').attr('height', '40').append('xhtml:input').attr('type', 'checkbox').style('display', 'none')

      // 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)"
          }
        })
    }

    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;
    }
  </script>
</body>

</html>

我相信您可以从这里完成剩下的工作。

关于javascript - 悬停在节点上时显示节点内的复选框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50251227/

相关文章:

javascript - 使用 API key 加载 D3 Url

JavaScript - 将时间戳转换为日期,然后转换回时间戳

javascript - BigCommerce -- 将全局变量分配为 javascript 变量

javascript - 像饼图这样的 css 样式不适用于 jquery append

javascript - 对 d3.js 版本 3 的选择感到困惑

javascript - d3js v5 + Topojson v3 单击图例显示/隐藏元素

javascript - Node : Save generated Stripe "Customer ID" in variable

javascript - 错误: 'CanvasElement' is undefined

javascript - 特定用途的javascript解析器

javascript - <Span> 未在 <p> 内显示