2

My question is further modification of Force directed graph from here. Here from every source to target it has a arrow at the target end this works fine however if we stick to the theory that all circle are with radius 5.

However in my case I have modified the example that all the nodes will be of different radius based on a parameter, so in such scenario the arrow hides behind the target as the radius is big so the path and arrow hides beside, Im not looking for a solution to bring path and arrow in front instead what I'm trying is to find the new point. In layman language I need to subtract the radius unit from the path so that I get a point on the outer circle.

Sample fiddle is HERE

I guess some modification to this code will be required here in tick function

  function tick() {
    path.attr("d", function(d) {
        var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y,
                dr = Math.sqrt(dx * dx + dy * dy);
        return "M" +
                d.source.x + "," +
                d.source.y + "A" +
                dr + "," + dr + " 0 0,1 " +
                d.target.x + "," +
                d.target.y;
   });

I tried to modify this code as HERE but not a good fix it behaving weird Can anyone please comment how can we calculate this point

Vinod Louis
  • 4,812
  • 1
  • 25
  • 46

2 Answers2

1

One way would be to show the marker in the middle

doing like this:

.attr("marker-mid", "url(#end)");

instead of

.attr("marker-end", "url(#end)");

working example here

Cyril Cherian
  • 32,177
  • 7
  • 46
  • 55
  • 1
    Yeah this is also an good alternative at least the context remains same, Is there anything you find with the point to be calculated that would really help me – Vinod Louis Nov 28 '16 at 12:03
1

This is a "classic" solution (based on this answer):

path.attr("d", function(d) {

    diffX = d.target.x - d.source.x;
    diffY = d.target.y - d.source.y;

    pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));

    offsetX = (diffX * d.target.radius) / pathLength;
    offsetY = (diffY * d.target.radius) / pathLength;


    var dx = (d.target.x - offsetX) - d.source.x,
        dy = (d.target.y - offsetY) - d.source.y,
        dr = Math.sqrt(dx * dx + dy * dy);
    return "M" +
        d.source.x + "," +
        d.source.y + "A" +
        dr + "," + dr + " 0 0,1 " +
        (d.target.x - offsetX) + "," +
        (d.target.y - offsetY);
});

The only problem with this math is that the arc starts and ends in the straight line going from the source to the target. That gives the impression that the arrowhead is a little bit "to the right".

Here is the demo:

// get the data
var graph = {
    "nodes": [{
        "name": "Ilya I",
        "group": 0
    }, {
        "name": "Betty  B",
        "group": 1
    }, {
        "name": "Andy N",
        "group": 2
    }, {
        "name": "Harper P",
        "group": 3
    }, {
        "name": "Holly V",
        "group": 4
    }, {
        "name": "Elijah W",
        "group": 5
    }, {
        "name": "Kalvin L",
        "group": 6
    }, {
        "name": "Chris  D",
        "group": 7
    }, {
        "name": "Alexa U",
        "group": 8
    }],
    "links": [{
        "source": 2,
        "target": 5,
        "value": 1,
        "type": "arrow"
    }, {
        "source": 2,
        "target": 6,
        "value": 3,
        "type": "arrow"
    }, {
        "source": 2,
        "target": 7,
        "value": 1,
        "type": "arrow"
    }, {
        "source": 2,
        "target": 8,
        "value": 1,
        "type": "arrow"
    }, {
        "source": 3,
        "target": 2,
        "value": 1,
        "type": "arrow"
    }, {
        "source": 3,
        "target": 4,
        "value": 1,
        "type": "arrow"
    }, {
        "source": 3,
        "target": 6,
        "value": 2,
        "type": "arrow"
    }, {
        "source": 3,
        "target": 7,
        "value": 1,
        "type": "arrow"
    }, {
        "source": 3,
        "target": 8,
        "value": 3,
        "type": "arrow"
    }, {
        "source": 5,
        "target": 5,
        "value": 1,
        "type": "arrow"
    }, {
        "source": 6,
        "target": 2,
        "value": 1,
        "type": "arrow"
    }]
};

var nodecolor = d3.scale.category20();

var nodes = {};

// Compute the distinct nodes from the links.
var links = graph.links;

var width = 500,
    height = 400;

var force = d3.layout.force()
    .nodes(graph.nodes)
    .links(links)
    .size([width, height])
    .linkDistance(function(d) {
        return 1 / d.value * 250;
    })
    .charge(-500)
    .on("tick", tick)
    .start();

// Set the range
var v = d3.scale.linear().range([0, 100]);

// Scale the range of the data
v.domain([0, d3.max(links, function(d) {
    return d.value;
})]);

// asign a type per value to encode opacity
links.forEach(function(link) {
    if (v(link.value) <= 25) {
        link.type = "twofive";
    } else if (v(link.value) <= 50 && v(link.value) > 25) {
        link.type = "fivezero";
    } else if (v(link.value) <= 75 && v(link.value) > 50) {
        link.type = "sevenfive";
    } else if (v(link.value) <= 100 && v(link.value) > 75) {
        link.type = "onezerozero";
    }
});

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

// build the arrow.
svg.append("svg:defs").selectAll("marker")
    .data(["end"]) // Different link/path types can be defined here
    .enter().append("svg:marker") // This section adds in the arrows
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 10)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");

// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
    .data(force.links())
    .enter().append("svg:path")
    .attr("class", function(d) {
        return "link " + d.type;
    })
    .attr("marker-end", "url(#end)");

// define the nodes
var node = svg.selectAll(".node")
    .data(force.nodes())
    .enter().append("g")
    .attr("class", "node")
    //.on("click", click)
    //.on("dblclick", dblclick)
    .call(force.drag);

// add the nodes
node.append("circle")
    .attr("r", function(d) {
        d.radius = d.group * 5;
        return d.radius
    })
    .style("fill", function(d) {
        return nodecolor(d.group);
    });

// add the text
node.append("text")
    .attr("x", 12)
    .attr("dy", ".35em")
    .text(function(d) {
        return d.name;
    });

// add the curvy lines
function tick() {
    path.attr("d", function(d) {

        diffX = d.target.x - d.source.x;
        diffY = d.target.y - d.source.y;

        pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));

        offsetX = (diffX * d.target.radius) / pathLength;
        offsetY = (diffY * d.target.radius) / pathLength;


        var dx = (d.target.x - offsetX) - d.source.x,
            dy = (d.target.y - offsetY) - d.source.y,
            dr = Math.sqrt(dx * dx + dy * dy);
        return "M" +
            d.source.x + "," +
            d.source.y + "A" +
            dr + "," + dr + " 0 0,1 " +
            (d.target.x - offsetX) + "," +
            (d.target.y - offsetY);
    });

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

// action to take on mouse click
function click() {
    d3.select(this).select("text").transition()
        .duration(750)
        .attr("x", 22)
        .style("fill", "steelblue")
        .style("stroke", "lightsteelblue")
        .style("stroke-width", ".5px");
    d3.select(this).select("circle").transition()
        .duration(750)
        .attr("r", 16)
        .style("fill", function(d) {
            return nodecolor(d.group);
        });
}

// action to take on mouse double click
function dblclick() {
    d3.select(this).select("circle").transition()
        .duration(750)
        .attr("r", 6)
        .style("fill", function(d) {
            return nodecolor(d.group);
        });
    d3.select(this).select("text").transition()
        .duration(750)
        .attr("x", 12)
        .style("stroke", "none")
        .style("fill", "black")
        .style("stroke", "none")
        .style("font", "10px sans-serif");
}
path.link {
        fill: none;
        stroke: #666;
        stroke-width: 1.5px;
    }

    path.link.twofive {
        opacity: 0.25;
    }

    path.link.fivezero {
        opacity: 0.50;
    }

    path.link.sevenfive {
        opacity: 0.75;
    }

    path.link.onezerozero {
        opacity: 1.0;
    }

    circle {
        fill: #ccc;
        stroke: #fff;
        stroke-width: 1.5px;
    }

    text {
        fill: #000;
        pointer-events: none;
    }
    #content {
        padding: 7px;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Community
  • 1
  • 1
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171