|
<!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 outerRadius = 100; |
|
const scalingFactor = 1.188096; |
|
const stemWeight = 0.25; |
|
const stemLength = 74; |
|
const epsilon = 0.001; |
|
const arc = d3.arc() |
|
.innerRadius(0) |
|
.outerRadius(outerRadius); |
|
|
|
const slice = d3.component("path") |
|
.render((selection, d) => { |
|
selection |
|
.attr("d", arc(d)) |
|
.attr("fill", "#efefef") |
|
.attr("stroke", "#707070") |
|
.attr("stroke-width", 6) |
|
.attr("stroke-linejoin", "round"); |
|
}); |
|
|
|
const connector = d3.component("line") |
|
.render((selection, {angle, sliceFraction, stem}) => { |
|
const degrees = angle / Math.PI * 180 + 180; |
|
selection |
|
.attr("x1", -outerRadius) |
|
.attr("y1", 0) |
|
.attr("x2", -outerRadius - stem) |
|
.attr("y2", 0) |
|
.attr("stroke", "#727272") |
|
.attr("stroke-width", 4) |
|
.attr("transform", `rotate(${degrees})`); |
|
}); |
|
|
|
const fractal = d3.component("g") |
|
.render((selection, d) => { |
|
const { |
|
angle, |
|
children, |
|
sliceFraction, |
|
stem |
|
} = d; |
|
|
|
const slices = pie(children); |
|
const degrees = angle / Math.PI * 180; |
|
|
|
const scale = sliceFraction * scalingFactor; |
|
const fractals = slices |
|
.filter(d => d.data.children) |
|
.map(d => { |
|
const sliceFraction = (d.endAngle - d.startAngle) / (2*Math.PI); |
|
const scale = sliceFraction * scalingFactor; |
|
const stem = stemWeight * stemLength*scale + (1-stemWeight) * stemLength; |
|
return Object.assign({}, d.data, { |
|
angle: (d.startAngle + d.endAngle) / 2 - Math.PI/2, |
|
sliceFraction: sliceFraction, |
|
stem: stemWeight * stemLength*scale + (1-stemWeight) * stemLength |
|
}); |
|
}); |
|
|
|
selection |
|
.call(slice, slices) |
|
.call(connector, fractals) |
|
.call(fractal, fractals); |
|
d3.timer((time) => { |
|
let t = time/200; |
|
t = 1/(1+Math.pow(2, -t/3+10)) * t; // Sigmoid-ish |
|
selection.attr("transform", ` |
|
rotate(${degrees}) |
|
translate(${outerRadius + outerRadius*scale + stem}) |
|
rotate(${-degrees + t}) |
|
scale(${scale})`) |
|
}) |
|
}); |
|
|
|
const generateData = (value) => { |
|
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); |
|
|
|
const rootFractal = d3.select("svg").append("g") |
|
.call(fractal, data, { |
|
sliceFraction: 1 |
|
}); |
|
|
|
d3.timer((time) => { |
|
let t = time/200; |
|
t = 1/(1+Math.pow(2, -t/3+10)) * t; // Sigmoid-ish |
|
rootFractal.attr("transform", `translate(313, 245) rotate(${t})`); |
|
}); |
|
|
|
</script> |
|
</body> |