Nest same object n times in array - javascript

Here is an example object array I'd like to transform into a much more complex structure:
[
{
"name": "Blue (3/8)"
},
{
"name": "Green (5/8)"
}
]
To give some context, I am trying to create a visual tree diagram demonstrating the probabilities of different-colored marbles being drawn out of a bag (that's why you see a number followed by a fraction). Below is the statistical-type tree I'm referring to.
I need to repeat adding this same array into an object in a nested format, assigning it to a key titled "children". This needs to be done n amount of times (based on the number of marble draws). The below object would be a result of nesting the same object array 3 times and then it would make a nice tree (using d3.js):
var treeData =
{
"children": [ // Level 1
{
"name": "Blue (3/8)",
"children": [ // Level 2
{ "name": "Blue (3/8)",
"children": [ // Level 3
{ "name": "Blue (3/8)" },
{ "name": "Green (5/8)" }
]
},
{ "name": "Green (5/8)",
"children": [ // Level 3
{ "name": "Blue (3/8)" },
{ "name": "Green (5/8)" }
]
}
]
},
{
"name": "Green (5/8)",
"children": [ // Level 2
{ "name": "Blue (3/8)",
"children": [ // Level 3
{ "name": "Blue (3/8)" },
{ "name": "Green (5/8)" }
]
},
{ "name": "Green (5/8)",
"children": [ // Level 3
{ "name": "Blue (3/8)" },
{ "name": "Green (5/8)" }
]
}
]
}
]
};
// set the dimensions and margins of the diagram
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 660 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// declares a tree layout and assigns the size
var treemap = d3.tree()
.size([height, width]);
// assigns the data to a hierarchy using parent-child relationships
var nodes = d3.hierarchy(treeData, function(d) {
return d.children;
});
// maps the node data to the tree layout
nodes = treemap(nodes);
// 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.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// adds the links between the nodes
var link = g.selectAll(".link")
.data( nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x
+ "C" + (d.y + d.parent.y) / 2 + "," + d.x
+ " " + (d.y + d.parent.y) / 2 + "," + d.parent.x
+ " " + d.parent.y + "," + d.parent.x;
});
// adds each node as a group
var node = g.selectAll(".node")
.data(nodes.descendants())
.enter().append("g")
.attr("class", function(d) {
return "node" +
(d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
// adds the circle to the node
node.append("circle")
.attr("r", 10);
// adds the text to the node
node.append("text")
.attr("dy", ".35em")
.attr("x", function(d) { return d.children ? -13 : 13; })
.style("text-anchor", function(d) {
return d.children ? "end" : "start"; })
.text(function(d) { return d.data.name; });
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text { font: 12px sans-serif; }
.node--internal text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
This happens to be the format of the object needed for d3.js to display the desired output in a tree. Here is the closest thing I could salvage to get my desired output:
var objectArray = [{
name: "Blue (5/8)"
}, {
name: "Green (3/8)"
}, {
name: "Blue (5/8)"
}, {
name: "Green (3/8)"
}, {
name: "Blue (5/8)"
}, {
name: "Green (3/8)"
}];
var newArr = objectArray.reverse().reduce((acc, item) => {
return [{
...item,
children: acc
}]
});
console.log(newArr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

You could take a simple mapping with a look for the wanted depth.
const
create = (data, depth) => data.map((o, i) => ({
name: `${o.name} (${o.count}/${o.total})`,
...(depth > 1 ? { children: create(data.map(({ name, total, count }, j) => ({ name, count: count - (i === j), total: total - 1 })), depth - 1) } : {})
})),
data = [{ name: "Blue", count: 3, total: 8 }, { name: "Green", count: 5, total: 8 }],
result = create(data, 3);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use a recursive function:
const probabilityDrawingMarblesWithoutReplacement = ( marbles, remainingDraws ) => {
let totalMarbles = 0;
for ( let colour in marbles )
{
totalMarbles += Math.max( marbles[colour], 0 );
}
const output = [];
for ( let colour in marbles )
{
const amount = marbles[colour];
if ( amount <= 0 )
{
continue;
}
const colourOutput = {
"name": `${colour} (${amount}/${totalMarbles})`
}
if ( remainingDraws > 1 )
{
const remainingMarbles = Object.assign( {}, marbles );
remainingMarbles[colour]--;
colourOutput.children = probabilityDrawingMarblesWithoutReplacement( remainingMarbles, remainingDraws - 1 );
}
output.push( colourOutput );
}
return output;
}
console.log( probabilityDrawingMarblesWithoutReplacement( {"blue":3,"green":5}, 4 ) );
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Programmatically open nested, collapsed (hidden) nodes in d3.js v6

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
}

show checkbox inside node while hovering on that node

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 to update D3 Zoomable sunburst wheel view with updating json data

Here i am try to update json data and its D3 wheel view but i Don't know why its not update the D3 sunbrust and update the data
This is my code
// in html body
//css code
svg{
width: 100% !important;
background:#ffffff;
}
.slice {
cursor: pointer;
}
.slice .main-arc {
stroke: #fff;
stroke-width: 1px;
}
.slice .hidden-arc {
fill: none;
}
.slice text {
pointer-events: none;
dominant-baseline: middle;
text-anchor: middle;
}
#tooltip { background-color: white;
padding: 3px 5px;
border: 1px solid black;
text-align: center;}
div.tooltip {
position: absolute;
text-align: left;
width: auto;
height: auto;
padding: 8px;
font: 12px sans-serif;
background: #0a2538;
border: #0a2538 1px solid;
border-radius: 0px;
pointer-events: none;
color: white;
}
//D3 Script code for https://d3js.org/d3.v4.min.js
const initItems ={
"name": "Core root",
"id":3,
"description":"Lorem ipsum dolor sit amet",
"children": [
{
"name": "VM.HSN.5.A",
"children": [
{
"name": "eiusmod",
"children": [
{"name": "G.8.1", "id": 5},
{"name": "G.8.2", "id": 4}
]
},
{
"name": "F.8.0",
"children": [
{"name": "F.8.4", "id": 1},
{"name": "F.8.5", "id":1}
]
},
{
"name": "EE.8.5-CLX",
"children": [
{"name": "EE.8.5-CLX2", "id": 1}
]
}
]
},
{
"name": "NS.8.2-CLX",
"children": [
{"name": "NS.8.2-CLX4", "id": 1},
{"name": "NS.8.2-CLX5", "id": 1},
{
"name": "NS.8.0",
"children": [
{"name": "NS.8.2", "id": 1},
{"name": "NS.8.3", "id": 1}
]
},
{"name": "NS.8.1-CLX1", "id":1},
{"name": "NS.8.1-CLX2", "id": 1},
{"name": "NS.8.1-CLX3", "id":1},
{"name": "NS.8.1-CLX4", "id": 1},
{"name": "NS.8.1-CLX5", "id": 1}
]
}
]
}
var newItems = {
"name": "Second Root",
"children": [{
"name": "A2",
"children": [{
"name": "B4",
"size": 40
}, {
"name": "B5",
"size": 30
}, {
"name": "B6",
"size": 10
}]
}, {
"name": "A3",
"children": [{
"name": "B7",
"size": 50
}, {
"name": "B8",
"size": 15
}
]
}]
}
const width = window.innerWidth,
height = window.innerHeight,
maxRadius = (Math.min(width, height) / 2) - 20;
const formatNumber = d3.format(',d');
const x = d3.scaleLinear()
.range([0, 2 * Math.PI])
.clamp(true);
const y = d3.scaleSqrt()
.range([maxRadius*.1, maxRadius]);
const color = d3.scaleOrdinal(d3.schemeCategory20);
const partition = d3.partition();
const arc = d3.arc()
.startAngle(d => x(d.x0))
.endAngle(d => x(d.x1))
.innerRadius(d => Math.max(0, y(d.y0)))
.outerRadius(d => Math.max(0, y(d.y1)));
const middleArcLine = d => {
const halfPi = Math.PI/2;
const angles = [x(d.x0) - halfPi, x(d.x1) - halfPi];
const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);
const middleAngle = (angles[1] + angles[0]) / 2;
const invertDirection = middleAngle > 0 && middleAngle < Math.PI; // On lower quadrants write text ccw
if (invertDirection) { angles.reverse(); }
const path = d3.path();
path.arc(0, 0, r, angles[0], angles[1], invertDirection);
return path.toString();
};
const textFits = d => {
const CHAR_SPACE = 6;
const deltaAngle = x(d.x1) - x(d.x0);
const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);
const perimeter = r * deltaAngle;
return d.data.name.length * CHAR_SPACE < perimeter;
};
const svg = d3.select('#vdata').append('svg')
.style('width', '100vw')
.style('height', '100vh')
.attr('viewBox', `${-width / 2} ${-height / 2} ${width} ${height}`)
.on('click', () => focusOn()); // Reset zoom on canvas click
var updateChart = function (items) {
// var root = items;
console.log(items);
//d3.json('dummy4.json', (error, root) => {
///if (error) throw error;
//start custom code
var root = d3.hierarchy(items, function(d) { return d.children })
.sum( function(d) {
if(d.children) {
return 0
} else {
return 2
}
});
//end custom code
//root = d3.hierarchy(root);
//var ad =root.sum(d => d.depth);
//root.sum(d => d.id);
const slice = svg.selectAll('g.slice')
.data(partition(root).descendants());
slice.exit().remove();
const newSlice = slice.enter()
.append('g').attr('class', 'slice')
.on('click', d => {
console.log(d.data.name);
d3.event.stopPropagation();
focusOn(d);
});
newSlice.append('title')
//.text(d => d.data.name + '\n' + d.data.id);
//.on("mouseover", mouseover);
newSlice.append('path')
.attr('class', 'main-arc')
.style('fill',colour)
//.style('fill', d => color((d.children ? d : d.parent).data.name))
//console.log(data.id)
.on("mouseover", mouseover)
.on("mouseout", mouseOutArc)
.attr('d', arc);
newSlice.append('path')
.attr('class', 'hidden-arc')
.attr('id', (_, i) => `hiddenArc${i}`)
.attr('d', middleArcLine);
const text = newSlice.append('text')
.attr('display', d => textFits(d) ? null : 'none');
// Add white contour
text.append('textPath')
.attr('startOffset','50%')
.attr('xlink:href', (_, i) => `#hiddenArc${i}` )
.text(function(d) {
return d.data.name;
})
.style('fill', 'none')
.style('stroke', '#fff')
.style('stroke-width', 5)
.style('stroke-linejoin', 'round');
text.append('textPath')
.attr('startOffset','50%')
.attr('xlink:href', (_, i) => `#hiddenArc${i}` )
.text(function(d) {
return d.data.name;
});
//});
}
// tooltip
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
function colour(d) {
//if (d.id==5) {
var colours;
console.log(d.data.id);
if(d.data.id==5)
{
// There is a maximum of two children!
colours ='#b00';
}else if(d.data.name=='Apps'){
colours='#cc0001';
}
else{
colours=color((d.children ? d : d.parent).data.name)
} // L*a*b* might be better here...
// return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
//}
return colours;
}
function mouseover(d) {
d3.select(this).style("cursor", "pointer")
var descript;
if(d.data.description!=null)
{
descript= "<br/><br/>"+d.data.description;
}else{
descript='';
}
tooltip.html(d.data.name + descript)
.style("opacity", 0.8)
.style("left", (d3.event.pageX) + 0 + "px")
.style("top", (d3.event.pageY) - 0 + "px");
}
function mouseOutArc(){
d3.select(this).style("cursor", "default")
tooltip.style("opacity", 0);
}
function focusOn(d = { x0: 0, x1: 1, y0: 0, y1: 1 }) {
// Reset to top-level if no data point specified
const transition = svg.transition()
.duration(750)
.tween('scale', () => {
const xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]);
return t => { x.domain(xd(t)); y.domain(yd(t)); };
});
transition.selectAll('path.main-arc')
.attrTween('d', d => () => arc(d));
transition.selectAll('path.hidden-arc')
.attrTween('d', d => () => middleArcLine(d));
transition.selectAll('text')
.attrTween('display', d => () => textFits(d) ? null : 'none');
moveStackToFront(d);
//
function moveStackToFront(elD) {
svg.selectAll('.slice').filter(d => d === elD)
.each(function(d) {
this.parentNode.appendChild(this);
if (d.parent) { moveStackToFront(d.parent); }
})
}
}
updateChart(initItems);
I am trying to update json data and regenerate the D3 sunburst wheel but it not update.
On developer console it show the json data updated but it not update the text in wheel and not properly regenerate the D3 sunburst wheel.
Please help me out.!
Thanks in advance.
I used this code and it works for me great..!
setTimeout(function () {d3.selectAll("svg > *").remove(); updateChart(newItems); }, 2000);

d3 - dougnut bubble pie chart

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*/

Centering stacked bars vertically in d3

I'm trying to visualize sold items from timeseries. I'm using Nick Rabinowitz's alluvial chart as a basis but have made few modifications to it. Everything else looks good but I would like to center the stacked bars vertically.
This is what my chart looks like at the moment:
/*Original code obtained from http://nickrabinowitz.com/projects/d3/alluvial/alluvial.html*/
var data = {
"times": [
[{
"id": "item1",
"nodeName": "Item 1 50/2015",
"nodeValue": 9,
"incoming": []
}, {
"id": 1,
"nodeName": "Item 2 50/2015",
"nodeValue": 6,
"incoming": []
}, {
"id": 2,
"nodeName": "Item 3 50/2015",
"nodeValue": 3,
"incoming": []
}],
[{
"id": "item12",
"nodeName": "Item 1 51/2015",
"nodeValue": 8,
"incoming": []
}, {
"id": 4,
"nodeName": "Item 2 51/2015",
"nodeValue": 2,
"incoming": []
}, {
"id": 5,
"nodeName": "Item 3 51/2015",
"nodeValue": 5,
"incoming": []
}],
[{
"id": 6,
"nodeName": "Item 1 52/2015",
"nodeValue": 1,
"incoming": []
}, {
"id": 7,
"nodeName": "Item 2 52/2015",
"nodeValue": 7,
"incoming": []
}, {
"id": 8,
"nodeName": "Item 3 50/2015",
"nodeValue": 4,
"incoming": []
}]
],
"links": [{
"source": "item1",
"target": "item12",
"outValue": 9,
"inValue": 8
}, {
"source": "item12",
"target": 6,
"outValue": 8,
"inValue": 1
}, {
"source": 1,
"target": 4,
"outValue": 6,
"inValue": 2
}, {
"source": 4,
"target": 7,
"outValue": 2,
"inValue": 7
}, {
"source": 2,
"target": 5,
"outValue": 3,
"inValue": 5
}
/*,
{
"source": 5,
"target": 8,
"outValue": 5,
"inValue": 4
}*/
]
};
/* Process Data */
// make a node lookup map
var nodeMap = (function() {
var nm = {};
data.times.forEach(function(nodes) {
nodes.forEach(function(n) {
nm[n.id] = n;
// add links and assure node value
n.links = [];
n.incoming = [];
n.nodeValue = n.nodeValue || 0;
})
});
console.log(nm);
return nm;
})();
// attach links to nodes
data.links.forEach(function(link) {
console.log(link);
nodeMap[link.source].links.push(link);
nodeMap[link.target].incoming.push(link);
});
// sort by value and calculate offsets
data.times.forEach(function(nodes) {
var nCumValue = 0;
nodes.sort(function(a, b) {
return d3.descending(a.nodeValue, b.nodeValue)
});
nodes.forEach(function(n, i) {
n.order = i;
n.offsetValue = nCumValue;
nCumValue += n.nodeValue;
// same for links
var lInCumValue;
var lOutCumValue;
// outgoing
if (n.links) {
lOutCumValue = 0;
n.links.sort(function(a, b) {
return d3.descending(a.outValue, b.outValue)
});
n.links.forEach(function(l) {
l.outOffset = lOutCumValue;
lOutCumValue += l.outValue;
});
}
// incoming
if (n.incoming) {
lInCumValue = 0;
n.incoming.sort(function(a, b) {
return d3.descending(a.inValue, b.inValue)
});
n.incoming.forEach(function(l) {
l.inOffset = lInCumValue;
lInCumValue += l.inValue;
});
}
})
});
data = data.times;
// calculate maxes
var maxn = d3.max(data, function(t) {
return t.length
}),
maxv = d3.max(data, function(t) {
return d3.sum(t, function(n) {
return n.nodeValue
})
});
/* Make Vis */
// settings and scales
var w = 960,
h = 500,
gapratio = .5,
padding = 7,
x = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeBands([0, w], gapratio),
y = d3.scale.linear()
.domain([0, maxv])
.range([0, h - padding * maxn]),
area = d3.svg.area()
.interpolate('monotone');
// root
var vis = d3.select("#alluvial")
.append("svg:svg")
.attr("width", w)
.attr("height", h);
// time slots
var times = vis.selectAll('g.time')
.data(data)
.enter().append('svg:g')
.attr('class', 'time')
.attr("transform", function(d, i) {
return "translate(" + x(i) + ",0)"
});
// node bars
var nodes = times.selectAll('g.node')
.data(function(d) {
return d
})
.enter().append('svg:g')
.attr('class', 'node');
nodes.append('svg:rect')
.attr('fill', 'steelblue')
.attr('y', function(n, i) {
return y(n.offsetValue) + i * padding;
})
.attr('width', x.rangeBand())
.attr('height', function(n) {
return y(n.nodeValue)
})
.append('svg:title')
.text(function(n) {
return n.nodeName
});
// links
var links = nodes.selectAll('path.link')
.data(function(n) {
return n.links || []
})
.enter().append('svg:path')
.attr('class', 'link')
.attr('d', function(l, i) {
var source = nodeMap[l.source];
var target = nodeMap[l.target];
var gapWidth = x(0);
var bandWidth = x.rangeBand() + gapWidth;
var sourceybtm = y(source.offsetValue) +
source.order * padding +
y(l.outOffset) +
y(l.outValue);
var targetybtm = y(target.offsetValue) +
target.order * padding +
y(l.inOffset) +
y(l.inValue);
var sourceytop = y(source.offsetValue) +
source.order * padding +
y(l.outOffset);
var targetytop = y(target.offsetValue) +
target.order * padding +
y(l.inOffset);
var points = [
[x.rangeBand(), sourceytop],
[x.rangeBand() + gapWidth / 5, sourceytop],
[bandWidth - gapWidth / 5, targetytop],
[bandWidth, targetytop],
[bandWidth, targetybtm],
[bandWidth - gapWidth / 5, targetybtm],
[x.rangeBand() + gapWidth / 5, sourceybtm],
[x.rangeBand(), sourceybtm]
];
return area(points);
});
body {
margin: 3em;
}
.node {
stroke: #fff;
stroke-width: 2px;
}
.link {
fill: #000;
stroke: none;
opacity: .3;
}
.node {
stroke: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="alluvial"></div>
Here is a JSFiddle if you like to play around with the code.
The solution probably lies somewhere in counting the full height of the bar and calculating the node offsets from the centerpoint.
The way the original code is structured looks like to be calculating offsets per node and then using these offsets to calculate node positions. I probably need to be able to modify this calculated offset in someway at somepoint but I just can't figure out how and where. If it is even possible.
If that isn't possible, is there another way in d3 to achieve visually similar results?
You could try calculated the maximum full height using (I've just added the lines that change, the rest is the same):
//calculate the max full height
var maxHeight=0;
data.times.forEach(function(nodes,p) {
var curHeight=0;
nodes.forEach(function(n) {
curHeight+=n.nodeValue;
});
if(curHeight > maxHeight) maxHeight=curHeight
});
And then adding (maxHeight/2 - curHeight/2) to the offset, curHeight being the total height of the nodes for each band.
To do this you can add a couple lines to the loop calculating the offset:
// sort by value and calculate offsets
data.times.forEach(function(nodes,p) {
var nCumValue = 0;
nodes.sort(function(a, b) {
return d3.descending(a.nodeValue, b.nodeValue)
});
var bandHeight = 0;
nodes.forEach(function(n) {
bandHeight+=n.nodeValue;
});
nodes.forEach(function(n, i) {
n.order = i;
n.offsetValue = nCumValue + (maxHeight/2-bandHeight/2);
Here's a JSFiddle with these changes.

Categories