0

Below is my code snippet where I would like to have multiple arrowhead markers at the end of my path pointing it towards the children instead of the parent. But I have been able to point a single arrow marker towards my parent but not a child. Please let me know what needs to be done The below image shows the arrow pointing towards the parent. I would like to have it point towards children.

The below image shows is the plain code without arrow markers Tree Diagram

I would like to see the below image like type functionality to my code Code with markers

<!DOCTYPE html>
<meta charset="UTF-8">
<style>
    .node circle {
        fill: #fff;
        stroke: steelblue;
        stroke-width: 3px;
    }

    .node text {
        font: 12px sans-serif;
    }

    .link {
        fill: none;
        stroke: #ccc;
        stroke-width: 2px;
    }

    .link path {
        fill: none;
        stroke: #ccc;
        stroke-width: 2px;
    }

    .link text {
        font: 12px sans-serif;
        stroke: #333;
        stroke-width: 1;
    }
</style>

<body>

    <!-- load the d3.js library -->
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script>

        var treeData =
        {
            "name": "Top Level",
            "linkname": "null",
            "children": [
                {
                    "name": "Level 2: A",
                    "linkname": "Link_1",
                    "children": [
                        { "name": "Son of A", "linkname": "Link_2.1" },
                        { "name": "Daughter of A", "linkname": "Link_2.2" }
                    ]
                },
                { "name": "Level 2: B", "linkname": "Link_3", }
            ]
        };

        // Set the dimensions and margins of the diagram
        var margin = { top: 20, right: 90, bottom: 30, left: 90 },
            width = 960 - margin.left - margin.right,
            height = 500 - margin.top - margin.bottom;

        // append the svg object to the body of the page
        // appends a 'group' element to 'svg'
        // moves the 'group' element to the top left margin
        var svg = d3.select("body").append("svg")
            .attr("width", width + margin.right + margin.left)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate("
                + margin.left + "," + margin.top + ")");

        var i = 0,
            duration = 750,
            root;

        // declares a tree layout and assigns the size
        var treemap = d3.tree().size([height, width]);

        // Assigns parent, children, height, depth
        root = d3.hierarchy(treeData, function (d) { return d.children; });
        root.x0 = height / 2;
        root.y0 = 0;

        // Collapse after the second level
        root.children.forEach(collapse);

        update(root);

        // Collapse the node and all it's children
        function collapse(d) {
            if (d.children) {
                d._children = d.children
                d._children.forEach(collapse)
                d.children = null
            }
        }

        function update(source) {

            // Assigns the x and y position for the nodes
            var treeData = treemap(root);

            // Compute the new tree layout.
            var nodes = treeData.descendants(),
                links = treeData.descendants().slice(1);

            // Normalize for fixed-depth.
            nodes.forEach(function (d) {
                d.y = d.depth * 180
            });

            // ****************** Nodes section ***************************

            // Update the nodes...
            var node = svg.selectAll('g.node')
                .data(nodes, function (d) {
                    return d.id || (d.id = ++i);
                });

            // Enter any new modes at the parent's previous position.
            var nodeEnter = node.enter().append('g')
                .attr('class', 'node')
                .attr("transform", function (d) {
                    return "translate(" + source.y0 + "," + source.x0 + ")";
                })
                .on('click', click);

            // Add Circle for the nodes
            nodeEnter.filter(function (d) {
                return (!d.data.type || d.data.type !== 'data');
            }).append('circle')
                .attr('class', 'node')
                .attr('r', 1e-6)
                .style("fill", function (d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            nodeEnter.filter(function (d) {
                return (d.data.type && d.data.type === 'data');
            }).append('rect')
                .attr('class', 'node')
                .attr('width', 20)
                .attr('height', 20)
                .attr('y', -10)
                .attr('x', -10)
                .style("fill", function (d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            // Add labels for the nodes
            nodeEnter.append('text')
                .attr("dy", "2em")
                .attr("x", function (d) {
                    return d.children || d._children ? 13 : 13;
                })
                .attr("text-anchor", function (d) {
                    return d.children || d._children ? "start" : "start";
                })
                .text(function (d) {
                    return d.data.name;
                });

            // UPDATE
            var nodeUpdate = nodeEnter.merge(node);

            // Transition to the proper position for the node
            nodeUpdate.transition()
                .duration(duration)
                .attr("transform", function (d) {
                    return "translate(" + d.y + "," + d.x + ")";
                });

            // Update the node attributes and style
            nodeUpdate.select('circle.node')
                .attr('r', 10)
                .style("fill", function (d) {
                    return d._children ? "lightsteelblue" : "#fff";
                })
                .attr('cursor', 'pointer');


            // Remove any exiting nodes
            var nodeExit = node.exit().transition()
                .duration(duration)
                .attr("transform", function (d) {
                    return "translate(" + source.y + "," + source.x + ")";
                })
                .remove();

            // On exit reduce the node circles size to 0
            nodeExit.select('circle')
                .attr('r', 1e-6);

            // On exit reduce the opacity of text labels
            nodeExit.select('text')
                .style('fill-opacity', 1e-6);

            // ****************** links section ***************************

            // Update the links...
            var link = svg.selectAll('g.link')
                .data(links, function (d) {
                    return d.id;
                });

            // Enter any new links at the parent's previous position.
            var linkEnter = link.enter().insert('g', 'g')
                .attr("class", "link");

            linkEnter.append('text')
                .attr("class","linkLabels")
                .text(function (d, i) {
                    if (d.parent && d.parent.children.length > 1) {
                        if (!d.parent.index) d.parent.index = 0;
                        return d.data.linkname;
                    }
                })
                .attr("opacity",0)
                .attr('dy', "-1em");

            linkEnter.append('path')
                .attr('d', function (d) {
                    var o = {
                        x: source.x0,
                        y: source.y0
                    }
                    return diagonal(o, o)
                })
                .on("mouseover", function(){
                    d3.select(this.parentNode).select("text").attr("opacity",1);
                })
                .on("mouseleave", function(){
                    d3.select(this.parentNode).select("text").attr("opacity",0);
                })


            // UPDATE
            var linkUpdate = linkEnter.merge(link);

            // Transition back to the parent element position
            linkUpdate.select('path').transition()
                .duration(duration)
                .attr('d', function (d) {
                    return diagonal(d, d.parent)
                });

            linkUpdate.select('text').transition()
                .duration(duration)
                .attr('transform', function (d) {
                    if (d.parent) {
                        return 'translate(' + ((d.parent.y + d.y) / 2) + ',' + ((d.parent.x + d.x) / 2) + ')'
                    }
                })

            // Remove any exiting links
            link.exit().each(function (d) {
                d.parent.index = 0;
            })

            var linkExit = link.exit()
                .transition()
                .duration(duration);

            linkExit.select('path')
                .attr('d', function (d) {
                    var o = {
                        x: source.x,
                        y: source.y
                    }
                    return diagonal(o, o)
                })

            linkExit.select('text')
                .style('opacity', 0);

            linkExit.remove();

            // Store the old positions for transition.
            nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });

            // Creates a curved (diagonal) path from parent to the child nodes
            function diagonal(s, d) {
                path = `M ${s.y} ${s.x}
                        C ${(s.y + d.y) / 2} ${s.x},
                            ${(s.y + d.y) / 2} ${d.x},
                            ${d.y} ${d.x}`
                return path
            }

            // Toggle children on click.
            function click(d) {
                if (d.children) {
                    d._children = d.children;
                    d.children = null;
                } else {
                    d.children = d._children;
                    d._children = null;
                }
                update(d);
            }
        }


    </script>
</body>
Aryama I
  • 77
  • 1
  • 10
  • Have you tried `.attr("marker-start", "url(#end)");` instead of `.attr("marker-end", "url(#end)");`? – altocumulus Feb 21 '20 at 10:01
  • Tried It. No luck – Aryama I Feb 21 '20 at 10:24
  • Try to **also** set `.attr("refX", 18)` to `.attr("refX", -18)` to not have the arrow be placed under the rectangle. – altocumulus Feb 21 '20 at 10:48
  • It's working but arrow alignment is missing. Did both the steps u suggested. Added the new image in question. Please look into it and help out – Aryama I Feb 21 '20 at 11:14
  • You should really set up a [mcve] to make it easier to help you out. Your code as it is is not even executable. You could ditch all the JavaScript and create just a reduced SVG as this has nothing to do with how you create the SVG but with the contents of the created SVG. That way the problem will stick out much clearer. – altocumulus Feb 21 '20 at 11:18
  • I edited the question with more executable code. I was using it for cross-platform. So modified it now. Please let know what needs to be added – Aryama I Feb 21 '20 at 11:55

1 Answers1

1

Probably not 100% what you want, but can be a starting point for you, basically I did it by manipulating marker attributes .attr("orient", "auto-start-reverse") check the MDN documentation for more information about this attribute, also refX, and refY finally the path attribute I used marker-start instead of marker-end since in this example the path drawn from the children to the parent not the reverse

if this is not what you want, there is another stack-overflow question that is a bit close to your case but its not a tree but can help:

Align Marker on node edges D3 Force Layout

var treeData = {
  name: "Top Level",
  linkname: "null",
  children: [
    {
      name: "Level 2: A",
      linkname: "Link_1",
      children: [
        { name: "Son of A", linkname: "Link_2.1" },
        { name: "Daughter of A", linkname: "Link_2.2" }
      ]
    },
    { name: "Level 2: B", linkname: "Link_3" }
  ]
};

// Set the dimensions and margins of the diagram
var margin = { top: 20, right: 90, bottom: 30, left: 90 },
  width = 960 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom;

// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3
  .select("body")
  .append("svg")
  .attr("width", width + margin.right + margin.left)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

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", 15)
  .attr("refY", 0)
  .attr("markerWidth", 6)
  .attr("markerHeight", 6)
  .attr("orient", "auto-start-reverse")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

var i = 0,
  duration = 750,
  root;

// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);

// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) {
  return d.children;
});
root.x0 = height / 2;
root.y0 = 0;

// Collapse after the second level
root.children.forEach(collapse);

update(root);

// Collapse the node and all it's children
function collapse(d) {
  if (d.children) {
    d._children = d.children;
    d._children.forEach(collapse);
    d.children = null;
  }
}

function update(source) {
  // Assigns the x and y position for the nodes
  var treeData = treemap(root);

  // Compute the new tree layout.
  var nodes = treeData.descendants(),
    links = treeData.descendants().slice(1);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 180;
  });

  // ****************** Nodes section ***************************

  // Update the nodes...
  var node = svg.selectAll("g.node").data(nodes, function(d) {
    return d.id || (d.id = ++i);
  });

  // Enter any new modes at the parent's previous position.
  var nodeEnter = node
    .enter()
    .append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click", click);

  // Add Circle for the nodes
  nodeEnter
    .filter(function(d) {
      return !d.data.type || d.data.type !== "data";
    })
    .append("circle")
    .attr("class", "node")
    .attr("r", 1e-6)
    .style("fill", function(d) {
      return d._children ? "lightsteelblue" : "#fff";
    });

  nodeEnter
    .filter(function(d) {
      return d.data.type && d.data.type === "data";
    })
    .append("rect")
    .attr("class", "node")
    .attr("width", 20)
    .attr("height", 20)
    .attr("y", -10)
    .attr("x", -10)
    .style("fill", function(d) {
      return d._children ? "lightsteelblue" : "#fff";
    });

  // Add labels for the nodes
  nodeEnter
    .append("text")
    .attr("dy", "2em")
    .attr("x", function(d) {
      return d.children || d._children ? 13 : 13;
    })
    .attr("text-anchor", function(d) {
      return d.children || d._children ? "start" : "start";
    })
    .text(function(d) {
      return d.data.name;
    });

  // UPDATE
  var nodeUpdate = nodeEnter.merge(node);

  // Transition to the proper position for the node
  nodeUpdate
    .transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  // Update the node attributes and style
  nodeUpdate
    .select("circle.node")
    .attr("r", 10)
    .style("fill", function(d) {
      return d._children ? "lightsteelblue" : "#fff";
    })
    .attr("cursor", "pointer");

  // Remove any exiting nodes
  var nodeExit = node
    .exit()
    .transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  // On exit reduce the node circles size to 0
  nodeExit.select("circle").attr("r", 1e-6);

  // On exit reduce the opacity of text labels
  nodeExit.select("text").style("fill-opacity", 1e-6);

  // ****************** links section ***************************

  // Update the links...
  var link = svg.selectAll("g.link").data(links, function(d) {
    return d.id;
  });

  // Enter any new links at the parent's previous position.
  var linkEnter = link
    .enter()
    .insert("g", "g")
    .attr("class", "link");

  linkEnter
    .append("text")
    .attr("class", "linkLabels")
    .text(function(d, i) {
      if (d.parent && d.parent.children.length > 1) {
        if (!d.parent.index) d.parent.index = 0;
        return d.data.linkname;
      }
    })
    .attr("opacity", 0)
    .attr("dy", "-1em");

  linkEnter
    .append("path")
    .attr("d", function(d) {
      var o = {
        x: source.x0,
        y: source.y0
      };
      return diagonal(o, o);
    })
    .on("mouseover", function() {
      d3.select(this.parentNode)
        .select("text")
        .attr("opacity", 1);
    })
    .on("mouseleave", function() {
      d3.select(this.parentNode)
        .select("text")
        .attr("opacity", 0);
    })
    .attr("stroke-linecap", "round")
    .attr("marker-start", "url(#end)");

  // UPDATE
  var linkUpdate = linkEnter.merge(link);

  // Transition back to the parent element position
  linkUpdate
    .select("path")
    .transition()
    .duration(duration)
    .attr("d", function(d) {
      return diagonal(d, d.parent);
    });

  linkUpdate
    .select("text")
    .transition()
    .duration(duration)
    .attr("transform", function(d) {
      if (d.parent) {
        return (
          "translate(" +
          (d.parent.y + d.y) / 2 +
          "," +
          (d.parent.x + d.x) / 2 +
          ")"
        );
      }
    });

  // Remove any exiting links
  link.exit().each(function(d) {
    d.parent.index = 0;
  });

  var linkExit = link
    .exit()
    .transition()
    .duration(duration);

  linkExit.select("path").attr("d", function(d) {
    var o = {
      x: source.x,
      y: source.y
    };
    return diagonal(o, o);
  });

  linkExit.select("text").style("opacity", 0);

  linkExit.remove();

  // Store the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });

  // Creates a curved (diagonal) path from parent to the child nodes
  function diagonal(s, d) {
    path = `M ${s.y} ${s.x}
            C ${(s.y + d.y) / 2} ${s.x},
                ${(s.y + d.y) / 2} ${d.x},
                ${d.y} ${d.x}`;
    return path;
  }

  // Toggle children on click.
  function click(d) {
    if (d.children) {
      d._children = d.children;
      d.children = null;
    } else {
      d.children = d._children;
      d._children = null;
    }
    update(d);
  }
}
.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 3px;
}

.node text {
  font: 12px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 2px;
}

.link path {
  fill: none;
  stroke: #ccc;
  stroke-width: 2px;
}

.link text {
  font: 12px sans-serif;
  stroke: #333;
  stroke-width: 1;
}
<!DOCTYPE html>
<meta charset="UTF-8">
<style>
    
</style>

<body>
  <!-- load the d3.js library -->
  <script src="https://d3js.org/d3.v4.min.js"></script>
</body>
ROOT
  • 11,363
  • 5
  • 30
  • 45
  • Hi if we are having more childs then the alignment of the arrow goes above the link not in same position can you please resolve it – Aryama I Feb 24 '20 at 09:44