Published
Edited
Nov 6, 2021
11 forks
65 stars
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("font-size", "10pt")
.attr("cursor", "default")
.attr("viewBox", [0, 0, width, height]);
document.body.append(svg.node());
var tspace = 0;
svg.append("g").call(g => tspace = drawStartLines(g));
svg.append("g").call(g => drawRadialBars(g, tspace));

svg.node().remove();
return svg.node();
}
Insert cell
drawStartLines = g => {
const starts = g.selectAll(".start")
.data(chartData)
.join("g")
.attr("opacity", 1)
.attr("fill", d => color(d.territory))
.attr("transform", (d, i) => `translate(${bar.width},${innerRadius(numOfBars - 1 - i) - minRadius + margin.top})`)
.call(g => g.append("circle").attr("cy", bar.width / 2).attr("r", bar.width / 2))
.call(g => g.append("rect").attr("width", start.left).attr("height", bar.width))
.call(title)
.on("mouseover", highlight)
.on("mouseout", restore);
const texts = starts.append("text")
.attr("class", "start")
.attr("font-weight", "bold")
.attr("alignment-baseline", "hanging")
.attr("dx", start.left + start.padding)
.attr("dy", 4)
.text(d => `${d.territory} ${d3.format("$.2s")(d.values[year])}`);
var widths = texts.nodes().map(d => d.getComputedTextLength());
const ext = d3.extent(widths);
const min = ext[0], max = ext[1];
starts.append("rect")
.attr("width", (d, i) => start.right + (max - widths[i]))
.attr("height", bar.width)
.attr("transform", (d, i) => `translate(${widths[i] + start.left + start.padding * 2},0)`)
const startLength = start.left + start.right + start.padding * 2 + max + bar.width + (triangle.num ? 3 : 0);
starts.append("g")
.selectAll("polygon")
.data(seq(triangle.num))
.join("polygon")
.attr("points", `0,2 ${triangle.width},${triangle.width} 0,${triangle.height - 2}`)
.attr("transform", (d, i) => `translate(${startLength - bar.width + i * (triangle.width + triangle.padding)},0)`);
const y = d => innerRadius(d) - bar.padding / 2 - minRadius + margin.top,
tspace = startLength + (triangle.width + triangle.padding) * triangle.num;
g.selectAll("line")
.data(seq(numOfBars + 1))
.join("line")
.attr("stroke", "#ccc")
.attr("x1", bar.width).attr("y1", y)
.attr("x2", tspace).attr("y2", y);
parts.start = starts;
return tspace;
}
Insert cell
drawRadialBars = (g, tspace) => {
const ticks = x.ticks(15).slice(1, -1);
ticks.push(maxValue * 1.05);
g.attr("transform", `translate(${tspace},${maxRadius + margin.top})`);
const marks = g.append("g")
.selectAll(".tick")
.data(ticks)
.join("g")
.attr("class", "tick")
.attr("transform", d => `rotate(${deg(x(d)) - 90})`)
.call(g => g.append("line").attr("x1", minRadius - bar.padding / 2).attr("x2", maxRadius + bar.padding / 2))
.call(g => g.append("text")
.attr("class", "tick")
.attr("transform", d => `translate(${maxRadius + bar.padding * 1.5},0)`)
.text(x.tickFormat(1, "$.1s")));
const bars = g.selectAll(".bar")
.data(chartData)
.join("g")
.attr("class", "bar")
.attr("opacity", 1)
.attr("fill", d => color(d.territory))
.call(g => g.append("path").attr("d", arc))
.call(g => g.append("circle")
.attr("r", bar.width / 2)
.attr("cx", (d, i) => innerRadius(i) + bar.width / 2)
.attr("transform", (d, i) => `rotate(${deg(x(d.values[year])) - 90})`))
.call(title)
.on("mouseover", highlight)
.on("mouseout", restore);
g.selectAll(".track")
.data(seq(chartData.length + 1))
.join("path")
.attr("class", "track")
.attr("stroke", "#ccc")
.attr("d", axisArc);
parts.bar = bars;
}
Insert cell
parts = ({ start: null, bar: null })
Insert cell
highlight = (e, d) => {
parts.start.transition().duration(500).attr("opacity", a => a === d ? 1 : 0.5);
parts.bar.transition().duration(500).attr("opacity", a => a === d ? 1 : 0.5);
}
Insert cell
restore = () => {
parts.start.transition().duration(500).attr("opacity", 1);
parts.bar.transition().duration(500).attr("opacity", 1);
}
Insert cell
title = g => g.append("title").text(d => `${years[year]} - ${d.territory}\n${d3.format("$,.2f")(d.values[year])}`)
Insert cell
x = d3.scaleLinear()
.domain([0, maxValue * 1.05])
.range([0, 1.5 * Math.PI])
Insert cell
color = d3.scaleOrdinal(d3.schemeTableau10)
.domain(chartData.map(d => d.territory))
Insert cell
seq = (length) => Array.apply(null, {length: length}).map((d, i) => i)
Insert cell
innerRadius = i => minRadius + (bar.width + bar.padding) * i
Insert cell
outerRadius = i => innerRadius(i) + bar.width
Insert cell
deg = a => a * 180 / Math.PI;
Insert cell
arc = (d, i) => d3.arc()
.innerRadius(innerRadius(i))
.outerRadius(outerRadius(i))
.startAngle(0)
.endAngle(x(d.values[year]))()
Insert cell
axisArc = i => d3.arc()
.innerRadius(innerRadius(i) - bar.padding / 2)
.outerRadius(innerRadius(i) - bar.padding / 2)
.startAngle(0)
.endAngle(1.5 * Math.PI)()
Insert cell
data = d3.csvParse(await FileAttachment("sales4yrs.csv").text(), d3.autoType)
Insert cell
chartData = data.map(d => {
return {
territory: d["territory"],
values: data.columns.slice(1).map(y => d[y])
}
})
Insert cell
maxValue = d3.max(chartData.map(d => d.values[year]))
Insert cell
year = options.year
Insert cell
years = data.columns.slice(1)
Insert cell
width = 1024
Insert cell
height = 768
Insert cell
margin = ({top: 30, left: 0, bottom: 0, right: 0})
Insert cell
minRadius = 100
Insert cell
maxRadius = outerRadius(numOfBars - 1)
Insert cell
bar = ({width: 20, padding: 10})
Insert cell
triangle = ({width: bar.width / 2, height: bar.width, padding: 2, num: 3})
Insert cell
start = ({left: 100, right: 350, padding: 5})
Insert cell
numOfBars = chartData.length
Insert cell
import {form} from "@mbostock/form-input"
Insert cell
d3 = require("d3@6")
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more