如何使d3.forceSimulation()节点均匀散布而不重叠

时间:2018-07-30 22:22:01

标签: 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位置我只放置了= 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

1 个答案:

答案 0 :(得分:7)

您的代码有很多错误

  • 不使用链接和节点调用update()函数
  • 请勿为circle 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。拖动或单击后,模拟运行很长时间才停止。 alpaTargetalphaMin上方,因此它将“永不”停止。最终它将由于某种原因而停止运行,也许它会检测位置是否有重大变化。我已经取消注释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

    <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位置和yCenter位置。 最好从原点(0,0)
  • 开始
  • 您可以选择让力进行X放置或将其强制到X位置。寻找评论// or let the force figure it out。我发现使用力(如完整示例中所设置的)对节点重新排序要容易一些

这是主要修改

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>