D3.js ошибка дочерней карты?Любые гуру заметили мою ошибку? - PullRequest
0 голосов
/ 27 февраля 2019

Сначала код можно найти здесь: работает - почти - код

У меня было несколько указателей и я научился «совсем немного», пытаясь заставить егоработа.

По сути, я строю или пытаюсь построить иерархическое дерево для офисного персонала с некоторыми базовыми функциями.

Все идет довольно хорошо, за исключением одного последнего вопроса и независимо от того, как я выгляжуЯ не могу понять, почему он не работает.

Проблема:

Если навести курсор мыши на узел, появится 4 маленьких всплывающих меню -зеленый и красный добавить и удалить узлы - это работает.

В верхней части холста находится кнопка «Сохранить», которую я пытаюсь пролистать через все узлы, отдавая их родителю.-детское отношение - опять это работает до тех пор, пока вы не добавите узел, а затем другой узел, он не увидит потомка нового узла.

Если кто-нибудь знает способ обновления "дочерняя карта ", которую я использую в приведенном ниже фрагменте кода, будет очень цениться:

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

1 Ответ

0 голосов
/ 27 февраля 2019

Я не знаю, правильно ли я поняла вашу проблему, возможно, я полностью ошибаюсь в своем предположении, но ваши данные мне подходят.Приведенный вами пример выдаст ошибку при нажатии кнопки сохранения для тех узлов без дочерних элементов в строке 25:

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

Поскольку в некоторых случаях p.children не определен (его нет), цикл прерывается.

Поскольку приложение работает визуально, нет оснований полагать, что привязки данных неверны.Просто чтобы быть уверенным, я написал слегка модифицированную версию вашего цикла сохранения.Добавьте несколько детей и проверьте консоль.

Теперь, немного выходя за рамки вашего вопроса, но может в следующий раз избавить вас от головной боли: d3 просто отображает ваши данные в ваши элементы.При создании, уничтожении и обновлении вещей оставьте все манипуляции с DOM d3 и сконцентрируйтесь на своей модели.

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>
...