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 want to create an interactive collapsible tree diagram from a csv table with D3.
The following code illustrates how to do it but not from a csv file:
https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd
The following code illustrates how to construct a tree from a csv file but it is not interactive.
http://bl.ocks.org/d3noob/fa0f16e271cb191ae85f
For example, the csv can look like this:
name,parent
Level 2: A,Top Level
Top Level,null
Son of A,Level 2: A
Daughter of A,Level 2: A
Level 2: B,Top Level
My idea was to use these codes to produce I want. I used the first code and add some lines from the second to use the csv file. However, it doesn't work. Do you have any ideas how to do it correctly?
Thanks.
This is the code I did, which doesn't work (error message : "d3.v4.min.js:2 Uncaught TypeError: t.eachBefore is not a function") :
<!DOCTYPE html>
<meta charset="UTF-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
d3.csv("treedata.csv", function(error, data) {
// Set the dimensions and margins of the diagram
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
var i = 0,
duration = 750;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// Assigns parent, children, height, depth
// *********** 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);
}
});
var root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function(d){ d.y = d.depth * 180});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) {return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', click);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) { return d.data.name; });
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
var o = {x: source.x0, y: source.y0}
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function(d){ return diagonal(d, d.parent) });
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.remove();
// Store the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
}
});
</script>
</body>
I have written the following that should do what you need it to. If not you can adjust it for your needs:
function convertToJsonTree(lines) {
// create the base
let parent = {};
// iterate through the lines
for (let i = 0; i < lines.length; i++) {
let _s = lines[i].split(',');
let _name = _s[0];
let _parent = _s[1];
// if the parents name is null, we must be at the base
if (_parent === 'null') {
// set the name
parent.name = _name;
// get rid of the current line
delete lines[i];
let subLines = lines.filter(function(el) {
return el != null;
});
// check for the first set of children
for (let j = 0; j < subLines.length; j++) {
let __s = subLines[j].split(',');
let __name = __s[0];
let __parent = __s[1];
// if there's a child that has the current level as the parent
if (parent && parent.name === __parent) {
// if the children array is undefined, create an array
if (!parent.children)
parent.children = [];
// create the child
let child = {
name: __name
};
// add it to the parents children
parent.children.push(child);
// remove the line that was already checked
delete subLines[j];
let _subLines = subLines.filter(function(el) {
return el != null;
});
// check if the child has any children
getChildren(child, _subLines);
}
}
}
}
return parent;
}
function getChildren(parent, lines) {
for (let i = 0; i < lines.length; i++) {
let _s = lines[i].split(',');
let _name = _s[0];
let _parent = _s[1];
// if there's a child that has the current level as the parent
if (parent && parent.name === _parent) {
// if the children array is undefined, create an array
if (!parent.children)
parent.children = [];
// create the child
let child = {
name: _name
};
// add it to the parents children
parent.children.push(child);
// remove the line that was already checked
delete lines[i];
let subLines = lines.filter(function(el) {
return el != null;
});
// check if the child has any children
getChildren(child, subLines);
}
}
}
Its use:
let csv = `name,parent
Level 2: A,Top Level
Top Level,null
Son of A,Level 2: A
Daughter of A,Level 2: A
Level 2: B,Top Level`;
let lines = csv.split(/\r\n|\n/);
let treeData = convertToJsonTree(lines);
console.log(treeData);
JSFiddle: https://jsfiddle.net/dbheale/Lrhsxw9e/
EDIT:
To read in your CSV from a file:
function loadDiagramFromCsvFile(file)
{
var rawFile = new XMLHttpRequest();
rawFile.open("GET", file, false);
rawFile.onreadystatechange = function ()
{
if(rawFile.readyState === 4)
{
if(rawFile.status === 200 || rawFile.status == 0)
{
var csv = rawFile.responseText;
let lines = csv.split(/\r\n|\n/);
let treeData = convertToJsonTree(lines);
// Do what you need with your treeData here
// Maybe create a method that generates the diagram?
createDiagram(treeData);
}
}
}
rawFile.send(null);
}
I am trying to display d3 collapsable tree with search, here is the reference example am using [Search tree][1]
[1]: https://bl.ocks.org/jjzieve/a743242f46321491a950 I am getting error select2() is not a function. I did not get any solution on v5, how to change the above example based on my code.
Here is my code
function searchTree(obj, search, path) {
if (obj.name === search) { //if search is found return, add the object to the path and return it
path.push(obj);
return path;
}
else if (obj.children || obj._children) { //if children are collapsed d3 object will have them instantiated as _children
var children = (obj.children) ? obj.children : obj._children;
for (var i = 0; i < children.length; i++) {
path.push(obj);// we assume this path is the right one
var found = searchTree(children[i], search, path);
if (found) {// we were right, this should return the bubbled-up path from the first if statement
return found;
}
else {//we were wrong, remove this parent from the path and continue iterating
path.pop();
}
}
}
else {//not the right object, return false so it will continue to iterate in the loop
return false;
}
}
function extract_select2_data(node, leaves, index) {
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
index = extract_select2_data(node.children[i], leaves, index)[0];
}
}
else {
leaves.push({ id: ++index, text: node.name });
}
return [index, leaves];
}
console.log("left side hitting ");
// Set the dimensions and margins of the diagram
var margin = { top: 20, right: 120, bottom: 20, left: 120 },
width = 1280 * 10 - margin.right - margin.left,
height = 1200 *10 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select(".col-md-6").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
var i = 0,
duration = 750,
root, path,select2_data;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
function openPaths(paths) {
for (var i = 0; i < paths.length; i++) {
if (paths[i].id !== "1") {//i.e. not root
paths[i].class = 'found';
if (paths[i]._children) { //if children are hidden: open them, otherwise: don't do anything
paths[i].children = paths[i]._children;
paths[i]._children = null;
}
update(paths[i]);
}
}
}
// Assigns parent, children, height, depth
root = d3.hierarchy(this.parentTreeObj, function (d) { return d.children });
select2_data = extract_select2_data(this.parentTreeObj,[],0)[1];
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
$(".col-md-6").select2({
data: select2_data,
containerCssClass: "search"
});
//attach search box listener
$(".col-md-6").on("select2-selecting", function (e) {
var paths = searchTree(root, e.object.text, []);
if (typeof (paths) !== "undefined") {
openPaths(paths);
}
else {
alert(e.object.text + " not found!");
}
})
d3.select(self.frameElement).style("height", "800px");
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// compute the new height
var levelWidth = [1];
var childCount = function (level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function (d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 20;
treemap = d3.tree().size([newHeight, width]);
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function (d) { d.y = (d.depth * 180); });
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function (d) {
return d.id || (d.id = ++i);
});
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function (d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', click);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function (d) {
return d._children ? "#eb8c00" : "#da5506";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function (d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "start";
})
.text(function (d) {
return d.data.name;
});
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function (d) {
return d._children || d._id ? "#eb8c00" : "#da5506";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function (d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function (d) {
var o = { x: source.x0, y: source.y0 }
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function (d) { return diagonal(d, d.parent) });
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function (d) {
var o = { x: source.x, y: source.y }
return diagonal(o, o)
})
.remove();
// Store the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
console.log("left side hitting inside ");
console.log("in Left");
// console.log("idddd",d.data.id);
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
// d._id = d.data.id;
// d.data.id = null;
} else {
d.children = d._children;
d._children = null;
// d.data.id = d._id;
// d._id = null;
}
update(d);
}
}
Can anyone please help me out this. Thanks in advance
Have you linked your file to the select2 library? For example, do you have this:
<link rel="stylesheet" type="text/css" href="http://cdnjs.cloudflare.com/ajax/libs/select2/3.5.2/select2.min.css"></link>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/select2/3.5.2/select2.min.js"></script>
If you could share a link through JSFiddle, bl.ocks, or in a repository, that would help out a lot too. Let me know if that helps.
I am attempting to make a tree viewing web application. I am using the d3 tree layout to generate my trees. What I would like to be able to do is scale the size of the tree in either axis. In otherwords being able to scale the x and y axis of the viewer. The would effectively just increase either the vertical distance between nodes or increase the length of the branches.
I have been unable to accomplish this. I have attempted to change the nodeSize() of the tree layout, and then update which had no effect. I have also attempted to modify the x or y coordinate of each node which again had no effect. I have been completely unable to modify the physical layout of the tree once it has been rendered.
I have read and attempted the fixes in the following threads:
Dynamically resize the d3 tree layout based on number of childnodes
D3 Tree Layout Separation Between Nodes using NodeSize
But they did not solve my problem.
I am implementing a TreeGenerator object, which handles all of the rendering of the SVG, using prototyped functions. I am going for a full screen feeling, so the TreeGenerator appends an SVG to a div with width: 100%; height: 100%. I should node that I have implemented both zooming and panning functionality.
Here is the constructor:
TreeGenerator = function(target, treeString) {
// Set up everything here
// colors
this.nodeColor = "#3B6073";
this.highlightColor = "#F22248";
this.searchColor = "#0DFFC2";
this.nodeHeight = 5;
this.nodeLength = 20;
this.zoomX = 1;
this.zoomY = 5;
this._winHeight = window.innerHeight;
this._winWidth = window.innerWidth;
//this.xScale = d3.scale.linear().domain([0,100]).range([0, this._winWidth])
//this.yScale = d3.scale.linear().domain([0,100]).range([0, this._winHeight])
// keep the matching results from last search
this._searchResults = [];
// path lengths
this.maxPathLength = 0;
this._loadSVG(target);
this._tree(treeString);
}
And here are the two functions _loadSVG and _tree which are called from the constructor:
TreeGenerator.prototype._loadSVG = function(target) {
var zoom = d3.behavior.zoom()
//.size([this._winWidth, this._winHeight])
.scaleExtent([this.zoomX,this.zoomY])
//.x(this.xScale)
//.y(this.yScale)
//.center([height/2, width/2])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
this.svg = d3.select(target, ":first-child").append("svg")
.append("g")
//.attr("transform", "translate("+width / 2+","+height / 2+")")
.call(zoom)
.on("dblclick.zoom", null)
var rect = this.svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all");
this.container = this.svg.append("g")
// scope it for d3 funcs
var container = this.container
var self = this;
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
}
And _tree:
TreeGenerator.prototype._tree = function (treeString) {
// needs an array for d3
this.treeData = this.parseTree(treeString);
this.root = this.treeData[0];
// set up the layout
this.tree = d3.layout.tree()
.nodeSize([this.nodeHeight,this.nodeLength]);
// set path dists
this._setPathDist();
this.yScale = d3.scale.linear().domain([0, this.maxPathLength]).range([0, 20]);
this.xScale = d3.scale.linear().domain([1,100]).range([10, 30]);
var self = this;
update(this.root);
function update(source) {
var i = 0;
// generator for paths from
// node ---> node
var horizontal = d3.svg.line().interpolate('step-before')
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
// Compute the new tree layout.
var nodes = self.tree.nodes(source).reverse()
nodes.forEach(function (d) {
if(!d.pathLength == 0) {
d.y = d.y * self.yScale(d.pathLength);
}
else if(d.children != undefined) {
d.y += 5;
}
});
links = self.tree.links(nodes);
// Declare the nodes
var node = self.container.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); })
// Enter the nodes.
doubleclickTimer = false // dont ask
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("id", function(d) { return "node-"+d.id.toString(); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; })
.on("click", clicked);
nodeEnter.append("circle")
.attr("r", 1)
.style("fill", self.nodeColor)
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -1 : 1; })
.attr("y", function(d) {
return d.children == undefined ? 1 : -1;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1);
// try and update
//var nodeUpdate = node.update()
// .attr("x", function(d) { return d.x });
// Declare the links
var link = self.container.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
return horizontal([
{y: d.source.x, x: d.source.y},
{y: d.target.x, x: d.target.y}
]);
});
function clicked(d) {
// need the g group for highlight
node = this;
// if double click timer is active, this click is the double click
if ( doubleclickTimer )
{
clearTimeout(doubleclickTimer);
doubleclickTimer = false;
collapse(d);
}
// otherwise, what to do after single click (double click has timed out)
else {
doubleclickTimer = setTimeout( function() {
doubleclickTimer = false;
highlight(node, d);
}, 175);
}
}
function highlight(node,d) {
// we want to bold the text
// and color the node
self._unhighlightNode();
// toggle if clicking again
if(self._highlightedNode == node) {
self._highlightedNode = undefined;
self.selectedNode = undefined;
return;
}
// set the new one
var circle = d3.select(node).select("circle");
var text = d3.select(node).select("text");
circle.style("fill", self.highlightColor);
text.style("font-size","4px");
// update the pointer
self._highlightedNode = node;
self.selectedNode = d;
}
function collapse(d) {
}
}
};
I know the code is a bit messy, but it is just full of commented out things I did in an attempt to fix this issue. I appreciate any help that can be offered. Hopefully I included enough info.
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.