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>
Related
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
I am able to draw tree structure with json data on initial load of the application.
Now my requirement is, on click of button I should be able to add more text/css to each nodes which is working fine.
I am actually using the same function used to draw tree initially, able to interchange data on trigger of button.
When I do so, on click the first level node (to expand to second level), first level paths disappears from g tag and selected node changes the position and lies in second level data.
Tired all possible ways, can anyone help to resolve this.
thanks in advance
Below is the tree function used
//To draw tree structure for the selected data
drawTree: function() {
var driverTree = this;
var id = "#" + this.getView().byId("chartArea").sId;
// ************** Generate the tree diagram *****************
var treeData = driverTree.treeModel.oData;
var radius = 40;
var width = $(id).width();
var height = window.innerHeight;
var maxLabel = 1500;
var i = 0,
duration = 450,
root;
var tree = d3.layout.tree()
.size([height, width])
.separation(function(a, b) {
return (a.parent == b.parent ? 1 : 2);
});
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
}
var slider = d3.scale.linear()
.domain([1, 100])
.range([1, 100])
.clamp(true);
var canvasWidth = radius * 2 + margin.left + margin.right,
canvasHeight = radius * 2 + margin.top + margin.bottom;
var color = d3.scale.category10();
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var pie = d3.layout.pie()
.value(function(d) {
if (isNaN(parseFloat(d.value)))
return 0;
else
return parseFloat(d.value);
})
.sort(null);
// arc object
var arc = d3.svg.arc()
.outerRadius(40)
.innerRadius(20);
//var id = "#" + chartArea.sId;
d3.select(id).select("svg").remove();
driverTree.zoomListener = d3.behavior.zoom()
.scaleExtent([0.1, 3])
.on("zoom", function(d) {
zoomed(d);
});
var svgGroup = d3.select(id).append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "tooltip")
.call(driverTree.zoomListener);
function zoomed(d) {
if (driverTree.hoizMode)
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")rotate(90,50,50)");
else
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var svg = svgGroup.append("g")
.attr("transform", "translate(" + width + "," + (height / 2) / 2 + ") rotate(90,50,50)");
root = treeData;
root.x0 = height / 2;
root.y0 = 0;
var div = d3.select("body").append("div").attr("class", "tooltip").style("opacity", "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");
var circumference_r = 35;
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 newHeight = d3.max(levelWidth) * 263.6; // 25 pixels per line
tree = tree.size([newHeight, window.innerHeight]);
// 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("id", function(d) {
return "id" + d.title;
})
.attr("class", "node")
.attr("transform", function(d) {
if (driverTree.hoizMode)
return "translate(" + source.y0 + "," + source.x0 + ") rotate(-90)";
else
return "translate(" + source.y0 + "," + source.x0 + ") rotate(0)";
})
.on("mouseover", function(d) {
mouseover(d);
})
.on("mousemove", function(d) {
mousemove(d);
})
.on("mouseout", function(d) {
mouseout(d);
});
if (driverTree.editTree) {
nodeEnter.append("rect")
.attr("id", "hideButton")
.attr("width", function(d) {
if (d.id != "NODE1") return "40px";
})
.attr("transform", function(d) {
if (d.id != "NODE1") return "translate(-40,-70)";
})
.attr("rx", "5")
.attr("ry", "5")
.attr("height", function(d) {
if (d.id != "NODE1") return "25px";
})
.style("stroke", "#999faa")
.style("stroke-width", "2px")
.style("fill", function(d) {
if (d.isHidden) return "#000f2b";
else return "#8E94A1";
})
.on("click", function(d) {
d.isHidden = !d.isHidden;
if (d.isHidden) return this.style.fill = "#000f2b";
else return this.style.fill = "#8E94A1";
});
nodeEnter.append("text")
.attr("id", "hideText")
.attr("transform", "translate(-31,-54)")
.style("fill", "#eee")
.text(function(d) {
if (d.id != "NODE1") return "Hide";
})
.on("click", function(d) {
d.isHidden = !d.isHidden;
if (d.isHidden) return d3.select(this.parentNode).select("rect").style("fill", "#000f2b");
else return d3.select(this.parentNode).select("rect").style("fill", "#8E94A1");
});
}
nodeEnter.append("circle")
.attr("r", "40")
.on("click", click);
nodeEnter.append("text")
.attr("id", function(d) {
return "ct" + d.title;
})
.attr("x", "-8")
.attr("y", "4")
.text(function(d) {
return brevoVDT.util.Formatter.amountToMillions(d.value);
})
.on("click", click);
nodeEnter.append("text")
.attr("transform", "translate(55,-10)")
.text(function(d) {
return "Org Value - " + brevoVDT.util.Formatter.amountToMillions(d.value_org);
})
nodeEnter.append("text")
.attr("id", function(d) {
return "cv" + d.title;
})
.attr("transform", "translate(55,5)")
.text(function(d) {
return "Currenet Value - " + brevoVDT.util.Formatter.amountToMillions(d.value);
})
nodeEnter.append("text")
.attr("id", function(d) {
return "dv" + d.title;
})
.attr("transform", "translate(55,20)")
.text(function(d) {
return "Difference " + brevoVDT.util.Formatter.amountToMillions(parseFloat(d.value_org) - parseFloat(d.value));
})
.style("fill", function(d) {
var value = parseFloat(d.value_org) - parseFloat(d.value);
if (value > 0) return "green";
else if (value < 0) return "red";
else return "black";
})
/*nodeEnter.append("text")
.attr("transform", "translate(-50,58)")
.attr("text-anchor", "start")
.text(function(d) {
return d.title;
})*/
nodeEnter.append("rect")
.attr("width", "130px")
.attr("transform", "translate(48,-25)")
.attr("height", "50px")
.attr("rx", "15")
.attr("ry", "15")
.style("fill", "rgba(238, 238, 238, 0)")
.style("opacity", "0.9")
.style("stroke",
function(d) {
if (d.children == null && d._children == null)
return "black";
else
return "orange";
})
.style("stroke-width", "2");
nodeEnter.append("g").attr("class", "slicers");
nodeEnter.append("g").attr("class", "lines");
var pieNodes = nodeEnter.select(".slicers").selectAll("path")
.data(function(d, i) {
if (d.id == "NODE1") var value = 0;
else var value = d.parent.value;
value = value == 0 ? 1 : value;
return pie([d, {
value: value
}]);
})
.enter()
.append("svg:path")
.attr("class", "slice")
.attr("fill", function(d, i) {
return color(i);
})
.each(function(d) {
d;
})
.attr('d', arc)
//toolbar
function mouseover(d) {
div.transition()
.duration(500)
.style("opacity", 0.9)
.style("display", "block");
}
function mousemove(d) {
var name = d.name.length > 13 ? d.name.slice("0", "13") : d.name;
div.html("<b>" + d.title + "</b><table>" +
"<tr><td>" + name + ":</td><td>" + parseFloat(d.value).toFixed(1) + "</td></tr></table>")
.style("left", (d3.event.pageX + 50) + "px")
.style("top", (d3.event.pageY + 0) + "px");
}
function mouseout(d) {
div.transition()
.duration(500)
.style("display", "none");
}
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (driverTree.hoizMode)
return "translate(" + d.y + "," + d.x + ") rotate(-90)";
else
return "translate(" + d.y + "," + d.x + ") rotate(0)";
});
nodeUpdate.select("circle");
nodeUpdate.select("text");
nodeUpdate.select("rect");
nodeUpdate.select("foreignObject");
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
if (driverTree.hoizMode)
return "translate(" + source.y + "," + source.x + ") rotate(-90)";
else
return "translate(" + source.y + "," + source.x + ") rotate(0)";
})
.remove();
nodeExit.select("circle");
nodeExit.select("text");
nodeExit.select("rect");
nodeExit.select("foreignObject");
// 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 + 100
};
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;
});
}
// Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
function centerNode(source) {
var scale = driverTree.zoomListener.scale();
var x = -source.y0;
var y = -source.x0;
if (driverTree.hoizMode) {
d3.select("g").transition()
.duration(1)
.attr("transform", "translate(" + width + "," + (height / 2) / 2 + ")scale(" + scale + ")rotate(90,50,50)");
driverTree.zoomListener.translate([width, (height / 2) / 2]);
} else {
d3.select("g").transition()
.duration(1)
.attr("transform", "translate(" + height / 2 + "," + -width / 2 + ")scale(" + scale + ")");
driverTree.zoomListener.translate([height / 2, -width / 2]);
}
driverTree.zoomListener.scale(scale);
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
centerNode(d)
}
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
update(root);
centerNode(root);
Below is my edit function code
// To make the tree editable
handleEdit: function(evt) {
var that = this;
this.editTree = !this.editTree;
this.treeModel.oData.x = 0;
this.treeModel.oData.y = 0;
this.treeModel.oData.x0 = 0;
this.treeModel.oData.y0 = 0;
this.treeModel.oData.children.forEach(function(d){
d.x = 0; d.y = 0; d.x0 = 0; d.y0 = 0;
var children = (d._children == undefined ? d.children : d._children );
children.forEach(function(a){
a.x = 0; a.y = 0; a.x0 = 0; a.y0 = 0;
});
});
this.drawTree();
},
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);
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
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.