D3 Partition - Show next level when clicked - javascript

I have a D3 partition which shows all the levels for the entire partition.
I would like to only show the first level when the chart loads and then show subsequent levels on click.
For example in this Tree the next level is shown on click of a node: D3Tree
Here is the code for my partition: Plunker link
$(document).ready(function(){
var width = 600,
height = 400,
radius = (Math.min(width, height) / 2) - 10;
var formatNumber = d3.format(",d");
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category20c();
var partition = d3.layout.partition()
.value(function(d) {
if(d.depth == 2)
console.log(d.depth, d);
return 1; // d.size;
});
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
d3.json("flare.json", function(error, root) {
if (error) throw error;
svg.selectAll("path")
.data(partition.nodes(root))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", click)
.append("title")
.text(function(d) { return d.name + "\n" + formatNumber(d.value); });
});
function click(d) {
svg.transition()
.duration(750)
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); };
})
.selectAll("path")
.attrTween("d", function(d) { return function() { return arc(d); }; });
}
d3.select(self.frameElement).style("height", height + "px");
});
I would like to do something like toggle on click:
// Toggle children.
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
Where the children get set and unset, then redrawn

To do something like the tree layout would be a little tough doing it with the help of display is a a cake walk.
When the path are drawn for the first time make all the nodes whose depth > 1 disappear using display:none:
svg.selectAll("path")
.data(partition.nodes(root))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.style("display", function(d) {
if (d.depth > 1) {
return "none";//nodes whose depth is more than 1 make its vanish
} else {
return "";
}
})
Now on node click make all nodes reappear except when root node is clicked.
.style("display", function(d1) {
if (d.depth == 0 && d1.depth > 1) {
return "none"//root node clicked so show only 2 depths.
} else {
return "";
}
})
Working code here

Related

Replicate pure JS version of D3 Zoomable Sunburst (observable)

I have been trying to replicate https://beta.observablehq.com/#mbostock/d3-zoomable-sunburst this in pure JS to use it in one of my projects. I am using PHP and Ajax to load Dynamic Data in JavaScript. I think the code in the Observable link is not in pure JS but rather Node or something else.
I am a newbie in Scripting, hence it is becoming very difficult for me to understand the written code. I do know that a pure JS will need the data (flare.json) in a specific format, which will generate the expected output. I can control the JSON structure from the backend, but I am unable to generate an output like the link.
I have followed multiple examples online:
https://bl.ocks.org/mbostock/4348373
And the same in d3 version 4 (which is very similar to v5, used in the Observable example):
https://bl.ocks.org/maybelinot/5552606564ef37b5de7e47ed2b7dc099
I have been trying to convert the Observable Zoomable Sunburst into JS functions, but I am unable to make it work. I have the exact same flare.json file and tried to recreate exact functions as therein Observable one. But it still is not working.
I am attaching my work. How can I get it working?
Sample Work
I have also tried to seek help on the Google Groups for d3-js, but I haven't got any help from there too.
The closest possible output which I have achieved till now is mentioned below:
var margin = {top: 288, right: 416, bottom: 288, left: 416},
radius = Math.min(margin.top, margin.right, margin.bottom, margin.left) - 5;
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 ; })
.padAngle(.01)
.padRadius(radius / 3)
.innerRadius(function(d) { return radius / 3 * d.depth; })
.outerRadius(function(d) { return radius / 3 * (d.depth + 1) - 1; });
// d3.json("https://api.myjson.com/bins/byw4q", function(error, root) {
d3.json("https://gist.githubusercontent.com/mbostock/4348373/raw/85f18ac90409caa5529b32156aa6e71cf985263f/flare.json", function(error, root) {
if (error) throw 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 path = svg.selectAll("path")
.data(partition.nodes(root).slice(1))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return d.fill; })
.each(function(d) { this._current = updateArc(d); })
.on("click", zoomIn);
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]);
path = path.data(partition.nodes(root).slice(1), 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)
.each(function(d) { this._current = enterArc(d); });
path.transition()
.style("fill-opacity", 1)
.attrTween("d", function(d) { return arcTween.call(this, updateArc(d)); });
});
}
});
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");
<!DOCTYPE html>
<meta charset="utf-8">
<style>
circle,
path {
cursor: pointer;
}
circle {
fill: none;
pointer-events: all;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
</body>
The code written is pure javascript tough, whatever the data you are getting from Ajax the same endpoint you just need to pass here,
The example I am running here means the same should work in your project as well, Instead of calling the Ajax you can pass your Json in this line
d3.json("https://gist.githubusercontent.com/mbostock/4348373/raw/85f18ac90409caa5529b32156aa6e71cf985263f/flare.json", function(error, root)
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: #fff;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var width = 960,
height = 700,
radius = (Math.min(width, height) / 2) - 10;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear()
.range([0, 2 * Math.PI]);
var y = d3.scaleSqrt()
.range([0, radius]);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var partition = d3.partition();
var arc = d3.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x0)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x1)));
})
.innerRadius(function(d) {
return Math.max(0, y(d.y0));
})
.outerRadius(function(d) {
return Math.max(0, y(d.y1));
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
d3.json("https://gist.githubusercontent.com/mbostock/4348373/raw/85f18ac90409caa5529b32156aa6e71cf985263f/flare.json", function(error, root) {
if (error) throw error;
root = d3.hierarchy(root);
root.sum(function(d) {
return d.size;
});
svg.selectAll("path")
.data(partition(root).descendants())
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).data.name);
})
.on("click", click)
.append("title")
.text(function(d) {
return d.data.name + "\n" + formatNumber(d.value);
});
function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}
function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}
svg.selectAll("text")
.attr("dy", "0.35em")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.data(root.descendants().slice(1))
.enter().append("text")
.text(d => d.data.name);
});
function click(d) {
svg.transition()
.duration(750)
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]),
yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]);
return function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
};
})
.selectAll("path")
.attrTween("d", function(d) {
return function() {
return arc(d);
};
});
}
d3.select(self.frameElement).style("height", height + "px");
</script>

How can I alter the zooming capability on a d3 sunburst chart?

I am trying to alter the traditional zooming feature on a sunburst chart. Traditionally when you click on a partition, that partition grows to cover 100% of the base layer while all other partitions on the same layer disappear. The children of the selected partition all grow to fill the newly created space.
My current code does just what I stated above. I would like to alter my code to allow for the selected partition to only take up 75% of the base layer. The children elements will grow to cover this new space but the remaining 25% will still contain all other non-selected partitions.
I have tried altering the 't' value that is returned from d3.interpolate() but I have had unpredictable results.
I hope my description is clear.
Does anyone have any thoughts on this?
<script>
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.category20c();
function percent(d) {
var percentage = (d.value / 956129) * 100;
return percentage.toFixed(2);
}
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>" + d.name + "</strong> <span style='color:red'>" + percent(d) + "%</span>";
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
svg.call(tip);
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)) })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)) });
d3.json("flare.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
// .attr("stroke", 'black')
// .style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.style("fill", function(d, i) {
return color(i);
})
.on("click", click)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
var text = g.append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
if (percent(d) > 1.35) {
return d.name;
}
})
.attr('font-size', function(d) {
if (d.value < 100000) {
return '10px'
} else {
return '20px';
}
})
.on("click", click)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
function click(d) {
console.log(d)
// fade out all text elements
text.transition().attr("opacity", 0);
path
.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
}
});
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
console.log(d.name, x.domain())
console.log(d.name, y.domain())
console.log(d.name, y.range())
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) {
console.log(t)
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
I found the solution here: https://bl.ocks.org/mbostock/1306365. This example manages the zoom without getting rid of the sibling nodes.

D3.js Sunburst Incorrect Arc Scales

I have a Zoomable Sunburst diagram exhibiting strange problems with arc sizing.
http://colinwhite.net/Sunburst/
I would expect the size the arcs to be proportional to number of children (shown in the tool tip). Yet, I have parent arcs with few children, that are proportionally larger than their peers with far more children. Arc size is not reflective of number of children. I've tried various other d3.scales which haven't helped. What am I doing wrong?
My code is largely boiler plate from the D3 examples.
var width = 760, height = 700,
radius = Math.min(width, height) / 2.25,
color = d3.scale.category20c();
var x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.sqrt().range([0, radius]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height * .52 + ")");
var partition = d3.layout.partition()
.value(function(d) { return 1; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min( 2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min( 2 * Math.PI, x(d.x + d.x))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("opacity", 0);
d3.json("data/getJson.php", function(error, data) {
var treeData = genJSON(data, ['Location', 'Provider', 'Diagnosis', 'Procedure']);
console.log(treeData);
var path = svg.selectAll("path")
.data(partition.nodes(treeData))
.enter().append("svg:path")
.attr("d", arc)
.style("fill-rule", "evenodd")
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", click)
.on("mouseover", function(d) {
tooltip.html(function() {
return (d.children ? d : d.parent).name + " (" + d.value + ")";
});
return tooltip.transition()
.duration(50)
.style("opacity", 0.9);
})
.on("mousemove", function(d) {
return tooltip
.style("top", (d3.event.pageY-10)+"px")
.style("left", (d3.event.pageX+10)+"px");
})
.on("mouseout", function(){return tooltip.style("opacity", 0);});
function click(d) {
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
}
});
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d,i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
The JSON is nested with this genJSON function -http://colinwhite.net/Sunburst/js/treeRemapper.js
Thanks for any help or advice.
Changing the partition call, to include a .sort(null) like this -
var partition = d3.layout.partition()
.sort(null) //<-- was missing this
.value(function(d) { return 1; });
Seems to have resolved the strange arc scale problems.

Adding elements to a D3 circle pack nodes

I am trying to make a zoomable circle packing chart. I'd like each child circle to contain a smaller chart which would always have the same structure (i.e. 4 columns, only the heights of the bars would change).
I have tried adding a simple rect to my chart so far but the rects are not added in the circle and are statics:
JS:
var margin = 20,
diameter = 400;
var color = d3.scale.linear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "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(".container").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("flare.json", function(error, root) {
if (error) return console.error(error);
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", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
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; });
// Adding Rect to each child circle
var bar = svg.selectAll(".bar")
.data(nodes)
.enter().append("rect")
.attr("class", "bar")
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? null : "none"; })
.attr("width", 20)
.attr("x", 100)
.attr("height", function(d) { return 40; });
var node = svg.selectAll("circle,text");
d3.select(".container")
.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");
I have also added a plunkr: http://plnkr.co/edit/CfJqUQMISDzed2F71JpT?p=preview
How can I add this rect inside the child circles only?
Many thanks
Yes, zooming feature adds some difficulty.
Here is a little bit dirty example of how this can be achieved: plunkr
The key code is:
// Adding Rects to each child circle
var bar1 = svg.selectAll(".bar1")
.data(nodes)
.enter().append("rect")
.attr("class", "bar1")
.style("fill", "red")
.style("display", function(d) { return (d.depth == 4) ? null : "none"; });
var bar2 = svg.selectAll(".bar2")
.data(nodes)
.enter().append("rect")
.attr("class", "bar2")
.style("fill", "green")
.style("display", function(d) { return (d.depth == 4) ? null : "none"; });
var bar3 = svg.selectAll(".bar3")
.data(nodes)
.enter().append("rect")
.attr("class", "bar3")
.style("fill", "blue")
.style("display", function(d) { return (d.depth == 4) ? null : "none"; });
and
var node = svg.selectAll("circle,text,rect");
and
bar1.attr("y", function(d) { return -d.r/2 * k - d.r/4; })
bar1.attr("x", function(d) { return -d.r/20 * k; })
bar1.attr("width", function(d) { return d.r/10 * k -1; });
bar1.attr("height", function(d) { return d.r/2 * k; });
bar2.attr("y", function(d) { return -d.r/2 * k - d.r/4; })
bar2.attr("x", function(d) { return -d.r/20 * k + d.r/10 * k; })
bar2.attr("width", function(d) { return d.r/10 * k-1; });
bar2.attr("height", function(d) { return d.r/2 * k; });
bar3.attr("y", function(d) { return -d.r/2 * k - d.r/4; })
bar3.attr("x", function(d) { return -d.r/20 * k - d.r/10 * k; })
bar3.attr("width", function(d) { return d.r/10 * k-1; });
bar3.attr("height", function(d) { return d.r/2 * k; });

How to center root node circle in a d3.layout.pack graph?

I am refactoring the d3.layout.pack graph example here into a reusable module. However, the root node is no longer in the center - I couldn't find the culprit.
Here is a jsfiddle demo of the app:
d3.visio.clustering = function module(){
var width = 700,//default width
height= 500;//default height
var svg;
//takes a DOM container or an array of DOM containers
function exports(_selection){
console.log(_selection);
_selection.each(function(_data){
var r = Math.min(width, height) - 10;
var x = d3.scale.linear().range([0, r]),
y = d3.scale.linear().range([0, r]),
node,
root;
var pack = d3.layout.pack()
.size([r, r])
.value(function(d) { return d.size; });
//if the svg element is already defined, don't define it again
if(!svg){
svg = d3.select(this).append("svg:svg");
}
svg.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("transform", "translate(" + (width - r) / 2 + "," + (height - r) / 2 + ")");
node = root = _data;
var nodes = pack.nodes(root);
svg.selectAll("circle")
.data(nodes)
.enter().append("svg:circle")
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; })
.on("click", function(d) { return zoom(node == d ? root : d); });
svg.selectAll("text")
.data(nodes)
.enter().append("svg:text")
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("opacity", function(d) { return d.r > 20 ? 1 : 0; })
.text(function(d) { return d.name; });
d3.select(window).on("click", function() { zoom(root); });
function zoom(d, i) {
var k = r / d.r / 2;
x.domain([d.x - d.r, d.x + d.r]);
y.domain([d.y - d.r, d.y + d.r]);
var t = svg.transition()
.duration(d3.event.altKey ? 7500 : 750);
t.selectAll("circle")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", function(d) { return k * d.r; });
t.selectAll("text")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.style("opacity", function(d) { return k * d.r > 20 ? 1 : 0; });
node = d;
d3.event.stopPropagation();
}
});
}
//getter and setter for property width
exports.width = function(_x){
if(!arguments.length) return width;
width = parseInt(_x);
return this;//allow method chaining
}
//getter and setter for property height
exports.height = function(_x){
if(!arguments.length) return height;
height = parseInt(_x);
return this;//allow method chaining
}
//getter and setter for property radius = r
exports.radius = function(_x){
if(!arguments.length) return r;
r = parseInt(_x);
return this//allow method chaining
}
return exports;
};
var cluster = d3.visio.clustering()
.width(document.getElementById("vis").offsetWidth)
.height(document.getElementById("vis").offsetHeight);
d3.select('#vis')
.datum(data)
.call(cluster);
Could you please check what's wrong?
The problem in your code is that you're appending a g element that is correctly translated, but you're appending everything else to the top level svg, which is not translated. To make it work, you can simply reassign svg with the top level g, i.e.
svg = svg.append("g").attr("transform", "translate(" + ... + ")");
Complete jsfiddle here.

Categories

Resources