4

我正在尝试制作一个力有向图,其中子节点和孙节点正在环绕/环绕父节点。同时父节点连接到它的子节点,每个子节点连接到它们的每个孙节点。

在视觉上,它看起来像这样:

在此处输入图像描述

我已经尝试干预默认的力有向图(在这里那里),但似乎没有办法像我试图制作的视觉效果那样将它们整齐地排列成圆形/轨道。

我尝试查找轨道代码,但似乎需要一种完全不同的方法。

这是我的小提琴和代码:https ://jsfiddle.net/znqkcLhs/

function getNeighbors(node) {
  return links.reduce(function (neighbors, link) {
      if (link.target.id === node.id) {
        neighbors.push(link.source.id)
      } else if (link.source.id === node.id) {
        neighbors.push(link.target.id)
      }
      return neighbors
    },
    [node.id]
  )
}

function isNeighborLink(node, link) {
  return link.target.id === node.id || link.source.id === node.id
}


function getNodeColor(node, neighbors) {
  if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
    return node.level === 1 ? 'blue' : 'green'
  }

  return node.level === 1 ? 'red' : 'gray'
}


function getLinkColor(node, link) {
  return isNeighborLink(node, link) ? 'green' : '#E5E5E5'
}

function getTextColor(node, neighbors) {
  return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}

var width = window.innerWidth
var height = window.innerHeight

var svg = d3.select('svg')
svg.attr('width', width).attr('height', height)

// simulation setup with all forces
var linkForce = d3
  .forceLink()
  .id(function (link) { return link.id })
  .strength(function (link) { return link.strength })

var simulation = d3
  .forceSimulation()
  .force('link', linkForce)
  .force('charge', d3.forceManyBody().strength(-120))
  .force('center', d3.forceCenter(width / 2, height / 2))

var dragDrop = d3.drag().on('start', function (node) {
  node.fx = node.x
  node.fy = node.y
}).on('drag', function (node) {
  simulation.alphaTarget(0.7).restart()
  node.fx = d3.event.x
  node.fy = d3.event.y
}).on('end', function (node) {
  if (!d3.event.active) {
    simulation.alphaTarget(0)
  }
  node.fx = null
  node.fy = null
})

function selectNode(selectedNode) {
  var neighbors = getNeighbors(selectedNode)

  // we modify the styles to highlight selected nodes
  nodeElements.attr('fill', function (node) { return getNodeColor(node, neighbors) })
  textElements.attr('fill', function (node) { return getTextColor(node, neighbors) })
  linkElements.attr('stroke', function (link) { return getLinkColor(selectedNode, link) })
}

var linkElements = svg.append("g")
  .attr("class", "links")
  .selectAll("line")
  .data(links)
  .enter().append("line")
    .attr("stroke-width", function(link) { return link.value; })
	  .attr("stroke", "rgba(50, 50, 50, 0.2)")

var nodeElements = svg.append("g")
  .attr("class", "nodes")
  .selectAll("circle")
  .data(nodes)
  .enter().append("circle")
    .attr("r", 10)
    .attr("fill", getNodeColor)
    .call(dragDrop)
    .on('click', selectNode)

var textElements = svg.append("g")
  .attr("class", "texts")
  .selectAll("text")
  .data(nodes)
  .enter().append("text")
    .text(function (node) { return  node.label })
	  .attr("font-size", 15)
	  .attr("dx", 15)
    .attr("dy", 4)

simulation.nodes(nodes).on('tick', () => {
  nodeElements
    .attr('cx', function (node) { return node.x })
    .attr('cy', function (node) { return node.y })
  textElements
    .attr('x', function (node) { return node.x })
    .attr('y', function (node) { return node.y })
  linkElements
    .attr('x1', function (link) { return link.source.x })
    .attr('y1', function (link) { return link.source.y })
    .attr('x2', function (link) { return link.target.x })
    .attr('y2', function (link) { return link.target.y })
})

simulation.force("link").links(links)

有任何想法吗?

4

2 回答 2

3

新的 d3.forceRadial()

您需要的是d3.forceRadial,在 D3 v4.11 中引入。根据APId3.forceRadial(radius[, x][, y])将...

创建一个新的定位力,指向以 ⟨x,y⟩ 为中心的指定半径的圆。

在你的情况下,我level用来设置半径:

.force('radial', d3.forceRadial(function(d) {
    return d.level * 50
}, width / 2, height / 2))

当您只有节点时,事情会更容易。但是,由于您在该力中有链接,因此您必须调整链接力,直到获得所需的结果。

这是您的代码d3.forceRadial

var nodes = [{
  id: "pusat",
  group: 0,
  label: "pusat",
  level: 0
}, {
  id: "dki",
  group: 1,
  label: "dki",
  level: 1
}, {
  id: "jaksel",
  group: 1,
  label: "jaksel",
  level: 3
}, {
  id: "jakpus",
  group: 1,
  label: "jakpus",
  level: 3
}, {
  id: "jabar",
  group: 2,
  label: "jabar",
  level: 1
}, {
  id: "sumedang",
  group: 2,
  label: "sumedang",
  level: 3
}, {
  id: "bekasi",
  group: 2,
  label: "bekasi",
  level: 3
}, {
  id: "bandung",
  group: 2,
  label: "bandung",
  level: 3
}, {
  id: "jatim",
  group: 3,
  label: "jatim",
  level: 1
}, {
  id: "malang",
  group: 3,
  label: "malang",
  level: 3
}, {
  id: "lamongan",
  group: 3,
  label: "lamongan",
  level: 3
}, {
  id: "diy",
  group: 4,
  label: "diy",
  level: 1
}, {
  id: "sleman",
  group: 4,
  label: "sleman",
  level: 3
}, {
  id: "jogja",
  group: 4,
  label: "jogja",
  level: 3
}, {
  id: "bali",
  group: 5,
  label: "bali",
  level: 1
}, {
  id: "bali1",
  group: 5,
  label: "bali1",
  level: 3
}, {
  id: "bali2",
  group: 5,
  label: "bali2",
  level: 3
}, {
  id: "ntt",
  group: 6,
  label: "ntt",
  level: 1
}, {
  id: "ntt1",
  group: 6,
  label: "ntt1",
  level: 3
}, {
  id: "ntt2",
  group: 6,
  label: "ntt2",
  level: 3
}]

var links = [{
    target: "pusat",
    source: "dki",
    strength: 0.2,
    value: 1
  }, {
    target: "pusat",
    source: "jabar",
    strength: 0.2,
    value: 3
  }, {
    target: "pusat",
    source: "jatim",
    strength: 0.2,
    value: 6
  }, {
    target: "pusat",
    source: "diy",
    strength: 0.2,
    value: 1
  }, {
    target: "pusat",
    source: "bali",
    strength: 0.2,
    value: 1
  }, {
    target: "pusat",
    source: "ntt",
    strength: 0.2,
    value: 1
  },

  //{ target: "pusat", source: "malang" , strength: 0.2, value:3 },
  //{ target: "pusat", source: "lamongan" , strength: 0.2, value:6 },

  {
    target: "dki",
    source: "jaksel",
    strength: 0.7,
    value: 2
  }, {
    target: "dki",
    source: "jakpus",
    strength: 0.7,
    value: 3
  }, {
    target: "jabar",
    source: "sumedang",
    strength: 0.7,
    value: 0.5
  }, {
    target: "jabar",
    source: "bekasi",
    strength: 0.7,
    value: 2
  }, {
    target: "jabar",
    source: "bandung",
    strength: 0.7,
    value: 2
  }, {
    target: "jatim",
    source: "malang",
    strength: 0.7,
    value: 3
  }, {
    target: "jatim",
    source: "lamongan",
    strength: 0.7,
    value: 1
  }, {
    target: "diy",
    source: "sleman",
    strength: 0.7,
    value: 3
  }, {
    target: "diy",
    source: "jogja",
    strength: 0.7,
    value: 1
  }, {
    target: "bali",
    source: "bali1",
    strength: 0.7,
    value: 1
  }, {
    target: "bali",
    source: "bali2",
    strength: 0.7,
    value: 1
  }, {
    target: "ntt",
    source: "ntt1",
    strength: 0.7,
    value: 1
  }, {
    target: "ntt",
    source: "ntt2",
    strength: 0.7,
    value: 1
  }
]

function getNeighbors(node) {
  return links.reduce(function(neighbors, link) {
    if (link.target.id === node.id) {
      neighbors.push(link.source.id)
    } else if (link.source.id === node.id) {
      neighbors.push(link.target.id)
    }
    return neighbors
  }, [node.id])
}

function isNeighborLink(node, link) {
  return link.target.id === node.id || link.source.id === node.id
}


function getNodeColor(node, neighbors) {
  if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
    return node.level === 1 ? 'blue' : 'green'
  }

  return node.level === 1 ? 'red' : 'gray'
}


function getLinkColor(node, link) {
  return isNeighborLink(node, link) ? 'green' : '#E5E5E5'
}

function getTextColor(node, neighbors) {
  return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}

var width = window.innerWidth
var height = window.innerHeight

var svg = d3.select('svg')
svg.attr('width', width).attr('height', height);

var circles = svg.selectAll(null)
  .data([80,125])
  .enter()
  .append("circle")
  .attr("cx", width/2)
  .attr("cy", height/2)
  .attr("r", d=>d)
  .style("fill", "none")
  .style("stroke", "#ccc");

// simulation setup with all forces
var linkForce = d3
  .forceLink()
  .id(function(link) {
    return link.id 
  });

var simulation = d3
  .forceSimulation()
  .force('link', linkForce)
  .force('charge', d3.forceManyBody().strength(-120))
  .force('radial', d3.forceRadial(function(d) {
    return d.level * 50
  }, width / 2, height / 2))
  .force('center', d3.forceCenter(width / 2, height / 2))

var dragDrop = d3.drag().on('start', function(node) {
  node.fx = node.x
  node.fy = node.y
}).on('drag', function(node) {
  simulation.alphaTarget(0.7).restart()
  node.fx = d3.event.x
  node.fy = d3.event.y
}).on('end', function(node) {
  if (!d3.event.active) {
    simulation.alphaTarget(0)
  }
  node.fx = null
  node.fy = null
})

function selectNode(selectedNode) {
  var neighbors = getNeighbors(selectedNode)

  // we modify the styles to highlight selected nodes
  nodeElements.attr('fill', function(node) {
    return getNodeColor(node, neighbors) 
  })
  textElements.attr('fill', function(node) {
    return getTextColor(node, neighbors)
  })
  linkElements.attr('stroke', function(link) {
    return getLinkColor(selectedNode, link)
  })
}

var linkElements = svg.append("g")
  .attr("class", "links")
  .selectAll("line")
  .data(links)
  .enter().append("line")
  .attr("stroke-width", function(link) {
    return link.value;
  })
  .attr("stroke", "rgba(50, 50, 50, 0.2)")

var nodeElements = svg.append("g")
  .attr("class", "nodes")
  .selectAll("circle")
  .data(nodes)
  .enter().append("circle")
  .attr("r", 10)
  .attr("fill", getNodeColor)
  .call(dragDrop)
  .on('click', selectNode)

var textElements = svg.append("g")
  .attr("class", "texts")
  .selectAll("text")
  .data(nodes)
  .enter().append("text")
  .text(function(node) {
    return node.label
  })
  .attr("font-size", 15)
  .attr("dx", 15)
  .attr("dy", 4)

simulation.nodes(nodes).on('tick', () => {
  nodeElements
    .attr('cx', function(node) {
      return node.x
    })
    .attr('cy', function(node) {
      return node.y
    })
  textElements
    .attr('x', function(node) {
      return node.x
    })
    .attr('y', function(node) {
      return node.y
    })
  linkElements
    .attr('x1', function(link) {
      return link.source.x
    })
    .attr('y1', function(link) {
      return link.source.y
    })
    .attr('x2', function(link) {
      return link.target.x
    })
    .attr('y2', function(link) {
      return link.target.y
    })
})

simulation.force("link").links(links)
<svg width="960" height="600">
</svg>

<script src="https://d3js.org/d3.v4.min.js"></script>

正如我所说,因为你有链接,所以事情有点复杂。d3.forceRadial如果您只有节点(这里,连同),看看如何创建一个漂亮的径向图案d3.forceCollide

var nodes = [{
  id: "pusat",
  group: 0,
  label: "pusat",
  level: 0
}, {
  id: "dki",
  group: 1,
  label: "dki",
  level: 1
}, {
  id: "jaksel",
  group: 1,
  label: "jaksel",
  level: 3
}, {
  id: "jakpus",
  group: 1,
  label: "jakpus",
  level: 3
}, {
  id: "jabar",
  group: 2,
  label: "jabar",
  level: 1
}, {
  id: "sumedang",
  group: 2,
  label: "sumedang",
  level: 3
}, {
  id: "bekasi",
  group: 2,
  label: "bekasi",
  level: 3
}, {
  id: "bandung",
  group: 2,
  label: "bandung",
  level: 3
}, {
  id: "jatim",
  group: 3,
  label: "jatim",
  level: 1
}, {
  id: "malang",
  group: 3,
  label: "malang",
  level: 3
}, {
  id: "lamongan",
  group: 3,
  label: "lamongan",
  level: 3
}, {
  id: "diy",
  group: 4,
  label: "diy",
  level: 1
}, {
  id: "sleman",
  group: 4,
  label: "sleman",
  level: 3
}, {
  id: "jogja",
  group: 4,
  label: "jogja",
  level: 3
}, {
  id: "bali",
  group: 5,
  label: "bali",
  level: 1
}, {
  id: "bali1",
  group: 5,
  label: "bali1",
  level: 3
}, {
  id: "bali2",
  group: 5,
  label: "bali2",
  level: 3
}, {
  id: "ntt",
  group: 6,
  label: "ntt",
  level: 1
}, {
  id: "ntt1",
  group: 6,
  label: "ntt1",
  level: 3
}, {
  id: "ntt2",
  group: 6,
  label: "ntt2",
  level: 3
}]

var links = [{
    target: "pusat",
    source: "dki",
    strength: 0.2,
    value: 1
  }, {
    target: "pusat",
    source: "jabar",
    strength: 0.2,
    value: 3
  }, {
    target: "pusat",
    source: "jatim",
    strength: 0.2,
    value: 6
  }, {
    target: "pusat",
    source: "diy",
    strength: 0.2,
    value: 1
  }, {
    target: "pusat",
    source: "bali",
    strength: 0.2,
    value: 1
  }, {
    target: "pusat",
    source: "ntt",
    strength: 0.2,
    value: 1
  },

  //{ target: "pusat", source: "malang" , strength: 0.2, value:3 },
  //{ target: "pusat", source: "lamongan" , strength: 0.2, value:6 },

  {
    target: "dki",
    source: "jaksel",
    strength: 0.7,
    value: 2
  }, {
    target: "dki",
    source: "jakpus",
    strength: 0.7,
    value: 3
  }, {
    target: "jabar",
    source: "sumedang",
    strength: 0.7,
    value: 0.5
  }, {
    target: "jabar",
    source: "bekasi",
    strength: 0.7,
    value: 2
  }, {
    target: "jabar",
    source: "bandung",
    strength: 0.7,
    value: 2
  }, {
    target: "jatim",
    source: "malang",
    strength: 0.7,
    value: 3
  }, {
    target: "jatim",
    source: "lamongan",
    strength: 0.7,
    value: 1
  }, {
    target: "diy",
    source: "sleman",
    strength: 0.7,
    value: 3
  }, {
    target: "diy",
    source: "jogja",
    strength: 0.7,
    value: 1
  }, {
    target: "bali",
    source: "bali1",
    strength: 0.7,
    value: 1
  }, {
    target: "bali",
    source: "bali2",
    strength: 0.7,
    value: 1
  }, {
    target: "ntt",
    source: "ntt1",
    strength: 0.7,
    value: 1
  }, {
    target: "ntt",
    source: "ntt2",
    strength: 0.7,
    value: 1
  }
]

function getNeighbors(node) {
  return links.reduce(function(neighbors, link) {
    if (link.target.id === node.id) {
      neighbors.push(link.source.id)
    } else if (link.source.id === node.id) {
      neighbors.push(link.target.id)
    }
    return neighbors
  }, [node.id])
}

function isNeighborLink(node, link) {
  return link.target.id === node.id || link.source.id === node.id
}


function getNodeColor(node, neighbors) {
  if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
    return node.level === 1 ? 'blue' : 'green'
  }

  return node.level === 1 ? 'red' : 'gray'
}


function getLinkColor(node, link) {
  return isNeighborLink(node, link) ? 'green' : '#E5E5E5'
}

function getTextColor(node, neighbors) {
  return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}

var width = window.innerWidth
var height = window.innerHeight

var svg = d3.select('svg')
  .attr('width', width).attr('height', height)
  .append("g")
  .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")

// simulation setup with all forces


var simulation = d3.forceSimulation()
  .force('radial', d3.forceRadial(function(d) {
    return d.level * 50
  }).strength(1))
  .force('collide', d3.forceCollide().radius(35));

var dragDrop = d3.drag().on('start', function(node) {
  node.fx = node.x
  node.fy = node.y
}).on('drag', function(node) {
  simulation.alphaTarget(0.7).restart()
  node.fx = d3.event.x
  node.fy = d3.event.y
}).on('end', function(node) {
  if (!d3.event.active) {
    simulation.alphaTarget(0)
  }
  node.fx = null
  node.fy = null
})

function selectNode(selectedNode) {
  var neighbors = getNeighbors(selectedNode)

  // we modify the styles to highlight selected nodes
  nodeElements.attr('fill', function(node) {
    return getNodeColor(node, neighbors)
  })
  textElements.attr('fill', function(node) {
    return getTextColor(node, neighbors)
  })
  linkElements.attr('stroke', function(link) {
    return getLinkColor(selectedNode, link)
  })
}

var linkElements = svg.append("g")
  .attr("class", "links")
  .selectAll("line")
  .data(links)
  .enter().append("line")
  .attr("stroke-width", function(link) {
    return link.value;
  })
  .attr("stroke", "rgba(50, 50, 50, 0.2)")

var nodeElements = svg.append("g")
  .attr("class", "nodes")
  .selectAll("circle")
  .data(nodes)
  .enter().append("circle")
  .attr("r", 10)
  .attr("fill", getNodeColor)
  .call(dragDrop)
  .on('click', selectNode)

var textElements = svg.append("g")
  .attr("class", "texts")
  .selectAll("text")
  .data(nodes)
  .enter().append("text")
  .text(function(node) {
    return node.label
  })
  .attr("font-size", 15)
  .attr("dx", 15)
  .attr("dy", 4)

simulation.nodes(nodes).on('tick', () => {
  nodeElements
    .attr('cx', function(node) {
      return node.x
    })
    .attr('cy', function(node) {
      return node.y
    })
  textElements
    .attr('x', function(node) {
      return node.x
    })
    .attr('y', function(node) {
      return node.y
    })

})
<svg width="600" height="500">
</svg>

<script src="https://d3js.org/d3.v4.min.js"></script>

PS:我将第一个节点的级别设置为0.

于 2017-11-08T22:04:26.750 回答
2

forceLink().distance通过使用设置固定链接长度并增加,您将获得接近的结果forceManyBody().strength,例如:

var linkForce = d3
  .forceLink()
  .id(function (link) { return link.id })
  .distance(50)
  .strength(1)

var simulation = d3
  .forceSimulation()
  .force('link', linkForce)
  .force('charge', d3.forceManyBody().strength(-1000))
  .force('center', d3.forceCenter(width / 2, height / 2))

这是一个更新的小提琴

https://jsfiddle.net/znqkcLhs/1/

于 2017-11-08T19:45:37.597 回答