javascript - 如何保持 d3.forceSimulation() 节点均匀分布而不重叠

标签 javascript d3.js svg graph

我正在尝试制作一个图表,其中某些节点应该在左侧,而某些节点应该在“创世节点”的右侧,它应该在我的 svg 的中心。

设置力:

  • d3.forceManyBody() 所以节点应该互相排斥
  • d3.forceLink().distance(60).strength(1) 所以链接会保持 他们的长度
  • d3.forceCenter(width/2, height/2) 定位 中心的主要节点。我也试过删除它,但只使图表出现在左上角,只有一小部分节点可见

所以最后看起来像这样:

var simulation = d3.forceSimulation()
       .force("link", d3.forceLink().id(function (d) {
              return d.id;}).distance(60).strength(1))
       .force("charge", d3.forceManyBody())
       .force("center", d3.forceCenter(width / 2, height / 2));

设置节点: 我还尝试设置属性 xy,而不是 cxcy,但没有任何变化。 xCenteryCenter 是显示图形的 svg 中心。

node = svg.selectAll(".node")
    .data(nodes)
    .enter()
    .append("g")
    .attr("class", "node");

node.append("circle")
    .attr("r", 6)
    .attr("cx", function (d) {
       return colors(d.position*20 + xCenter);})
    .attr("cy", function (d) {return colors(yCenter);})
    .style("fill", function (d) {return colors(d.group);})

更新模拟

function ticked() {
   node
       .attr("transform", function (d) {
            return "translate(" + d.x + ", " + d.y + ")";});
}

我也试过这个,但不是运气:

function ticked() {
       node
           .attr("cx", function(d) { return d.cx; })
           .attr("cy", function(d) { return d.cy; });
    }

这就是我得到的:( enter image description here

而这正是我想要的(我可以自己拖动来设置我只是希望它直接出现这样) enter image description here

数据源结构: 位置0表示居中,负数表示向左,正数表示向右

{
  "graphNodes": [
    {
      "id": "Q20514253",
      "label": "label",
      "description": "description",
      "group": 0,
      "position": 0
    }],
  "graphLinks": [
   {
      "source": "Q8513",
      "target": "Q20514253",
      "type": "instanceOf"
   }],
 }

=============================== 编辑 ============ ======================

位置计算:

每个节点都有一个位置编号,负数表示在左边,正数表示在右边。 向左 1 级为 -1,向左 2 级为 -2。 向右1级是1,向右2级是2。

X位置是这样的: X = d.position*20 + xCenter//所以每一层相隔 20 像素

Y position 我只是把 = yCenter,所以它们都是垂直居中的,然后我希望因为带电的节点会相互排斥,所以它们会均匀地垂直分布

完整的工作代码:

var nodeDescription = document.querySelector(".node-description")
var colors = d3.scaleOrdinal(d3.schemeCategory10);

    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height"),
        node,
        link;

    svg.append('defs').append('marker')
        .attrs({'id':'arrowhead',
            'viewBox':'-0 -5 10 10',
            'refX':13,
            'refY':0,
            'orient':'auto',
            'markerWidth':13,
            'markerHeight':8,
            'xoverflow':'visible'})
        .append('svg:path')
        .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
        .attr('fill', '#999')
        .style('stroke','none');

    var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function (d) {return d.id;}).distance(60).strength(1))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2));



function update(links, nodes) {

        var graphPlaceholder = document.querySelector(".graph-placeholder")
        width = graphPlaceholder.offsetWidth
        var xCenter = width/2
        height = graphPlaceholder.offsetHeight
        var yCenter = height/2

        svg
          .attr("width", width)
          .attr("height", height)

        simulation.force("center", d3.forceCenter(xCenter, yCenter));

        link = svg.selectAll(".link")
            .data(links)
            .enter()
            .append("line")
            .attr("class", "link")
            .attr('marker-end','url(#arrowhead)')

        link.append("title")
            .text(function (d) {return d.type;});

        edgepaths = svg.selectAll(".edgepath")
            .data(links)
            .enter()
            .append('path')
            .attrs({
                'class': 'edgepath',
                'fill-opacity': 0,
                'stroke-opacity': 0,
                'id': function (d, i) {return 'edgepath' + i}
            })
            .style("pointer-events", "none");

        edgelabels = svg.selectAll(".edgelabel")
            .data(links)
            .enter()
            .append('text')
            .style("pointer-events", "none")
            .attrs({
                'class': 'edgelabel',
                'id': function (d, i) {return 'edgelabel' + i},
                'font-size': 10,
                'fill': '#aaa'
            });

        edgelabels.append('textPath')
            .attr('xlink:href', function (d, i) {return '#edgepath' + i})
            .style("text-anchor", "middle")
            .style("pointer-events", "none")
            .attr("startOffset", "50%")
            .text(function (d) {return d.type});

        node = svg.selectAll(".node")
            .data(nodes)
            .enter()
            .append("g")
            .attr("class", "node")
            .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    //.on("end", dragended)
            ).on("click", function(d){
                nodeDescription.innerHTML =  d.label + ": " + d.description;
            });



        node.append("circle")
            .attr("r", 6)
            .attr("x", function (d) {return colors(d.position*20 + xCenter);})
            .attr("y", function (d) {return colors(yCenter);})
            .style("fill", function (d) {return colors(d.group);})

        node.append("title")
            .text(function (d) {return d.id;});

        node.append("text")
            .attr("dy", -9)
            .text(function (d) {return d.label;});

        simulation
            .nodes(nodes)
            .on("tick", ticked);

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

    function ticked() {

        link
            .attr("x1", function (d) {return d.source.x;})
            .attr("y1", function (d) {return d.source.y;})
            .attr("x2", function (d) {return d.target.x;})
            .attr("y2", function (d) {return d.target.y;});

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

        edgepaths.attr('d', function (d) {
            return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
        });

        edgelabels.attr('transform', function (d) {
            if (d.target.x < d.source.x) {
                var bbox = this.getBBox();

                rx = bbox.x + bbox.width / 2;
                ry = bbox.y + bbox.height / 2;
                return 'rotate(180 ' + rx + ' ' + ry + ')';
            }
            else {
                return 'rotate(0)';
            }
        });
    }

    function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.3).restart()
        d.fx = d.x;
        d.fy = d.y;
    }

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

完整数据集

{
  "graphNodes": [
    {
      "id": "http://www.wikidata.org/entity/Q395",
      "label": "mathematics",
      "description": "field of study (numbers, quantity, structure, relationships, space, change)",
      "group": 0,
      "position": 0
    },
    {
      "id": "http://www.wikidata.org/entity/Q41511",
      "label": "universal language",
      "description": "hypothetical language that is supposed to have been spoken by all or most of the world's population",
      "group": 6,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q4671286",
      "label": "academic major",
      "description": "academic discipline to which a student formally commits",
      "group": 6,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q11862829",
      "label": "academic discipline",
      "description": "concentration in one academic field of study or profession",
      "group": 6,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q475023",
      "label": "exact science",
      "description": "",
      "group": 9,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q816264",
      "label": "formal science",
      "description": "disciplines concerned with formal systems, such as logic, mathematics, and game theory",
      "group": 9,
      "position": -1
    },
    {
      "id": "Mathematics",
      "label": "Mathematics",
      "description": "",
      "group": 13,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q30125896",
      "label": "scientific hypothesis",
      "description": "idea that proposes a tentative explanation about a phenomenon or a narrow set of phenomena observed in the natural world (primary features of a scientific hypothesis: falsifiability, testability)",
      "group": 6,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q2623733",
      "label": "fictional language",
      "description": "language in fictional stories",
      "group": 9,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q1047113",
      "label": "specialty",
      "description": "field limited to a specific area of ​​knowledge",
      "group": 9,
      "position": -2
    },
    {
      "id": "Academic disciplines",
      "label": "Academic disciplines",
      "description": "",
      "group": 13,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q2465832",
      "label": "branch of science",
      "description": "field or discipline of science",
      "group": 6,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q336",
      "label": "science",
      "description": "study and knowledge",
      "group": 9,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q3968",
      "label": "algebra",
      "description": "topic in mathematics and definition is Algebra uses letters (like x or y) or other symbols in place of values, and plays with them using special rules.",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q8087",
      "label": "geometry",
      "description": "branch of mathematics that measures the shape, size and position of objects",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q12479",
      "label": "number theory",
      "description": "branch of pure mathematics devoted primarily to the study of the integers",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q12482",
      "label": "set theory",
      "description": "branch of mathematics that studies sets, which are collections of objects",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q12483",
      "label": "statistics",
      "description": "study of the collection, organization, analysis, interpretation, and presentation of data",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q42989",
      "label": "topology",
      "description": "subfield of mathematics",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q131476",
      "label": "graph theory",
      "description": "study of graphs, which are mathematical structures used to model pairwise relations between objects",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q149972",
      "label": "calculus",
      "description": "branch of mathematics",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q217413",
      "label": "category theory",
      "description": "logic and mathematics",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q5862903",
      "label": "probability theory",
      "description": "branch of mathematics concerned with probability",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q1192971",
      "label": "network theory",
      "description": "study of graphs as a representation of either symmetric relations or, more generally, of asymmetric relations between discrete objects",
      "group": 1,
      "position": 2
    },
    {
      "id": "http://www.wikidata.org/entity/Q149999",
      "label": "differential calculus",
      "description": "subfield of calculus",
      "group": 1,
      "position": 2
    },
    {
      "id": "http://www.wikidata.org/entity/Q150008",
      "label": "integral calculus",
      "description": "subfield of calculus",
      "group": 1,
      "position": 2
    }
  ],
  "graphLinks": [
    {
      "source": "http://www.wikidata.org/entity/Q41511",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q4671286",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q11862829",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q475023",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q816264",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "subclassOf"
    },
    {
      "source": "Mathematics",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "CommonCategory"
    },
    {
      "source": "http://www.wikidata.org/entity/Q30125896",
      "target": "http://www.wikidata.org/entity/Q41511",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q2623733",
      "target": "http://www.wikidata.org/entity/Q41511",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q11862829",
      "target": "http://www.wikidata.org/entity/Q4671286",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q1047113",
      "target": "http://www.wikidata.org/entity/Q11862829",
      "type": "subclassOf"
    },
    {
      "source": "Academic disciplines",
      "target": "http://www.wikidata.org/entity/Q11862829",
      "type": "CommonCategory"
    },
    {
      "source": "http://www.wikidata.org/entity/Q2465832",
      "target": "http://www.wikidata.org/entity/Q475023",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q336",
      "target": "http://www.wikidata.org/entity/Q475023",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q2465832",
      "target": "http://www.wikidata.org/entity/Q816264",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q475023",
      "target": "http://www.wikidata.org/entity/Q816264",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q3968",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q8087",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q12479",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q12482",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q12483",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q42989",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q131476",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q149972",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q217413",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q5862903",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q131476",
      "target": "http://www.wikidata.org/entity/Q1192971",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q149972",
      "target": "http://www.wikidata.org/entity/Q149999",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q149972",
      "target": "http://www.wikidata.org/entity/Q150008",
      "type": "hasPart"
    }
  ],

============================= 编辑 2 ============= ==================

我设法得到了一些非常奇怪的东西 enter image description here

最佳答案

你的代码有很多问题

  • 不使用链接和节点调用 update() 函数
  • 不要将颜色分配给 xy 坐标

    node.append("circle")
        .attr("r", 6)
        .attr("x", function (d) {return colors(d.position*20 + xCenter);})
        .attr("y", function (d) {return colors(yCenter);})
        .style("fill", function (d) {return colors(d.group);})
    

    并且设置这些坐标对力模拟没有影响,即使它们是正确的坐标也是如此

  • 您没有 dragEnd。在停止之前,拖动或单击后模拟会运行很长时间。 alpaTarget 位于 alphaMin 之上,因此它将“永不”停止。最终它会因为某种原因停止,也许它会检测到是否有任何重大的位置变化。我取消了 on-dargenden 行的注释并添加了将 alphaTarget 设置为 0 的函数。

    function dragended(d) {
        if (!d3.event.active) simulation.alphaTarget(0);
        //d.fx = nodeFixX(d); // snap to its target position
        d.fx = null; // or let the force figure it out
        d.fy = null;
    }
    
  • 添加了缺失的样式

    <style>
    .link { stroke: steelblue;}
    </style>
    

    和 HTML

    <div class="node-description"></div>
    <svg width="800" height="600"></svg>
    
  • 为什么要设置两次 forceCenter()

  • 因为我没有您使用的 HTML,所以我禁用了所有关于 graphPlaceholder

你的问题的解决方案是

  • 移除forceCenter()
  • position=0节点锁定到中心位置
  • 使用 forceX() 将节点拉到特定列
  • 已经将节点定位在正确的 X 位置和 y 中心位置。 最好从原点 (0,0)
  • 开始
  • 您可以选择让力量处理 X 位置或将其强制到 X 位置。查找注释 //或让原力解决。我发现使用强制(如完整示例中所设置的那样)对节点重新排序会更容易一些

这里是主要的修改

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function (d) {return d.id;}).distance(columnXFactor)) // .strength(0.2)
    .force("charge", d3.forceManyBody().strength(-60))
    .force("columnX", d3.forceX(n => n.position*columnXFactor + xCenter))  //.strength(0.05)  // or let the force figure it out
    // .force("center", d3.forceCenter(xCenter, yCenter))
    ;

// force 1 node in the center
var pos0Node = data.graphNodes.filter(n => n.position === 0)[0];
pos0Node.fx = xCenter;
pos0Node.fy = yCenter;

function nodeFixX(n) {
    return n.position*columnXFactor + xCenter;
}
data.graphNodes.forEach(n => {
    //n.fx = nodeFixX(n); // snap to its target position
    n.x = nodeFixX(n); // or let the force figure it out
    n.y = yCenter;
});

update(data.graphLinks, data.graphNodes);

完整示例

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Force Mathematics</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<style>
.link { stroke: steelblue;}
</style>
</head>
<body>
<div class="node-description"></div>
<svg width="800" height="600"></svg>
<script>
var data = 
{
  "graphNodes": [
    {
      "id": "http://www.wikidata.org/entity/Q395",
      "label": "mathematics",
      "description": "field of study (numbers, quantity, structure, relationships, space, change)",
      "group": 0,
      "position": 0
    },
    {
      "id": "http://www.wikidata.org/entity/Q41511",
      "label": "universal language",
      "description": "hypothetical language that is supposed to have been spoken by all or most of the world's population",
      "group": 6,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q4671286",
      "label": "academic major",
      "description": "academic discipline to which a student formally commits",
      "group": 6,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q11862829",
      "label": "academic discipline",
      "description": "concentration in one academic field of study or profession",
      "group": 6,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q475023",
      "label": "exact science",
      "description": "",
      "group": 9,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q816264",
      "label": "formal science",
      "description": "disciplines concerned with formal systems, such as logic, mathematics, and game theory",
      "group": 9,
      "position": -1
    },
    {
      "id": "Mathematics",
      "label": "Mathematics",
      "description": "",
      "group": 13,
      "position": -1
    },
    {
      "id": "http://www.wikidata.org/entity/Q30125896",
      "label": "scientific hypothesis",
      "description": "idea that proposes a tentative explanation about a phenomenon or a narrow set of phenomena observed in the natural world (primary features of a scientific hypothesis: falsifiability, testability)",
      "group": 6,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q2623733",
      "label": "fictional language",
      "description": "language in fictional stories",
      "group": 9,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q1047113",
      "label": "specialty",
      "description": "field limited to a specific area of ​​knowledge",
      "group": 9,
      "position": -2
    },
    {
      "id": "Academic disciplines",
      "label": "Academic disciplines",
      "description": "",
      "group": 13,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q2465832",
      "label": "branch of science",
      "description": "field or discipline of science",
      "group": 6,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q336",
      "label": "science",
      "description": "study and knowledge",
      "group": 9,
      "position": -2
    },
    {
      "id": "http://www.wikidata.org/entity/Q3968",
      "label": "algebra",
      "description": "topic in mathematics and definition is Algebra uses letters (like x or y) or other symbols in place of values, and plays with them using special rules.",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q8087",
      "label": "geometry",
      "description": "branch of mathematics that measures the shape, size and position of objects",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q12479",
      "label": "number theory",
      "description": "branch of pure mathematics devoted primarily to the study of the integers",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q12482",
      "label": "set theory",
      "description": "branch of mathematics that studies sets, which are collections of objects",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q12483",
      "label": "statistics",
      "description": "study of the collection, organization, analysis, interpretation, and presentation of data",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q42989",
      "label": "topology",
      "description": "subfield of mathematics",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q131476",
      "label": "graph theory",
      "description": "study of graphs, which are mathematical structures used to model pairwise relations between objects",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q149972",
      "label": "calculus",
      "description": "branch of mathematics",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q217413",
      "label": "category theory",
      "description": "logic and mathematics",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q5862903",
      "label": "probability theory",
      "description": "branch of mathematics concerned with probability",
      "group": 1,
      "position": 1
    },
    {
      "id": "http://www.wikidata.org/entity/Q1192971",
      "label": "network theory",
      "description": "study of graphs as a representation of either symmetric relations or, more generally, of asymmetric relations between discrete objects",
      "group": 1,
      "position": 2
    },
    {
      "id": "http://www.wikidata.org/entity/Q149999",
      "label": "differential calculus",
      "description": "subfield of calculus",
      "group": 1,
      "position": 2
    },
    {
      "id": "http://www.wikidata.org/entity/Q150008",
      "label": "integral calculus",
      "description": "subfield of calculus",
      "group": 1,
      "position": 2
    }
  ],
  "graphLinks": [
    {
      "source": "http://www.wikidata.org/entity/Q41511",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q4671286",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q11862829",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q475023",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q816264",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "subclassOf"
    },
    {
      "source": "Mathematics",
      "target": "http://www.wikidata.org/entity/Q395",
      "type": "CommonCategory"
    },
    {
      "source": "http://www.wikidata.org/entity/Q30125896",
      "target": "http://www.wikidata.org/entity/Q41511",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q2623733",
      "target": "http://www.wikidata.org/entity/Q41511",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q11862829",
      "target": "http://www.wikidata.org/entity/Q4671286",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q1047113",
      "target": "http://www.wikidata.org/entity/Q11862829",
      "type": "subclassOf"
    },
    {
      "source": "Academic disciplines",
      "target": "http://www.wikidata.org/entity/Q11862829",
      "type": "CommonCategory"
    },
    {
      "source": "http://www.wikidata.org/entity/Q2465832",
      "target": "http://www.wikidata.org/entity/Q475023",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q336",
      "target": "http://www.wikidata.org/entity/Q475023",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q2465832",
      "target": "http://www.wikidata.org/entity/Q816264",
      "type": "instanceOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q475023",
      "target": "http://www.wikidata.org/entity/Q816264",
      "type": "subclassOf"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q3968",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q8087",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q12479",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q12482",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q12483",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q42989",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q131476",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q149972",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q217413",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q395",
      "target": "http://www.wikidata.org/entity/Q5862903",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q131476",
      "target": "http://www.wikidata.org/entity/Q1192971",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q149972",
      "target": "http://www.wikidata.org/entity/Q149999",
      "type": "hasPart"
    },
    {
      "source": "http://www.wikidata.org/entity/Q149972",
      "target": "http://www.wikidata.org/entity/Q150008",
      "type": "hasPart"
    }
  ]
};

var nodeDescription = document.querySelector(".node-description")
var colors = d3.scaleOrdinal(d3.schemeCategory10);

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    node,
    link;
var xCenter = width*0.5;
var yCenter = height*0.5;
var columnXFactor = 100;

svg.append('defs').append('marker')
    .attrs({'id':'arrowhead',
        'viewBox':'-0 -5 10 10',
        'refX':13,
        'refY':0,
        'orient':'auto',
        'markerWidth':13,
        'markerHeight':8,
        'xoverflow':'visible'})
    .append('svg:path')
    .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
    .attr('fill', '#999')
    .style('stroke','none');

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function (d) {return d.id;}).distance(columnXFactor)) // .strength(0.2)
    .force("charge", d3.forceManyBody().strength(-60))
    .force("columnX", d3.forceX(n => n.position*columnXFactor + xCenter))  //.strength(0.05)  // or let the force figure it out
    // .force("center", d3.forceCenter(xCenter, yCenter))
    ;

// force 1 node in the center
var pos0Node = data.graphNodes.filter(n => n.position === 0)[0];
pos0Node.fx = xCenter;
pos0Node.fy = yCenter;

function nodeFixX(n) {
    return n.position*columnXFactor + xCenter;
}
data.graphNodes.forEach(n => {
    //n.fx = nodeFixX(n); // snap to its target position
    n.x = nodeFixX(n); // or let the force figure it out
    n.y = yCenter;
});

update(data.graphLinks, data.graphNodes);

function update(links, nodes) {
    // var graphPlaceholder = document.querySelector(".graph-placeholder")
    // width = graphPlaceholder.offsetWidth
    // height = graphPlaceholder.offsetHeight

    // svg
    //     .attr("width", width)
    //     .attr("height", height)

    // simulation.force("center", d3.forceCenter(xCenter, yCenter));

    link = svg.selectAll(".link")
        .data(links)
        .enter()
        .append("line")
        .attr("class", "link")
        .attr('marker-end','url(#arrowhead)')

    link.append("title")
        .text(function (d) {return d.type;});

    edgepaths = svg.selectAll(".edgepath")
        .data(links)
        .enter()
        .append('path')
        .attrs({
            'class': 'edgepath',
            'fill-opacity': 0,
            'stroke-opacity': 0,
            'id': function (d, i) {return 'edgepath' + i}
        })
        .style("pointer-events", "none");

    edgelabels = svg.selectAll(".edgelabel")
        .data(links)
        .enter()
        .append('text')
        .style("pointer-events", "none")
        .attrs({
            'class': 'edgelabel',
            'id': function (d, i) {return 'edgelabel' + i},
            'font-size': 10,
            'fill': '#aaa'
        });

    edgelabels.append('textPath')
        .attr('xlink:href', function (d, i) {return '#edgepath' + i})
        .style("text-anchor", "middle")
        .style("pointer-events", "none")
        .attr("startOffset", "50%")
        .text(function (d) {return d.type});

    node = svg.selectAll(".node")
        .data(nodes)
        .enter()
        .append("g")
        .attr("class", "node")
        .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended)
        ).on("click", function(d){
            nodeDescription.innerHTML =  d.label + ": " + d.description;
        });

    node.append("circle")
        .attr("r", 6)
        //.attr("x", 0) // d => colors(d.position*20 + xCenter)
        //.attr("y", 0) // d => colors(yCenter)
        .style("fill", function (d) {return colors(d.group);})

    node.append("title")
        .text(function (d) {return d.id;});

    node.append("text")
        .attr("dy", -9)
        .text(function (d) {return d.label;});
        //.text(function (d) {return '' + d.position + d.label;});

    simulation
        .nodes(nodes)
        .on("tick", ticked);

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

function ticked() {

    link
        .attr("x1", function (d) {return d.source.x;})
        .attr("y1", function (d) {return d.source.y;})
        .attr("x2", function (d) {return d.target.x;})
        .attr("y2", function (d) {return d.target.y;});

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

    edgepaths.attr('d', function (d) {
        return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
    });

    edgelabels.attr('transform', function (d) {
        if (d.target.x < d.source.x) {
            var bbox = this.getBBox();
            rx = bbox.x + bbox.width / 2;
            ry = bbox.y + bbox.height / 2;
            return 'rotate(180 ' + rx + ' ' + ry + ')';
        }
        return 'rotate(0)';
    });
}

function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart()
    d.fx = d.x;
    d.fy = d.y;
}

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

function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    //d.fx = nodeFixX(d); // snap to its target position
    d.fx = null; // or let the force figure it out
    d.fy = null;
}

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

关于javascript - 如何保持 d3.forceSimulation() 节点均匀分布而不重叠,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51603044/

相关文章:

javascript - 如何向下舍入到最接近的 X 数字 - VBScript 的伪代码

JavaScript 装饰器模式。错误 : Maximum call stack size exceeded

javascript - D3 - 从第二个 CSV 加载数据

javascript - 更改D3 geoCircle的大小和 Angular

javascript - Snap.svg 和动态文本

javascript - 获取溢出隐藏页面中滚动的像素数量

javascript - 如何从同一解决方案中的另一个项目调用 javascript 函数?

javascript - d3.js:获取输入域范围之外的刻度值

html - div 全宽底部的中心三 Angular 形响应

javascript - 从 Adob​​e Illustrator 对象创建动画 SVG/GIF 图像