d3.js Tree - Paging of child nodes v3.5 - javascript
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
Related
Resize the cluster layout based on number of child nodes
i am working in d3 ,rotating cluster layout ..i want to resize the cluster layout based on number of child nodes.. based on my code ,when i open the parent with minimum child ,there is no problem when i open a parent that contains more child,there is some overlapping ,so want to reduce the cluster size further,when there is more number of child in the parent...how can i do this?? my entire code is myJSON= http://pastebin.com/vZ32jGQc treeData = myJSON; var selectedNode = null; var draggingNode = null; var panSpeed = 200; var panBoundary = 0; var i = 0; var duration = 750; var root; var width = 5000; var height = 5000; var diameter = 750; var tree = d3.layout.tree().size([360, diameter / 2 - 120]) .separation(function (a, b) { return (a.parent == b.parent ? 1 : 5) / a.depth; }); var diagonal = d3.svg.diagonal.radial() .projection(function (d) { return [d.y, d.x / 180 * Math.PI]; }); root = treeData; root.x0 = height / 2; root.y0 = 0; function sortTree() { tree.sort(function (a, b) { return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1; }); } sortTree(); var baseSvg = d3.select("#tree-container").append("svg") .attr("width", 1200) .attr("height",1200) .attr("class", "overlay") .attr("transform", "translate(" + 1000 + "," + 1000 + ")"); function collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; } update(d); } function expand(d) { if (d._children) { d.children = d._children; d.children.forEach(expand); d._children = null; } } 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(!d.parent){ return; } if (!d.children) myJSON.children.forEach(collapse); if (d3.event.defaultPrevented) return; d = toggleChildren(d); update(d); } function update(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 nodes = tree.nodes(root); links = tree.links(nodes); node = svgGroup.selectAll("g.node") .data(nodes, function (d) { return d.id || (d.id = ++i); }); var nodeEnter = node.enter().append("g") .attr("class", "node") .on('click', click) nodeEnter.append("circle") .attr("class", "smallcircle") .style("stroke", function(d) { return d.color; }) nodeEnter.append("text") .text(function (d) { return d.name; }) // .style("font", "12px serif") .style("opacity", 1) .style("fill-opacity", 0) .on("mouseover", function (d) { var r = d3.select(this).node().getBoundingClientRect(); d3.select("div#tooltip") .style("display", "inline") .style("top", (r.top-25) + "px") .style("top", 10 + "px") .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY - 40) + "px") .style("left", r.left + 40+"px") .style("left", + "px") .style("position", "absolute") .text(d.t); }) .on("mouseout", function(){ d3.select("div#tooltip").style("display", "none") }) node.select("circle.nodeCircle") .attr("r", 4.5) .style("fill", function (d) { return d._children ? "red" : "#fff"; }); var nodeUpdate = node.transition() .duration(duration) .attr("transform", function (d) { return "rotate(" + (d.x - 90) + ") translate(" + d.y + ")rotate(" + (-d.x + 90) + ")"; }); nodeUpdate.select("circle") .attr("r", 4.5) .style("fill", function (d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeUpdate.select("text") .style("fill-opacity", 9) .attr("fill",function(d){return (d.children?"red":"black");}) .attr("font-size",function(d) {return (d.children?"20px":"12px");}) .attr("dy", ".35em") .attr("text-anchor", function (d) { return d.x < 180 ? "start" : "end"; }) .attr("transform", function (d) { return d.x < 180 ? "translate(8)" : "rotate(360)translate(-8)"; }); var nodeExit = node.exit().transition() .duration(duration) .attr("transform", function (d) { return "translate(" + source.x + "," + source.y + ")"; }) .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.style("stroke", function(d) { return d.color; }) link.enter().insert("path", "g") .attr("class", "link") link.style("stroke", function(d) { return d.target.color; }) .attr("d", function (d) { var o = {x: source.x, y: source.y}; 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") .attr("transform", "translate(" + 550 + "," + 300 + ")") d3.selectAll("text").style("fill", function (d) { return d3.select(this).classed(d.cond, true); }) root.children.forEach(function (child) { collapse(child); }); update(root); d3.select(self.frameElement).style("height", width);
d3 tree <g> tag padding/border overall styling
So I've built a tree that will collapse/expand with animations using d3's library. I'm still very new to d3 and I'm not sure how to add my specific styling to the fray. Any suggestions on how to style <g> tags? Here is what I want it to look like: And here is what it currently looks like: Here is the relevant code: <!DOCTYPE html> <meta charset="utf-8"> <style> .node { cursor: pointer; } .node circle { fill: #fff; stroke: steelblue; stroke-width: 3px; } .node text { font: 12px sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 2px; } </style> <body> <script src="//code.jquery.com/jquery-1.11.3.min.js"></script> <script src="//d3js.org/d3.v3.min.js"></script> <script> var margin = { top: 20, right: 120, bottom: 20, left: 120 }, width = $(window).width() - margin.right - margin.left, height = $(window).height() - 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]; }); buildJson(function (data) { 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 = data; root.x0 = height / 2; root.y0 = 0; function collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; } } root.children.forEach(collapse); update(root); d3.select(self.frameElement).style("height", "800px"); 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 * 350; }); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // 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) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeEnter.append("text") .attr("x", function(d) { return d.children || d._children ? -12 : 12; }) .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", 4.5) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#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; }); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Remove Root Node, ultra mega important!!! // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ node.each(function(d){ if (d.name == "flare") d3.select(this).remove();}); link.each(function(d){ if (d.source.name == "flare") d3.select(this).remove();}); } // 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; } // If the node has a parent, then collapse its child nodes // except for this clicked node. if (d.parent) { d.parent.children.forEach(function(element) { if (d !== element) { collapse(element); } }); } update(d); } function collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; } } }); // Get Data and format it properly for presentation function getJsonData (pathToData, callback) { $.getJSON( pathToData, function() { console.log( "Retrieving: " + pathToData ); }).done(function(data) { callback({ error: false, data: data }); }) .fail(function() { callback({ error: true }); }); } function buildJson (builder) { var runOnce = true, categories_raw, technologies_raw, formatted_json = { 'name' : 'flare', 'children' : [] } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // First get all of the categories and subcategories //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ getJsonData('assets/json/categories.json', function (data) { if (!data.error) { //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Categories and Subcategories Retreived now build it out //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ categories_raw = data.data //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Technologies retreived now build it out //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ getJsonData('assets/json/technologies.json', function (data) { if (!data.error) { technologies_raw = data.data var categories = categories_raw[0], subcategories = categories_raw[1], technologies = technologies_raw, formatted_categories = [], formatted_subcategories = [], formatted_technologies = [] subcategories.forEach ( function (value, index) { formatted_subcategories.push({ 'name' : value.label, 'id' : value.id, 'children' : [], 'parent' : value.parent }) }) technologies.forEach ( function (value, index) { var result = formatted_subcategories.filter(function( obj ) { return obj.id == value.subCat; }); if (typeof result[0] != 'undefined') { result[0].children.push({ 'name' : value.label }) } }) categories.forEach ( function (value, index) { var result = formatted_subcategories.filter(function( obj ) { return obj.parent == value.id; }); formatted_json.children.push({ 'name' : value.label, 'id' : value.id, 'children' : result }) }) builder(formatted_json) } }) } }) } </script>
Well even though this was downvoted I still think it is important that an answer exists. https://gist.github.com/Quixomatic/f39d338bb4b4790d38f7d08b52f34b98 This link points to a gist that shows my solution to the problem. Thanks for the help, no one.
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
D3 regenerate(reload/reinitiate) collabsible tree
I am using D3.js to draw a tree, the tree are based on the json files. E.G. There two json files: a.json and b.json. When I click a.json, draw the tree for a.json and draw the tree for b.json when clicked. There are 4 nodes in a.json and 10 nodes in b.json file. The question is when I click a.json first and then click b.json, the first 4 nodes (nodes of a.json) will remained(does not disappear), and the left 6 nodes of b.json will be showed. If I click b.json first and click a.json later, the tree will only reduce 6 nodes, the node text will not change to the text of a.json. $("body").on("click",".hash_name",function(){ var hs = $(this).attr('id'); var repo_path = $("#repo_path").val(); var file_name = $(this).attr('name'); $("#file_name").val(file_name); var file_path = repo_path+'/'+file_name; $.post('../../admini/feature_matrix/git_operation.php', { hs:hs, repo_path:repo_path, type:5 }, function (output){ $('#source_feature_content').html(JSON.stringify(output)); root = output; root.x0 = height / 2; root.y0 = 0; function collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; } } root.children.forEach(collapse); update(root); },"json"); }); var margin = {top: 10, right: 20, bottom: 20, left: 80}, width = 1024 - margin.right - margin.left, height = 900 - margin.top - margin.bottom; var i = 0, duration = 300, root; var tree = d3.layout.tree() .size([height, width]); var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x]; }); // change to vertical var svg = d3.select("#feature_tree").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 + ")"); d3.select(self.frameElement).style("height", "800px"); function update(source) { // Recompute 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) * 50; // 50 pixels per line tree = tree.size([newHeight, width]); // 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) .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("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 + ")"; }); // change to vertical nodeUpdate.select("circle") .attr("r", 4.5) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#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 + ")"; }) // change to vertical .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(); nodes.forEach(function(d) { d.x0 = d.x; d.y0 = d.y; }); } function click(d) { if (d.children) { d._children = d.children; d.children = null; } else { d.children = d._children; d._children = null; } update(d); }
Change the layout of a tree in D3JS
Hello i am very new t D3JS and I am writing a tree visualization. I am having difficulty on changing the layout of the tree from horizontal to vertical(top-to-bottom). I am able to successfully display the nodes vertically but the links are always horizontal. Any help would be appriciated. Thank you. Here is my code: var m = [20, 120, 20, 120], w = 1280 - m[1] - m[3], h = 800 - m[0] - m[2], i = 0, root; var tree = d3.layout.tree() .size([h, w]); var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x]; }); var vis = d3.select("#visualize") .append("svg:svg") .attr("width", w + m[1] + m[3]) .attr("height", h + m[0] + m[2]) .append("svg:g") .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); d3.json("flare.json", function(json) { root = json; root.x0 = h / 2; root.y0 = 0; function toggleAll(d) { if (d.children) { d.children.forEach(toggleAll); toggle(d); } } // Initialize the display to show a few nodes. root.children.forEach(toggleAll); update(root); }); function update(source) { var duration = d3.event && d3.event.altKey ? 5000 : 500; // Compute the new tree layout. var nodes = tree.nodes(root).reverse(); // Normalize for fixed-depth. nodes.forEach(function(d) { d.y = d.depth * 180; }); // Update the nodes… var node = vis.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("svg:g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) .on("click", function(d) { toggle(d); update(d); }); nodeEnter.append("rect") .attr("y", function(d) { return d.children || d._children ? -5 : -5; }) .attr("width","9") .attr("height","9") .style("fill", function(d) { return d._children ? "skyblue" : "#fff"; }) .style("stroke","green") .style("stroke-width", "1.5px"); nodeEnter.append("svg:text") .attr("x", function(d) { return d.children || d._children ? -10 : 10; }) .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("rect") .attr("y", function(d) { return d.children || d._children ? -5 : -5; }) .attr("width","9") .attr("height","9") .style("fill", function(d) { return d._children ? "skyblue" : "#fff"; }) .style("stroke","green"); 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 = vis.selectAll("path.link") .data(tree.links(nodes), function(d) { return d.target.id; }); // Enter any new links at the parent's previous position. link.enter().insert("svg:path", "g") .attr("class", "link") .attr("d", function(d) { var o = {x: source.x0, y: source.y0}; return diagonal({source: o, target: o}); }) .transition() .duration(duration) .attr("d", diagonal); // 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. function toggle(d) { if (d.children) { d._children = d.children; d.children = null; } else { d.children = d._children; d._children = null; } }
This is how i solved it #dev_marshell08 <script type="text/javascript"> var w = 1600, h = 2600, r = 6, fill = d3.scale.category20(); var root; var force = d3.layout.force() .charge(-120) .linkDistance(30) .size([w, h]); var svg = d3.select("#visualize").append("svg:svg") .attr("width", w) .attr("height", h); d3.json("flare.json", function(json) { root = json; var nodes = flatten(root), links = d3.layout.tree().links(nodes); var link = svg.selectAll("line") .data(links) .enter().append("svg:line") .attr("class", "link"); var node = svg.selectAll("circle") .data(nodes) .enter().append("svg:circle") .attr("r", r - .75) .style("fill", function(d) { return fill(d.group); }) .style("stroke", function(d) { return d3.rgb(fill(d.group)).darker(); }) .call(force.drag); force .nodes(nodes) .links(links) .on("tick", tick) .start(); function tick(e) { // Push sources up and targets down to form a weak tree. var k = 6 * e.alpha; links.forEach(function(d, i) { d.source.y -= k; d.target.y += k; }); node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); link.attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); } }); function flatten(root) { var nodes = [], i = 0; function recurse(node) { if (node.children) node.children.forEach(recurse); if (!node.id) node.id = ++i; nodes.push(node); } recurse(root); return nodes; } </script>