NoStressDeveloper
NoStressDeveloper

Reputation: 543

How to invert direction of nodes in diagram in d3.js

So d3.js i am using to display a nodes diagram. right now parent start form left and children on right. is there any way to invert that direction so that children on left and parent on right.

Below is the function render tree which will display tree node. I call renderTree for example like vm.renderTree(vm.tree, "#tree-container");

  vm.renderTree = function (treeData, treeId) {
    var totalNodes = 0;
    var maxLabelLength = 0;
    var selectedNode = null;
    var draggingNode = null;
    var panSpeed = 200;
    var panBoundary = 20;
    var i = 0;
    var duration = 750;
    var root;
    var viewerWidth = $(document).width();
    var viewerHeight = $(document).height();
    vm.tree = d3.layout.tree()
        .size([viewerHeight, viewerWidth]);
    var diagonal = d3.svg.diagonal()
        .projection(function (d) {
        return [d.y, d.x];
    });
    function visit(parent, visitFn, childrenFn) {
        if (!parent)
            return;
        visitFn(parent);
        var children = childrenFn(parent);
        if (children) {
            var count = children.length;
            for (var i = 0; i < count; i++) {
                visit(children[i], visitFn, childrenFn);
            }
        }
    }
    visit(treeData, function (d) {
        totalNodes++;
        if (typeof d.lename !== "undefined") {
            if (treeId == "#tree-container-legalTree") {
                maxLabelLength = Math.max(d.lename.length, maxLabelLength);
            }
            else {
                maxLabelLength = Math.max(d.name.length, maxLabelLength);
            }
        }
    }, function (d) {
        return d.children && d.children.length > 0 ? d.children : null;
    });
    var sortTree = function () {
        vm.tree.sort(function (a, b) {
            if (typeof d !== "undefined") {
                if (treeId == "#tree-container-legalTree") {
                    return b.lename.toLowerCase() < a.lename.toLowerCase() ? 1 : -1;
                }
                else {
                    return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
                }
            }
        });
    };
    sortTree();
    var pan = function (domNode, direction) {
        var speed = panSpeed;
        if (panTimer) {
            clearTimeout(panTimer);
            var translateCoords = d3.transform(svgGroup.attr("transform"));
            if (direction == 'left' || direction == 'right') {
                translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
                translateY = translateCoords.translate[1];
            }
            else if (direction == 'up' || direction == 'down') {
                translateX = translateCoords.translate[0];
                translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
            }
            scaleX = translateCoords.scale[0];
            scaleY = translateCoords.scale[1];
            scale = zoomListener.scale();
            svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
            d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
            zoomListener.scale(zoomListener.scale());
            zoomListener.translate([translateX, translateY]);
            panTimer = setTimeout(function () {
                pan(domNode, speed, direction);
            }, 50);
        }
    };
    function zoom() {
        svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
    }
    var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
    var initiateDrag = function (d, domNode) {
        draggingNode = d;
        d3.select(domNode).select('.ghostCircle').attr('pointer-events', 'none');
        d3.selectAll('.ghostCircle').attr('class', 'ghostCircle show');
        d3.select(domNode).attr('class', 'node activeDrag');
        svgGroup.selectAll("g.node").sort(function (a, b) {
            if (a.id != draggingNode.id)
                return 1;
            else
                return -1;
        });
        if (nodes.length > 1) {
            links = vm.tree.links(nodes);
            nodePaths = svgGroup.selectAll("path.link")
                .data(links, function (d) {
                return d.target.id;
            }).remove();
            nodesExit = svgGroup.selectAll("g.node")
                .data(nodes, function (d) {
                return d.id;
            }).filter(function (d, i) {
                if (d.id == draggingNode.id) {
                    return false;
                }
                return true;
            }).remove();
        }
        parentLink = vm.tree.links(vm.tree.nodes(draggingNode.parent));
        svgGroup.selectAll('path.link').filter(function (d, i) {
            if (d.target.id == draggingNode.id) {
                return true;
            }
            return false;
        }).remove();
        dragStarted = null;
    };
    var baseSvg = d3.select(treeId).append("svg")
        .attr("width", viewerWidth)
        .attr("height", viewerHeight)
        .attr("class", "overlay")
        .call(zoomListener);
    dragListener = d3.behavior.drag()
        .on("dragstart", function (d) {
        if (d == root) {
            return;
        }
        dragStarted = true;
        nodes = vm.tree.nodes(d);
        d3.event.sourceEvent.stopPropagation();
    })
        .on("drag", function (d) {
        if (d == root) {
            return;
        }
        if (dragStarted) {
            domNode = vm;
            initiateDrag(d, domNode);
        }
        var relCoords = d3.mouse($('svg').get(0));
        if (relCoords[0] < panBoundary) {
            panTimer = true;
            pan(vm, 'left');
        }
        else if (relCoords[0] > ($('svg').width() - panBoundary)) {
            panTimer = true;
            pan(vm, 'right');
        }
        else if (relCoords[1] < panBoundary) {
            panTimer = true;
            pan(vm, 'up');
        }
        else if (relCoords[1] > ($('svg').height() - panBoundary)) {
            panTimer = true;
            pan(vm, 'down');
        }
        else {
            try {
                clearTimeout(panTimer);
            }
            catch (e) {
            }
        }
        d.x0 += d3.event.dy;
        d.y0 += d3.event.dx;
        var node = d3.select(vm);
        node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")");
        updateTempConnector();
    }).on("dragend", function (d) {
        if (d == root) {
            return;
        }
        domNode = vm;
        if (selectedNode) {
            var index = draggingNode.parent.children.indexOf(draggingNode);
            if (index > -1) {
                draggingNode.parent.children.splice(index, 1);
            }
            if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') {
                if (typeof selectedNode.children !== 'undefined') {
                    selectedNode.children.push(draggingNode);
                }
                else {
                    selectedNode._children.push(draggingNode);
                }
            }
            else {
                selectedNode.children = [];
                selectedNode.children.push(draggingNode);
            }
            expand(selectedNode);
            sortTree();
            endDrag();
        }
        else {
            endDrag();
        }
    });
    function endDrag() {
        selectedNode = null;
        d3.selectAll('.ghostCircle').attr('class', 'ghostCircle');
        d3.select(domNode).attr('class', 'node');
        d3.select(domNode).select('.ghostCircle').attr('pointer-events', '');
        updateTempConnector();
        if (draggingNode !== null) {
            update(root);
            centerNode(draggingNode);
            draggingNode = null;
        }
    }
    function collapse(d) {
        if (d.children) {
            d._children = d.children;
            d._children.forEach(collapse);
            d.children = null;
        }
    }
    function expand(d) {
        if (d._children) {
            d.children = d._children;
            d.children.forEach(expand);
            d._children = null;
        }
    }
    var overCircle = function (d) {
        selectedNode = d;
        updateTempConnector();
    };
    var outCircle = function (d) {
        selectedNode = null;
        updateTempConnector();
    };
    var updateTempConnector = function () {
        var data = [];
        if (draggingNode !== null && selectedNode !== null) {
            data = [{
                    source: {
                        x: selectedNode.y0,
                        y: selectedNode.x0
                    },
                    target: {
                        x: draggingNode.y0,
                        y: draggingNode.x0
                    }
                }];
        }
        var link = svgGroup.selectAll(".templink").data(data);
        link.enter().append("path")
            .attr("class", "templink")
            .attr("d", d3.svg.diagonal())
            .attr('pointer-events', 'none');
        link.attr("d", d3.svg.diagonal());
        link.exit().remove();
    };
    function centerNode(source) {
        scale = zoomListener.scale();
        x = -source.y0;
        y = -source.x0;
        x = 150;
        y = y * scale + viewerHeight / 2;
        d3.select('g').transition()
            .duration(duration)
            .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
        zoomListener.scale(scale);
        zoomListener.translate([x, y]);
    }
    function toggleChildren(d) {
        if (d.children) {
            d._children = d.children;
            d.children = null;
        }
        else if (d._children) {
            d.children = d._children;
            d._children = null;
        }
        return d;
    }
    function click(d) {
        if (d3.event.defaultPrevented)
            return;
        d = toggleChildren(d);
        update(d);
        centerNode(d);
    }
    var update = function (source) {
        var levelWidth = [1];
        var childCount = function (level, n) {
            if (n.children && n.children.length > 0) {
                if (levelWidth.length <= level + 1)
                    levelWidth.push(0);
                levelWidth[level + 1] += n.children.length;
                n.children.forEach(function (d) {
                    childCount(level + 1, d);
                });
            }
        };
        childCount(0, root);
        var newHeight = d3.max(levelWidth) * 25;
        vm.tree = vm.tree.size([newHeight, viewerWidth]);
        var nodes = vm.tree.nodes(root).reverse(), links = vm.tree.links(nodes);
        nodes.forEach(function (d) {
            d.y = (d.depth * (maxLabelLength * 10));
        });
        node = svgGroup.selectAll("g.node")
            .data(nodes, function (d) {
            return d.id || (d.id = ++i);
        });
        var nodeEnter = node.enter().append("g")
            .call(dragListener)
            .attr("class", "node")
            .attr("transform", function (d) {
            return "translate(" + source.y0 + "," + source.x0 + ")";
        })
            .on('click', click);
        nodeEnter.append("circle")
            .attr('class', 'nodeCircle')
            .attr("r", 0)
            .style("fill", function (d) {
            return d._children ? "lightsteelblue" : "#fff";
        });
        nodeEnter.append("text")
            .attr("x", function (d) {
            return d.children || d._children ? -10 : 10;
        })
            .attr("dy", ".35em")
            .attr('class', 'nodeText')
            .attr("text-anchor", function (d) {
            return d.children || d._children ? "end" : "start";
        })
            .text(function (d) {
                if (vm.showName == "LE Name") {
                    if (treeId == "#tree-container-legalTree") {
                        return d.lename;
                    }
                    return d.name;
                }
               
        })
            .style("fill-opacity", 0);
        nodeEnter.append("circle")
            .attr('class', 'ghostCircle')
            .attr("r", 30)
            .attr("opacity", 0.2)
            .style("fill", "red")
            .attr('pointer-events', 'mouseover')
            .on("mouseover", function (node) {
            overCircle(node);
        })
            .on("mouseout", function (node) {
            outCircle(node);
        });
        node.select('text')
            .attr("x", function (d) {
            return d.children || d._children ? -10 : 10;
        })
            .attr("text-anchor", function (d) {
            return d.children || d._children ? "end" : "start";
        })
            .text(function (d) {
                if (vm.showName == "LE Name") {
                    if (treeId == "#tree-container-legalTree") {
                        return d.lename;
                    }
                    return d.name;
                }
               
        });
        node.select("circle.nodeCircle")
            .attr("r", 4.5)
            .style("fill", function (d) {
            return d._children ? "lightsteelblue" : "#fff";
        });
        var nodeUpdate = node.transition()
            .duration(duration)
            .attr("transform", function (d) {
            return "translate(" + d.y + "," + d.x + ")";
        });
        nodeUpdate.select("text")
            .style("fill-opacity", 1);
        var nodeExit = node.exit().transition()
            .duration(duration)
            .attr("transform", function (d) {
            return "translate(" + source.y + "," + source.x + ")";
        })
            .remove();
        nodeExit.select("circle")
            .attr("r", 0);
        nodeExit.select("text")
            .style("fill-opacity", 0);
        var link = svgGroup.selectAll("path.link")
            .data(links, function (d) {
            return d.target.id;
        });
        link.enter().insert("path", "g")
            .attr("class", "link")
            .attr("d", function (d) {
            var o = {
                x: source.x0,
                y: source.y0
            };
            return diagonal({
                source: o,
                target: o
            });
        });
        link.transition()
            .duration(duration)
            .attr("d", diagonal);
        link.exit().transition()
            .duration(duration)
            .attr("d", function (d) {
            var o = {
                x: source.x,
                y: source.y
            };
            return diagonal({
                source: o,
                target: o
            });
        })
            .remove();
        nodes.forEach(function (d) {
            d.x0 = d.x;
            d.y0 = d.y;
        });
    };
    var svgGroup = baseSvg.append("g");
    root = treeData;
    root.x0 = viewerHeight / 2;
    root.y0 = 0;
    update(root);
    centerNode(root);
};

and below is example of treeData

{
  "id": 1,
  "code": "a",
  "name": "b",
  "type": "t",
  "leId": 2,
  
  "leName": "d",
  
  "children": [
    {
      "id": 2,
      "code": "e",
      "name": "f",
      "type": "g",
      "leId": 4,
      
      "lename": "e",
      
      "childrenCount": 0
    }
  ],
  "childrenCount": 1
}

Upvotes: 1

Views: 649

Answers (1)

Michael Rovinsky
Michael Rovinsky

Reputation: 7230

Here is a snippet of a tree with inverse direction.

Replace d.y with height - d.y, swap positionning of text (start / end) and modify call of diagonal for links:

var data = [
    { "name" : "Level 2: A", "parent":"Top Level" },
    { "name" : "Top Level", "parent":"null" },
    { "name" : "Son of A", "parent":"Level 2: A" },
    { "name" : "Daughter of A", "parent":"Level 2: A" },
    { "name" : "Level 2: B", "parent":"Top Level" }
    ];

// *********** Convert flat data into a nice tree ***************
// create a name: node map
var dataMap = data.reduce(function(map, node) {
    map[node.name] = node;
    return map;
}, {});

// create the tree array
var treeData = [];
data.forEach(function(node) {
    // add to parent
    var parent = dataMap[node.parent];
    if (parent) {
        // create child array if it doesn't exist
        (parent.children || (parent.children = []))
            // add node to child array
            .push(node);
    } else {
        // parent is null or missing
        treeData.push(node);
    }
});

// ************** Generate the tree diagram  *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
    width = 960 - margin.right - margin.left,
    height = 500 - margin.top - margin.bottom;
    
var i = 0;

var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

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

root = treeData[0];
  
update(root);

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

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

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

  // Enter the nodes.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { 
          return "translate(" + (height - d.y) + "," + d.x + ")"; });

  nodeEnter.append("circle")
      .attr("r", 10)
      .style("fill", "#fff");

  nodeEnter.append("text")
      .attr("x", function(d) { 
          return d.children || d._children ? 13 : -13; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { 
          return d.children || d._children ? "start" : "end"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1);

  // Declare the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter the links.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", d => {
        console.log(d);
      const source = {x: d.source.x, y: height - d.source.y};
      const target = {x: d.target.x, y: height - d.target.y};
        return diagonal({source, target});
        //diagonal({x: d.x, y: height - d.y})
    });

}
    .node circle {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 3px;
    }

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

    .link {
      fill: none;
      stroke: #ccc;
      stroke-width: 2px;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.7/d3.min.js"></script>

Upvotes: 1

Related Questions