D3.js parsing error when trying to generate a node tree - javascript

Google Chrome console is returning the following error:
Error: Problem parsing d="M296.20961279999995,NaNC462.51521919999993,NaN 311.0200934399999,NaN 477.3256998399999,NaN"
Posted is my code:
var taskflows = [
{
"description" : "Taskflow #1",
"taskExecutions" : [
{"description" : "First","step" : 1},
{"description" : "Second","step" : 2},
{"description" : "Second", "step" : 2},
{"description" : "Third", "step" : 3},
{"description" : "Fourth", "step" : 4},
{"description" : "Fifth","step" : 5},
{"description" : "Sixth", "step" : 6},
{"description" : "Sixth", "step" : 6},
{"description" : "Seventh","step" : 7},
{"description" : "Eighth", "step" : 8},
{"description" : "Ninth", "step" : 9},
{"description" : "Tenth", "step" : 10},
{"description" : "Eleventh", "step" : 11},
{"description" : "Twelve", "step" : 12},
{"description" : "Twelve","step" : 12}
]
}];
var taskflowTree = function (taskflow) {
var tree = taskflow.map(function(a) {
return { "name": a.description, "step": a.step }
});
tree.forEach(function(v, i, arr) {
v.children = arr.filter(function(a) {
return a.step == v.step+1;
});
});
return tree.filter(function(a) {
return a.step == 1;
});
}
var formattedTaskflowTree = taskflowTree(taskflows[0].taskExecutions);
(function(treeData) {
// Calculate total nodes, max label length
var totalNodes = 0;
var maxLabelLength = 0;
// variables for drag/drop
var selectedNode = null;
var draggingNode = null;
// panning variables
var panSpeed = 200;
var panBoundary = 20; // Within 20px from edges will pan when dragging.
// Misc. variables
var i = 0;
var duration = 750;
var root;
// size of the diagram
var viewerWidth = $(document).width();
var viewerHeight = $(document).height();
var tree = d3.layout.tree()
.size([viewerHeight, viewerWidth]);
// define a d3 diagonal projection for use by the node paths later on.
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
// A recursive helper function for performing some setup by walking through all nodes
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);
}
}
}
// Call visit function to establish maxLabelLength
visit(treeData, function(d) {
totalNodes++;
maxLabelLength = Math.max(d.name.length, maxLabelLength);
}, function(d) {
return d.children && d.children.length > 0 ? d.children : null;
});
// sort the tree according to the node names
function sortTree() {
tree.sort(function(a, b) {
return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
});
}
// Sort the tree initially incase the JSON isn't in a sorted order.
sortTree();
// TODO: Pan function, can be better implemented.
function pan(domNode, direction) {
var speed = panSpeed;
if (panTimer) {
clearTimeout(panTimer);
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);
}
}
// Define the zoom function for the zoomable tree
function zoom() {
svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
// define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
function initiateDrag(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) { // select the parent and sort the path's
if (a.id != draggingNode.id) return 1; // a is not the hovered element, send "a" to the back
else return -1; // a is the hovered element, bring "a" to the front
});
// if nodes has children, remove the links and nodes
if (nodes.length > 1) {
// remove link paths
links = tree.links(nodes);
nodePaths = svgGroup.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
}).remove();
// remove child nodes
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();
}
// remove parent link
parentLink = tree.links(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;
}
// define the baseSvg, attaching a class for styling and the zoomListener
var baseSvg = d3.select("#tree-container").append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "overlay")
.call(zoomListener);
// Helper functions for collapsing and expanding nodes.
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();
};
// Function to update the temporary connector indicating dragging affiliation
var updateTempConnector = function() {
var data = [];
if (draggingNode !== null && selectedNode !== null) {
// have to flip the source coordinates since we did this for the existing connectors on the original tree
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 to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
function centerNode(source) {
scale = zoomListener.scale();
x = -source.y0;
y = -source.x0;
x = x * scale + viewerWidth / 2;
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]);
}
// Toggle children function
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;
}
// Toggle children on click.
function click(d) {
if (d3.event.defaultPrevented) return; // click suppressed
d = toggleChildren(d);
update(d);
centerNode(d);
}
function update(source) {
// Compute the new height, function counts total children of root node and sets tree height accordingly.
// This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
// This makes the layout more consistent.
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; // 25 pixels per line
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
// alternatively to keep a fixed scale one can set a fixed depth per level
// Normalize for fixed-depth by commenting out below line
// d.y = (d.depth * 500); //500px per level.
});
// Update the nodes…
node = svgGroup.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
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) {
return d.name;
})
.style("fill-opacity", 0);
// phantom node to give us mouseover in a radius around it
nodeEnter.append("circle")
.attr('class', 'ghostCircle')
.attr("r", 30)
.attr("opacity", 0.2) // change this to zero to hide the target area
.style("fill", "red")
.attr('pointer-events', 'mouseover')
.on("mouseover", function(node) {
overCircle(node);
})
.on("mouseout", function(node) {
outCircle(node);
});
// Update the text to reflect whether node has children or not.
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) {
return d.name;
});
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", 4.5)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
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);
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
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
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Append a group which holds all nodes and which the zoom Listener can act upon.
var svgGroup = baseSvg.append("g");
// Define the root
root = treeData;
root.x0 = viewerHeight / 2;
root.y0 = 0;
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
})(formattedTaskflowTree[0]);
Posted is a screenshot of the re-occurring errors that I'm receiving upon loading up the tree.

Related

d3.js Tree - Paging of child nodes v3.5

I have a previous question which solves a problem with paging of many child nodes
There are few problems though with implementing in my d3.js context:
The first and last child nodes that initiate the paging are not named
'...' according to the script functionality. This appears to be a v7 to v3.5 issue.
The paging should only occur on the leaf nodes, not any middle
branches.
It needs to work with d3.js v3.5 (yes I know, but this is pending
migrating the tree to v7...). Basically the main issue is with naming of the first and last paging nodes...
See fiddle for implementation and issues in d3.js v3.5
Relevant from previous solution:
function pageNodes(d, maxNode) {
if (d.children) {
d.children.forEach(c => pageNodes(c, maxNode));
if (d.children.length > maxNode) {
d.pages = {}
const count = maxNode - 2;
const l = Math.ceil(d.children.length / count);
for (let i = 0; i < l; i++) {
let startRange = i * count;
let endRange = i * count + count;
d.pages[i] = d.children.slice(startRange, endRange);
d.pages[i].unshift({
...d.pages[i][0],
data: {
name: "..."
},
page: i == 0 ? l - 1 : i - 1
})
// console.log(i, d.pages[i]);
d.pages[i].push({
...d.pages[i][0],
data: {
name: "..."
},
page: i != (l - 1) ? i + 1 : 0,
});
}
d.children = d.pages[0];
console.log(d.pages)
}
}
}
root.children.forEach(c => pageNodes(c, 8));
function click(d) {
if (d.hasOwnProperty('page')) {
d.parent.children = d.parent.pages[d.page];
} else if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
Notes
The order is mandatory pageNodes function needs to be called before collapse function call, As collapse changes the children key to _children and pageNodes function works with children key
I created pageNodes function in a way so it does not require calling again and again which would waste computation power, so we call it once on start and the structure is adjusted as per requirement then we just work with click function without requiring to call pageNodes again and again.
I have modified the pageNodes function to only page if data contain page: true, for reference you can check unit-group data segment.
Example 1 - require adding page key in data to enable paging for selective data segment.
var treeData = {"name":"Program","column_to_sort_by":null,"type":"program","children":[{"name":"ProgramGroup1","column_to_sort_by":null,"type":"program_group","children":[{"name":"POGroup1","column_to_sort_by":null,"type":"1program_outcome_group","children":[{"name":"PO1","column_to_sort_by":null,"type":"program_outcome","children":[{"name":"Unit1","column_to_sort_by":"Unit1","children":[{"name":"UG1-LE","column_to_sort_by":null,"page":true,"type":"unit_group","children":[{"name":"LE1","column_to_sort_by":"LE1","type":"learning_event"},{"name":"LE10","column_to_sort_by":"LE10","type":"learning_event"},{"name":"LE11","column_to_sort_by":"LE11","type":"learning_event"},{"name":"LE12","column_to_sort_by":"LE12","type":"learning_event"},{"name":"LE13","column_to_sort_by":"LE13","type":"learning_event"},{"name":"LE14","column_to_sort_by":"LE14","type":"learning_event"},{"name":"LE15","column_to_sort_by":"LE15","type":"learning_event"},{"name":"LE2","column_to_sort_by":"LE2","type":"learning_event"},{"name":"LE4","column_to_sort_by":"LE4","type":"learning_event"},{"name":"LE5","column_to_sort_by":"LE5","type":"learning_event"},{"name":"LE6","column_to_sort_by":"LE6","type":"learning_event"},{"name":"LE7","column_to_sort_by":"LE7","type":"learning_event"},{"name":"LE8","column_to_sort_by":"LE8","type":"learning_event"},{"name":"LE9","column_to_sort_by":"LE9","type":"learning_event"}]},{"name":"UG1-Assessments","column_to_sort_by":null,"page":true,"type":"unit_group","children":[{"name":"ASST1","column_to_sort_by":"ASST1","type":"assessment"},{"name":"ASST10","column_to_sort_by":"ASST10","type":"assessment"},{"name":"ASST11","column_to_sort_by":"ASST11","type":"assessment"},{"name":"ASST13","column_to_sort_by":"ASST13","type":"assessment"},{"name":"ASST14","column_to_sort_by":"ASST14","type":"assessment"},{"name":"ASST15","column_to_sort_by":"ASST15","type":"assessment"},{"name":"ASST2","column_to_sort_by":"ASST2","type":"assessment"},{"name":"ASST3","column_to_sort_by":"ASST3","type":"assessment"},{"name":"ASST4","column_to_sort_by":"ASST4","type":"assessment"},{"name":"ASST5","column_to_sort_by":"ASST5","type":"assessment"},{"name":"ASST6","column_to_sort_by":"ASST6","type":"assessment"},{"name":"ASST7","column_to_sort_by":"ASST7","type":"assessment"},{"name":"ASST8","column_to_sort_by":"ASST8","type":"assessment"},{"name":"ASST9","column_to_sort_by":"ASST9","type":"assessment"}]}],"type":"unit"}]},{"name":"PO2","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO3","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO4","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO5","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO6","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO7","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO8","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO9","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO10","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO11","column_to_sort_by":null,"type":"program_outcome"}]},{"name":"POGroup2","column_to_sort_by":null,"type":"1program_outcome_group"}]},{"name":"ProgramGroup2","column_to_sort_by":null,"type":"program_group"}]};
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2000 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
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;
root.x0 = height / 2;
root.y0 = 0;
function pageNodes(d, options) {
if (d.children) {
d.children.forEach(c => pageNodes(c, options));
if (d.page && d.children.length > options.maxNode) {
d.pages = {}
const count = options.maxNode - 2;
const l = Math.ceil(d.children.length / count);
for (let i = 0; i < l; i++) {
const startRange = i * count;
const endRange = i * count + count;
let pageNumber = i == 0 ? l - 1 : i - 1;
d.pages[i] = d.children.slice(startRange, endRange);
d.pages[i].unshift({
...d.pages[i][0],
data: {
name: options.getLabel ? options.getLabel(pageNumber) : "..."
},
pageNumber,
name: "..."
})
// console.log(i, d.pages[i]);
pageNumber = i != (l - 1) ? i + 1 : 0;
d.pages[i].push({
...d.pages[i][0],
data: {
name: options.getLabel ? options.getLabel(pageNumber) : "..."
},
pageNumber,
name: "..."
});
}
d.children = d.pages[0];
console.log(d.pages)
}
}
}
root.children.forEach(c => pageNodes(c, {
maxNode: 8,
}));
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
//svg.style("height", "500px");
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;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes 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);
nodeEnter.append("circle")
.attr("r", 1e-6)
.attr('stroke', function(d) {
return d.color ? d.color : 'blue';
})
.style("fill", function(d) {
return d._children ? "#ccc" : "#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 ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 10)
.style("fill", function(d) {
var collapseColor = d.color ? d.color : '#ccc';
return d._children ? collapseColor : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
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
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.hasOwnProperty('pageNumber')) {
d.parent.children = d.parent.pages[d.pageNumber];
} else if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
Here working example
Example 2 - through page options which is a function which would return true/false to enable/disable paging for selective data segment.
var treeData = {"name":"Program","column_to_sort_by":null,"type":"program","children":[{"name":"ProgramGroup1","column_to_sort_by":null,"type":"program_group","children":[{"name":"POGroup1","column_to_sort_by":null,"type":"1program_outcome_group","children":[{"name":"PO1","column_to_sort_by":null,"type":"program_outcome","children":[{"name":"Unit1","column_to_sort_by":"Unit1","children":[{"name":"UG1-LE","column_to_sort_by":null,"type":"unit_group","children":[{"name":"LE1","column_to_sort_by":"LE1","type":"learning_event"},{"name":"LE10","column_to_sort_by":"LE10","type":"learning_event"},{"name":"LE11","column_to_sort_by":"LE11","type":"learning_event"},{"name":"LE12","column_to_sort_by":"LE12","type":"learning_event"},{"name":"LE13","column_to_sort_by":"LE13","type":"learning_event"},{"name":"LE14","column_to_sort_by":"LE14","type":"learning_event"},{"name":"LE15","column_to_sort_by":"LE15","type":"learning_event"},{"name":"LE2","column_to_sort_by":"LE2","type":"learning_event"},{"name":"LE4","column_to_sort_by":"LE4","type":"learning_event"},{"name":"LE5","column_to_sort_by":"LE5","type":"learning_event"},{"name":"LE6","column_to_sort_by":"LE6","type":"learning_event"},{"name":"LE7","column_to_sort_by":"LE7","type":"learning_event"},{"name":"LE8","column_to_sort_by":"LE8","type":"learning_event"},{"name":"LE9","column_to_sort_by":"LE9","type":"learning_event"}]},{"name":"UG1-Assessments","column_to_sort_by":null,"type":"unit_group","children":[{"name":"ASST1","column_to_sort_by":"ASST1","type":"assessment"},{"name":"ASST10","column_to_sort_by":"ASST10","type":"assessment"},{"name":"ASST11","column_to_sort_by":"ASST11","type":"assessment"},{"name":"ASST13","column_to_sort_by":"ASST13","type":"assessment"},{"name":"ASST14","column_to_sort_by":"ASST14","type":"assessment"},{"name":"ASST15","column_to_sort_by":"ASST15","type":"assessment"},{"name":"ASST2","column_to_sort_by":"ASST2","type":"assessment"},{"name":"ASST3","column_to_sort_by":"ASST3","type":"assessment"},{"name":"ASST4","column_to_sort_by":"ASST4","type":"assessment"},{"name":"ASST5","column_to_sort_by":"ASST5","type":"assessment"},{"name":"ASST6","column_to_sort_by":"ASST6","type":"assessment"},{"name":"ASST7","column_to_sort_by":"ASST7","type":"assessment"},{"name":"ASST8","column_to_sort_by":"ASST8","type":"assessment"},{"name":"ASST9","column_to_sort_by":"ASST9","type":"assessment"}]}],"type":"unit"}]},{"name":"PO2","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO3","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO4","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO5","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO6","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO7","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO8","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO9","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO10","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO11","column_to_sort_by":null,"type":"program_outcome"}]},{"name":"POGroup2","column_to_sort_by":null,"type":"1program_outcome_group"}]},{"name":"ProgramGroup2","column_to_sort_by":null,"type":"program_group"}]};
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2000 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
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;
root.x0 = height / 2;
root.y0 = 0;
function pageNodes(d, options) {
if (d.children) {
d.children.forEach(c => pageNodes(c, options));
if (options.page && options.page(d) && d.children.length > options.maxNode) {
d.pages = {}
const count = options.maxNode - 2;
const l = Math.ceil(d.children.length / count);
for (let i = 0; i < l; i++) {
const startRange = i * count;
const endRange = i * count + count;
let pageNumber = i == 0 ? l - 1 : i - 1;
d.pages[i] = d.children.slice(startRange, endRange);
d.pages[i].unshift({
...d.pages[i][0],
data: {
name: options.getLabel ? options.getLabel(pageNumber) : "..."
},
pageNumber,
name: "..."
})
// console.log(i, d.pages[i]);
pageNumber = i != (l - 1) ? i + 1 : 0;
d.pages[i].push({
...d.pages[i][0],
data: {
name: options.getLabel ? options.getLabel(pageNumber) : "..."
},
pageNumber,
name: "..."
});
}
d.children = d.pages[0];
console.log(d.pages)
}
}
}
root.children.forEach(c => pageNodes(c, {
maxNode: 8,
page: function(d) {
return d.type == "unit_group";
}
}));
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
//svg.style("height", "500px");
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;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes 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);
nodeEnter.append("circle")
.attr("r", 1e-6)
.attr('stroke', function(d) {
return d.color ? d.color : 'blue';
})
.style("fill", function(d) {
return d._children ? "#ccc" : "#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 ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 10)
.style("fill", function(d) {
var collapseColor = d.color ? d.color : '#ccc';
return d._children ? collapseColor : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
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
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.hasOwnProperty('pageNumber')) {
d.parent.children = d.parent.pages[d.pageNumber];
} else if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
Here working example

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

D3(v5) collapsable tree with search

I am trying to display d3 collapsable tree with search, here is the reference example am using [Search tree][1]
[1]: https://bl.ocks.org/jjzieve/a743242f46321491a950 I am getting error select2() is not a function. I did not get any solution on v5, how to change the above example based on my code.
Here is my code
function searchTree(obj, search, path) {
if (obj.name === search) { //if search is found return, add the object to the path and return it
path.push(obj);
return path;
}
else if (obj.children || obj._children) { //if children are collapsed d3 object will have them instantiated as _children
var children = (obj.children) ? obj.children : obj._children;
for (var i = 0; i < children.length; i++) {
path.push(obj);// we assume this path is the right one
var found = searchTree(children[i], search, path);
if (found) {// we were right, this should return the bubbled-up path from the first if statement
return found;
}
else {//we were wrong, remove this parent from the path and continue iterating
path.pop();
}
}
}
else {//not the right object, return false so it will continue to iterate in the loop
return false;
}
}
function extract_select2_data(node, leaves, index) {
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
index = extract_select2_data(node.children[i], leaves, index)[0];
}
}
else {
leaves.push({ id: ++index, text: node.name });
}
return [index, leaves];
}
console.log("left side hitting ");
// Set the dimensions and margins of the diagram
var margin = { top: 20, right: 120, bottom: 20, left: 120 },
width = 1280 * 10 - margin.right - margin.left,
height = 1200 *10 - 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(".col-md-6").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, path,select2_data;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
function openPaths(paths) {
for (var i = 0; i < paths.length; i++) {
if (paths[i].id !== "1") {//i.e. not root
paths[i].class = 'found';
if (paths[i]._children) { //if children are hidden: open them, otherwise: don't do anything
paths[i].children = paths[i]._children;
paths[i]._children = null;
}
update(paths[i]);
}
}
}
// Assigns parent, children, height, depth
root = d3.hierarchy(this.parentTreeObj, function (d) { return d.children });
select2_data = extract_select2_data(this.parentTreeObj,[],0)[1];
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
$(".col-md-6").select2({
data: select2_data,
containerCssClass: "search"
});
//attach search box listener
$(".col-md-6").on("select2-selecting", function (e) {
var paths = searchTree(root, e.object.text, []);
if (typeof (paths) !== "undefined") {
openPaths(paths);
}
else {
alert(e.object.text + " not found!");
}
})
d3.select(self.frameElement).style("height", "800px");
// 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) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// compute the new height
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) * 20;
treemap = d3.tree().size([newHeight, width]);
// 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.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function (d) {
return d._children ? "#eb8c00" : "#da5506";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function (d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "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 || d._id ? "#eb8c00" : "#da5506";
})
.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('path.link')
.data(links, function (d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function (d) {
var o = { x: source.x0, y: source.y0 }
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function (d) { return diagonal(d, d.parent) });
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function (d) {
var o = { x: source.x, y: source.y }
return diagonal(o, o)
})
.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) {
console.log("left side hitting inside ");
console.log("in Left");
// console.log("idddd",d.data.id);
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;
// d._id = d.data.id;
// d.data.id = null;
} else {
d.children = d._children;
d._children = null;
// d.data.id = d._id;
// d._id = null;
}
update(d);
}
}
Can anyone please help me out this. Thanks in advance
Have you linked your file to the select2 library? For example, do you have this:
<link rel="stylesheet" type="text/css" href="http://cdnjs.cloudflare.com/ajax/libs/select2/3.5.2/select2.min.css"></link>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/select2/3.5.2/select2.min.js"></script>
If you could share a link through JSFiddle, bl.ocks, or in a repository, that would help out a lot too. Let me know if that helps.

D3Js Tree layout auto-zoom functionality

I m trying to find out a way to add auto-zoom to the current code. The usual zoom panning is working fine. However, when the number of nodes in a tree is high, it got cut off by the corners. Is there anyway to zoom automatically as soon the tree passed the boundary of the width and height of the svg. Maybe based on the depth size or the total number of nodes.
function buildTree(treeData){
var contextMenuList = [
{
title: 'Remove Node',
action: function(elm, d, i) {
if (d.parent && d.parent.children){
var nodeToDelete = _.where(d.parent.children, {name: d.name});
if (nodeToDelete){
d.parent.children = _.without(d.parent.children, nodeToDelete[0]);
}
update(d);
}
}
},
{
title: 'Synopsis',
action: function(elm, d, i) {
console.log("Option 2 clicked");
console.log("Node Name: "+ d.name);
setNodeTopic(d.name);
}
}
];
var margin = {top:40, right:120,bottom:20,left:20};
var width = 960 - margin.right - margin.left;
var height = 900 - margin.top - margin.bottom;
var i = 0,duration = 750;
//refers to the tree itself
var tree = d3.layout.tree()
.size([height,width])
.nodeSize([100,100]);
var diagonal = d3.svg.diagonal()
.projection(function(d){
return [d.x, d.y];
});
//refers to the rectangle outside
var zm;
var svg = d3.select("#tree").append("svg")
.attr("width",width+margin.right+margin.left)
.attr("height",height+margin.top+margin.bottom)
.append("svg:g")
.attr("transform","translate("+margin.left+","+margin.top+")")
.call(zm = d3.behavior.zoom().scaleExtent([0.5,2]).on("zoom", redraw)).append("g")
.attr("transform","translate("+400+","+50+")");
zm.translate([400,20]);
var root = treeData;
function autoOpen(head, time) {
window.setTimeout(function() {
nodeclick(head); //do node click
if (head._children) {
//if has children
var timeOut = 1000; //set the timout variable
head._children.forEach(function(child) {
autoOpen(child, timeOut); //open the child recursively
timeOut = timeOut + 1000;
})
}
}, time);
}
autoOpen(root,1000);
update(root);
function update(source){
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
nodes.forEach(function(d){
d.y = d.depth * 150;
});
var node = svg.selectAll("g.node")
.data(nodes,function(d){
return d.id || (d.id = ++ i);
});
var nodeEnter = node.enter().append("svg:g")
.attr("class","node")
.attr("transform",function(d){
if(!source.x0 && !source.y0)
return "";
return "translate("+source.x0 + "," + source.y0 + ")";
})
.on("click",nodeClick)
.on('contextmenu', d3.contextMenu(contextMenuList));
nodeEnter.append("circle")
.attr("r",50)
.attr("stroke",function(d){
return d.children || d._children ? "steelblue" : "#00c13f";
})
.style("fill",function(d){
return d.children || d._children ? "lightsteelblue" : "#fff";
})
nodeEnter.append("text")
.attr("y",function(d){
//return d.children || d._children ? -18 : 18;
return -10;
})
.attr("dy",".35em")
.attr("text-anchor","middle")
.style("fill-opacity",1e-6)
.each(function (d) {
var arr = d.name.split(" ");
for (i = 0; i < arr.length; i++) {
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i);
}
});
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform",function(d){
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("circle")
.attr("r",50)
.style("fill",function(d){
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity",1);
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform",function(d){
return "translate("+ source.x+","+source.y+")";
})
.remove();
nodeExit.select("circle")
.attr("r",1e-6);
nodeExit.select("text")
.style("fill-opacity",1e-6);
var link = svg.selectAll("path.link")
.data(links,function(d){
return d.target.id;
});
link.enter().insert("svg:path","g")
.attr("class","link")
.attr("d",function(d){
if(!source.x0 && !source.y0)
return "";
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;
});
}
function nodeClick(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
}
buildTree(that.objectList);
};
My solution to this problem is by using viewbox. Making the svg responsible allows the svg to be able to resize based on browser window size.
Here is an example:
Making SVG Responsive

Change the size of an existing d3 tree

I am attempting to make a tree viewing web application. I am using the d3 tree layout to generate my trees. What I would like to be able to do is scale the size of the tree in either axis. In otherwords being able to scale the x and y axis of the viewer. The would effectively just increase either the vertical distance between nodes or increase the length of the branches.
I have been unable to accomplish this. I have attempted to change the nodeSize() of the tree layout, and then update which had no effect. I have also attempted to modify the x or y coordinate of each node which again had no effect. I have been completely unable to modify the physical layout of the tree once it has been rendered.
I have read and attempted the fixes in the following threads:
Dynamically resize the d3 tree layout based on number of childnodes
D3 Tree Layout Separation Between Nodes using NodeSize
But they did not solve my problem.
I am implementing a TreeGenerator object, which handles all of the rendering of the SVG, using prototyped functions. I am going for a full screen feeling, so the TreeGenerator appends an SVG to a div with width: 100%; height: 100%. I should node that I have implemented both zooming and panning functionality.
Here is the constructor:
TreeGenerator = function(target, treeString) {
// Set up everything here
// colors
this.nodeColor = "#3B6073";
this.highlightColor = "#F22248";
this.searchColor = "#0DFFC2";
this.nodeHeight = 5;
this.nodeLength = 20;
this.zoomX = 1;
this.zoomY = 5;
this._winHeight = window.innerHeight;
this._winWidth = window.innerWidth;
//this.xScale = d3.scale.linear().domain([0,100]).range([0, this._winWidth])
//this.yScale = d3.scale.linear().domain([0,100]).range([0, this._winHeight])
// keep the matching results from last search
this._searchResults = [];
// path lengths
this.maxPathLength = 0;
this._loadSVG(target);
this._tree(treeString);
}
And here are the two functions _loadSVG and _tree which are called from the constructor:
TreeGenerator.prototype._loadSVG = function(target) {
var zoom = d3.behavior.zoom()
//.size([this._winWidth, this._winHeight])
.scaleExtent([this.zoomX,this.zoomY])
//.x(this.xScale)
//.y(this.yScale)
//.center([height/2, width/2])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
this.svg = d3.select(target, ":first-child").append("svg")
.append("g")
//.attr("transform", "translate("+width / 2+","+height / 2+")")
.call(zoom)
.on("dblclick.zoom", null)
var rect = this.svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all");
this.container = this.svg.append("g")
// scope it for d3 funcs
var container = this.container
var self = this;
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
}
And _tree:
TreeGenerator.prototype._tree = function (treeString) {
// needs an array for d3
this.treeData = this.parseTree(treeString);
this.root = this.treeData[0];
// set up the layout
this.tree = d3.layout.tree()
.nodeSize([this.nodeHeight,this.nodeLength]);
// set path dists
this._setPathDist();
this.yScale = d3.scale.linear().domain([0, this.maxPathLength]).range([0, 20]);
this.xScale = d3.scale.linear().domain([1,100]).range([10, 30]);
var self = this;
update(this.root);
function update(source) {
var i = 0;
// generator for paths from
// node ---> node
var horizontal = d3.svg.line().interpolate('step-before')
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
// Compute the new tree layout.
var nodes = self.tree.nodes(source).reverse()
nodes.forEach(function (d) {
if(!d.pathLength == 0) {
d.y = d.y * self.yScale(d.pathLength);
}
else if(d.children != undefined) {
d.y += 5;
}
});
links = self.tree.links(nodes);
// Declare the nodes
var node = self.container.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); })
// Enter the nodes.
doubleclickTimer = false // dont ask
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("id", function(d) { return "node-"+d.id.toString(); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; })
.on("click", clicked);
nodeEnter.append("circle")
.attr("r", 1)
.style("fill", self.nodeColor)
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -1 : 1; })
.attr("y", function(d) {
return d.children == undefined ? 1 : -1;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1);
// try and update
//var nodeUpdate = node.update()
// .attr("x", function(d) { return d.x });
// Declare the links
var link = self.container.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
return horizontal([
{y: d.source.x, x: d.source.y},
{y: d.target.x, x: d.target.y}
]);
});
function clicked(d) {
// need the g group for highlight
node = this;
// if double click timer is active, this click is the double click
if ( doubleclickTimer )
{
clearTimeout(doubleclickTimer);
doubleclickTimer = false;
collapse(d);
}
// otherwise, what to do after single click (double click has timed out)
else {
doubleclickTimer = setTimeout( function() {
doubleclickTimer = false;
highlight(node, d);
}, 175);
}
}
function highlight(node,d) {
// we want to bold the text
// and color the node
self._unhighlightNode();
// toggle if clicking again
if(self._highlightedNode == node) {
self._highlightedNode = undefined;
self.selectedNode = undefined;
return;
}
// set the new one
var circle = d3.select(node).select("circle");
var text = d3.select(node).select("text");
circle.style("fill", self.highlightColor);
text.style("font-size","4px");
// update the pointer
self._highlightedNode = node;
self.selectedNode = d;
}
function collapse(d) {
}
}
};
I know the code is a bit messy, but it is just full of commented out things I did in an attempt to fix this issue. I appreciate any help that can be offered. Hopefully I included enough info.

Categories

Resources