Public
Edited
Oct 28, 2022
Importers
Insert cell
Insert cell
container = html`<svg id="container"></svg>`
Insert cell
area = d3
.areaRadial()
.curve(d3.curveLinearClosed)
.angle((d) => (d.x0 * Math.PI) / 180)
.innerRadius(0)
.outerRadius((d) => y(d.length))(bins)
Insert cell
// the histogram is plotted separately so that only this part redraws when data changes
d3.select(".plot-layer")
.selectAll(".histogram-plot")
.data([null])
.join("path")
.attr("class", "histogram-plot")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.2)
.attr("d", area)
.style("pointer-events", "none");
Insert cell
histogram = {
const svg = d3
.select(container)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("width", width)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.style("user-select", "none");

svg.append("g").call(xAxis);

svg.append("g").call(yAxis);

svg.append("g").attr("class", "plot-layer");
svg.append("g").call(compassSelector);
return svg.node();
}
Insert cell
width = 400
Insert cell
height = width
Insert cell
margin = 10
Insert cell
innerRadius = width / 5
Insert cell
outerRadius = width / 2 - margin
Insert cell
x = d3.scaleLinear().domain([0, 360]).range([0, 360])
Insert cell
y = d3
.scaleSqrt()
.domain([0, 1.1 * d3.max(binFunc(rawdata).map((d) => d.length))])
.range([innerRadius, outerRadius])
Insert cell
xAxis = (g) =>
g
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.call((g) =>
g
.selectAll("g")
.data(Array.from(Array(13), (x, i) => 45 * i + 22.5)) // see https://observablehq.com/@d3/scale-ticks
.join("g")
.call((g) =>
// draw ticks from inside to outside
g
.append("path")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.attr(
"d",
(d) => `
M${d3.pointRadial((d * Math.PI) / 180, innerRadius)}
L${d3.pointRadial((d * Math.PI) / 180, outerRadius)}
`
)
)
.call((g) =>
// draw paths along innerRadius circle
g
.append("path")
.attr("id", (d) => "xTick" + d)
.attr("fill", "none")
// .attr("stroke", "crimson") // uncomment to debug baseline position
.attr(
"d",
(a) => `
M${d3.pointRadial(((a - 10) * Math.PI) / 180, innerRadius - 20)}
A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(
((a + 10) * Math.PI) / 180,
innerRadius - 20
)}
`
)
)
.call(
(g) =>
g
.append("text")
.append("textPath")
.attr("text-anchor", "middle")
.attr("startOffset", "50%") // https://stackoverflow.com/questions/44860320/how-to-center-text-inside-an-svg-path
.attr("xlink:href", (d) => "#xTick" + d) // tells it which path to associate with which text
.text((d) => (d ? d + "°" : "")) // inline if statement prevents "0" from overlapping with 360
)
)
Insert cell
yAxis = (g) =>
g
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.call((g) =>
g
.selectAll("g")
.data(y.ticks(5).reverse())
.join("g")
.attr("fill", "none")
.call((g) =>
// draw circles at the tick radii
g
.append("circle")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.attr("r", y)
)
.call((g) =>
// add labels to axis
g
.append("text")
.attr("y", (d) => -y(d))
.attr("dy", "0.35em")
// white halo
.attr("stroke", "#fff")
.attr("stroke-width", 5)
.text((x, i) => `${x > 0 && !(i % 2) ? x.toFixed(0) : ""}`)
.clone(true)
.attr("y", (d) => y(d))
.selectAll(function () {
return [this, this.previousSibling];
})
.clone(true)
.attr("fill", "currentColor")
.attr("stroke", "none")
)
)
Insert cell
binFunc = d3
.bin() // see https://observablehq.com/@d3/d3-bin
.domain([0,315])
.thresholds(Array.from(Array(8), (x, i) => 45 * i))
.value((d) => d[1])
Insert cell
bins = binFunc(data)
Insert cell
Insert cell
import {compassSelector, selectedData as data} with {rawdata, innerRadius as outerRadius} from "07143278ed0ba6b1"
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