Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep d3.forceSimulation() nodes evenly spread without overlapping

I am trying to make a graph, where certain nodes should be on the left side, and certain nodes should be on the right side from the "genesis node" which should be in the center of my svg.

Setting up the force:

  • d3.forceManyBody() so the nodes should repel each other
  • d3.forceLink().distance(60).strength(1) so the links will maintain their length
  • d3.forceCenter(width / 2, height / 2) to position the main node in the center. I have also tried removing this but only makes the graph appear in the top left corner with only a tiny bit of a node visible

So at the end it looks like this:

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));

Setting up the nodes: I have also tried setting the attr x and y, instead of cx and cy, but nothing changes. xCenter and yCenter are the center of the svg where the graph is displayed.

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);})

Updating the simulation

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

I have also tried this, but not luck:

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

This is what I get :( enter image description here

And this is more what I was looking for (I can set it up by dragging myself I just want it to appear like this directly) enter image description here

Data source structure: The position 0 means in the center, negative positions mean to go to the left, positive positions mean to go to the right

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

=============================== EDIT ==================================

POSITION CALCULATION:

Each node has a position number, negative means to the left, positive means to the right. 1 level to the left is -1, 2 levels to the left is -2. 1 level to the right is 1, 2 levels to the right is 2.

X position is like this: X = d.position*20 + xCenter // so each level is 20 pixeles apart

Y position I just put = yCenter, so they are all vertically centered, and then I hoped because the nodes where charged to repel each other they would distribute evenly and vertically

Full working code:

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;
    }

Full dataset

{
  "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"
    }
  ],

}

============================= EDIT 2 ===============================

I managed to get something very weird enter image description here

like image 203
PMT Avatar asked Jul 30 '18 22:07

PMT


1 Answers

There are a number of things wrong with your code

  • no call to the update() function with the links and nodes
  • do not assign colors to circle x and y coords

    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);})
    

    and setting these coords have no influence on the force simulation even if they are the correct coords

  • you do not have a dragEnd. The simulation runs very long after a drag or a click before it stops. The alpaTarget is above alphaMin so it will "never" stop. Eventually it will stop for some reason, maybe it detects if there is any significant position change. I have uncommented the on-dargenden line and added the function to set an alphaTarget of 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;
    }
    
  • added the missing style

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

    and HTML

    <div class="node-description"></div>
    <svg width="800" height="600"></svg>
    
  • why do you set the forceCenter() twice?

  • because I do not have the HTML you use I disabled all regarding graphPlaceholder

The solution to your problem is

  • remove the forceCenter() force
  • lock the position=0 node to the center position
  • use a forceX() to pull the nodes to a specific column
  • already position the nodes at the correct X position and yCenter position. Better then starting them all from the origin (0,0)
  • you can choose to let the force deal the X-placement or force it to the X-position. Look for the comment // or let the force figure it out. I find using the force (as set in the complete example) reordering the nodes a bit easier

Here is the main modification

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);

Complete Example

<!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>
like image 103
rioV8 Avatar answered Oct 14 '22 06:10

rioV8