Search code examples
d3.jsgraphlocationnodesdirected-graph

Locating the nodes according to the coordinates in the json


I am trying to make the nodes of the directed network appear to be located at the coordinates reflected in the json document when the page is started. However, the actual code recalculates the node positions, which does not fit the requirements of my application . Any help?

MAIN CODE

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style type="text/css">
    .node {}

    .link { 
        stroke: #999; 
        stroke-opacity: .6; 
        stroke-width: 1px; 
        }
    line {
        stroke: rgb(212, 212, 212);
        stroke-width: 1px;
        shape-rendering: crispEdges; 
        }
    svg { 
        box-sizing: border-box;
        border: 1px solid rgb(212, 212, 212);
        }
    </style>
    </head>
    <body>
    <svg width="960" height="600"></svg>

    <script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
    <script src="https://d3js.org/d3-selection-multi.v1.js"></script>

    <script type="text/javascript">

    var width = 960,
    height = 500,
    resolution = 150,
    r = 15;

    var colors = d3.scaleOrdinal(d3.schemeCategory10);

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

    svg.selectAll('.vertical')
    .data(d3.range(1, width / resolution))
    .enter().append('line')
    .attr('class', 'vertical')
    .attr('x1', function(d) { return d * resolution; })
    .attr('y1', 0)
    .attr('x2', function(d) { return d * resolution; })
    .attr('y2', height);

    svg.selectAll('.horizontal')
    .data(d3.range(1, height / resolution))
    .enter().append('line')
    .attr('class', 'horizontal')
    .attr('x1', 0)
    .attr('y1', function(d) { return d * resolution; })
     .attr('x2', width)
    .attr('y2', function(d) { return d * resolution; });

    svg.append('defs').append('marker')
    .attrs({'id':'arrowhead',
        'viewBox':'-0 -5 10 10',
        'refX':13,
        'refY':0,
        'orient':'auto',
        'markerWidth':13,
        'markerHeight':13,
        '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(150).strength(1))
    //.force("charge", d3.forceManyBody())
    //.force("center", d3.forceCenter(width / 2, height / 2));

    d3.json("graph.json", function (error, graph) {
    if (error) throw error;
    update(graph.links, graph.nodes);
    })

    function update(links, nodes) {
    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' + d.type}
        })
        .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': 15,
            'fill': '#000'
        });

    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")
        .attr('cx', function(d) { return d.x; })
        .attr('cy', function(d) { return d.y; })
        .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                //.on("end", dragended)
        );

    node.append("circle")
        .attr("r", 15)
        .style("fill", function (d, i) {return colors(i);})

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

    node.append("text")
        .attr("dy", -3)
        .text(function (d) {return d.name+":"+d.social;});

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

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

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

    gridX = round(Math.max(r, Math.min(width - r, d.fx)), resolution);
    gridY = round(Math.max(r, Math.min(height - r, d.fy)), resolution);

    d3.select(this).attr('cx', d.fx = gridX).attr('cy', d.fy = gridY);


    }

    function round(p, n) {
    return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
    }

    </script>

    </body>
    </html>

Here I attach the data of the json document that hosts the directed graph data:

    {
     "nodes": [
    {
      "name": "1",
      "label": "1",
      "social": "I",
      "id": 1,
      "x": 150, 
      "y": 450
    },
    {
      "name": "2",
      "label": "2",
      "social": "G",
      "id": 1,
      "x": 300, 
      "y": 150
    },
    {
      "name": "3",
      "label": "3",
      "social": "T",
      "id": 1,
      "x": 450, 
      "y": 300
    }
    ],
    "links": [
    {
      "source": 1,
      "target": 2,
      "type": "N:1"
    },
    {
      "source": 2,
      "target": 3,
      "type": "1:N"
    }
    ]
    }

Solution

  • This code will not work:

    node = svg.selectAll(".node")
      .data(nodes)
      .enter()
      .append("g")
      .attr("class", "node")
      .attr('cx', function(d) { return d.x; })
      .attr('cy', function(d) { return d.y; })
      ...
    

    replace it with:

    node = svg.selectAll(".node")
      .data(nodes)
      .enter()
      .append('g')
      .classed('node', true)
      .attr('transform', d => `translate(${d.x},${d.y})`)
      ...
    

    <g> element does not have cx and cy attributes, it uses transform instead. Also, d3 classed routine is more usable than attr('class').

    UPD: The newly created links are missing their initial coordinates:

    link = svg.selectAll(".link")
      .data(links)
      .enter()
      .append("line")
      .classed("link", true)
      .attr('marker-end','url(#arrowhead)')
      .attr("x1", d => d.source.x)
      .attr("x2", d => d.target.x)
      .attr("y1", d => d.source.y)
      .attr("y1", d => d.target.y)