Finally after having sketched out this visualization idea in notebooks for years, it has come into reality. This example highlights the utility of d3-component for recursive visualization components.
The idea here is to extend the notion of a pie chart to represent hierarchical data. Each slice gets expanded into a "sub-pie" whose area is equal to the area of the slice. The sub-pie slices get broken out recursively.
Have some fun and play with it on Blockbuilder!
forked from curran's block: Fractal Pie Chart
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/d3@4"></script>
<script src="https://unpkg.com/d3-component@3"></script>
</head>
<body style="margin: 0">
<svg width="960" height="500"></svg>
<script>
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
const pie = d3.pie().value(d => d.value);
const arc = d3.arc().innerRadius(0);
const slice = d3.component("path")
.render((selection, d) => {
selection
.attr("d", arc(d))
.attr("fill", "#f4f4f4")
.attr("stroke", "#707070")
.attr("stroke-width", 1)
.attr("stroke-linejoin", "round");
});
const connector = d3.component("line")
.render((selection, {parentRadius, stemDistance, radius}) => {
if(parentRadius !== 0){
selection
.attr("x1", -radius)
.attr("y1", 0)
.attr("x2", -(stemDistance - parentRadius))
.attr("y2", 0)
.attr("stroke", "#a0a0a0");
}
});
const fractal = d3.component("g")
.render((selection, d) => {
const {
radius,
parentRadius,
angle,
children,
stemSize,
stemWeight,
rotateChildren
} = d;
const slices = pie(children);
arc.outerRadius(radius);
//const translate = (parentRadius + radius)/2 * stem;
//const translate = radius * stem;
const base = parentRadius + radius;
const stemDistance = base + (
(parentRadius * stemWeight + radius * (1 - stemWeight)) / 2
) * stemSize;
const degrees = angle / Math.PI * 180;
const fractals = slices
.filter(d => d.data.children)
.map(d => {
const parentRadius = radius;
const parentSliceFraction = (d.endAngle - d.startAngle) / (2*Math.PI);
const parentTotalArea = Math.PI * radius * radius;
const parentSliceArea = parentTotalArea * parentSliceFraction;
const childRadius = Math.sqrt(parentSliceArea / Math.PI);
return Object.assign({}, d.data, {
radius: childRadius,
parentRadius: radius,
angle: (d.startAngle + d.endAngle) / 2 - Math.PI/2,
stemSize,
stemWeight,
rotateChildren
});
});
selection
.attr("transform", `rotate(${degrees}) translate(${stemDistance})`)
.call(connector, d, {stemDistance, rotateChildren, degrees})
.call(slice, slices)
.call(fractal, fractals);
});
const generateData = (value) => {
const epsilon = 0.02;
const childRatios = [1/2, 1/4, 1/8, 1/8 * 2/3, 1/8 * 1/3];
const d = { value };
if(value > epsilon){
d.children = childRatios
.map(ratio => ratio * value)
.map(generateData);
}
return d;
}
const data = generateData(1);
d3.select("svg").append("g")
.attr("transform", "translate(313, 224)")
.call(fractal, data, {
radius: 53,
parentRadius: 0,
angle: 0,
stemSize: 3.2,
// 1 = weighted by parent radius,
// 0 = weighted by child radius
stemWeight: 1.007155150848
});
</script>
</body>
https://unpkg.com/d3@4
https://unpkg.com/d3-component@3