Skip to content

Instantly share code, notes, and snippets.

@curran
Last active March 27, 2017 13:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curran/2aafab81bb2029e1f4f24d258b790ce4 to your computer and use it in GitHub Desktop.
Save curran/2aafab81bb2029e1f4f24d258b790ce4 to your computer and use it in GitHub Desktop.
Fractal Pie Chart
license: mit
border: no

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!

web counter
<!DOCTYPE html>
<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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment