D3V4:通过子节点拖动树路径

时间:2019-06-28 00:16:38

标签: javascript d3.js

我目前正在D3V4中进行双向树布局。我想合并一个功能,使用户能够在树的子节点周围拖动,并且路径自然而然地跟随被拖动的节点。

我想象的一个例子是Mike Bostock的Spline Editor代码。除了我的情况外,我只有两个节点(路径边缘的节点)。

到目前为止,我已经能够拖动节点,但是我不确定如何将路径与拖动的节点链接。在样条线编辑器的代码中,数据来自一个来源,而由于d3的层次结构功能,数据被分为两个来源(即节点和链接)。

在给定了根和实际树数据集的情况下,链接的数据集位于var links = root.links();中,节点位于var nodes = root.descendants();中。因此它与样条线编辑器不完全兼容。我的主要问题是因为树的路径是从层次结构生成的,我知道我需要对其进行更新。但是我不确定如何。

有人能温柔地指出我能做什么吗?

HTML

  <div id="div-mindMap">

CSS

.linkMindMap {
    fill: none;
    stroke: #555;
    stroke-opacity: 0.4;
}    
rect {
    fill: white;
    stroke: #3182bd;
    stroke-width: 1.5px;  
 }

JS

const widthMindMap = 700;
const heightMindMap = 700;
let parsedData;

let parsedList = {
  "name": " Stapler",
  "children": [{
      "name": " Bind",
      "children": []
    },
    {
      "name": "   Nail",
      "children": []
    },
    {
      "name": "   String",
      "children": []
    },
    {
      "name": " Glue",
      "children": [{
          "name": "Gum",
          "children": []
        },
        {
          "name": "Sticky Gum",
          "children": []
        }
      ]
    },
    {
      "name": " Branch 3",
      "children": []
    },
    {
      "name": " Branch 4",
      "children": [{
          "name": "   Branch 4.1",
          "children": []
        },
        {
          "name": "   Branch 4.2",
          "children": []
        },
        {
          "name": "   Branch 4.1",
          "children": []
        }
      ]
    },
    {
      "name": " Branch 5",
      "children": []
    },
    {
      "name": " Branch 6",
      "children": []
    },
    {
      "name": " Branch 7",
      "children": []
    },
    {
      "name": "   Branch 7.1",
      "children": []
    },
    {
      "name": "   Branch 7.2",
      "children": [{
          "name": "   Branch 7.2.1",
          "children": []
        },
        {
          "name": "   Branch 7.2.1",
          "children": []
        }
      ]
    }
  ]
}
let currNodeId = 0;
function idData(node) {
    node.nodeId = ++currNodeId;
  node.children.forEach(idData);
}
idData(parsedList);

let svgMindMap = d3.select('#div-mindMap')
  .append("svg")
  .attr("id", "svg-mindMap")
  .attr("width", widthMindMap)
  .attr("height", heightMindMap);


let backgroundLayer = svgMindMap.append('g')
  .attr("width", widthMindMap)
  .attr("height", heightMindMap)
  .attr("class", "background")

let gLeft = backgroundLayer.append("g")
  .attr("transform", "translate(" + widthMindMap / 2 + ",0)")
  .attr("class", "g-left");
let gLeftLink = gLeft.append('g')
  .attr('class', 'g-left-link');
let gLeftNode = gLeft.append('g')
  .attr('class', 'g-left-node');


function loadMindMap(parsed) {
  var data = parsed;
  var split_index = Math.round(data.children.length / 2);

  parsedData = {
    "name": data.name,
    "nodeId": data.nodeId,
    "children": JSON.parse(JSON.stringify(data.children.slice(split_index)))
  };

  var left = d3.hierarchy(parsedData, d => d.children);

  drawLeft(left, "left");
}

// draw single tree
function drawLeft(root, pos) {
  var SWITCH_CONST = 1;
  if (pos === "left") SWITCH_CONST = -1;

  update(root, SWITCH_CONST);
}

function update(source, SWITCH_CONST) {
  var tree = d3.tree()
    .size([heightMindMap, SWITCH_CONST * (widthMindMap - 150) / 2]);
  var root = tree(source);

  console.log(root)

  var nodes = root.descendants();
  var links = root.links();

  console.log(nodes)
  console.log(links)
  // Set both root nodes to be dead center vertically
  nodes[0].x = heightMindMap / 2

  //JOIN new data with old elements
  var link = gLeftLink.selectAll(".link-left")
    .data(links, d => d.target.data.nodeId)
    .style('stroke-width', 1.5);

  var linkEnter = link.enter().append("path")
    .attr("class", "linkMindMap link-left");

  var linkUpdate = linkEnter.merge(link);

  linkUpdate.transition()
    .duration(750)
    .attr("d", d3.linkHorizontal()
      .x(d => d.y)
      .y(d => d.x));
  var linkExit = link.exit()
    .transition()
    .duration(750)
    .attr('x1', function(d) {
      return root.x;
    })
    .attr('y1', function(d) {
      return root.y;
    })
    .attr('x2', function(d) {
      return root.x;
    })
    .attr('y2', function(d) {
      return root.y;
    })
    .remove();

  //JOIN new data with old elements
  var node = gLeftNode.selectAll(".nodeMindMap-left")
    .data(nodes, d => d.data.nodeId);

  console.log(nodes);


  //ENTER new elements present in new data
  var nodeEnter = node.enter().append("g")
    .attr("class", function(d) {
      return "nodeMindMap-left " + "nodeMindMap" + (d.children ? " node--internal" : " node--leaf");
    })
    .classed("enter", true)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    })
    .attr("id", function(d) {
      let str = d.data.name;
      str = str.replace(/\s/g, '');
      return str;
    })
         .call(d3.drag()
      .on("drag", dragged));

  nodeEnter.append("circle")
    .attr("r", function(d, i) {
      return 2.5
    });

  nodeEnter.append("foreignObject")
    .style("fill", "blue")
    .attr("x", -50)
    .attr("y", -7)
    .attr("height", "20px")
    .attr("width", "100px")
    .append('xhtml:div')
    .append('div')
    .attr("class", 'clickable-node')
    .attr("id", function(d) {
      let str = d.data.name;
      str = "div-" + str.replace(/\s/g, '');
      return str;
    })
    .attr("ondblclick", "this.contentEditable=true")
    .attr("onblur", "this.contentEditable=false")
    .attr("contentEditable", "false")
    .style("text-align", "center")
    .text(d => d.data.name);

  //TODO: make it dynamic
  nodeEnter.insert("rect", "foreignObject")
    .attr("ry", 6)
    .attr("rx", 6)
    .attr("y", -10)
    .attr("height", 20)
    .attr("width", 100)
    // .filter(function(d) { return d.flipped; })
    .attr("x", -50)
    .classed("selected", false)
    .attr("id", function(d) {
      let str = d.data.name;
      str = "rect-" + str.replace(/\s/g, '');
      return str;
    })

  var nodeUpdate = nodeEnter.merge(node);
  // Transition to the proper position for the node
  nodeUpdate.transition()
    .duration(750)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  // Remove any exiting nodes
  var nodeExit = node.exit()
    .transition()
    .duration(750)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  // On exit reduce the node circles size to 0
  nodeExit.select('circle').attr('r', 0);
  // node = nodeEnter.merge(node)
}


function dragged(d){
  // Current position:

  var translateCoords = getTranslation(d3.select(this).attr("transform")); 

console.log("yooo", translateCoords)
 d.x = translateCoords[0]; // set x coordinate
        d.y = translateCoords[1]; // set y coordinate
        d.x += d3.event.dx
        d.y += d3.event.dy  

  d3.select(this).attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}

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];
}
loadMindMap(parsedList);

可以找到here一个jfiddle。小提琴只显示树的左侧,但是可以假定树是对称的。为了简单起见,我只显示一侧。

0 个答案:

没有答案