add edges in zoomable circle-pack graph in d3 library - javascript
I want to create zoomable circle-pack graph with edges between most internal children,I used this code for base,
and my code is :
var year=[]
var edges
var svg
function main_graph(){
svg = d3.select("svg"),
margin = 20,
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);
svg.append("svg:defs")
.append("svg:marker")
.attr("id", "arrow")
.attr("viewBox", "0 0 10 10")
.attr("refX", 10)
.attr("refY", 5)
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 6)
.attr("markerHeight", 3)
.attr("orient", "auto")
.append("svg:path")
.style("stroke","none")
.attr("d", "M 0 0 L 10 5 L 0 10 z");
var pack = d3.pack()
.size([diameter/20, diameter/20])
.padding(20)
d3.json("flare.json", function(error, root) {
edges=root.links
var leyer1=root.children
var leyer1_len=leyer1.length
console.log(leyer1_len)
for(i=0;i<leyer1_len;i++){
var leyer2_len=(leyer1[i]).children.length
var leyer2=(leyer1[i]).children
for(j=0;j<leyer2_len;j++){
var from_year=leyer2[j].from
var to_year=leyer2[j].to
var id_auther=leyer2[j].id
year.push({from:from_year,to:to_year,id:id_auther})
}
}
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)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node
node--root"; })
.attr("id",function(d){return d.data.id})
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) {if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = g.selectAll("text")
.data(nodes)
.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; });
var node = g.selectAll("circle,text");
const arrow = svg.selectAll('path.arrow').data(edges);
arrow.enter()
.append("path")
.attr("class","arrow")
.attr("x1", function(d){
debugger
let translate = getTranslate(d.source);
console.log(translate[0])
return translate[0]
})
.attr("x2", function(d){
let translate = getTranslate(d.target);
console.log(translate[0])
return translate[0]
})
.attr("y1", function(d){
let translate = getTranslate(d.source);
console.log(translate[1])
return translate[1]
})
.attr("y2", function(d){
let translate = getTranslate(d.target);
console.log(translate[1])
return translate[1]
})
.attr("d", function(d) {
let source = getTranslate(d.source),
target = getTranslate(d.target),
x1= source[0],
x2= target[0],
y1= source[1],
y2= target[1];
diffX = x2 - x1;
diffY = y2 - y1;
// Length of path from center of source node to center of target node
pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
// x and y distances from center to outside edge of target node
offsetX = (diffX * 10) / pathLength;
offsetY = (diffY * 10) / pathLength;
return "M" + x1 + "," + y1 + "L" + (x2 - offsetX) + "," + (y2 - offsetY);
})
.style("stroke", "black")
.style("fill", "none")
.style("stroke-width", 0.5)
.attr("marker-end", "url(#arrow)");
function getTranslate(datum) {
debugger
var myselect=d3.select('#'+datum)
const string = myselect.attr("transform");
const translate = string.substring(string.indexOf("(")+1, string.indexOf(")")).split(",");
return translate;
}
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)); };
});
transition.selectAll("text")
.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) {
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; });
}
});
}
I append path to graph but I don't get result.
my json format also is here
{"name":"variants","links":[{"source":"i0","target":"i2"},
{"source":"i12","target":"i13"}],"id":14,"children":[{"name":"19","id":15,"children":
[{"name":"aaaacxi7gjuvmascvqjaabaadm","size":25,"from":1930,"to":2006,"id":'i15'},
{"name":"undefined","size":25,"from":1910,"to":2008,"id":"i1"},
{"name":"aaaacxi7gjuvmascvqjaabaadi","size":25,"from":1955,"to":1965,"id":"i14"},
{"name":"aaaacxi7gjuvmascvqjaabaade","size":25,"from":1980,"to":2015,"id":"i0"},
{"name":"aaaacxi7gjuvmascvqjaabaac4","size":25,"from":1950,"to":2070,"id":"i2"},
{"name":"aaaacxi7gjuvmascvqjaabaada","size":25,"from":1900,"to":2020,"id":"i3"},
{"name":"aaaacxi7gjuvmascvqjaabaadu","size":25,"from":1980,"to":2008,"id":"i4"},
{"name":"aaaacxi7gjuvmascvqjaabaadq","size":25,"from":1990,"to":2000,"id":"i5"}]}
,{"name":"22","id":"i16","children":[{"name":"neda","size":25,"from":2000,"to":2020,"id":"i8"},
{"name":"aaaacxi7gka5qascvqjaabaaai","size":25,"from":1940,"to":2000,"id":"i9"},
{"name":"aaaacxi7gka5oascvqjaabaacq","size":55,"from":1960,"to":2010,"id":"i10"},
{"name":"aaaacxi7gka5qascvqjaabaaay","size":55,"from":1940,"to":2000,"id":"i11"},
{"name":"aaaacxi7gj4eqascvqjaabaab4","size":55,"from":1980,"to":2020,"id":"i12"},
{"name":"aaaacxi7gka5oascvqjaabaacm","size":55,"from":1940,"to":2000,"id":"i13"}]}
]}
my code add path in svg element but I think they are not in correct position,
and also this post is nearly to my purpose
Thank you very much for helping me.
Related
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>
Horizontally Flipping Text Halfway Around a Circle with D3.js
I am making a Sunburst chart and am almost complete, but I want the text to flip half-way around the circle to make it easier to read. Using help from another question here on stackoverflow I have been able to get the text to flip, but it drops down a level. I have tried modifying the computeTextRotation function to account for this, but to no prevail. I am curious if anyone is able to help me solve this problem. Here is a picture of the chart: Here is the computeTextRotation functions code: function computeTextRotation(d) { var rotation = (d.x + d.dx / 2) * 180 / Math.PI - 90; return { global: rotation, correction: rotation > 90 ? 180 : 0 }; } ... .attr("transform", function(d) { var r = computeTextRotation(d); return "rotate(" + r.global + ")" + "translate(" + radius / 3. * d.depth + ")" + "rotate(" + -r.correction + ")"; } ) Here is the entire JavaScript code: var margin = {top: 500, right: 500, bottom: 500, left: 500}, radius = Math.min(margin.top, margin.right, margin.bottom, margin.left) - 150; function filter_min_arc_size_text(d, i) {return (d.dx*d.depth*radius/1)>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", margin.left + margin.right) .attr("height", margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 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 angle=(d.x +d.dx/2)*180/Math.PI - 90 return angle; } 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("data/davis-aroma-wheel.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 < 3 ? 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");
Your corrective factor rotates the text 180 degrees, this is only half of what you need: By rotating 180 degrees, you get text that is right way up, but now that moves in the opposite direction because the direction of the text is also rotated. For the second half of the circle, you need to specify a text-anchor of "end" so that the text is anchored where it should be. Currently it is anchored where it starts, which is fine for the first half of the circle. When styling the text you'll need to perform a check to see if the text anchor needs to be set to "end" as opposed to "start": text.style("text-anchor",function(d) { return isRotated(d) ? "end" : "start" }) With the check looking something like: function isRotated(d) { var rotation = (d.x + d.dx / 2) * 180 / Math.PI - 90; return rotation > 90 ? true : false } The margins also need to be adjusted: .attr("dx", function(d) {return isRotated(d) ? "-6" : "6"}) //margin
d3.js radial treed downloads differently to svg
I was creating a radial tree and trying to download it to SVG but it has some issues It downloads with those black thick strokes which are not displayed on the webpage. Any idea where the black lines are coming from? var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"), g = svg.append("g").attr("transform", "translate(" + (width / 2 - 15) + "," + (height / 2 + 25) + ")"); var stratify = d3.stratify() .parentId(function(d) { console.log(d.id.substring(0, d.id.lastIndexOf("."))); return d.id.substring(0, d.id.lastIndexOf(".")); }); var tree = d3.cluster() tree.size([360, 360]) tree.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; }); d3.csv("flare.csv", function(error, data) { if (error) throw error; var root = tree(stratify(data) .sort(function(a, b) { return (a.height - b.height + 100) || a.id.localeCompare(b.id); })); var link = g.selectAll(".link") .data(root.descendants().slice(1)) .enter().append("path") .attr("class", "link") .attr("d", function(d) { return "M" + project(d.x, d.y) + "C" + project(d.x, (d.y + d.parent.y) / 2) + " " + project(d.parent.x, (d.y + d.parent.y) / 2) + " " + project(d.parent.x, d.parent.y); }); link.attr('stroke', function(d) { if (d.id.startsWith("Mother.Biological")){ return "#386eff"; } if (d.id.startsWith("Mother.Env")){ return "#45cbf2"; } else return '#70f2ad'; }); var node = g.selectAll(".node") .data(root.descendants()) .enter().append("g") .attr("class", function(d) { return "node" + (d.children ? " node-- internal" : " node--leaf"); }) .attr("transform", function(d) { return "translate(" + project(d.x, d.y) + ")"; }); node.append("circle") .attr("r", function(d) { console.log(d.value); if (d.id == "Mother") return 4.4; else return 2.4; } ) .style('fill', function(d) { if (d.id.startsWith("Mother.Biological")){ return "#386eff"; } if (d.id.startsWith("Mother.Env")){ return "#45cbf2"; } if (d.id.startsWith("Mother.Biological")){ return "#386eff"; } if (d.id.startsWith("Mother.Form")){ return '#70f2ad'; } d.color = 'red'; return d.color}); node.append("text") .attr("dy", ".31em") .attr("x", function(d) { return d.x < 180 === !d.children ? 6 : -6; }) .style("text-anchor", function(d) { return d.x < 180 === !d.children ? "start" : "end"; }) .attr("transform", function(d) { return "rotate(" + (d.x < 180 ? d.x - 90 : d.x + 90) + ")"; }) .text(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1); }); }); function project(x, y) { var angle = (x - 90) / 180 * Math.PI, radius = y; return [radius * Math.cos(angle), radius * Math.sin(angle)]; } d3.select("#download") .on("mouseover", writeDownloadLink); function writeDownloadLink(){ var html = d3.select("svg") .attr("title", "svg_title") .attr("version", 1.1) .attr("xmlns", "http://www.w3.org/2000/svg") .node().parentNode.innerHTML; d3.select(this) .attr("href-lang", "image/svg+xml") .attr("href", "data:image/svg+xml;base64,\n" + btoa(html)) .on("mousedown", function(){ if(event.button != 2){ d3.select(this) .attr("href", null) .html("Use right click"); } }) .on("mouseout", function(){ d3.select(this) .html("Download"); }); }; It looks ok in any browser but when I download it and convert it to SVG or EFM it comes back with those lines.
Page that you provided contains style tag. I'm not sure how exactly do you export SVG, but some css rules seems to be lost along the way. In similar case I managed to get current css properties and put them into style property: d3.selectAll('...').each(function() { var that = d3.select(this); ['stroke', 'stroke-width', 'opacity', 'fill', ...].forEach(function(property) { that.style(property, that.style(property)) }); }); Hope this will help you.
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.
Zoomable Circle Packing with Automatic Text Sizing in D3.js
I'm trying to merge two of Mike's examples: Zoomable Circle Packing + Automatic Text Sizing. It works when initially displayed at the top-level. However, if you zoom in to the next level, the fonts are not sized correctly. I'm not sure if I need to modify the transform, or modify the part which calculates the font size. Here's my codepen: http://codepen.io/anon/pen/GJWqrL var circleFill = function(d) { if (d['color']) { return d.color; } else { return d.children ? color(d.depth) : '#FFF'; } } var calculateTextFontSize = function(d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 11) + "px"; } var margin = 20, diameter = 960; var color = d3.scale.linear() .domain([-1, 18]) .range(["hsl(0,0%,100%)", "hsl(228,30%,40%)"]) .interpolate(d3.interpolateHcl); var pack = d3.layout.pack() .padding(2) .size([diameter - margin, diameter - margin]) .value(function(d) { return d.size; }) var svg = d3.select("body").append("svg") .attr("width", window.innerWidth) .attr("height", window.innerHeight) .append("g") .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")"); var focus = root, nodes = pack.nodes(root), view; var circle = svg.selectAll("circle") .data(nodes) .enter().append("circle") .attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; }) .style("fill", circleFill) .on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); }); circle.append("svg:title") .text(function(d) { return d.name; }) var text = svg.selectAll("text") .data(nodes) .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 ? null : "none"; }) .text(function(d) { return d.name; }) .style("font-size", calculateTextFontSize) .attr("dy", ".35em"); var node = svg.selectAll("circle,text"); d3.select("body") .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)); }; }); transition.selectAll("text") .filter(function(d) { return d.parent === focus || this.style.display === "inline"; }) .style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; }) .each("start", function(d) { if (d.parent === focus) this.style.display = "inline"; }) .each("end", function(d) { if (d.parent !== focus) this.style.display = "none"; }); } function zoomTo(v) { 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; }); } d3.select(self.frameElement).style("height", diameter + "px"); Clicking the largest sub-circle in the "vis" circle illustrates the problem. https://dl.dropboxusercontent.com/u/3040414/vis-circle.png
First give an id to the circle, here I am giving text name as the circle ID so that i can link the text and its circle via text name. var circle = svg.selectAll("circle") .data(nodes) .enter().append("circle") .attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; }) .style("fill", circleFill) .attr("r", function(d) { return d.r; }) .attr("id", function(d) { return d.name;//setting text name as the ID }) .on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); }); On transition complete of zoom(d) function(i.e when you click on a circle and it zooms) add a timeout function which will recalculate the text font size based on the zoom. setTimeout(function() { d3.selectAll("text").filter(function(d) { return d.parent === focus || this.style.display === "inline"; }).style("font-size", calculateTextFontSize);//calculate the font }, 500) Your calculateTextFontSize function will look like this(I am using the real DOM radius to calculate the font size): var calculateTextFontSize = function(d) { var id = d3.select(this).text(); var radius = 0; if (d.fontsize){ //if fontsize is already calculated use that. return d.fontsize; } if (!d.computed ) { //if computed not present get & store the getComputedTextLength() of the text field d.computed = this.getComputedTextLength(); if(d.computed != 0){ //if computed is not 0 then get the visual radius of DOM var r = d3.selectAll("#" + id).attr("r"); //if radius present in DOM use that if (r) { radius = r; } //calculate the font size and store it in object for future d.fontsize = (2 * radius - 8) / d.computed * 24 + "px"; return d.fontsize; } } } Working code here
I also had same problem as you and I tried this one and it works for me. D3.js Auto font-sizing based on nodes individual radius/diameter