A follow-on question / issue to
Programmatically open nested, collapsed (hidden) node in d3.js v4
updated for d3.js v6. The issue is the loading of external JSON data in the d3 collapsible menu visualization, and the programmatic access of nested (collapsed, hidden) nodes.
It appears that "treeData", which is the loaded Object, is not being delivered.
Uncaught ReferenceError: treeData is not defined
JSFiddle: https://jsfiddle.net/vstuart/kant09hm/6/
JSFiddle (updated with answer): https://jsfiddle.net/vstuart/kant09hm/9/
ontology_for_d3_test.json
gist: https://gist.github.com/victoriastuart/abbcf355bf1590be02f6dec297be2706
{ "name": "Root",
"children": [
{ "name": "Culture",
"children": [
{ "name": "Entertainment" },
{ "name": "LGBT" }
]
},
{ "name": "Nature",
"id": "nature",
"children": [
{ "name": "Earth",
"id": "earth",
"children": [
{ "name": "Environment" },
{ "name": "Geography" },
{ "name": "Geology" },
{ "name": "Geopolitical" },
{ "name": "Geopolitical - Countries" },
{ "name": "Geopolitical - Countries - Canada" },
{ "name": "Geopolitical - Countries - United States" },
{ "name": "Nature" },
{ "name": "Regions" }
]
},
{ "name": "Cosmos" },
{ "name": "Outer space" }
]
},
{ "name": "Humanities",
"children": [
{ "name": "History" },
{ "name": "Philosophy" },
{ "name": "Philosophy - Theology" }
]
},
{ "name": "Miscellaneous",
"children": [
{ "name": "Wikipedia",
"url": "https://wikipedia.com" },
{ "name": "Example.com",
"url": "https://example.com" }
]
},
{ "name": "Science",
"children": [
{ "name": "Biology" },
{ "name": "Health" },
{ "name": "Health - Medicine" },
{ "name": "Sociology" }
]
},
{ "name": "Technology",
"children": [
{ "name": "Computers" },
{ "name": "Computers - Hardware" },
{ "name": "Computers - Software" },
{ "name": "Computing" },
{ "name": "Computing - Programming" },
{ "name": "Internet" },
{ "name": "Space" },
{ "name": "Transportation" }
]
},
{ "name": "Society",
"children": [
{ "name": "Business" },
{ "name": "Economics" },
{ "name": "Economics - Business" },
{ "name": "Economics - Capitalism" },
{ "name": "Economics - Commerce" },
{ "name": "Economics - Finance" },
{ "name": "Politics" },
{ "name": "Public services" }
]
}
]
}
index.html [standalone working copy of JSFiddle; edit: updated with answer]
<!DOCTYPE html>
<html lang="en-US" xmlns:xlink="http://www.w3.org/1999/xlink">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
#includedContent {
position: static !important;
display: inline-block;
}
#d3_object {
width: 75%;
margin: 0.5rem 0.5rem 1rem 0.25rem;
}
</style>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<!-- <script src="https://d3js.org/d3.v4.min.js"></script> -->
<!-- <script src="https://d3js.org/d3.v5.min.js"></script> -->
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="d3_object">
<object>
<p>apple</p>
<div id="includedContent"></div>
<p>banana</p>
</object>
</div>
<script type="text/javascript">
// 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;
// ----------------------------------------
// PAN, ZOOM:
// https://www.d3-graph-gallery.com/graph/interactivity_zoom.html
// var svg = d3.select("body").append("svg")
var svg = d3.select("#includedContent").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
// d3.js v4, v5:
// .call(d3.zoom().on("zoom", function () {
// svg.attr("transform", d3.event.transform)
// d3.js v6:
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
// ----------------------------------------
var i = 0,
duration = 250,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// ----------------------------------------
// LOAD THE EXTERNAL DATA:
//d3.json("https://gist.githubusercontent.com/mbostock/4339083/raw/9585d220bef18a0925922f4d384265ef767566f5/flare.json", function(error, treeData) {
// ----------------------------------------
// d3.js v4 [ https://d3js.org/d3.v4.min.js ]
/*
d3.json("https://gist.githubusercontent.com/victoriastuart/abbcf355bf1590be02f6dec297be2706/raw/2418e5f6b7626b3c5842665a51b7d0d27f74e909/ontology_for_d3_test.json", function(error, treeData) {
if (error) throw error;
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
});
*/
// ----------------------------------------
// ----------------------------------------
// d3.js v5 https://d3js.org/d3.v5.min.js
// https://gist.github.com/d3noob/1a96af738c89b88723eb63456beb6510
// d3.js v6 https://d3js.org/d3.v5.min.js
// https://gist.github.com/d3noob/9de0768412ac2ce5dbec430bb1370efe
// https://stackoverflow.com/questions/49768165/code-within-d3-json-callback-is-not-executed
// https://www.tutorialsteacher.com/d3js/loading-data-from-file-in-d3js
// https://stackoverflow.com/questions/47664292/d3-json-method-doesnt-return-my-data-array
// ----------------------------------------
// LOAD EXTERNAL JSON DATA FILE (via PROMISE):
// https://stackoverflow.com/questions/49768165/code-within-d3-json-callback-is-not-executed
// https://stackoverflow.com/questions/49534470/d3-js-v5-promise-all-replaced-d3-queue
// https://www.roelpeters.be/explaining-promises-in-d3-js-the-what-and-the-why/
//
// https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call
// This callback function should return the "treeData" Object:
d3.json("https://gist.githubusercontent.com/victoriastuart/abbcf355bf1590be02f6dec297be2706/raw/2418e5f6b7626b3c5842665a51b7d0d27f74e909/ontology_for_d3_test.json")
.then(function(treeData) {
console.log('[d3.js] treeData:', treeData, '| type:', typeof(treeData), '| length:', treeData['children'].length)
for (let i = 0; i < treeData['children'].length; i++) {
console.log('node:', treeData['children'][i].name);
if ( treeData['children'][i].id !== undefined ) {
console.log(' id:', treeData['children'][i].id);
}
}
// ASSIGN PARENT, CHILDREN, HEIGHT, DEPTH:
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// COLLAPSE AFTER THE SECOND LEVEL:
root.children.forEach(collapse);
update(root);
// ----------------------------------------
// Per answers at
// https://stackoverflow.com/questions/67527258/programmatically-open-nested-collapsed-hidden-node-in-d3-js-v4/67530786?noredirect=1#comment119390942_67530786
// https://jsfiddle.net/mrovinsky/ujwsd7qz/
// https://stackoverflow.com/questions/67549992/accessing-promised-data-in-d3-js-v6-programmatically-opening-nested-collapsed-n
const findNodeAncestors = (root, name) => {
if (root.name === name) {
return [name];
}
if (root.children)
for (let i = 0; i < root.children.length; i++) {
const chain = findNodeAncestors(root.children[i], name);
if (chain) {
chain.push(root.name);
return chain;
}
}
return null;
};
const chain = findNodeAncestors(treeData, 'Earth');
if (chain) {
console.log('[d3.js] chain:', chain)
for (let i = chain.length - 1; i >= 0; i--) {
const node = d3.select(`.node[node-name="${chain[i]}"]`);
const nodeData = node.datum();
if (!nodeData.children && nodeData.data.children) {
node.node().dispatchEvent(new Event('click'));
}
}
}
else {
console.log('[d3.js] "chain" is either "null" or "undefined"')
}
// ----------------------------------------
})
// IF (ERROR) THROW ERROR:
.catch(function(error) {
console.log('[d3.js] JSON callback function error')
if (error) throw error;
});
// ----------------------------------------
// 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')
// --------------------------------------------
// Per answer at
// https://stackoverflow.com/questions/67480339/programmatically-opening-d3-js-v4-collapsible-tree-nodes
.attr('node-name', d => d.data.name)
// --------------------------------------------
.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;
});
// CREATE 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) {
// ***** New in d3.js v6: *****
function click(event, d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
d.children = d._children;
d._children = null;
} else {
// This was a leaf node, so redirect.
console.log('d:', d)
console.log('d.data:', d.data)
console.log('d.name:', d.name)
console.log('d.data.name:', d.data.name)
console.log('urlMap[d.data.name]:', urlMap[d.data.name])
window.location = d.data.url;
// window.open("https://www.example.com", "_self");
}
update(d);
}
// ----------------------------------------
}
// ----------------------------------------
// Per answer at
// https://stackoverflow.com/questions/67480339/programmatically-opening-d3-js-v4-collapsible-tree-nodes
///*
setTimeout(() => {
//const node = d3.select('.node[node-name="Earth"]').node();
//const node = d3.select('.node[node-name="Nature"]').node();
const node = d3.select('.node[node-name="Society"]').node();
console.log('[setTimeout()] NODE: ', node);
node.dispatchEvent(new Event('click'));
}, 2500);
//*/
// ----------------------------------------
// ----------------------------------------
// Per answer at
// https://stackoverflow.com/questions/67527258/programmatically-open-nested-collapsed-hidden-node-in-d3-js-v4/67530786?noredirect=1#comment119390942_67530786
// https://jsfiddle.net/mrovinsky/ujwsd7qz/
const findNodeAncestors = (root, name) => {
if (root.name === name) {
return [name];
}
if (root.children)
for (let i = 0; i < root.children.length; i++) {
const chain = findNodeAncestors(root.children[i], name);
if (chain) {
chain.push(root.name);
return chain;
}
}
return null;
};
const chain = findNodeAncestors(treeData, 'Earth');
// Console: "Uncaught ReferenceError: treeData is not defined"
for (let i = chain.length - 1; i >= 0; i--) {
const node = d3.select(`.node[node-name="${chain[i]}"]`);
const nodeData = node.datum();
if (!nodeData.children && nodeData.data.children) {
node.node().dispatchEvent(new Event('click'));
}
}
// ----------------------------------------
</script>
</body>
</html>
The treeData variable can be used only in the scope of the function where it's defined as an argument:
d3.json("https://...json")
.then(function(treeData) {
// Scope of the function, treeData can be used here
});
// Out of scope, treeData is not defined
The solution is to move the block that starts with const findNodeAncestors... (~25 lines) inside the function body (right after the last occurence of update with a closing brace):
update(d);
}
// ----------------------------------------
// HERE
}
Related
I am new in D3 and developing a family tree using d3v4(for some constraints not using latest version of d3. Though I am using latest d3 cdn but my requirement is d3v4). The family tree is vertical i.e., parent is in top and children are in bottom. But I want to turn the tree and want to make it horizontal i.e., parent is in left and children are in right.
Kindly find my code below:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 600)
.append("g").attr("transform", "translate(50, 50)");
var data = [
{
"child": {
"type": "from",
"name": "John",
"factor": 5
},
"parent": ""
},
{
"child": {
"type": "cc",
"name": "Kevin",
"factor": 4
},
"parent": {
"type": "from",
"name": "John",
"factor": 5
}
},
{
"child": {
"type": "bcc",
"name": "Mary",
"factor": 6
},
"parent": {
"type": "from",
"name": "John",
"factor": 5
}
},
{
"child": {
"type": "cc",
"name": "Mars",
"factor": 5
},
"parent": {
"type": "from",
"name": "John",
"factor": 5
}
}
]
var datastructure = d3.stratify()
.id(function(d) { return d.child.name; })
.parentId(function(d) { return d.parent.name; })
(data);
var treeStructure = d3.tree().size([500, 300]);
var information = treeStructure(datastructure);
// console.log(information.descendants());
// console.log(information.links());
var circles = svg.append("g").selectAll("circle")
.data(information.descendants());
var markerBoxWidth = 20;
var markerBoxHeight = 20;
var refX = markerBoxWidth / 2;
var refY = markerBoxHeight / 2;
var markerWidth = markerBoxWidth / 2;
var markerHeight = markerBoxHeight / 2;
var arrowPoints = [[0, 0], [0, 20], [20, 10]];
svg
.append('defs')
.append('marker')
.attr('id', 'arrow')
.attr('viewBox', [0, 0, markerBoxWidth, markerBoxHeight])
.attr('refX', refX)
.attr('refY', refY)
.attr('markerWidth', markerBoxWidth)
.attr('markerHeight', markerBoxHeight)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d', d3.line()(arrowPoints))
.attr('fill', 'red');
circles.enter().append("circle")
.attr("cx", function(d) {
// console.log(d)
return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) {
//console.log()
return d.data.child.factor * 3;
})
.attr("fill", function(d) {
if (d.data.child.type === 'from') {
return "blue";
} else if (d.data.child.type === 'cc') {
return "yellow";
} else if (d.data.child.type === 'bcc') {
return "red";
}
});
var connections = svg.append("g").selectAll("path")
.data(information.links());
connections.enter().append("path")
.attr("d", function(d) {
console.log(d);
return "M" + d.source.x + "," + (d.source.y + (d.source.data.child.factor * 3))
+ " C " +
d.source.x + "," + (d.source.y + d.target.y)/2 + " " +
d.target.x + "," + (d.source.y + d.target.y)/2 + " " +
d.target.x + "," + (d.target.y - (d.target.data.child.factor * 3));
}).attr("fill", "none").attr("stroke", "red")
.attr('marker-end', 'url(#arrow)')
.attr('fill', 'none');
var names = svg.append("g").selectAll("text")
.data(information.descendants());
const xScale = d3.scaleLinear()
.domain([0, 8])
.range([0, 1000]);
names.enter().append("text")
.text(function(d) { return d.data.child.name; })
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
</script>
</body>
</html>
Thanks in advance.
I wanted to show the checkbox when hovering on the node so that when the checkbox is clicked the name of that node should be shown at the top of the spider graph and if unticked then that name should disappear.
I used the mouseover event to show the checkbox but is not working
.on("mouseover", function(d) {
d3.select(this).transition()
.duration(200)
.style('cursor', 'pointer')
.html('<input type="checkbox" name="name" class="checkbox" />')
})
Here is the full code with demo
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
</head>
<body>
<svg className='spider-graph-svg'>
</svg>
<script>
var data = {
"name": "root#gmail.com",
"children": [{
"name": "Person Name 1",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person name 2",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 3",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 4",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}]
};
const LAST_CHILDREN_WIDTH = 13;
let flagForChildren = false;
let groups = [];
data.children.forEach(d => {
let a = [];
if (d.children.length > 0) {
flagForChildren = true;
}
for (let i = 0; i < d.children.length; i += 2) {
let b = d.children.slice(i, i + 2);
if (b[0] && b[1]) {
a.push(Object.assign(b[0], {
children: [b[1]]
}));
} else {
let child = b[0];
if (i >= 6) {
child = Object.assign(child, {
children: [{
name: "..."
}]
});
}
a.push(child);
}
}
d.children = a;
groups.push(d);
});
data.children = groups;
let split_index = Math.round(data.children.length / 2);
let rectangleHeight = 45;
let leftData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
let leftDataArray = [];
leftDataArray.push(leftData);
// Right data
let rightData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
let right = d3.hierarchy(rightData);
let left = d3.hierarchy(leftData);
// Render both trees
drawTree(right, "right");
drawTree(left, "left");
// draw single tree
function drawTree(root, pos) {
let SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
const margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = window.innerWidth - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
let svg = d3
.select("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.right + margin.left)
.attr('view-box', '0 0 ' + (width + margin.right) + ' ' + (height + margin.top + margin.bottom))
.style("margin-top", "20px")
.style("margin-left", "88px");
const myTool = d3.select("body").append("div")
.attr("class", "mytooltip")
.style("opacity", "0")
.style("display", "none");;
// Shift the entire tree by half it's width
let g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
let deductWidthValue = flagForChildren ? 0 : width * 0.33;
// Create new default tree layout
let tree = d3
.tree()
// Set the size
// Remember the tree is rotated
// so the height is used as the width
// and the width as the height
.size([height - 50, SWITCH_CONST * (width - deductWidthValue) / 2])
.separation((a, b) => a.parent === b.parent ? 4 : 4.25);
tree(root);
let nodes = root.descendants();
let links = root.links();
// Set both root nodes to be dead center vertically
nodes[0].x = height / 2;
// Create links
let link = g
.selectAll(".link")
.data(links)
.enter();
link
.append("line")
.attr("class", function(d) {
if (d.target.depth === 2) {
return 'link'
} else {
return 'hard--link'
}
})
.attr("x1", function(d) {
if (
d.target.depth === 3
) {
return 0;
}
return d.source.y + 100 / 2; //d.source.y + 100/2
})
.attr("x2", function(d) {
if (
d.target.depth === 3
) {
return 0;
} else if (d.target.depth === 2) {
return d.target.y;
}
return d.target.y + 100 / 2; //d.target.y + 100/2;
})
.attr("y1", function(d) {
if (
d.target.depth === 3
) {
return 0;
}
return d.source.x + 50 / 2;
})
.attr("y2", function(d) {
if (
d.target.depth === 3
) {
return 0;
} else if (d.target.depth === 2) {
return d.target.x + LAST_CHILDREN_WIDTH / 2;
}
return d.target.x + 50 / 2;
});
//Rectangle width
let node = g
.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.on("mouseover", function(d) {
d3.select(this).transition()
.duration(200)
.style('cursor', 'pointer')
.html('<input type="checkbox" name="name" class="checkbox" />')
})
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
if (d.parent && d.parent.parent) { // this is the leaf node
if (d.parent.parent.parent) {
return (
"translate(" +
d.parent.y +
"," +
(d.x + LAST_CHILDREN_WIDTH + 15) +
")"
);
}
return "translate(" + d.y + "," + d.x + ")";
}
return "translate(" + d.y + "," + d.x + ")";
});
// topic rect
node
.append("rect")
.attr("height", (d, i) => d.parent && d.parent.parent ? 15 : rectangleHeight)
.attr("width", (d, i) => d.parent && d.parent.parent ? 15 : rectangleWidth(d))
.attr("rx", (d, i) => d.parent && d.parent.parent ? 5 : 5)
.attr("ry", (d, i) => d.parent && d.parent.parent ? 5 : 5)
// topic edges
node.append('line')
.attr('x1', d => {
if (d.depth === 2) {
return 10
}
})
.attr('x2', d => {
if (d.depth === 2) {
return 10
}
})
.attr('y1', d => {
if (d.depth === 2) {
if (d.children) {
return 0;
}
return 40;
}
})
.attr('y2', d => {
if (d.depth === 2) {
return 40
}
})
.attr('class', 'hard--link')
// topic names
node
.append("text")
.attr("dy", function(d, i) {
return d.parent && d.parent.parent ? 10 : rectangleHeight / 2;
})
.attr("dx", function(d, i) {
if (!(d.parent && d.parent.parent)) {
return 12;
} else {
return 20;
}
})
.style("fill", function(d, i) {
return d.parent && d.parent.parent ? "Black" : "White";
})
.text(function(d) {
let name = d.data.topic_name || d.data.name;
return name.length > 12 ? `${name.substring(0, 12)}...` : name;
})
.style("text-anchor", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" && "end"
}
})
.style("font-size", "12")
.attr("transform", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" ? "translate(-30,0)" : "translate(5,0)"
}
})
}
function rectangleWidth(d) {
const MIN_WIDTH = 50;
const MAX_WIDTH = 100;
let dynamicLength = 6;
if (d.data.topic_name) {
dynamicLength = d.data.topic_name.length;
} else if (d.data.name) {
dynamicLength = d.data.name.length;
}
dynamicLength = dynamicLength < 3 ? MIN_WIDTH : MAX_WIDTH;
return dynamicLength;
}
</script>
</body>
</html>
Since the input element is not an svg element, you cannot simply insert a checkbox like and make it work like an html element. We would have to make use of foreignObject and append the input inside it.
Once you create those small rects, we need to append a foreignObject containing our checkboxes and hide them initially like so:
// topic rect
node
.append("rect")
.attr("height", (d, i) => d.parent && d.parent.parent ? 15 : rectangleHeight)
.attr("width", (d, i) => d.parent && d.parent.parent ? 15 : rectangleWidth(d))
.attr("rx", (d, i) => d.parent && d.parent.parent ? 5 : 5)
.attr("ry", (d, i) => d.parent && d.parent.parent ? 5 : 5)
node.append('foreignObject').attr('width', '40')
.attr('height', '40').append('xhtml:input')
.attr('type', 'checkbox').style('display', 'none')
// An on click function for the checkboxes
.on("click",function(d){
console.log(d)
});
When the user hovers over the rects we can then simply unhide the checkboxes, and hide them again if they are not checked (else keep showing until user unchecks) :
.on("mouseover", function(d) {
d3.select(this).select('input').style('display', '');
})
.on('mouseout', function(d) {
if (!d3.select(this).select('input').property('checked')) {
d3.select(this).select('input').style('display', 'none');
}
});
Here is the full code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
</head>
<body>
<svg className='spider-graph-svg'>
</svg>
<script>
var prevElem;
var data = {
"name": "root#gmail.com",
"children": [{
"name": "Person Name 1",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person name 2",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 3",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 4",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}, {
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}]
};
const LAST_CHILDREN_WIDTH = 13;
let flagForChildren = false;
let groups = [];
data.children.forEach(d => {
let a = [];
if (d.children.length > 0) {
flagForChildren = true;
}
for (let i = 0; i < d.children.length; i += 2) {
let b = d.children.slice(i, i + 2);
if (b[0] && b[1]) {
a.push(Object.assign(b[0], {
children: [b[1]]
}));
} else {
let child = b[0];
if (i >= 6) {
child = Object.assign(child, {
children: [{
name: "..."
}]
});
}
a.push(child);
}
}
d.children = a;
groups.push(d);
});
data.children = groups;
let split_index = Math.round(data.children.length / 2);
let rectangleHeight = 45;
let leftData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
let leftDataArray = [];
leftDataArray.push(leftData);
// Right data
let rightData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
let right = d3.hierarchy(rightData);
let left = d3.hierarchy(leftData);
// Render both trees
drawTree(right, "right");
drawTree(left, "left");
// draw single tree
function drawTree(root, pos) {
let SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
const margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = window.innerWidth - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
let svg = d3
.select("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.right + margin.left)
.attr('view-box', '0 0 ' + (width + margin.right) + ' ' + (height + margin.top + margin.bottom))
.style("margin-top", "20px")
.style("margin-left", "88px");
const myTool = d3.select("body").append("div")
.attr("class", "mytooltip")
.style("opacity", "0")
.style("display", "none");;
// Shift the entire tree by half it's width
let g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
let deductWidthValue = flagForChildren ? 0 : width * 0.33;
// Create new default tree layout
let tree = d3
.tree()
// Set the size
// Remember the tree is rotated
// so the height is used as the width
// and the width as the height
.size([height - 50, SWITCH_CONST * (width - deductWidthValue) / 2])
.separation((a, b) => a.parent === b.parent ? 4 : 4.25);
tree(root);
let nodes = root.descendants();
let links = root.links();
// Set both root nodes to be dead center vertically
nodes[0].x = height / 2;
// Create links
let link = g
.selectAll(".link")
.data(links)
.enter();
link
.append("line")
.attr("class", function(d) {
if (d.target.depth === 2) {
return 'link'
} else {
return 'hard--link'
}
})
.attr("x1", function(d) {
if (
d.target.depth === 3
) {
return 0;
}
return d.source.y + 100 / 2; //d.source.y + 100/2
})
.attr("x2", function(d) {
if (
d.target.depth === 3
) {
return 0;
} else if (d.target.depth === 2) {
return d.target.y;
}
return d.target.y + 100 / 2; //d.target.y + 100/2;
})
.attr("y1", function(d) {
if (
d.target.depth === 3
) {
return 0;
}
return d.source.x + 50 / 2;
})
.attr("y2", function(d) {
if (
d.target.depth === 3
) {
return 0;
} else if (d.target.depth === 2) {
return d.target.x + LAST_CHILDREN_WIDTH / 2;
}
return d.target.x + 50 / 2;
});
//Rectangle width
let node = g
.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.on("mouseover", function(d) {
d3.select(this).select('input').style('display', '')
})
.on('mouseout', function(d) {
if (!d3.select(this).select('input').property('checked')) {
d3.select(this).select('input').style('display', 'none')
}
})
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
if (d.parent && d.parent.parent) { // this is the leaf node
if (d.parent.parent.parent) {
return (
"translate(" +
d.parent.y +
"," +
(d.x + LAST_CHILDREN_WIDTH + 15) +
")"
);
}
return "translate(" + d.y + "," + d.x + ")";
}
return "translate(" + d.y + "," + d.x + ")";
});
// topic rect
node
.append("rect")
.attr("height", (d, i) => d.parent && d.parent.parent ? 15 : rectangleHeight)
.attr("width", (d, i) => d.parent && d.parent.parent ? 15 : rectangleWidth(d))
.attr("rx", (d, i) => d.parent && d.parent.parent ? 5 : 5)
.attr("ry", (d, i) => d.parent && d.parent.parent ? 5 : 5)
// Create foreign object checkboxes and keep them hidden
node.append('foreignObject').attr('width', '40').attr('height', '40').append('xhtml:input').attr('type', 'checkbox').style('display', 'none')
// topic edges
node.append('line')
.attr('x1', d => {
if (d.depth === 2) {
return 10
}
})
.attr('x2', d => {
if (d.depth === 2) {
return 10
}
})
.attr('y1', d => {
if (d.depth === 2) {
if (d.children) {
return 0;
}
return 40;
}
})
.attr('y2', d => {
if (d.depth === 2) {
return 40
}
})
.attr('class', 'hard--link')
// topic names
node
.append("text")
.attr("dy", function(d, i) {
return d.parent && d.parent.parent ? 10 : rectangleHeight / 2;
})
.attr("dx", function(d, i) {
if (!(d.parent && d.parent.parent)) {
return 12;
} else {
return 20;
}
})
.style("fill", function(d, i) {
return d.parent && d.parent.parent ? "Black" : "White";
})
.text(function(d) {
let name = d.data.topic_name || d.data.name;
return name.length > 12 ? `${name.substring(0, 12)}...` : name;
})
.style("text-anchor", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" && "end"
}
})
.style("font-size", "12")
.attr("transform", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" ? "translate(-30,0)" : "translate(5,0)"
}
})
}
function rectangleWidth(d) {
const MIN_WIDTH = 50;
const MAX_WIDTH = 100;
let dynamicLength = 6;
if (d.data.topic_name) {
dynamicLength = d.data.topic_name.length;
} else if (d.data.name) {
dynamicLength = d.data.name.length;
}
dynamicLength = dynamicLength < 3 ? MIN_WIDTH : MAX_WIDTH;
return dynamicLength;
}
</script>
</body>
</html>
I'm sure you can do the rest from here.
How can I capture the click event for the last node?
I followed this tutorial(http://bl.ocks.org/ganeshv/6a8e9ada3ab7f2d88022) to make the tree map. In my purpose I want to make the last node clickable, then get the node data send it to server.
I used the developer tool to get the structure of tree map.
My code in the js file is that:
function display(d) {
lastGroupArray = [];
// collectLastGroup(d);
console.log(lastGroupArray);
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function (d) { return d._children; })
.classed("children", true)
.on("click", transition);
var children = g.selectAll(".child")
.data(function(d) { return d._children || [d]; })
.enter().append("g");
children.append("rect")
.attr("class", "child")
.call(rect)
.append("title")
.text(function(d) { return d.name + " (" + formatNumber(d.value) + ")"; });
//append child text
children.append("text")
.attr("class", "ctext")
.text(function(d) { return d.name; })
.call(text2);
//append parent text
g.append("rect")
.attr("class", "parent")
.call(rect);
var ccc = g.selectAll("rect").on("click", function (d){
if(typeof d.lastGroup !== 'undefined'){
d.lastGroup.forEach( function (dd) {
// lastGroupArray.push(new LastGroup(dd));
console.log(dd.filePath);
});
}
})
My function above only can capture the last node when it has a parent.
The function will not work if the node is the last child when the treemap is loaded.
The Payee is the last node when the treemap is loaded.
Here is my JSON data:
The payee is on the bottom, it only has one parent which is the root "digit-first2".
{
"children": [
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result1.csv",
"name": "0~2",
"value": 112
}],
"children": [
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result3.csv",
"name": "0~2",
"value": 218
}],
"children": [{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result7.csv",
"name": "0~2",
"value": 836
}],
"name": "Payee",
"value": 836
}],
"name": "Tran Type",
"value": 218
},
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result5.csv",
"name": "0~2",
"value": 834
}],
"name": "Payee",
"value": 834
}
],
"name": "[Code-Finger-Print, [Memo]]",
"value": 112
},
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result2.csv",
"name": "0~2",
"value": 138
}],
"children": [{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result6.csv",
"name": "0~2",
"value": 766
}],
"name": "Payee",
"value": 766
}],
"name": "Tran Type",
"value": 138
},
{
"lastGroup": [{
"filePath": "Jarfile/output/result/ResultFolder/digit-first2-2017-10-22T16-05-53/result4.csv",
"name": "0~2",
"value": 731
}],
"name": "Payee",
"value": 731
}
],
"name": "digit-first2"
}
My function above will only work when the root is not the parent of the last node.
My question is how can I capture the click event for the node if the root is this node`s parent.
You can achieve the last node click by changing the json structure. The last child should have one more children key with all the last child's properties copied in the children key with the same name .
For example if the last child is AgglomerativeCluster . Then the structure can be
children: [
{
name: "cluster",
children: [
{ name: "AgglomerativeCluster",
children: [{name: "AgglomerativeCluster",value: 3938,count:39}] },
{ name: "CommunityStructure", value: 3812 },
]
}
Find the full implementation here https://codesandbox.io/s/affectionate-thunder-l024x
There is simple example;
function display(d) {
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function (d) { return d._children; })
.classed("children", true)
.on("click", transition);
var children = g.selectAll(".child")
.data(function (d) { return d._children || [d]; })
.enter().append("g");
children.append("rect")
.attr("class", "child")
.call(rect)
.append("title")
.text(function (d) {
return d.key + " (" + formatNumber(d.value) + ")";
});
children.append("text")
.attr("class", "ctext")
.text(function (d) { return d.key; })
.call(text2);
g.append("rect")
.attr("class", "parent")
.call(rect);
var t = g.append("text")
.attr("class", "ptext")
.attr("dy", ".75em")
t.append("tspan")
.text(function (d) { return d.key; });
t.append("tspan")
.attr("dy", "1.0em")
.text(function (d) { return formatNumber(d.value); });
t.call(text);
var isDeph = false;
g.selectAll("rect")
.style("fill", function (d) {
if (d.values != null) {
isDeph = true;
}
return d.color;
})
.attr('onclick', function (d) {
if (!isDeph) {
return d.data
}
return ''
});
function transition(d) {
if (transitioning || !d)
return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function (a, b) {
return a.depth - b.depth;
});
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
t1.selectAll(".ptext").call(text).style("fill-opacity", 0);
t1.selectAll(".ctext").call(text2).style("fill-opacity", 0);
t2.selectAll(".ptext").call(text).style("fill-opacity", 1);
t2.selectAll(".ctext").call(text2).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function () {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
I am trying to create a merged pie/bubble chart.
-- looking for a starting bubble chart base - maybe this one.
http://jsfiddle.net/xsafy/
^ need to cluster these bubbles - maybe sub bubbles per slice.
//bubble chart base.
http://jsfiddle.net/NYEaX/1450/
(function() {
var diameter = 250;
var svg = d3.select('#graph').append('svg')
.attr('width', diameter)
.attr('height', diameter);
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.size;
})
.padding(3);
var color = d3.scale.ordinal()
.domain(["Lorem ipsum", "dolor sit", "amet", "consectetur", "adipisicing"])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"]);
function randomData() {
var data1 = {
"children": [
{
name: "AA",
className: "aa",
size: 170
},
{
name: "BB",
className: "bb",
size: 393
},
{
name: "CC",
className: "cc",
size: 293
},
{
name: "DD",
className: "dd",
size: 89
}
]
};
var data2 = {
"children": [
{
name: "AA",
className: "aa",
size: 120
},
{
name: "BB",
className: "bb",
size: 123
},
{
name: "CC",
className: "cc",
size: 193
},
{
name: "DD",
className: "dd",
size: 289
}
]
};
var j = Math.floor((Math.random() * 2) + 1);
console.log("j", j);
if (j == 1) {
return data1;
} else {
return data2;
}
}
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
console.log("data", data);
// generate data with calculated layout values
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var vis = svg.selectAll('circle')
.data(nodes);
vis.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
})
.attr('class', function(d) {
return d.className;
});
vis
.transition().duration(1000)
vis.exit()
.remove();
};
})();
//doughnut chart base.
derived from these examples.
http://bl.ocks.org/dbuezas/9306799
https://bl.ocks.org/mbostock/1346410
http://jsfiddle.net/NYEaX/1452/
var svg = d3.select("#graph")
.append("svg")
.append("g")
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "labels");
svg.append("g")
.attr("class", "lines");
var width = 560,
height = 450,
radius = Math.min(width, height) / 2;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d) {
return d.data.label;
};
var color = d3.scale.ordinal()
.domain(["Lorem ipsum", "dolor sit", "amet", "consectetur", "adipisicing"])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"]);
function randomData() {
var data1 = [{
"label": "AA",
"value": 0.911035425558026
}, {
"label": "BB",
"value": 0.08175111844879179
}, {
"label": "CC",
"value": 0.25262439557273275
}, {
"label": "DD",
"value": 0.8301366989535612
}, {
"label": "EE",
"value": 0.0517762265780517
}];
var data2 = [{
"label": "AA",
"value": 0.243879179
}, {
"label": "BB",
"value": 0.243879179
}, {
"label": "CC",
"value": 0.2342439557273275
}, {
"label": "DD",
"value": 0.2349535612
}, {
"label": "EE",
"value": 0.2345780517
}];
var j = Math.floor((Math.random() * 2) + 1);
if (j == 1) {
return data1;
} else {
return data2;
}
}
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
/* ------- PIE SLICES -------*/
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) {
return color(d.data.label);
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
};
This is as close I have got so far.. I've merged the two charts together as such - although there is a bug still trying to update the bubbles -- at least get them to scale/morph/move/animate. Ideally I want them to stick close to their group segments..
here is the fiddle.
https://jsfiddle.net/tk5xog0g/1/
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>doughpie</title>
<link rel="stylesheet" href="css/generic.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<button class="randomize">randomize</button>
<div data-role="doughpie" data-width="450" data-height="450" id="graph"></div>
<script>
//__invoke pie chart
$('[data-role="doughpie"]').each(function(index) {
createDoughnut(this);
});
function bubbledata(data){
//loop through data -- and MERGE children
var childs = [];
$.each(data, function( index, value ) {
childs.push(value.children);
});
var merged = [].concat.apply([], childs);//flatterns multidimensional array
return $.extend(true, {}, {"children": merged});// return deep clone
}
function createDoughnut(el){
var width = $(el).data("width"),
height = $(el).data("height"),
radius = Math.min(width, height) / 2;
var svg = d3.select($(el)[0])
.append("svg")
.attr("width", width)
.attr("height", height)
//_create doughpie shell
var doughpie = svg.append("g")
.attr("class", "doughpie");
doughpie.append("g")
.attr("class", "slices");
doughpie.append("g")
.attr("class", "labels");
doughpie.append("g")
.attr("class", "lines");
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
doughpie.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d) {
return d.data.label;
};
var color = d3.scale.ordinal()
.range(["#46a2de", "#7b3cce", "#31d99c", "#de5942", "#ffa618"]);
//_create doughpie shell
//_create bubble
var diameter = width/2;//take half/width
var bubs = svg.append("g")
.attr("class", "bubs");
bubs.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.size;
})
.padding(3);
//_create bubble
function randomData() {
var data1 = [{
"label": "AA",
"value": 0.911035425558026,
"children": [
{
name: "some text aa",
group: "AA",
size: 120
}
]
}, {
"label": "BB",
"value": 0.08175111844879179,
"children": [
{
name: "some text bb",
group: "BB",
size: 123
}
]
}, {
"label": "CC",
"value": 0.25262439557273275,
"children": [
{
name: "some text cc",
group: "CC",
size: 193
}
]
}, {
"label": "DD",
"value": 0.8301366989535612,
"children": [
{
name: "some text dd",
group: "DD",
size: 29
},
{
name: "some text dd",
group: "DD",
size: 289
}
]
}, {
"label": "EE",
"value": 0.0517762265780517,
"children": [
{
name: "some text ee",
group: "EE",
size: 389
},
{
name: "some text ee",
group: "EE",
size: 89
}
]
}];
var data2 = [{
"label": "AA",
"value": 0.243879179,
"children": [
{
name: "some text aa",
group: "AA",
size: 120
}
]
}, {
"label": "BB",
"value": 0.243879179,
"children": [
{
name: "some text bb",
group: "BB",
size: 123
}
]
}, {
"label": "CC",
"value": 0.2342439557273275,
"children": [
{
name: "some text cc",
group: "CC",
size: 193
}
]
}, {
"label": "DD",
"value": 0.2349535612,
"children": [
{
name: "some text dd",
group: "DD",
size: 29
},
{
name: "some text dd",
group: "DD",
size: 289
}
]
}, {
"label": "EE",
"value": 0.2345780517,
"children": [
{
name: "some text ee",
group: "EE",
size: 389
},
{
name: "some text ee",
group: "EE",
size: 89
}
]
}];
var j = Math.floor((Math.random() * 2) + 1);
if (j == 1) {
return data1;
} else {
return data2;
}
}
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
/* ------- ANIMATE PIE SLICES -------*/
var slice = doughpie.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) {
return color(d.data.label);
})
.style("transform", function(d, i){
//return "translate(0, 0)";
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
/* ------- ANIMATE PIE SLICES -------*/
/* ------- ANIMATE BUBBLES -------*/
// generate data with calculated layout values
var data = bubbledata(data);
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var bubbles = bubs.selectAll('circle')
.data(nodes);
bubbles.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.group);
});
bubbles
.transition().duration(1000)
bubbles.exit()
.remove();
/* ------- ANIMATE BUBBLES -------*/
};
}
</script>
</body>
</html>
I thought I will try and first plot mid dot points on the arcs -- and maybe from THOSE -- start to get a batch of zone coordinates to master control the coloured bubbles.
I've been trying to follow this -- How to get coordinates of slices along the edge of a pie chart?
https://jsfiddle.net/tk5xog0g/28/
/*placing mid dots*/
var midDotsArrayCooridnates = [];
slice
.each(function(d) {
x = 0 + (radius * 0.85) * Math.cos(d.startAngle);
y = 0 + (radius * 0.85) * Math.sin(d.startAngle);
var obj = {
"x": x,
"y": y
}
midDotsArrayCooridnates.push(obj);
});
$.each(midDotsArrayCooridnates, function(index, value) {
var dot = doughpie.select(".slicedots").append('circle')
.attr('cx', value.x)
.attr('cy', value.y)
.attr('r', 5)
.style("fill", "red")
.attr('class', "ccc")
});
/*placing mid dots*/
I'm building a Tree using D3.js and what I am trying to do is add two buttons, Expand All and Collapse All to the top of the page like this.
When I click "Expand All", all the nodes should expand. And when I click "Collapse All", all the nodes should collapse to the root element.
Here's my current code http://bl.ocks.org/anonymous/ab8d7f85cca6f745a107
But the problem is, it isn't working. Can somebody suggest how to make it work?
Try this code. Here is the working JsFiddle.
function expand(d){
if (d._children) {
d.children = d._children;
d._children = null;
}
var children = (d.children)?d.children:d._children;
if(children)
children.forEach(expand);
}
function expandAll(){
expand(root);
update(root);
}
function collapseAll(){
root.children.forEach(collapse);
collapse(root);
update(root);
}
just add this
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;
}
}
$("#expand_button").click(function(){
source.children.forEach(expand);
update(source);
});
$("#collapse_button").click(function(){
root.children.forEach(collapse);
update(root);
});
try this fiddle: http://jsfiddle.net/z9tmgpwd/
function expand(d) {
var children = (d.children) ? d.children : d._children;
if (d._children) {
d.children = d._children;
d._children = null;
}
if (children) children.forEach(expand);
}
function expandAll() {
expand(root);
update(root);
}
function collapseAll() {
root.children.forEach(collapse);
collapse(root);
update(root);
}
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - 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 + ")");
var flare = {
"name": "flare",
"children": [{
"name": "analytics",
"children": [{
"name": "cluster",
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "CommunityStructure",
"size": 3812
}, {
"name": "HierarchicalCluster",
"size": 6714
}]
}, {
"name": "graph",
"children": [{
"name": "BetweennessCentrality",
"size": 3534
}, {
"name": "LinkDistance",
"size": 5731
}]
}]
}, {
"name": "animate",
"children": [{
"name": "Easing",
"size": 17010
}, {
"name": "FunctionSequence",
"size": 5842
}, {
"name": "Transitioner",
"size": 19975
}, {
"name": "TransitionEvent",
"size": 1116
}, {
"name": "Tween",
"size": 6006
}]
}]
};
root = flare;
root.x0 = height / 2;
root.y0 = 0;
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).on("mouseover", mouseover).on("mouseout", mouseout);
nodeEnter.append("circle").attr("r", 1e-6).style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text").attr("x", function (d) {
return d.children || d._children ? -10 : 10;
}).attr("dy", ".35em").attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "start";
}).text(function (d) {
return d.name;
}).style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition().duration(duration).attr("transform",
function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle").attr("r", 4.5).style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text").style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition().duration(duration).attr("transform",
function (d) {
return "translate(" + source.y + "," + source.x + ")";
}).remove();
nodeExit.select("circle").attr("r", 1e-6);
nodeExit.select("text").style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link").data(links, function (d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g").attr("class", "link").attr("d", function
(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition().duration(duration).attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition().duration(duration).attr("d", function (d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
}).remove();
// Stash the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// 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);
}
function mouseover(d) {
d3.select(this).append("text").attr("class", "hover").attr('transform',
function (d) {
return 'translate(5, -10)';
}).text(d.name + ": " + d.id);
}
// Toggle children on click.
function mouseout(d) {
d3.select(this).select("text.hover").remove();
}
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
update(root);
d3.select(self.frameElement).style("height", "800px");