d3.js Force Layout with Images - javascript
I'm trying to use this example http://bl.ocks.org/eesur/be2abfb3155a38be4de4 in my own project. I'm trying to generalize the code block to use. I changed the code a little but now it's not generating similar graph in example. You can see my attempt in jsfiddle https://jsfiddle.net/wykbbvzf/1/
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) { return d._children ? -d.size / 100 : -40; })
.linkDistance(function(d) { return d.target._children ? 80 : 25; })
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {return 1; })
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
path.enter().insert("svg:path")
.attr("class", "link")
// .attr("marker-end", "url(#end)")
.style("stroke", "#eee");
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) { return d.id; });
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) { return d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on( 'click', function (d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html ("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢"+ "</a>" );
})
.on( 'mouseenter', function() {
// select element in current context
d3.select( this )
.transition()
.attr("x", function(d) { return -60;})
.attr("y", function(d) { return -60;})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on( 'mouseleave', function() {
d3.select( this )
.transition()
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser +15)
.attr("fill", tcBlack)
.text(function(d) { return d.hero; });
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", this.nodeTransform);
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth/2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight/2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.update();
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
Do you have any idea about my mistakes?
You will just need to update node transform attribute inside tick function. this.nodeTransform is not seems to be defined in your code. So the tick function should be as shown below.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
EDIT: Adding link labels
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) {
return d._children ? -d.size / 100 : -40;
})
.linkDistance(function(d) {
return d.target._children ? 80 : 25;
})
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {
return 1;
})
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id;
});
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) {
return Math.sqrt(d.size) / 10 || 4.5;
})
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) {
return d.img;
})
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on('click', function(d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢" + "</a>");
})
.on('mouseenter', function() {
// select element in current context
d3.select(this)
.transition()
.attr("x", function(d) {
return -60;
})
.attr("y", function(d) {
return -60;
})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on('mouseleave', function() {
d3.select(this)
.transition()
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser + 15)
.attr("fill", tcBlack)
.text(function(d) {
return d.hero;
});
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth / 2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight / 2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
body {
font-family: "Source Code Pro", Consolas, monaco, monospace;
line-height: 160%;
font-size: 16px;
margin: 0;
}
path.link {
fill: none;
stroke-width: 2px;
}
.node:not(:hover) .nodetext {
display: none;
}
h1 {
font-size: 36px;
margin: 10px 0;
text-transform: uppercase;
font-weight: normal;
}
h2,
h3 {
font-size: 18px;
margin: 5px 0;
font-weight: normal;
}
header {
padding: 20px;
position: absolute;
top: 0;
left: 0;
}
a:link {
color: #EE3124;
text-decoration: none;
}
a:visited {
color: #EE3124;
}
a:hover {
color: #A4CD39;
text-decoration: underline;
}
a:active {
color: #EE3124;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="visualization"></div>
Related
In d3.js treemap layout how to make the last child clickable and fill the entire treemap layout
Im using d3.js treemap layout . In the treemap , on clicking the parent segment with the children property it transitions into its respective child sub segments . But on clicking the last child without any children property it wont make the transition happens to fill only that child node in the entire layout . In the code that im working on I can redirect to a new window on click of the last child but not sure how to make the transition happening . Find my code in the link https://codesandbox.io/s/affectionate-thunder-l024x class TreemapChart extends React.Component { componentDidMount() { const self = this; var margin = { top: 20, right: 0, bottom: 0, left: 0 }, width = 960, height = 500 - margin.top - margin.bottom, transitioning; var x = d3.scale .linear() .domain([0, width]) .range([0, width]); var y = d3.scale .linear() .domain([0, height]) .range([0, height]); var treemap = d3.layout .treemap() .children(function(d, depth) { return depth ? null : d._children; }) .sort(function(a, b) { return a.value - b.value; }) .ratio((height / width) * 0.5 * (1 + Math.sqrt(5))) .round(false); var svg = d3 .select("#chart") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.bottom + margin.top) .style("margin-left", -margin.left + "px") .style("margin.right", -margin.right + "px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .style("shape-rendering", "crispEdges"); // .on("mousemove", function (d) { // tool.style("left", d3.event.pageX + 10 + "px") // tool.style("top", d3.event.pageY - 20 + "px") // tool.style("display", "inline-block"); // tool.html(d.children ? null : d.name + "<br>" ); // }).on("mouseout", function (d) { // tool.style("display", "none"); // }); var grandparent = svg.append("g").attr("class", "grandparent"); grandparent .append("rect") .attr("y", -margin.top) .attr("width", width) .attr("height", margin.top); grandparent .append("text") .attr("x", 6) .attr("y", 6 - margin.top) .attr("dy", ".35em"); function dataMap(root) { initialize(root); accumulate(root); accumulateCount(root); layout(root); display(root); function initialize(root) { root.x = root.y = 0; root.dx = width; root.dy = height; root.depth = 0; } // Aggregate the values for internal nodes. This is normally done by the // treemap layout, but not here because of our custom implementation. // We also take a snapshot of the original children (_children) to avoid // the children being overwritten when when layout is computed. function accumulate(d) { return (d._children = d.children) ? (d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0)) : d.value; } function accumulateCount(d) { return (d._children = d.children) ? (d.count = d.children.reduce(function(p, v) { return p + accumulateCount(v); }, 0)) : d.count; } function layout(d) { if (d._children) { treemap.nodes({ _children: d._children }); d._children.forEach(function(c) { c.x = d.x + c.x * d.dx; c.y = d.y + c.y * d.dy; c.dx *= d.dx; c.dy *= d.dy; c.parent = d; layout(c); }); } } function display(d) { // console.log(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); g.selectAll(".child") .data(function(d) { return d._children || [d]; }) .enter() .append("rect") .attr("class", "child") .call(rect); g.append("rect") .attr("class", "parent") .call(rect) .on("click", function(d) { if (!d._children) { window.open(d.url); } }) .append("title") .text(function(d) { return `${d.name} (${d.count})`; }); g.append("text") .attr("dx", "1rem") .attr("dy", "2rem") .text(function(d) { return `${d.name}`; }) .call(text); function transition(d) { self.props.onClickSegment(d.metricsValue); 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("text") .call(text) .style("fill-opacity", 0); t2.selectAll("text") .call(text) .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; } function text(text) { text .attr("x", function(d) { return x(d.x) + 6; }) .attr("y", function(d) { return y(d.y) + 6; }); } function rect(rect) { rect .attr("x", function(d) { return x(d.x); }) .attr("y", function(d) { return y(d.y); }) .attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) .attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }); } function name(d) { return d.parent ? name(d.parent) + " / " + d.name : d.name; } } dataMap(dataObj); } handleClick = d => { // this.props.onClickSegment(d.metricsValue); console.log("The text", d.metricsValue); }; render() { return <p id="chart" />; } } I need to click the last child and on clicking the last child should fill the layout with the respective breadcrumb like in the code.
d3 v4 add arrows to force-directed graph
I'm trying to create a force-directed graph in d3.js with arrows pointing to non-uniform sized nodes. I came across this example here but for the life of me I can't adapt my code to add the arrows in. I'm not sure if I need to be adding something extra to the tick function, or if I'm referencing something incorrectly. Can anyone help me out? Codepen - https://codepen.io/quirkules/pen/dqXRwj var links_data = [{"source":"ABS","target":"ABS","count":8},{"source":"ABS","target":"ATS","count":1},{"source":"ABS","target":"CR","count":8},{"source":"ABS","target":"ENV","count":1},{"source":"ABS","target":"INT","count":16},{"source":"ABS","target":"ITS","count":9},{"source":"ABS","target":"PDG","count":1},{"source":"ABS","target":"PER","count":4},{"source":"ABS","target":"PRAC","count":3},{"source":"AC","target":"AC","count":1},{"source":"AC","target":"INT","count":9},{"source":"AC","target":"ITS","count":1},{"source":"ACDC","target":"ACDC","count":1},{"source":"ACDC","target":"CR","count":2},{"source":"ACDC","target":"ITS","count":13},{"source":"ACDC","target":"PER","count":4},{"source":"APL","target":"APL","count":8},{"source":"APL","target":"CR","count":3},{"source":"APL","target":"ENV","count":1},{"source":"APL","target":"INT","count":1},{"source":"APL","target":"ITS","count":29},{"source":"APL","target":"LA","count":1},{"source":"APL","target":"PEG","count":1},{"source":"APL","target":"PER","count":3},{"source":"AST","target":"AST","count":17},{"source":"AST","target":"COP","count":1},{"source":"AST","target":"DBT","count":2},{"source":"AST","target":"DEVOPS","count":1},{"source":"AST","target":"IGN","count":1},{"source":"AST","target":"INT","count":2},{"source":"AST","target":"ITS","count":32},{"source":"AST","target":"PDG","count":2},{"source":"AST","target":"PER","count":8},{"source":"ATS","target":"ABS","count":1},{"source":"ATS","target":"ATS","count":21},{"source":"ATS","target":"DBT","count":1},{"source":"ATS","target":"INT","count":3},{"source":"ATS","target":"PDG","count":1},{"source":"ATS","target":"PEG","count":1},{"source":"CAR","target":"APL","count":1},{"source":"CAR","target":"CAR","count":9},{"source":"CAR","target":"COP","count":1},{"source":"CAR","target":"INT","count":9},{"source":"CAR","target":"ITS","count":8},{"source":"IGN","target":"CR","count":4},{"source":"IGN","target":"IGN","count":13},{"source":"IGN","target":"INT","count":5},{"source":"IGN","target":"ITS","count":13},{"source":"IGN","target":"PER","count":4},{"source":"IGN","target":"PRAC","count":1},{"source":"LA","target":"AC","count":1},{"source":"LA","target":"INT","count":1},{"source":"LA","target":"ITS","count":37},{"source":"LA","target":"LA","count":18},{"source":"LA","target":"PER","count":2},{"source":"LOT","target":"LOT","count":18},{"source":"PDG","target":"ABS","count":1},{"source":"PDG","target":"AST","count":4},{"source":"PDG","target":"ATS","count":1},{"source":"PDG","target":"CAR","count":1},{"source":"PDG","target":"CR","count":8},{"source":"PDG","target":"ICS","count":1},{"source":"PDG","target":"IGN","count":3},{"source":"PDG","target":"INT","count":18},{"source":"PDG","target":"ITS","count":6},{"source":"PDG","target":"NRB","count":4},{"source":"PDG","target":"ONT","count":1},{"source":"PDG","target":"PDG","count":24},{"source":"PDG","target":"PER","count":1},{"source":"PEG","target":"CAR","count":1},{"source":"PEG","target":"ENV","count":1},{"source":"PEG","target":"INFRA","count":1},{"source":"PEG","target":"ITS","count":22},{"source":"PEG","target":"LA","count":1},{"source":"PEG","target":"PEG","count":51},{"source":"PEG","target":"PER","count":6},{"source":"RPT","target":"ABS","count":1},{"source":"RPT","target":"APL","count":1},{"source":"RPT","target":"IGN","count":1},{"source":"RPT","target":"INT","count":9},{"source":"RPT","target":"ITS","count":2},{"source":"RPT","target":"RPT","count":11},{"source":"RPT","target":"RTR","count":1},{"source":"RWWA","target":"INT","count":1},{"source":"RWWA","target":"ITS","count":1},{"source":"RWWA","target":"PER","count":1},{"source":"RWWA","target":"RWWA","count":1},{"source":"SCOR","target":"SCOR","count":5},{"source":"SPK","target":"INT","count":4},{"source":"SPK","target":"ITS","count":4},{"source":"SPK","target":"SPK","count":21},{"source":"TS","target":"CS","count":1},{"source":"TS","target":"TS","count":10}]; var nodes_data = [{"name":"ABS","total":11},{"name":"ATS","total":23},{"name":"CR","total":25},{"name":"ENV","total":3},{"name":"INT","total":78},{"name":"ITS","total":177},{"name":"PDG","total":28},{"name":"PER","total":33},{"name":"PRAC","total":4},{"name":"AC","total":2},{"name":"ACDC","total":1},{"name":"APL","total":10},{"name":"LA","total":20},{"name":"PEG","total":53},{"name":"AST","total":21},{"name":"COP","total":2},{"name":"DBT","total":3},{"name":"DEVOPS","total":1},{"name":"IGN","total":18},{"name":"CAR","total":11},{"name":"LOT","total":18},{"name":"ICS","total":1},{"name":"NRB","total":4},{"name":"ONT","total":1},{"name":"INFRA","total":1},{"name":"RPT","total":11},{"name":"RTR","total":1},{"name":"RWWA","total":1},{"name":"SCOR","total":5},{"name":"SPK","total":21},{"name":"CS","total":1},{"name":"TS","total":10}]; //create node size scale var nodeSizeScale = d3.scaleLinear() .domain(d3.extent(nodes_data, d => d.total)) .range([30, 70]); //create node size scale var linkSizeScale = d3.scaleLinear() .domain(d3.extent(links_data, d => d.count)) .range([5, 30]); //create node size scale var linkColourScale = d3.scaleLinear() .domain(d3.extent(links_data, d => d.count)) .range(['blue', 'red']); //document.getElementsByTagName('body')[0].innerHTML = '<div>' + JSON.stringify(nodes_data) + '</div>'; //create somewhere to put the force directed graph var height = 650, width = 950; var svg = d3.select("body").append("svg") .attr('width',width) .attr('height',height); var radius = 15; //set up the simulation and add forces var simulation = d3.forceSimulation() .nodes(nodes_data); var link_force = d3.forceLink(links_data) .id(function(d) { return d.name; }) ; var charge_force = d3.forceManyBody() .strength(-1000); var center_force = d3.forceCenter(width / 2, height / 2); simulation .force("charge_force", charge_force) .force("center_force", center_force) .force("link",link_force) ; //add tick instructions: simulation.on("tick", tickActions ); // THIS CODE SECTION ISN'T RENDERING // Per-type markers, as they don't inherit styles. svg.append("defs").selectAll("marker") .data(["dominating"]) .enter().append("marker") .attr("id", function (d) { return d; }) .attr("viewBox", "0 -5 10 10") .attr("refX", 15) .attr("refY", -1.5) .attr("markerWidth", 12) .attr("markerHeight", 12) .attr("orient", "auto") .append("path") .attr("d", "M0,-5L10,0L0,5"); //add encompassing group for the zoom var g = svg.append("g") .attr("class", "everything"); // add the curved links to our graphic var link = g.selectAll(".link") .data(links_data) .enter() .append("path") .attr("class", "link") .style('stroke', d => {return linkColourScale(d.count);}) .attr('stroke-opacity', 0.5) .attr('stroke-width', d => {return linkSizeScale(d.count);}); //draw circles for the nodes var node = g.append("g") .attr("class", "nodes") .selectAll("circle") .data(nodes_data) .enter() .append("circle") .attr("r", d => {return nodeSizeScale(d.total);}) .attr("fill", "#333") .on("mouseover", mouseOver(.1)) .on("mouseout", mouseOut); //add text labels var text = g.append("g") .attr("class", "labels") .selectAll("text") .data(nodes_data) .enter().append("text") .style("text-anchor","middle") .style("font-weight", "bold") .style("pointer-events", "none") .attr("dy", ".35em") .text(function(d) { return d.name }); //add drag capabilities var drag_handler = d3.drag() .on("start", drag_start) .on("drag", drag_drag) .on("end", drag_end); drag_handler(node); //add zoom capabilities var zoom_handler = d3.zoom() .on("zoom", zoom_actions); zoom_handler(svg); /** Functions **/ //Drag functions //d is the node function drag_start(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } //make sure you can't drag the circle outside the box function drag_drag(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function drag_end(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } //Zoom functions function zoom_actions(){ g.attr("transform", d3.event.transform) } function tickActions() { //update circle positions each tick of the simulation node .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); //update link positions link.attr("d", positionLink); text.attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); } // links are drawn as curved paths between nodes, // through the intermediate nodes function positionLink(d) { var offset = 30; var midpoint_x = (d.source.x + d.target.x) / 2; var midpoint_y = (d.source.y + d.target.y) / 2; var dx = (d.target.x - d.source.x); var dy = (d.target.y - d.source.y); var normalise = Math.sqrt((dx * dx) + (dy * dy)); var offSetX = midpoint_x + offset * (dy / normalise); var offSetY = midpoint_y - offset * (dx / normalise); return "M" + d.source.x + "," + d.source.y + "S" + offSetX + "," + offSetY + " " + d.target.x + "," + d.target.y; } // build a dictionary of nodes that are linked var linkedByIndex = {}; links_data.forEach(function(d) { linkedByIndex[d.source.index + "," + d.target.index] = 1; }); // check the dictionary to see if nodes are linked function isConnected(a, b) { return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index; } // fade nodes on hover function mouseOver(opacity) { return function(d) { // check all other nodes to see if they're connected // to this one. if so, keep the opacity at 1, otherwise // fade node.style("stroke-opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; }); node.style("fill-opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; }); text.style("fill-opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; }); // also style link accordingly link.style("stroke-opacity", function(o) { return o.source === d || o.target === d ? 1 : opacity; }); link.style("stroke", function(o) { return o.source === d || o.target === d ? linkColourScale(o.count) : "#333"; }); }; } function mouseOut() { node.style("stroke-opacity", 1); node.style("fill-opacity", 1); text.style("fill-opacity", 1); link.style("stroke-opacity", 0.5); link.style("stroke", d => {return linkColourScale(d.count);}); }
I have applied the same code changes in the example you referred, to your code and it seems to work fine. The only extra update required was, since you had nodes connecting to itself, I had to apply marker-end property and path length updates only to the filtered set of links. var links_data = [{"source":"ABS","target":"ABS","count":8},{"source":"ABS","target":"ATS","count":1},{"source":"ABS","target":"CR","count":8},{"source":"ABS","target":"ENV","count":1},{"source":"ABS","target":"INT","count":16},{"source":"ABS","target":"ITS","count":9},{"source":"ABS","target":"PDG","count":1},{"source":"ABS","target":"PER","count":4},{"source":"ABS","target":"PRAC","count":3},{"source":"AC","target":"AC","count":1},{"source":"AC","target":"INT","count":9},{"source":"AC","target":"ITS","count":1},{"source":"ACDC","target":"ACDC","count":1},{"source":"ACDC","target":"CR","count":2},{"source":"ACDC","target":"ITS","count":13},{"source":"ACDC","target":"PER","count":4},{"source":"APL","target":"APL","count":8},{"source":"APL","target":"CR","count":3},{"source":"APL","target":"ENV","count":1},{"source":"APL","target":"INT","count":1},{"source":"APL","target":"ITS","count":29},{"source":"APL","target":"LA","count":1},{"source":"APL","target":"PEG","count":1},{"source":"APL","target":"PER","count":3},{"source":"AST","target":"AST","count":17},{"source":"AST","target":"COP","count":1},{"source":"AST","target":"DBT","count":2},{"source":"AST","target":"DEVOPS","count":1},{"source":"AST","target":"IGN","count":1},{"source":"AST","target":"INT","count":2},{"source":"AST","target":"ITS","count":32},{"source":"AST","target":"PDG","count":2},{"source":"AST","target":"PER","count":8},{"source":"ATS","target":"ABS","count":1},{"source":"ATS","target":"ATS","count":21},{"source":"ATS","target":"DBT","count":1},{"source":"ATS","target":"INT","count":3},{"source":"ATS","target":"PDG","count":1},{"source":"ATS","target":"PEG","count":1},{"source":"CAR","target":"APL","count":1},{"source":"CAR","target":"CAR","count":9},{"source":"CAR","target":"COP","count":1},{"source":"CAR","target":"INT","count":9},{"source":"CAR","target":"ITS","count":8},{"source":"IGN","target":"CR","count":4},{"source":"IGN","target":"IGN","count":13},{"source":"IGN","target":"INT","count":5},{"source":"IGN","target":"ITS","count":13},{"source":"IGN","target":"PER","count":4},{"source":"IGN","target":"PRAC","count":1},{"source":"LA","target":"AC","count":1},{"source":"LA","target":"INT","count":1},{"source":"LA","target":"ITS","count":37},{"source":"LA","target":"LA","count":18},{"source":"LA","target":"PER","count":2},{"source":"LOT","target":"LOT","count":18},{"source":"PDG","target":"ABS","count":1},{"source":"PDG","target":"AST","count":4},{"source":"PDG","target":"ATS","count":1},{"source":"PDG","target":"CAR","count":1},{"source":"PDG","target":"CR","count":8},{"source":"PDG","target":"ICS","count":1},{"source":"PDG","target":"IGN","count":3},{"source":"PDG","target":"INT","count":18},{"source":"PDG","target":"ITS","count":6},{"source":"PDG","target":"NRB","count":4},{"source":"PDG","target":"ONT","count":1},{"source":"PDG","target":"PDG","count":24},{"source":"PDG","target":"PER","count":1},{"source":"PEG","target":"CAR","count":1},{"source":"PEG","target":"ENV","count":1},{"source":"PEG","target":"INFRA","count":1},{"source":"PEG","target":"ITS","count":22},{"source":"PEG","target":"LA","count":1},{"source":"PEG","target":"PEG","count":51},{"source":"PEG","target":"PER","count":6},{"source":"RPT","target":"ABS","count":1},{"source":"RPT","target":"APL","count":1},{"source":"RPT","target":"IGN","count":1},{"source":"RPT","target":"INT","count":9},{"source":"RPT","target":"ITS","count":2},{"source":"RPT","target":"RPT","count":11},{"source":"RPT","target":"RTR","count":1},{"source":"RWWA","target":"INT","count":1},{"source":"RWWA","target":"ITS","count":1},{"source":"RWWA","target":"PER","count":1},{"source":"RWWA","target":"RWWA","count":1},{"source":"SCOR","target":"SCOR","count":5},{"source":"SPK","target":"INT","count":4},{"source":"SPK","target":"ITS","count":4},{"source":"SPK","target":"SPK","count":21},{"source":"TS","target":"CS","count":1},{"source":"TS","target":"TS","count":10}]; var nodes_data = [{"name":"ABS","total":11},{"name":"ATS","total":23},{"name":"CR","total":25},{"name":"ENV","total":3},{"name":"INT","total":78},{"name":"ITS","total":177},{"name":"PDG","total":28},{"name":"PER","total":33},{"name":"PRAC","total":4},{"name":"AC","total":2},{"name":"ACDC","total":1},{"name":"APL","total":10},{"name":"LA","total":20},{"name":"PEG","total":53},{"name":"AST","total":21},{"name":"COP","total":2},{"name":"DBT","total":3},{"name":"DEVOPS","total":1},{"name":"IGN","total":18},{"name":"CAR","total":11},{"name":"LOT","total":18},{"name":"ICS","total":1},{"name":"NRB","total":4},{"name":"ONT","total":1},{"name":"INFRA","total":1},{"name":"RPT","total":11},{"name":"RTR","total":1},{"name":"RWWA","total":1},{"name":"SCOR","total":5},{"name":"SPK","total":21},{"name":"CS","total":1},{"name":"TS","total":10}]; //create node size scale var nodeSizeScale = d3.scaleLinear() .domain(d3.extent(nodes_data, d => d.total)) .range([30, 70]); //create node size scale var linkSizeScale = d3.scaleLinear() .domain(d3.extent(links_data, d => d.count)) .range([5, 30]); //create node size scale var linkColourScale = d3.scaleLinear() .domain(d3.extent(links_data, d => d.count)) .range(['blue', 'red']); //document.getElementsByTagName('body')[0].innerHTML = '<div>' + JSON.stringify(nodes_data) + '</div>'; //create somewhere to put the force directed graph var height = 650, width = 950; var svg = d3.select("body").append("svg") .attr('width',width) .attr('height',height); var radius = 15; //set up the simulation and add forces var simulation = d3.forceSimulation() .nodes(nodes_data); var link_force = d3.forceLink(links_data) .id(function(d) { return d.name; }) ; var charge_force = d3.forceManyBody() .strength(-1000); var center_force = d3.forceCenter(width / 2, height / 2); simulation .force("charge_force", charge_force) .force("center_force", center_force) .force("link",link_force) ; //add tick instructions: simulation.on("tick", tickActions ); // THIS CODE SECTION ISN'T RENDERING // Per-type markers, as they don't inherit styles. svg.append("defs").selectAll("marker") .data(["dominating"]) .enter().append("marker") .attr('markerUnits', 'userSpaceOnUse') .attr("id", function (d) { return d; }) .attr("viewBox", "0 -5 10 10") .attr("refX", 0) .attr("refY", 0) .attr("markerWidth", 12) .attr("markerHeight", 12) .attr("orient", "auto-start-reverse") .append("path") .attr("d", "M0,-5L10,0L0,5") .attr("fill", "red"); //add encompassing group for the zoom var g = svg.append("g") .attr("class", "everything"); // add the curved links to our graphic var link = g.selectAll(".link") .data(links_data) .enter() .append("path") .attr("class", "link") .style('stroke', d => {return linkColourScale(d.count);}) .attr('stroke-opacity', 0.5) .attr('stroke-width', d => {return linkSizeScale(d.count);}) .attr("marker-end", function(d) { if(JSON.stringify(d.target) !== JSON.stringify(d.source)) return "url(#dominating)"; }); //draw circles for the nodes var node = g.append("g") .attr("class", "nodes") .selectAll("circle") .data(nodes_data) .enter() .append("circle") .attr("r", d => {return nodeSizeScale(d.total);}) .attr("fill", "#333") .on("mouseover", mouseOver(.1)) .on("mouseout", mouseOut); //add text labels var text = g.append("g") .attr("class", "labels") .selectAll("text") .data(nodes_data) .enter().append("text") .style("text-anchor","middle") .style("font-weight", "bold") .style("pointer-events", "none") .attr("dy", ".35em") .text(function(d) { return d.name }); //add drag capabilities var drag_handler = d3.drag() .on("start", drag_start) .on("drag", drag_drag) .on("end", drag_end); drag_handler(node); //add zoom capabilities var zoom_handler = d3.zoom() .on("zoom", zoom_actions); zoom_handler(svg); /** Functions **/ //Drag functions //d is the node function drag_start(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } //make sure you can't drag the circle outside the box function drag_drag(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function drag_end(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } //Zoom functions function zoom_actions(){ g.attr("transform", d3.event.transform) } function tickActions() { //update circle positions each tick of the simulation node .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); //update link positions link.attr("d", positionLink1); link.filter(function(d){ return JSON.stringify(d.target) !== JSON.stringify(d.source); }) .attr("d",positionLink2); text.attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); } function positionLink1(d) { var dx = d.target.x - d.source.x, dy = d.target.y - d.source.y, dr = Math.sqrt(dx * dx + dy * dy); return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y; } // recalculate and back off the distance function positionLink2(d) { // length of current path var pl = this.getTotalLength(), // radius of circle plus marker head r = nodeSizeScale(d.target.total)+ 12, //12 is the "size" of the marker Math.sqrt(12**2 + 12 **2) // position close to where path intercepts circle m = this.getPointAtLength(pl - r); var dx = m.x - d.source.x, dy = m.y - d.source.y, dr = Math.sqrt(dx * dx + dy * dy); return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + m.x + "," + m.y; } // build a dictionary of nodes that are linked var linkedByIndex = {}; links_data.forEach(function(d) { linkedByIndex[d.source.index + "," + d.target.index] = 1; }); // check the dictionary to see if nodes are linked function isConnected(a, b) { return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index; } // fade nodes on hover function mouseOver(opacity) { return function(d) { // check all other nodes to see if they're connected // to this one. if so, keep the opacity at 1, otherwise // fade node.style("stroke-opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; }); node.style("fill-opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; }); text.style("fill-opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; }); // also style link accordingly link.style("stroke-opacity", function(o) { return o.source === d || o.target === d ? 1 : opacity; }); link.style("stroke", function(o) { return o.source === d || o.target === d ? linkColourScale(o.count) : "#333"; }); }; } function mouseOut() { node.style("stroke-opacity", 1); node.style("fill-opacity", 1); text.style("fill-opacity", 1); link.style("stroke-opacity", 0.5); link.style("stroke", d => {return linkColourScale(d.count);}); } body { width:99%; height:100%; background: #111111; } .svg { width:1000; height:1000; } .link { fill: none; } .labels { font-family: Arial; fill: white; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
D3: Rotating labels in Bi-Level Partition
I'm using D3 and javascript to create a BiLevel Partition following this example. The labels in the left side of the diagram are upside down, and I was trying to rotate them, but I've not been successful yet. I found numerous cases of people with the same problem, but using Sunburst. Also tried to implement those solutions, but I'm still unable to solve this problem. <body> <script src="http://d3js.org/d3.v3.min.js"></script> <script> /*var margin = {top: 350, right: 480, bottom: 350, left: 480}, radius = Math.min(margin.top, margin.right, margin.bottom, margin.left) - 10;*/ var width = 1200, height = 1200, radius = Math.min(width, height) / 2; function filter_min_arc_size_text(d, i) {return (d.dx*d.depth*radius/3)>14}; var hue = d3.scale.category10(); var luminance = d3.scale.sqrt() .domain([0, 1e6]) .clamp(true) .range([90, 20]); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")"); var partition = d3.layout.partition() .sort(function(a, b) { return d3.ascending(a.name, b.name); }) .size([2 * Math.PI, radius]); var arc = d3.svg.arc() .startAngle(function(d) { return d.x; }) .endAngle(function(d) { return d.x + d.dx - .01 / (d.depth + .5); }) .innerRadius(function(d) { return radius / 3 * d.depth; }) .outerRadius(function(d) { return radius / 3 * (d.depth + 1) - 1; }); //Tooltip description var tooltip = d3.select("body") .append("div") .attr("id", "tooltip") .style("position", "absolute") .style("z-index", "10") .style("opacity", 0); function format_number(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } function format_description(d) { var description = d.description; return /* '<b>' + d.name + '</b></br>'+*/ d.description + '<br> (' + format_number(d.value) + ')'; } function computeTextRotation(d) { var ang = ((d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180; return (ang > 90) ? 180 + ang : ang; } function mouseOverArc(d) { d3.select(this).attr("stroke","black") tooltip.html(format_description(d)); return tooltip.transition() .duration(50) .style("opacity", 0.9); } function mouseOutArc(){ d3.select(this).attr("stroke","") return tooltip.style("opacity", 0); } function mouseMoveArc (d) { return tooltip .style("top", (d3.event.pageY-10)+"px") .style("left", (d3.event.pageX+10)+"px"); } var root_ = null; d3.json("flare.json", function(error, root) { if (error) return console.warn(error); // Compute the initial layout on the entire tree to sum sizes. // Also compute the full name and fill color for each node, // and stash the children so they can be restored as we descend. partition .value(function(d) { return d.size; }) .nodes(root) .forEach(function(d) { d._children = d.children; d.sum = d.value; d.key = key(d); d.fill = fill(d); }); // Now redefine the value function to use the previously-computed sum. partition .children(function(d, depth) { return depth < 2 ? d._children : null; }) .value(function(d) { return d.sum; }); var center = svg.append("circle") .attr("r", radius / 3) .on("click", zoomOut); center.append("title") .text("zoom out"); var partitioned_data=partition.nodes(root).slice(1) var path = svg.selectAll("path") .data(partitioned_data) .enter().append("path") .attr("d", arc) .style("fill", function(d) { return d.fill; }) .each(function(d) { this._current = updateArc(d); }) .on("click", zoomIn) .on("mouseover", mouseOverArc) .on("mousemove", mouseMoveArc) .on("mouseout", mouseOutArc); var texts = svg.selectAll("text") .data(partitioned_data) .enter().append("text") .filter(filter_min_arc_size_text) .attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; }) .attr("x", function(d) { return radius / 3 * d.depth; }) .attr("dx", "6") // margin .attr("dy", ".35em") // vertical-align .text(function(d,i) {return d.name}) function zoomIn(p) { if (p.depth > 1) p = p.parent; if (!p.children) return; zoom(p, p); } function zoomOut(p) { if (!p.parent) return; zoom(p.parent, p); } // Zoom to the specified new root. function zoom(root, p) { if (document.documentElement.__transition__) return; // Rescale outside angles to match the new layout. var enterArc, exitArc, outsideAngle = d3.scale.linear().domain([0, 2 * Math.PI]); function insideArc(d) { return p.key > d.key ? {depth: d.depth - 1, x: 0, dx: 0} : p.key < d.key ? {depth: d.depth - 1, x: 2 * Math.PI, dx: 0} : {depth: 0, x: 0, dx: 2 * Math.PI}; } function outsideArc(d) { return {depth: d.depth + 1, x: outsideAngle(d.x), dx: outsideAngle(d.x + d.dx) - outsideAngle(d.x)}; } center.datum(root); // When zooming in, arcs enter from the outside and exit to the inside. // Entering outside arcs start from the old layout. if (root === p) enterArc = outsideArc, exitArc = insideArc, outsideAngle.range([p.x, p.x + p.dx]); var new_data=partition.nodes(root).slice(1) path = path.data(new_data, function(d) { return d.key; }); // When zooming out, arcs enter from the inside and exit to the outside. // Exiting outside arcs transition to the new layout. if (root !== p) enterArc = insideArc, exitArc = outsideArc, outsideAngle.range([p.x, p.x + p.dx]); d3.transition().duration(d3.event.altKey ? 7500 : 750).each(function() { path.exit().transition() .style("fill-opacity", function(d) { return d.depth === 1 + (root === p) ? 1 : 0; }) .attrTween("d", function(d) { return arcTween.call(this, exitArc(d)); }) .remove(); path.enter().append("path") .style("fill-opacity", function(d) { return d.depth === 2 - (root === p) ? 1 : 0; }) .style("fill", function(d) { return d.fill; }) .on("click", zoomIn) .on("mouseover", mouseOverArc) .on("mousemove", mouseMoveArc) .on("mouseout", mouseOutArc) .each(function(d) { this._current = enterArc(d); }); path.transition() .style("fill-opacity", 1) .attrTween("d", function(d) { return arcTween.call(this, updateArc(d)); }); }); texts = texts.data(new_data, function(d) { return d.key; }) texts.exit() .remove() texts.enter() .append("text") texts.style("opacity", 0) .attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; }) .attr("x", function(d) { return radius / 3 * d.depth; }) .attr("dx", "6") // margin .attr("dy", ".35em") // vertical-align .filter(filter_min_arc_size_text) .text(function(d,i) {return d.name}) .transition().delay(750).style("opacity", 1) } }); function key(d) { var k = [], p = d; while (p.depth) k.push(p.name), p = p.parent; return k.reverse().join("."); } function fill(d) { var p = d; while (p.depth > 1) p = p.parent; var c = d3.lab(hue(p.name)); c.l = luminance(d.sum); return c; } function arcTween(b) { var i = d3.interpolate(this._current, b); this._current = i(0); return function(t) { return arc(i(t)); }; } function updateArc(d) { return {depth: d.depth, x: d.x, dx: d.dx}; } d3.select(self.frameElement).style("height", margin.top + margin.bottom + "px"); </script> The problem I have is that it is only showing the right half of the Partition.
D3 zoomable circle packing with Bar Chart (Bar chart zoom)
I have done basic things in D3 so I don't have deep knowledge about it. I have created the zoomable circle packing chart with bar chart. Here is the code: var xr, yr, xaxis, yaxis, bar, bg; var svg = d3.select("svg"), margin = 20, width = +svg.attr("width") - margin.left - margin.right, height = +svg.attr("height") - margin.top - margin.bottom; diameter = +svg.attr("width"), g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")"); var color = d3.scaleLinear() .domain([-1, 5]) .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"]) .interpolate(d3.interpolateHcl); var pack = d3.pack() .size([diameter - margin, diameter - margin]) .padding(2); d3.json("occupation.json", function(error, root) { if (error) throw error; root = d3.hierarchy(root) .sum(function(d) { return d.size; }) .sort(function(a, b) { return b.value - a.value; }); var focus = root, nodes = pack(root).descendants(), view; var circle = g.selectAll("circle") .data(nodes.filter(function(d) { return d.height > 0 })) .enter().append("circle") .attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; }) .style("fill", function(d) { return d.children ? color(d.depth) : null; }) .on("click", function(d) { if (focus !== d) { if (d.children) { zoom(d), d3.event.stopPropagation(); } else { var nextMonthVal = prompt("Please enter target value you want to set"); if (nextMonthVal) { alert("You have set the target of Rs. " + nextMonthVal + " for next Month"); } } } }); var leaf = g.selectAll(".bars") .data(nodes.filter(function(d) { return d.height == 1 })) .enter() .append("g") .attr("x", 0) .attr("y", 0) .attr("height", function(d) { return d.x + d.r }) .attr("width", function(d) { return d.y + d.r }) .attr("class", "bars") .each(function(d) { drawBarData(this, this.__data__, d); }) var text = g.selectAll(".label") .data(nodes.filter(function(d) { return d.height > 0 })) .enter().append("text") .attr("class", "label") .style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; }) .style("display", function(d) { return d.parent === root ? "inline" : "none"; }) .text(function(d) { return d.data.name + " " + d.data.size + " Rs."; }); var node = g.selectAll("circle,.bars,.label"); svg .style("background", color(-1)) .on("click", function() { zoom(root); }); zoomTo([root.x, root.y, root.r * 2 + margin]); function zoom(d) { var focus0 = focus; focus = d; var transition = d3.transition() .duration(d3.event.altKey ? 7500 : 750) .tween("zoom", function(d) { var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]); return function(t) { zoomTo(i(t), focus); }; }); transition.selectAll(".label") .filter(function(d) { return d.parent === focus || this.style.display === "inline"; }) .style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; }) .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; }) .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; }); } function zoomTo(v, focus) { var k = diameter / v[2]; view = v; node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; }); circle.attr("r", function(d) { return d.r * k; }); if (focus && focus.height == 1) { var data2 = focus.children.map(function(d) { return d.data }) var data1 = [] rectwidth = focus.r, rectheight = focus.r; barsize = data2.length; maxDataPoint = d3.max(data2, function(d) { return d.size }); var linearScale = d3.scaleLinear() .domain([0, maxDataPoint]) .range([0, rectheight]); for (var i = 0; i < data2.length; i++) { data1.push({ name: data2[i].name, size: linearScale(data2[i].size) }) } bg.attr("transform", function(d) { console.log(d); return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; }) bar.attr("x", function(d, i) { console.log("D ::: >", d); return i * (rectwidth / data1.length); }) .attr("y", function(d) { return rectheight - d.size; }) .attr("width", focus.r / data1.length - 2) .attr("height", function(d) { return d.size; }); } } function drawBarData(ele, data, d) { if (!data && !data.parent) return; var data2 = data.children.map(function(e) { return e.data }) var data1 = [] rectwidth = d.r, rectheight = d.r; barsize = data2.length; maxDataPoint = d3.max(data2, function(d) { return d.size }); var linearScale = d3.scaleLinear() .domain([0, maxDataPoint]) .range([0, rectheight]); for (var i = 0; i < data2.length; i++) { data1.push({ name: data2[i].name, size: linearScale(data2[i].size) }) } bg = d3.select(ele).attr("transform", "translate(" + 0 + "," + 0 + ")").append("g") .attr("class", "chart-wrapper") .attr("transform", function(d) { console.log('BG ::: >>>', d); return "translate(" + -d.r / 2 + "," + -d.r / 2 + ")"; }); bar = bg.selectAll(".bar") .data(data1) .enter() .append('rect') .attr("class", "bar") .attr("x", function(d, i) { return i * (rectwidth / data1.length); }) .attr("y", function(d) { return rectheight - d.size; }) .attr("width", d.r / data1.length - 2) .attr("height", function(d) { return d.size; }); } }); body { padding: 50px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } a { color: #00B7FF; } .container { width: 400px; } .node { cursor: pointer; } .node:hover { border: #000; border-width: 1.5px; } .node--leaf { color: white; } .label { font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif; text-align: center; text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff; } .label, .node--root { pointer-events: none; } <script src="https://d3js.org/d3.v4.min.js"></script> <svg width="960" height="960"></svg> (not enough room for json - refer to plunkr) I've placed the bar chart as per my understanding. But I also want barchart to be zoomed on the focus of each node. I am not getting any idea how to do it. Please help me if anyone having idea or solution.
how do i add a zoom in and zoom out button in a graph on d3
I used d3.select(".graph") .call(d3.behavior.zoom().on("zoom", redraw)).on("dblclick.zoom", null).on("wheel.zoom", null); function redraw() { console.log("here", d3.event.translate, d3.event.scale); g.attr("transform","translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); } to create pan, but when i create an .on event listener and call redraw() with anything other than the way i have it, it comes back with nothing for d3.event.translate and d3.event.scale, so i can't update the transform. And i've seen code out there for zooming with a button for maps, but not a graph. It must be possible some how, but i don't see how. The code i have so far is... <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <style> .node { stroke: #000; stroke-width: 0px; } .link { stroke: #999; stroke-opacity: .6; } .graphmap { border: 1px solid black; } </style> </head> <body> <div class="graph"></div> <script src="http://d3js.org/d3.v3.min.js"></script> <script> // start draw zoom buttons var zoom = d3.select(".zoom").append("svg") .attr("width", 40) .attr("height", 40); // start zoom behavior var mapZoom = d3.behavior.zoom() .on("zoom", redraw); function zoomButton(zoomDirection) { if (zoomDirection == "in") { var newZoom = mapZoom.scale() * 1.5; var newX = ((mapZoom.translate()[0] - (width / 2)) * 1.5) + width / 2; var newY = ((mapZoom.translate()[1] - (height / 2)) * 1.5) + height / 2; } else if (zoomDirection == "out") { var newZoom = mapZoom.scale() * .75; var newX = ((mapZoom.translate()[0] - (width / 2)) * .75) + width / 2; var newY = ((mapZoom.translate()[1] - (height / 2)) * .75) + height / 2; } mapZoom.scale(newZoom).translate([newX,newY]) redraw(); } function zoomed() { projection.translate(mapZoom.translate()).scale(mapZoom.scale()); d3.selectAll("path.graticule").attr("d", geoPath); d3.selectAll("path.countries").attr("d", geoPath); d3.selectAll("circle.cities") .attr("cx", function(d) {return projection([d.x,d.y])[0]}) .attr("cy", function(d) {return projection([d.x,d.y])[1]}); } d3.select(".zoomin").on("click", function (){ zoomButton("in"); console.log(d3.behavior.zoom().event(d3.select(".zoomin"))) }); d3.select(".graph") .call(d3.behavior.zoom().on("zoom", redraw)).on("dblclick.zoom", null).on("wheel.zoom", null); d3.select(".zoomin") .call(d3.behavior.zoom().on("zoom", redraw)).on("dblclick.zoom", redraw); function redraw() { console.log("here", d3.event.translate, d3.event.scale); g.attr("transform","translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); } // start svg var width = 1100, height = 900; var color = d3.scale.category20(); var force = d3.layout.force() .gravity(.05) .charge(-700) .linkDistance(150) .size([width, height]); var svg = d3.select(".graph").append("svg") .attr("width", width) .attr("height", height) .attr("class", "graphmap"); zoomin = svg.append("g") .attr("class", "zoomin"); zoomin.append("rect") .attr("x", 10) .attr("y", 10) .attr("width", 30) .attr("height", 30) .attr("rx", 4) .attr("ry", 4) .attr("fill", "#dadae6"); var g = svg.append('g'); d3.json("miserables.json", function(error, graph) { force .nodes(graph.nodes) .links(graph.links) .start(); var link = g.selectAll(".link") .data(graph.links) .enter().append("line") .attr("class", "link") .style("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = g.selectAll("g") .data(graph.nodes) .enter().append("g") .attr("class","node") .call(force.drag); node.append("circle") .attr("r", function(d) { return Math.sqrt(d.group * 20); }) .style("fill", function(d) { return color(d.group); }) .attr("pointer-events", "auto") .attr("class", "circlenode"); node.append("text") .attr("text-anchor", "right") .attr("fill","black") .style("pointer-events", "none") .attr("font-size", function(d) { 20 + 'px'; }) .attr("font-weight", function(d) { return "bold"; }) .text( function(d) { return d.name + ' (' + d.group + ')';}); setTimeout(function() { node.classed("fixed", function(d) { return d.fixed = true; }); }, 9000); force.on("tick", function() { link.attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")";}); }); }); function dump(obj) { var out = ''; for (var i in obj) { out += i + ": " + obj[i] + "\n"; } // or, if you wanted to avoid alerts... var pre = document.createElement('pre'); pre.innerHTML = out; document.body.appendChild(pre) } </script> </body> </html> You'll see i was playing around with making a zoomButton function but i don't know how to see how to set it up to make it work. I've seen some demos out there of different ideas with the zoom functionality but i don't really understand how they work and what the functions are for. And the d3 documentation doesn't seem to provide much insight. And i haven't found any tutorials that go over what each of the functions do and how to handle events. Any help an explanation of how the zoom functions actually work would be appreciated.
It turns out to be very simple. var zoomfactor = 1; var zoomlistener = d3.behavior.zoom() .on("zoom", redraw); d3.select(".zoomin").on("click", function (){ zoomfactor = zoomfactor + 0.2; zoomlistener.scale(zoomfactor).event(d3.select(".graph")); }); d3.select(".zoomout").on("click", function (){ zoomfactor = zoomfactor - 0.2; zoomlistener.scale(zoomfactor).event(d3.select(".graph")); }); function redraw() { console.log("here", d3.event.translate, d3.event.scale); g.attr("transform","translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); } scale() sets the amount you want to zoom and event() calls the portion of the page you want to update.