Skip to content

Instantly share code, notes, and snippets.

@freaktiful
Created February 11, 2021 16:55
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 freaktiful/5aa20944b783a04d4d1ff3050a74138f to your computer and use it in GitHub Desktop.
Save freaktiful/5aa20944b783a04d4d1ff3050a74138f to your computer and use it in GitHub Desktop.
Radial stacked bar chart with donut chart around it (tooltips and transitions)
<!DOCTYPE html>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/bumbeishvili/d3-tip-for-v6@4/d3-tip.min.css">
<meta charset="utf-8">
<svg width="500" height="500" font-family="sans-serif" font-size="10"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://unpkg.com/d3-v6-tip@1.0.6/build/d3-v6-tip.js"></script>
<script src="https://cdn.jsdelivr.net/npm/underscore@1.12.0/underscore-min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
innerRadius = 30,
outerRadius = Math.min(width, height) / 4,
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var xScaleOffset = 0;
var x = d3.scaleBand()
.range([xScaleOffset, 2 * Math.PI + xScaleOffset])
.align(xScaleOffset);
var y = d3.scaleLinear()
.domain([0, 1])
.range([innerRadius, outerRadius]);
var z = d3.scaleOrdinal()
.domain(['completion', 'potential'])
.range(['#000078', 'rgba(0,0,120, 0.2)'])
var c = d3.scaleOrdinal()
.domain(["transversal", "specific", "general"])
.range(["#00ecff", "#f7e98b", "#00ffd0"])
var l = d3.scaleOrdinal()
.domain(["transversal", "specific", "general"])
.range(["C. TRANSVERSALES", "C. ESPECÍFICAS", "C. GENERALES"])
var zClasses = ['completion', 'potential'];
var arc = d3.arc()
.outerRadius(outerRadius + 15)
.innerRadius(outerRadius + 1);
var pie = d3.pie()
.sort(null)
.value((d) => d.number);
d3.json("progress.json").then(data => {
x.domain(data.map((d) => d.label))
//adjust the donut chart with the stacked bar chart.
pie.startAngle(x(data[0].label)).endAngle(x(data[0].label) + 2 * Math.PI)
var tip = d3.tip().attr('class', 'd3-tip')
.html((d) => {
var completion = Math.round(d.data.completion * 100)
var potential = Math.round((d.data.potential + d.data.completion) * 100)
if (this.selectedSemester) {
return completion
} else {
return completion + "/" + potential + "%"
}
})
this.g.call(tip)
var arc = d3.arc()
.innerRadius((d) => y(d[0]))
.outerRadius((d) => y(d[1]))
.startAngle((d) => x(d.data.label))
.endAngle((d) => x(d.data.label) + x.bandwidth())
.padAngle(0.01)
.padRadius(innerRadius)
var graf = g.append('g')
.selectAll("g")
.data(d3.stack().keys(['completion', 'potential'])(data))
.enter().append("g")
.attr("fill", d => z(d.key))
.selectAll("path")
.data((d) => d)
.enter().append("path")
.attr("d", arc)
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.transition()
.ease(d3.easeBounce)
.duration(1500)
//.delay((d, i) => i * 100)
.attrTween("d", function (d, index) {
var i = d3.interpolate(d[0], d[1]);
return function(t) { d[1] = i(t); return arc(d, index)};
});
//radial lines xAxis
var lines = g.selectAll("line")
.data(data.map((d) => d.label))
.enter().append("line")
.attr("y1", -80)
.attr("y2", -outerRadius)
.style("stroke", "rgba(0,0,120, 0.2)")
.attr("transform", (d, i) => {
var value = (i * 360 / data.length) + (xScaleOffset * (180 / Math.PI))
return "rotate(" + value + ")";
})
//yAxis
var yAxis = g.append("g")
.attr("text-anchor", "middle")
.call(g => g.selectAll("g")
.data(y.ticks(4))
.join("g")
.attr("fill", "none")
.call(g => g.append("circle")
.attr("stroke", "rgba(0,0,120, 0.2)")
.attr("r", y))
.call(g => g.append("text")
.attr("y", d => y(d) - 7)
.attr("dy", "0.35em")
.style("font-size", "11px")
.text(y.tickFormat(4, "%"))
.clone(true)
.attr("fill", "#000")
.attr("stroke", "none")))
// Labels for xAxis
xLabels = g.append("g")
.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("text-anchor", function(d) { return (x(d.label) + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "end" : "start"; })
.attr("transform", function(d) {
var lines = wordwrap(d.label.toUpperCase(), 15).length + 1;
var offset = ((x(d.label) + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI) ? 10 : -10;
if (lines > 1) offset = offset * lines/2
return "rotate(" + ((x(d.label) + x.bandwidth() / 2) * 180 / Math.PI - 90) + ")"+"translate(" + (y(1) + 20) + ", " + offset + ")"; })
.append("text")
.attr("transform", function(d) { return (x(d.label) + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)"; })
.style("font-size", "8px")
.attr("font-weight", "640")
.attr("id", (d,i) => "text_"+i)
.style("fill", "#000078")
.attr("alignment-baseline", "middle")
.each((d, i) => {
var lines = wordwrap(d.label.toUpperCase(), 15)
for (var j = 0; j < lines.length; j++) {
d3.select("#text_"+i).append("tspan")
.text(lines[j])
.attr("x", 0)
.attr("dy", 10)
}
var completion = Math.round(d.completion * 100)
var potential = Math.round((d.completion + d.potential) * 100)
d3.select("#text_"+i).append("tspan")
.text(completion + "/" + potential + "%")
.attr("x", 0)
.attr("dy", 12)
})
// Legend
var legend = g.append("text")
.attr("font-size", "7px")
.attr("class", "legend-text")
.attr("font-weight", "640")
.attr("width", 80)
.attr("transform", "translate(0, -25)")
.style("fill", "#000078")
var title = "Disseny,\nidentitat visual\ni construcció de\nmarca i a més\nlorem ip..."
// Add a <tspan class="text"> for every text line.
legend.selectAll("tspan.text")
.data(title.split("\n"))
.enter()
.append("tspan")
.attr("class", "text")
.text(d => d)
.attr("text-anchor", "middle")
.attr("x", 0)
.attr("dy", 8);
});
d3.json("progress.json").then(data => {
var donutData = calculateDonutData(data)
console.log(donutData)
// donut chart outside radial the stacked bar chart
var g2 = g.selectAll(".arc")
.data(pie(donutData))
.enter().append("g")
.attr("class", "arc");
var path = g2.append("path")
.attr("d", arc)
.attr("id", function(d,i) { return "genusArc_"+i; })
.style("fill", (d) => c(d.data.genus));
path.transition().duration(1000).attrTween('d', function(d) {
var i = d3.interpolate(d.startAngle + 0.1, d.endAngle);
return function(t) {
d.endAngle = i(t);
return arc(d);
}
});
// Text inside the donut chart
var text = g2.selectAll(".genusText")
.data(donutData)
.enter().append("text")
.attr("class", "genusText")
.attr("font-weight", "640")
.attr("font-size", "12px")
.attr("x",20)
.attr("dy", 12)
.attr("text-anchor", "middle")
.append("textPath")
.attr("xlink:href",function(d,i){return "#genusArc_"+i;})
.text((d) => l(d.genus))
.attr("startOffset", (d, i) => {
var length = path.nodes()[i].getTotalLength();
return (25-(50 *80)/length+(50 *20)/length) + "%";
});
})
function wordwrap(text, max) {
var regex = new RegExp(".{0,"+max+"}(?:\\s|$)","g");
var lines = [];
var line;
while ((line = regex.exec(text))!="") {
lines.push(line);}
return lines
}
function calculateDonutData (data) {
var res = []
var aux = data
var numGenerals = 0
var numSpecific = 0
var numTransversals = 0
for (var i = 0; i < aux.length; i++) {
if (aux[i].genus === 'transversal') numTransversals += 1
if (aux[i].genus === 'specific') numSpecific += 1
if (aux[i].genus === 'general') numGenerals += 1
}
if (numTransversals > 0) res.push({genus: 'transversal', number: numTransversals})
if (numSpecific > 0) res.push({genus: 'specific', number: numSpecific})
if (numGenerals > 0) res.push({genus: 'general', number: numGenerals})
return res
}
</script>
[
{"label": "Liderazgo en la gestión de personas y equipos de trabajo", "completion": 0.444, "potential": 0.35, "genus": "transversal"},
{"label": "Resolució de situacions complexes", "completion": 0.17, "potential": 0.35, "genus": "transversal"},
{"label": "Creativitat", "completion": 0.414, "potential": 0.35, "genus": "transversal"},
{"label": "Planificació i organització", "completion": 0.29, "potential": 0.35, "genus": "transversal"},
{"label": "Aprendre a aprendre", "completion": 0.4, "potential": 0.35, "genus": "specific"},
{"label": "Ús i aplicació de les tic", "completion": 0, "potential": 0.35, "genus": "specific"},
{"label": "Anàlisi, síntesi i pensament crític", "completion": 0.4667, "potential": 0.35, "genus": "general"}
]
svg {
margin-left: auto;
margin-right: auto;
display: block;
}
.legend-text {
color: #000078;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment