使用节点编辑V3至V5迁移D3.js拖放式可折叠树

时间:2019-02-19 08:37:00

标签: d3.js

我正在尝试将此v3项目迁移到v5: http://bl.ocks.org/stevenZlai/cf14ba9b6372bddd2b4661beb95fbee1

我的代码示例在这里 https://codepen.io/sancelot/pen/daajJr

为了拥有一个有效的版本,我面临许多问题:

  • 文本节点未显示。 -正在工作
  • 在圆上,不会出现路径templink。 正在工作
  • 移动节点时,父链接已删除,但不再显示正在工作
  • 移动节点时,子代不会移动正在工作
  • 将一个节点移动到另一个节点时,链接不会出现

主要代码:

 var data = {
  "name":"Goal: Become a Pilot",
  "nodeNo": 0,
  "value": 1,
  "status": "In",
  "type": "MainNode",
  "mainRoot": "Root1",
  "nodeBefore": "null",
  "linkWidth": 0,
  "children": [{
      "name": "Node1",
      "nodeNo": 1,
      "value": 5,
      "status": "In",
      "type": "Small",
      "mainRoot": "Root1",
      "nodeBefore": "Node1",
      "linkWidth": 10
      }, {
      "name": "Node2",
      "nodeNo": 2,
      "value": 10,
      "status": "In",
      "type": "Medium",
      "mainRoot": "Root1",
      "nodeBefore": "Node2",
      "linkWidth": 15,
      "children" : [{
          "name" : "Node2.1",
      },
      {
        "name" : "Node2.2",
      },
    ]

      }, {
      "name": "Node3",
      "nodeNo": 3,
      "value": 5,
      "status": "Out",
      "type": "Large",
      "mainRoot": "Root1",
      "nodeBefore": "Node3",
      "linkWidth": 50
      }
  ]
}   
    // dragging variables
    var panBoundary = 20; // Within 20px from edges will pan when dragging.
    var panSpeed = 200;
    // Misc. variables
    var i = 0;
    var duration = 750;
    // Calculate total nodes, max label length
    var totalNodes = 0;
    var maxLabelLength = 0;
// size of the diagram
var viewerWidth = 400;
var viewerHeight = 400;

console.log(viewerWidth, viewerHeight);

var treeLayout = d3.tree()
        .size([viewerWidth-20, viewerHeight-20]);
var root = d3.hierarchy(data,function(d) { return d.children; });
console.log("tree is ",root);
treeLayout(root);// Nodes


/* Obsolete, remplacé par link linkRadial

    // define a d3 diagonal projection for use by the node paths later on.
    var diagonal = d3.svg.diagonal()
        .projection(function(d) {
            return [d.y, d.x];
        });
*/

// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(source, target) {

    path = `M ${source.y} ${source.x}
            C ${(source.y + target.y) / 2} ${source.x},
              ${(source.y + target.y) / 2} ${target.x},
              ${target.y} ${target.x}`

    return path
  }

// A recursive helper function for performing some setup by walking through all nodes

function visit(parent, visitFn, childrenFn) {
  if (!parent) return;

  visitFn(parent);

  var children = childrenFn(parent);
  if (children) {
      var count = children.length;
      for (var i = 0; i < count; i++) {
          visit(children[i], visitFn, childrenFn);
      }
  }
}

// Call visit function to establish maxLabelLength
visit(data, function(d) {
  totalNodes++;
  maxLabelLength = Math.max(d.name.length, maxLabelLength);

}, function(d) {
  return d.children && d.children.length > 0 ? d.children : null;
});

//retourne les transformations x,y d un element
//https://stackoverflow.com/questions/38224875/replacing-d3-transform-in-d3-v4
function getTranslation(transform) {
    // Create a dummy g for calculation purposes only. This will never
    // be appended to the DOM and will be discarded once this function 
    // returns.
    var g = document.createElementNS("http://www.w3.org/2000/svg", "g");

    // Set the transform attribute to the provided string value.
    g.setAttributeNS(null, "transform", transform);

    // consolidate the SVGTransformList containing all transformations
    // to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
    // its SVGMatrix. 
    var matrix = g.transform.baseVal.consolidate().matrix;

    // As per definition values e and f are the ones for the translation.
    return [matrix.e, matrix.f];
    }

// TODO: Pan function, can be better implemented.

function pan(domNode, direction) {
    var speed = panSpeed;
    if (panTimer) {
        clearTimeout(panTimer);
        translateCoords = getTranslation(svgGroup.attr("transform"));
        if (direction == 'left' || direction == 'right') {
            translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
            translateY = translateCoords.translate[1];
        } else if (direction == 'up' || direction == 'down') {
            translateX = translateCoords.translate[0];
            translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
        }
        scaleX = translateCoords.scale[0];
        scaleY = translateCoords.scale[1];
        scale = zoomListener.scale();
        svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
        d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
        zoomListener.scale(zoomListener.scale());
        zoomListener.translate([translateX, translateY]);
        panTimer = setTimeout(function() {
            pan(domNode, speed, direction);
        }, 50);
    }
}
    /*** ************ ZOOM  ******/
    // https://github.com/d3/d3-zoom
    function zoom() {
      baseSvg.attr("transform", "translate(" + d3.event.transform.x+","+d3.event.transform.y + ") scale(" + d3.event.transform.k + ")");
  }

  // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
  var zoomListener = d3.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);

/*******************************/


/*************  DRAG  ***********/
    // variables for drag/drop
    var selectedNode = null;
    var draggingNode = null;
    var dragStarted = false;

    function initiateDrag(d, domNode) {
      console.log('initiatedrah');
      draggingNode = d;
      d3.select(domNode).select('.ghostCircle').attr('pointer-events', 'none');
      d3.selectAll('.ghostCircle').attr('class', 'ghostCircle show');
      d3.select(domNode).attr('class', 'node activeDrag');

      svgGroup.selectAll("g.node").sort(function(a, b) { // select the parent and sort the path's
          if (a.id != draggingNode.id) return 1; // a is not the hovered element, send "a" to the back
          else return -1; // a is the hovered element, bring "a" to the front
      });
      // if nodes has children, remove the links and nodes
      if (nodes)
        if (nodes.length > 1) {
            // remove link paths
            links = root.links(nodes);
            var nodePaths = svgGroup.selectAll("path.link")
                .data(links, function(d) {
                    return d.target.id;
                }).remove();
            // remove child nodes
            var nodesExit = svgGroup.selectAll("g.node")
                .data(nodes, function(d) {
                    return d.id;
                }).filter(function(d, i) {
                    if (d.id == draggingNode.id) {
                        return false;
                    }
                    return true;
                }).remove();
        }

      // remove parent link
      parentLink = root.links(draggingNode.parent);
      svgGroup.selectAll('path.link').filter(function(d, i) {
          if (d.target.id == draggingNode.id) {
              return true;
          }
          return false;
      }).remove();

      dragStarted = null;
  }
// Define the drag listeners for drag/drop behaviour of nodes.
dragListener = d3.drag()
    .on("start", function(d) {
        console.log("dragstart");
        if (d == root) {
            return;
        }
        dragStarted = true;
        console.log("drag data : ",d.data);
        console.log(d.children);
        // A QUOI CA SERT ?????  :
        // nodes = root.data.nodes(d);
        nodes = d.children;
        d3.event.sourceEvent.stopPropagation();
        // it's important that we suppress the mouseover event on the node being dragged. Otherwise it will absorb the 
        //mouseover event and the underlying node will not detect it d3.select(this).attr('pointer-events', 'none');
    })
    .on("drag", function(d) {
        console.log("drag");
        if (d == root) {
            return;
        }

        if (dragStarted) {
            domNode = this;
            initiateDrag(d, domNode);
        }
        console.log("drag()");
        // get coords of mouseEvent relative to svg container to allow for panning
        relCoords = d3.mouse($('svg').get(0));
        if (relCoords[0] < panBoundary) {
            panTimer = true;
            pan(this, 'left');
        } else if (relCoords[0] > ($('svg').width() - panBoundary)) {

            panTimer = true;
            pan(this, 'right');
        } else if (relCoords[1] < panBoundary) {
            panTimer = true;
            pan(this, 'up');
        } else if (relCoords[1] > ($('svg').height() - panBoundary)) {
            panTimer = true;
            pan(this, 'down');
        } else {
            try {
                clearTimeout(panTimer);
            } catch (e) {

            }
        }

        d.x0 += d3.event.dy;
        d.y0 += d3.event.dx;
        var node = d3.select(this);
        node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")");
        updateTempConnector();
    }).on("end", function(d) {
        console.log("drag end");
        if (d == root) {
            return;
        }
        domNode = this;
        console.log("selectedNode ",selectedNode);
        if (selectedNode) {
            // now remove the element from the parent, and insert it into the new elements children
            var index = draggingNode.parent.children.indexOf(draggingNode);
            console.log("index us ",index);
            if (index > -1) {
                draggingNode.parent.children.splice(index, 1);
            }

            if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') {
                if (typeof selectedNode.children !== 'undefined') {
                    selectedNode.children.push(draggingNode);
                } else {
                    selectedNode._children.push(draggingNode);
                }
            } else {
                selectedNode.children = [];
                selectedNode.children.push(draggingNode);
            }
            // Make sure that the node being added to is expanded so user can see added node is correctly moved
            expand(selectedNode);
          //  sortTree();
            endDrag();
        } else {
            endDrag();
        }
    });

function endDrag() {
    console.log("enddrag()");
    selectedNode = null;
    d3.selectAll('.ghostCircle').attr('class', 'ghostCircle');
    d3.select(domNode).attr('class', 'node');
    // now restore the mouseover event or we won't be able to drag a 2nd time
    d3.select(domNode).select('.ghostCircle').attr('pointer-events', '');
    updateTempConnector();
    if (draggingNode !== null) {
        update(root);
      //  centerNode(draggingNode);
        draggingNode = null;
    }
}

function expand(d) {
    if (d._children) {
        d.children = d._children;
        d.children.forEach(expand);
        d._children = null;
    }
}

var overCircle = function(d) {
    selectedNode = d;
    updateTempConnector();
};
var outCircle = function(d) {
    selectedNode = null;
    updateTempConnector();
};

// Function to update the temporary connector indicating dragging affiliation
var updateTempConnector = function() {
    var data = [];
    if (draggingNode !== null && selectedNode !== null) {
        console.log("update Temp Connector draggingNode && selectedNode ok");
        console.log(selectedNode,draggingNode);
        // have to flip the source coordinates since we did this for the existing connectors on the original tree
        data = [{
            source: {
                'x': selectedNode.x0,
                'y': selectedNode.y0
            },
            target: {
                'x': draggingNode.x0,
                'y': draggingNode.y0
            }
        }];
    }
//    console.log("data is ",data);
    console.log("v3",data);
    var link_ = svgGroup.selectAll("path.templink").data(data);
    console.log("link is",link_);

    var linkEnter = link_.enter().append("path")
        .attr("class", "templink")
        .attr("d", function(d) { console.log("ENTER ",d);return diagonal(d.source,d.target);})
        .attr('pointer-events', 'none');

    link_.attr("d", function(d) { console.log("UPD ",d);return diagonal(d.source,d.target);})
    link_.exit().remove();
};

/********************************/
    // define the baseSvg, attaching a class for styling and the zoomListener
var baseSvg = d3.select("#tree-container").append("svg")
        .attr("width", viewerWidth)
        .attr("height", viewerHeight)
        .attr("class", "overlay")
        .call(zoomListener);



  // Toggle children function

  function toggleChildren(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else if (d._children) {
        d.children = d._children;
        d._children = null;
    }
    return d;
}

// Toggle children on click.

function click(d) {
    console.log("clicked") ;
    if (d3.event.defaultPrevented) return; // click suppressed
    d = toggleChildren(d);
    update(d);
    //centerNode(d);
}

/* =================== */
function update(source) {
  console.log("update() source is s",source);
  // Compute the new height, function counts total children of root node and sets tree height accordingly.
  // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
  // This makes the layout more consistent.
    var levelWidth = [1];
    var childCount = function(level, n) {
        if (n.children && n.children.length > 0) {
            if (levelWidth.length <= level + 1) levelWidth.push(0);
            levelWidth[level + 1] += n.children.length;
            n.children.forEach(function(d) {
                childCount(level + 1, d);
            });
        }
    };
    console.log("l302",levelWidth);
    childCount(0, root);
    var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line  
    treeLayout = treeLayout.size([newHeight, viewerWidth]);

     // Assigns the x and y position for the nodes
    var treeData = treeLayout(root);
    // Compute the new tree layout.
    var nodes = root.descendants(root).reverse(),
        links = root.links(nodes); 
  /*  var nodes = treeData.descendants(),
        links = treeData.descendants().slice(1); */

        console.log("maxLabelLength", maxLabelLength);
    // Set widths between levels based on maxLabelLength.
    nodes.forEach(function(d) {
        d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
        // alternatively to keep a fixed scale one can set a fixed depth per level
        // Normalize for fixed-depth by commenting out below line
        // d.y = (d.depth * 500); //500px per level.
    });
  var i= 0; // FIXME: debile !
  // Update the nodes…
  node = svgGroup.selectAll("g.node")
      .data(nodes, function(d) {
          console.log(d);
          var x = d.id || (d.id = ++i);
          console.log("select g.node ",x," name",d.data.name);
          return x; 
      });

      console.log("node is :",node);

      // Enter any new nodes at the parent's previous position.
  var nodeEnter = node
    .enter().append("g")
    .call(dragListener)
    .attr("class", "node")
    .attr("transform", function(d) {
            console.log("transform 1");
            var t = "translate(" + source.y0 + "," + source.x0 + ")";
            console.log(t);
            return(t);
        }) 
    .on('click', click);

// adding popup dialogue for changing/adding/deleting nodes for text captions too

    nodeEnter.append("circle")
        .attr('class', 'nodeCircle')
        .attr("r", 0)
        .style("fill", function(d) {
            return d._children ? "lightsteelblue" : "#fff";
        });
    //.on('contextmenu', d3.contextMenu(menu));
    // adding popup dialogue for changing/adding/deleting nodes to circles


    nodeEnter.append("text")
        .attr("x", function(d) {
            return d.children || d._children ? -10 : 10;
        })
        .attr("dy", ".35em")
        .attr('class', 'nodeText')
        .attr("text-anchor", function(d) {
            return d.children || d._children ? "end" : "start";
        })
        .text(function(d) {
            console.log(" nodeEnter name ",d.data.name);
            return d.data.name;
        })
        .style("fill-opacity", 0);
//.on('contextmenu', d3.contextMenu(menu));
// adding popup dialogue for changing/adding/deleting nodes for text captions too


  // phantom node to give us mouseover in a radius around it
  nodeEnter.append("circle")
      .attr('class', 'ghostCircle')
      .attr("r", 30)
      .attr("opacity", 0.2) // change this to zero to hide the target area
      .style("fill", "red")
      .attr('pointer-events', 'mouseover')
      .on("mouseover", function(node) {
          overCircle(node);
      })
      .on("mouseout", function(node) {
          outCircle(node);
      });


    node = svgGroup.selectAll("g.node");
    // Update the text to reflect whether node has children or not.
    node.select('text')
      .attr("x", function(d) {
          return d.children || d._children ? -10 : 10;
      })
      .attr("text-anchor", function(d) {
          return d.children || d._children ? "end" : "start";
      })
      .text(function(d) {
          console.log("node txt",d.data.name)
          return d.data.name;
      });

  // Change the circle fill depending on whether it has children and is collapsed
  node.select("circle.nodeCircle")
      .attr("r",function(d) { return 4.5;})
      .style("fill", function(d) {
          return d._children ? "lightsteelblue" : "#fff";
      });

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) {
          return "translate(" + d.y + "," + d.x + ")";
      });

  // Fade the text in
  nodeUpdate.select("text")
      .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) {
          return "translate(" + source.y + "," + source.x + ")";
      })
      .remove();

  nodeExit.select("circle")
      .attr("r", 0);

  nodeExit.select("text")
      .style("fill-opacity", 0);

  // Update the links…
  var link = svgGroup.selectAll("path.link")
      .data(links, function(d) {
          return d.target.id;
      });


  // Enter any new links at the parent's previous position.
 var linkEnter = link.enter().insert("path", "g")
      .attr("class", "link")      
      .attr("d", function(d) {
        var o = {
            x: source.x0,
            y: source.y0
        };
        return diagonal(o,o);
    });
  // UPDATE
  var linkUpdate = linkEnter.merge(link);
  // Transition links to their new position.
  linkUpdate.transition()
      .duration(duration)
      .attr("d", function(d){ return diagonal(d.source, d.target); });

  // Remove any exiting links
  var linkExit = link.exit().transition()
    .duration(duration)
    .attr('d', function(d) {
        var o = {x: source.x, y: source.y}
        return diagonal(o, o);
    })
    .remove();


  // Stash the old positions for transition.
  nodes.forEach(function(d) {
      d.x0 = d.x;
      d.y0 = d.y;
  });
}

/* end update */

// Append a group which holds all nodes and which the zoom Listener can act upon.
 var svgGroup = baseSvg.append("g").attr("class","nodes");

 var templink = baseSvg.append("path")
                .attr("class","templink")
                .attr('pointer-events', 'none');
//var svgLinks = baseSvg.append("g").attr("class","links");

/* d3.select('svg g.nodes')
  .selectAll('circle.node')
  .data(root.descendants())
  .enter()
  .append('rect')
  .classed('node', true)
  .attr('x', function(d) {return d.y;})
  .attr('y', function(d) {return d.x;})
  .attr('width', 24)
  .attr('height', 24);
*/
// Links
/* d3.select('svg g.links')
  .selectAll('line.link')
  .data(root.links())
  .enter()
  .append('line')
  .classed('link', true)
  .attr('x1', function(d) {return d.source.y;})
  .attr('y1', function(d) {return d.source.x;})
  .attr('x2', function(d) {return d.target.y;})
  .attr('y2', function(d) {return d.target.x;});
*/
  console.log(root);

console.log(JSON.stringify(root.data));
root.x0 = viewerHeight / 2;
root.y0 = 0;
// Layout the tree initially and center on the root node.
update(root);

0 个答案:

没有答案
相关问题