D3.js子映射失败?有上师发现我的错误吗? [英] D3.js child map failure ? Any gurus spot my error?

查看:83
本文介绍了D3.js子映射失败?有上师发现我的错误吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先可以在这里找到代码:工作-几乎-代码

First the code can be found here: working - almost - code

我已经有了一些指示,并且在尝试使其正常工作的过程中学到了不少"的知识.

I've had a few pointers and learnt "quite a bit" along to way trying to get this to work.

基本上,我正在构建或尝试为具有一些基本功能的办公室员工构建一棵层次结构树.

Basically I'm building, or trying to build a hierachy tree for office staff with some basic functions.

除了最后一个问题,一切都进行得很顺利,无论我如何看待它,也看不到它为什么不起作用.

Its all going pretty well except one last issue and no matter how I look at it or approach it, I cannot see why it isnt working.

问题:

如果将鼠标悬停在节点上,则会出现4个小的弹出菜单-绿色和红色的添加和删除节点-可以.

If you hold the mouse over a node, 4 little pop up menus appear - the green and the red add and remove nodes - this works.

画布顶部是一个保存"按钮,我试图遍历所有具有父子关系的节点-再次直到添加一个节点,然后再添加另一个节点,它将看不到新节点的子节点.

At the top of the canvas is a "Save" button, which I'm trying to get to go through all the nodes giving their parent-to-child relationship - again this works until you add a node and then another node, it will not see the a child of a new node.

如果有人知道刷新我在以下代码段中使用的子图"的方法,将不胜感激:

If anyone knows the way to refresh the "child map" that Im using in the snippit below, it would be much appreciated:

d3.selectAll('g.node')
      .each(function(p) {
        p.children.map(function(c) {
          alert(c.name + "(" + c.id + ")" + "- PARENT TO -" + p.name + "(" + 
p.id + ")")
        });
      });

推荐答案

我不知道我是否正确解决了您的问题,也许我的假设完全错了,但是您的数据对我来说似乎很好.当链接的示例在第25行上没有子节点的那些节点单击保存"时,链接的示例将引发错误:

I don't know if I got your problem right, maybe I'm totally wrong with my assumption but your data is seems fine to me. The example you linked will throw an error when clicking on save for those nodes without children on line 25:

p.children.map(function(c) {
  alert(c.name + "(" + c.id + ")" + "- PARENT TO -" + p.name + "(" + p.id + ")")
});

由于在某些情况下p.children未定义(没有),因此循环将中断.

Since in some cases p.children is undefined (there is none) the loop will break.

由于应用程序在视觉上运行,因此没有理由认为数据绑定不正确.可以肯定的是,我写了一个稍微修改过的保存"循环版本.添加一些孩子并检查控制台.

Since the application is working visually, there is no reason to think the data bindings aren't correct. Just to be sure, I wrote a slightly modified version of your "save" loop. Add some children and check the console.

现在,您的问题超出了范围,但下次可能会为您省去一些麻烦:d3只是将数据映射到元素.在创建,销毁和更新内容时,将所有DOM操作留给d3并专注于您的模型.

Now, a little bit out of scope to your question but may save you some headaches the next time: d3 just maps your data to your elements. When creating, destroying and updating stuff, leave all the DOM manipulation to d3 and concentrate on your model.

var diameter = 1000;
var height = diameter - 150;
var n = {
  "name": "A",
  "id": 1,
  "target": 0,
  "children": [{
      "name": "B",
      "id": 2,
      "target": 1,
      "children": [{
        "name": "Cr",
        "id": 8,
        "target": 2,
        "children": [{
          "name": "D",
          "id": 7,
          "target": 2
        }, {
          "name": "E",
          "id": 9,
          "target": 8
        }, {
          "name": "F",
          "id": 10,
          "target": 8
        }]
      }]
    },
    {
      "name": "G",
      "id": 3,
      "target": 0
    }, {
      "name": "H",
      "id": 4,
      "target": 0
    }, {
      "name": "I",
      "id": 5,
      "target": 0
    }, {
      "name": "J",
      "id": 6,
      "target": 0
    }
  ]
}


var tree = d3.layout.tree()
  .size([260, diameter / 2 - 120])
  .separation(function(a, b) {
    return (a.parent == b.parent ? 1 : 2) / a.depth;
  });

var diagonal = d3.svg.diagonal.radial()
  .projection(function(d) {
    return [d.y, d.x / 180 * Math.PI];
  });

var myZoom = d3.behavior.zoom()
  .scaleExtent([.5, 10])
  .on("zoom", zoom);

var container = d3.select("body").append("svg")
  .attr("width", diameter)
  .attr("height", height)
  .style('border', '3px solid black')
  .call(myZoom);


//I am centering my node here
var svg = container.append("g")
  .attr("transform", "translate(" + diameter / 2 + "," + height / 2 + ")");


myZoom.translate([diameter / 2, height / 2]);

var init = true;

function zoom() {
  svg.attr("transform", "translate(" + (d3.event.translate[0]) + "," + (d3.event.translate[1]) + ")scale(" + d3.event.scale + ")");
}

var nodes = tree(n);
//make sure to set the parent x and y for all nodes 

nodes.forEach(function(node) {
  if (node.id == 1) {
    node.px = node.x = 500;
    node.py = node.y = 304;

  } else {
    node.px = node.parent.x;
    node.py = node.parent.y;

  }
});

// Build a array for borken tree case 
var myCords = d3.range(50);
buildSingleTreeData();

var id = ++nodes.length;

function update(root) {

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



  nodes = tree.nodes(root);
  if (checkBrokenTree(root)) {
    if (!root.children || root.children.length == 0) {
      id = 2;
    } else {
      var returnId = resetIds(root, 1);
      id = nodes.length + 1;
    }
    singleNodeBuild(nodes);
  }

  links = tree.links(nodes);




  /*This is a data join on all nodes and links 
  if a node is added a link will also be added 
  they are based parsing of the root*/
  node = node.data(nodes, function(d) {
    return d.id;
  });
  link = link.data(links, function(d) {
    return d.source.id + "-" + d.target.id;
  });



  var enterNodes = node.enter().append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      d.tx = (d.parent ? d.parent.x : d.px) - 90;
      return "rotate(" + ((d.parent ? d.parent.x : d.px) - 90) +
        ")translate(" + d.py + ")";
    })


  enterNodes.append('g')
    .attr('class', 'label')
    .attr('transform', function(d) {
      return 'rotate(' + -d.px + ')';
    })
    .append('text')
    .attr("dx", "-1.6em")
    .attr("dy", "2.5em")
    .text(function(d) {
      return d.name;
    })
    .call(make_editable, function(d) {
      return d.name;
    });


  var circlesGroup = enterNodes.append('g')
    .attr('class', 'circles');

  var mainCircles = circlesGroup.append("circle")
    .attr('class', 'main')
    .attr("r", 9);

  circlesGroup.append("circle")
    .attr('class', 'delete')
    .attr('cx', 0)
    .attr('cy', 0)
    .attr('fill', 'red')
    .attr('opacity', 0.5)
    .attr("r", 0);

  circlesGroup.append("circle")
    .attr('class', 'add')
    .attr('cx', 0)
    .attr('cy', 0)
    .attr('fill', 'green')
    .attr('opacity', 0.5)
    .attr("r", 0);

  circlesGroup.append("circle")
    .attr('class', 'admin')
    .attr('cx', 0)
    .attr('cy', 0)
    .attr('fill', 'blue')
    .attr('opacity', 0.5)
    .attr("r", 0);

  circlesGroup.append("circle")
    .attr('class', 'userid')
    .attr('cx', 0)
    .attr('cy', 0)
    .attr('fill', 'yellow')
    .attr('opacity', 0.5)
    .attr("r", 0);



  circlesGroup.on("mouseenter", function() {
    var elem = this.__data__;
    elem1 = d3.selectAll(".delete").filter(function(d, i) {
      return elem.id == d.id ? this : null;
    });
    elem2 = d3.selectAll(".add").filter(function(d, i) {
      return elem.id == d.id ? this : null;
    });
    elem3 = d3.selectAll(".admin").filter(function(d, i) {
      return elem.id == d.id ? this : null;
    });
    elem4 = d3.selectAll(".userid").filter(function(d, i) {
      return elem.id == d.id ? this : null;
    });


    elem2.transition()
      .duration(duration)
      .attr('cx', -20)
      .attr('cy', 0)
      .attr("r", 8);

    elem1.transition()
      .duration(duration)
      .attr('cx', 20)
      .attr('cy', 0)
      .attr("r", 8);

    elem3.transition()
      .duration(duration)
      .attr('cx', -10)
      .attr('cy', -20)
      .attr("r", 8);

    elem4.transition()
      .duration(duration)
      .attr('cx', 10)
      .attr('cy', -20)
      .attr("r", 8);


  });




  circlesGroup.on("mouseleave", function() {
    var elem = this.__data__;
    elem1 = d3.selectAll(".delete").filter(function(d, i) {
      return elem.id == d.id ? this : null;
    });
    elem2 = d3.selectAll(".add").filter(function(d, i) {
      return elem.id == d.id ? this : null;
    });
    elem3 = d3.selectAll(".admin").filter(function(d, i) {
      return elem.id == d.id ? this : null;
    });


    elem2.transition()
      .duration(duration)
      .attr('cy', 0)
      .attr('cx', 0)
      .attr("r", 0);

    elem1.transition()
      .duration(duration)
      .attr('cy', 0)
      .attr('cx', 0)
      .attr("r", 0);

    elem3.transition()
      .duration(duration)
      .attr('cy', 0)
      .attr('cx', 0)
      .attr("r", 0);

    elem4.transition()
      .duration(duration)
      .attr('cy', 0)
      .attr('cx', 0)
      .attr("r", 0);

  });

  var linkEnter = link.enter()
    .insert("path", '.node')
    .attr("class", "link")
    .attr("d", function(d) {
      var o = {
        x: d.source.px,
        y: d.source.py
      };
      return diagonal({
        source: o,
        target: o
      });
    });



  // UserID node event handeler 
  node.select('.userid').on('click', function() {
    var p = this.__data__;
    alert(p.name + " Userid (" + p.username + ")" + "-->" + p.id + "<--" + p.children);

  });


  // Admin node event handeler 
  node.select('.admin').on('click', function() {
    var p = this.__data__;
    alert(p.name + " password is (" + p.pword + ")");

  });

  // Delete node event handeler 

  node.select('.delete').on('click', function() {
    var p = this.__data__;







    if (p.id != 1) {
      removeNode(p);
      var childArr = p.parent.children;
      childArr = childArr.splice(childArr.indexOf(p), 1);
      update(n);
    }

    function removeNode(p) {
      if (!p.children) {
        if (p.id) {
          p.id = null;
        }
        return p;
      } else {
        for (var i = 0; i < p.children.length; i++) {
          p.children[i].id == null;
          removeNode(p.children[i]);
        }
        p.children = null;
        return p;
      }
    }

    node.exit().remove();
    link.exit().remove();
    // alertify.alert(p.name + " has left the building..");






  });


  // The add node even handeler 
  node.select('.add').on('click', function() {
    var p = this.__data__;
    var aId = id++;
    var d = {
      name: 'name' + aId
    };
    d.id = aId;

    if (p.children) {
      p.children.push(d);
      //top node add
    } else {
      p.children = [d];
      //child of child 
    }

    d.px = p.x;
    d.py = p.x;
    d3.event.preventDefault();



    update(n)
    node.exit().remove();
    link.exit().remove();
  });




  /* this is the update section of the graph and nodes will be updated to their current positions*/

  var duration = 700;


  node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      d.utx = (d.x - 90);
      return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
    })

  link.transition()
    .duration(duration).attr("d", diagonal);

  node.select('g')
    .transition()
    .duration(duration)
    .attr('transform', function(d) {
      return 'rotate(' + -d.utx + ')';
    });

  node.select('.circles').attr('transform', function(d) {
    return 'rotate(' + -d.utx + ')';
  });
  node.exit().remove();
  link.exit().remove();

}

update(n);

/** make a manual tree for when it is just 
  a linked list. For some reason the algorithm will break down
  all nodes in tree only have one child.
*/
function buildSingleTreeData() {
  myCords = d3.range(50);
  var offset = 130;
  myCords = myCords.map(function(n, i) {
    return {
      x: 90,
      y: offset * i
    };
  });

}


/**
  This function will build single node tree where every node
  has 1 child. From testing this layout does not support 
  a layout for nodes tree less than size 3 so they must 
  be manually drawn. Also if evey node has one child then 
  the tree will also break down and as a result this fucntion
  is there to manually build a singe tree up to 50 nodes
*/
function resetIds(aNode, aId) {
  if (aNode.children) {
    for (var i = 0; i < aNode.children.length; i++) {
      aNode.children[i].id = ++aId;
      resetIds(aNode.children[i], aId);
    }
    return aId;
  }

}

/*
builds a liner tree D3 does not support this
and so it must be hard coded
*/
function singleNodeBuild(nodes) {
  nodes.forEach(function(elem) {
    var i = elem.id - 1;
    elem.x = myCords[i].x;
    elem.y = myCords[i].y;
  });
}

/* D3 does not support operations 
on where root nodes does not have atlest 
2 children. this case need to be check for
and hard coded
*/
function checkBrokenTree(rootNode) {
  var size = nodes.length;

  var val = 0;

  function recur(nod, i) {
    if (nod.children) {
      return recur(nod.children[0], i + 1);
    } else {
      return i + 1;
    }
  }
  return recur(rootNode, val) == nodes.length;
}

/*
Credit https://gist.github.com/GerHobbelt/2653660
This funciton make a text node editable 
*/
function make_editable(d, field) {
  this
    .on("mouseover", function() {
      d3.select(this).style("fill", "red");
    })
    .on("mouseout", function() {
      d3.select(this).style("fill", null);
    })
    .on("click", function(d) {

      var p = this.parentNode;

      //console.log(this, arguments);


      // inject a HTML form to edit the content here...

      // bug in the getBBox logic here, but don't know what I've done wrong here;
      // anyhow, the coordinates are completely off & wrong. :-((
      var xy = this.getBBox();
      var p_xy = p.getBBox();

      xy.x -= p_xy.x;
      xy.y -= p_xy.y;

      var el = d3.select(this);
      var p_el = d3.select(p);

      var frm = p_el.append("foreignObject");

      var inp = frm
        .attr("x", xy.x - 40)
        .attr("y", xy.y + 40)
        .attr("dx", "2em")
        .attr("dy", "-3em")
        .attr("width", 100)
        .attr("height", 25)
        .append("xhtml:form")
        .append("input")
        .attr("value", function() {
          // nasty spot to place this call, but here we are sure that the <input> tag is available
          // and is handily pointed at by 'this':
          this.focus();
          //console.log( d);

          return d.name;
        })
        .attr({
          maxlength: 16
        })
        .style({
          width: "100px"
        })
        // make the form go away when you jump out (form looses focus) or hit ENTER:
        .on("blur", function() {
          //console.log("blur", this, arguments);

          var txt = inp.node().value;

          d.name = txt;
          if (txt) {
            el
              .text(function(d) {
                return d.name;
              });
          }
          // Note to self: frm.remove() will remove the entire <g> group! Remember the D3 selection logic!
          p_el.select("foreignObject").remove();
        })
        .on("keypress", function() {
          // console.log("keypress", this, arguments);

          // IE fix
          if (!d3.event)
            d3.event = window.event;

          var e = d3.event;
          if (e.keyCode == 13) {
            if (typeof(e.cancelBubble) !== 'undefined') // IE
              e.cancelBubble = true;
            if (e.stopPropagation)
              e.stopPropagation();
            e.preventDefault();

            var txt = inp.node().value;
            if (txt) {

              d.name = txt;
              el
                .text(function(d) {
                  return d.name;
                });
            }
            // odd. Should work in Safari, but the debugger crashes on this instead.
            // Anyway, it SHOULD be here and it doesn't hurt otherwise.
            p_el.select("foreignObject").remove();

          }
        });
    });
}

.node .main {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

h1 {
  text-align: center;
}

.node .delete {
  stroke-width: 1.5px;
}

.node {
  font: 10px sans-serif;
}

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

svg {
  margin-left: auto;
  margin-right: auto;
  display: block;
}

text {
  font: 10px "Helvetica Neue", Helvetica, Arial, sans-serif;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

<html lang="en">

  <head>
    <meta charset="utf-8">
    <title> Staff</title>




  </head>
  <h1> STAFF </h1>
  <input type="button" id="button" value="Save" />

  <body style="text-align:center">



    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>

    <script>
      var onClick = function() {

        d3.selectAll('g.node')
          .each(function(p) {
          	if (p.children) {
            	console.log(`node ${p.name} has ${p.children.length} children: `, p.children.map(child => child.name));
              [...p.children].forEach((child) => {
                console.log(`${child.name} is child of ${child.parent.name}`);
              });
            } else {
            	console.log(`node ${p.name} has no children`);
            }
            
            console.log('----------------')
          });

      };

      $('#button').click(onClick);

    </script>

  </body>

  </head>

这篇关于D3.js子映射失败?有上师发现我的错误吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆