D3.js将节点放置在节点内

时间:2018-08-18 21:11:41

标签: d3.js

我正在尝试在强制布局节点内创建一个强制布局

我从数据中创建了三个数组,其中两个用于外部节点和链接,最后一个用于内部节点。并绘制了外部图表,但我不确定如何 为每个节点创建内部图表。 我应该为每个内部节点创建一个单独的模拟吗?

我正在添加当前代码,对您的帮助将不胜感激!

data.json

{
  "id": "group1",
  "members": [
    {"id": "member1"},
    {"id": "member2"}
  ],
  "children": [
    { 
      "id": "group2",
      "members": [
        {"id": "member1"},
        {"id": "member2"}
      ],
    },
    { 
      "id": "group3",
      "members": [
        {"id": "member1"},
        {"id": "member2"},
        {
          "id": "member3",
          "children": [
            { "id": "group4" },
            { "id": "group5" }
          ]
        }
      ]
    }
  ]
}

main.js

const width = window.innerWidth;
const height = window.innerHeight;
const svg = d3.select("body")
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .call(d3.zoom()
    .scaleExtent([1 / 2, 8])
    .on('zoom', zoomed)
  );

const outerNodes = [];
const outerLinks = [];
const innerNodes = [];
const canvas = svg.append("g");
const simulation = d3.forceSimulation()
  .force('center', d3.forceCenter(width / 2, height / 2))
  .force('charge', d3.forceManyBody().strength(-500))
  .force('link', d3.forceLink().id(d => d.id).distance(80).strength(1));

let outerGroup, node, link;

d3.json('data.json').then(data => {
  flatten(data);

  link = canvas.append('g')
    .attr('class', 'links')
    .selectAll('line')
    .data(outerLinks)
    .enter().append('line')
      .attr('stroke', '#777');

  outerGroup = canvas.append('g')
    .attr('class', 'nodes')
    .selectAll('path')
    .data(outerNodes)
    .enter().append('g')
    .attr('id', d => d.id)
    .call(d3.drag()
      .on('start', onDragStart)
      .on('drag', onDrag)
      .on('end', onDragEnd)
    );

  node = outerGroup.append('path')
    .attr('d', generateShapePath())
    .attr('fill', d => d.children ? 'blue' : 'green')

  simulation
    .nodes(outerNodes)
    .on('tick', ticked);

  simulation.force('link')
    .links(outerLinks);
});

function flatten(node) {
  outerNodes.push(node);

  if (node.members) {
    innerNodes.push({parent: node.id, nodes: node.members})
  }

  if (node.children) {   
    node.children.forEach(child => {
      outerLinks.push({ source: node.id, target: child.id });
      flatten(child);
    }); 
  }
}

function ticked() {
  link
    .attr('x1', d => d.source.x)
    .attr('y1', d => d.source.y)
    .attr('x2', d => d.target.x)
    .attr('y2', d => d.target.y);

  node.attr('d', generateShapePath);
}

1 个答案:

答案 0 :(得分:0)

您需要解耦所有单个仿真。相对于父位置对内部仿真进行建模。

  • 为每个节点添加一个g。此g将转换为tick()
  • 中的节点位置
  • 将节点五边形添加到相对于g建模的0,0
  • 对每个成员执行相同的操作:g和相对的五边形path
  • 为仿真设置独立的拖动功能=>鼠标事件坐标将是相对的
  • 对于该演示,我限制了widthheight
  • 如果您有更多的节点成员(如问题中所示),请对它们的每个组使用模拟。
  • d3.tree的用途是什么?

  • 如果要使用select,请不要使用each

    parent.select(d => {
      d.updateMembers = () => {
        innerSimulation.force('center', d3.forceCenter(d.x, d.y));
      }
    });
    

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<script>
run();

function run() {
const data = {
  "id": "group1",
  "members": [
    {"id": "member1"},
    {"id": "member2"}
  ],
  "children": [
    {"id": "group2"},
    {"id": "group3"},
    {"id": "group4"},
    {
      "id": "group5",
      "children": [
        {"id": "group6"},
        {"id": "group7"}
      ]
    }
  ]
};

const width = 500; // window.innerWidth;
const height = 400; // window.innerHeight;
const svg = d3.select("body")
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .call(d3.zoom()
  .scaleExtent([1 / 2, 8])
  .on("zoom", zoomed));

const outerNodes = [];
const outerLinks = [];
const innerNodes = [];
const canvas = svg.append("g");
const color = d3.scaleOrdinal(d3.schemeCategory10);
const tree = d3.tree().size(height, width);
const simulation = d3.forceSimulation()
  .force("center", d3.forceCenter(width / 2, height / 2))
  .force("charge", d3.forceManyBody().strength(-500))
  .force("link", d3.forceLink().id(d => d.id).distance(80).strength(1));

const innerSimulation = d3.forceSimulation()
  .force("charge", d3.forceManyBody().strength(5))
  .force('collision', d3.forceCollide().radius(10))
  .force('center', d3.forceCenter());

let link, node, nodeGroup;

onLoad(data);

function onLoad(data) {
  flatten(data);

  link = canvas.append('g')
    .attr('class', 'links')
    .selectAll('line')
    .data(outerLinks)
    .enter().append('line')
      .attr('stroke', '#eee');

  nodeGroup = canvas.append('g')
    .attr('class', 'nodes')
    .selectAll('path')
    .data(outerNodes)
    .enter().append('g')
    .attr('id', d => d.id)

    .call(d3.drag()
      .on('start', onDragStart)
      .on('drag', onDrag)
      .on('end', onDragEnd)
    );

  nodeGroup.append('path')
    .attr('d', generatePentagonPath(25))
    .attr('fill', d => d.children ? 'blue' : 'green');

  innerNodes.map(member => {
    const parent = canvas.select(`#${member.parent}`);

    const members = parent
      .selectAll('.member')
      .data(member.nodes)
      .enter()
      .append('g')
      .attr('class', 'member')
      .call(d3.drag()
        .on('start', (d) => {
          if (!d3.event.active) {
            innerSimulation.alphaTarget(.3).restart();
          }
          members.each(d => {
            d.fx = d.x;
            d.fy = d.y
          })
        })
        .on('drag', function (d) {
          d.fx = d3.event.x;
          d.fy = d3.event.y;
        })
        .on('end', (d) => {
          if (!d3.event.active) {
            innerSimulation.alphaTarget(0).restart();
          }
          d.fx = d.fy = null;
        })
      );
    members
      .append('path')
      .attr('d', generatePentagonPath(5))
      .attr('fill', 'orange');

    innerSimulation
      .nodes(member.nodes)
      .on('tick', () => {
        members.attr("transform", d => `translate(${d.x},${d.y})`);
      });
  });

  simulation
    .nodes(outerNodes)
    .on('tick', ticked);

  simulation.force('link')
    .links(outerLinks);
}

function onDragStart(d) {
  if (!d3.event.active) {
    simulation.alphaTarget(0.5).restart();
  }

  nodeGroup.each(d => {
    d.fx = d.x;
    d.fy = d.y;
  });
}

function onDrag(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function onDragEnd(d) {
  if (!d3.event.active) {
    simulation.alphaTarget(0).restart();
  }
  d.fx = null;
  d.fy = null;
}

function zoomed() {
  canvas.attr("transform", d3.event.transform);
}

function flatten(node) {
  outerNodes.push(node);

  if (node.members) {
    innerNodes.push({parent: node.id, nodes:  node.members});
  }

  if (node.children) {
    node.children.forEach(child => {
      outerLinks.push({ source: node.id, target: child.id });
      flatten(child);
    });
  }
}

function generatePentagonPath(radius = 25, x = 0, y = 0) {
  const numPoints = 5;
  const points = d3.range(numPoints)
    .map(i => {
      const angle = i / numPoints * Math.PI * 2 + Math.PI;
      return [Math.sin(angle) * radius + x, Math.cos(angle) * radius + y];
    });

  return d3.line()(points);
}

function ticked() {
  link
    .attr('x1', d => d.source.x)
    .attr('y1', d => d.source.y)
    .attr('x2', d => d.target.x)
    .attr('y2', d => d.target.y);

  nodeGroup.attr("transform", d => `translate(${d.x},${d.y})`);
}

}
</script>
</body>
</html>

相关问题