Public
Edited
May 6
Insert cell
Insert cell
chart = {
const root = tree(d3.hierarchy(data)
.sort((a, b) => d3.descending(a.data.value, b.data.value))
);

setColor(root);

// Circle size and opacity scale based on rating (value)
const CirSize = d3.scaleSqrt()
.domain(d3.extent(root.leaves(), d => d.data.value))
.range([2, 12]);

const CircleAlpha = d3.scaleLinear()
.domain(d3.extent(root.leaves(), d => d.data.value))
.range([0.3, 1]);

const svg = d3.create("svg");

// Title
svg.append("text")
.attr("x", 0)
.attr("y", -root.y - 40) // Place above the top of the chart
.attr("text-anchor", "middle")
.attr("font-size", 16)
.attr("font-weight", "bold")
.text("Top-Rated Restaurants in Seattle's U District by Category");

// Author, Date
svg.append("text")
.attr("x", 0)
.attr("y", root.y + 60) // Below the chart
.attr("text-anchor", "middle")
.attr("font-size", 10)
.text("By Emina Garcia | Created April 28, 2025");

// Data Source
svg.append("text")
.attr("x", 0)
.attr("y", root.y + 75) // Just below the author/date
.attr("text-anchor", "middle")
.attr("font-size", 9)
.attr("fill", "gray")
.text("Data source: Yelp Fusion API (https://www.yelp.com/developers)");

svg.append("g")
.attr("fill", "none")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(root.links())
.join("path")
.attr("d", d3.linkRadial()
.angle(d => d.x)
.radius(d => d.y))
.each(function(d) { d.target.linkNode = this; })
.attr("stroke", d => d.target.data.value ? d.source.color : "transparent");

svg.append("g")
.selectAll("circle")
.data(root.descendants())
.join("circle")
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
`)
.attr("fill", d => d.children ? color(d.data.name) : color(d.data.group))
.attr("fill-opacity", d => d.children ? 1 : CircleAlpha(d.data.value || 0))
.attr("r", d => {
if (d.depth >= 4 && d.children) return 4;
return CirSize(d.data.value || 0);
});

svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 8)
.attr("stroke-linejoin", "round")
.attr("stroke-width", 10)
.selectAll("text")
.data(root.descendants())
.join("text")
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`)
.attr("dy", "0.31em")
.attr("x", d => d.x < Math.PI === !d.children ? 6 : -6)
.attr("text-anchor", d => d.x < Math.PI === !d.children ? "start" : "end")
.text(d => {
if (!d.children && d.data.value !== undefined) {
return `${d.data.name} ( ${d.data.value})`;
}
return d.data.name;
})
.clone(true).lower()
.attr("stroke", "white");


svg.selectAll("text")
.each(function(d, i) { wrap_text_nchar(d3.select(this), 25) });

return svg.attr("viewBox", autoBox).node();
}

Insert cell
Insert cell
Insert cell
//get the min and max values of the topic word weightings. These values are obtained during the process of topic modeling. So, it is also possible to get the values from python and change the domain parts of CirSize and CircleAlpha manually.
toppicData = d3.hierarchy(data).leaves().map(d => d.data.name)
Insert cell
color = d3.scaleOrdinal()
.domain(toppicData)
.range(d3.schemeCategory10)
Insert cell
// Set the color of each node by recursively inheriting.
function setColor(d) {
var name = d.data.name;
d.color = color.domain().indexOf(name) >= 0 ? color(name) : d.parent ? d.parent.color : null;
if (d.children) d.children.forEach(setColor);
}
Insert cell
function autoBox() {
document.body.appendChild(this);
const {x, y, width, height} = this.getBBox();
document.body.removeChild(this);
return [x, y, width, height];
}
Insert cell
data = FileAttachment("yelp_rating_data.json").json()
Insert cell
//get the min and max values of the topic word weightings. These values are obtained during the process of topic modeling. So, it is also possible to get the values from python and change the domain parts of CirSize and CircleAlpha manually.
getMinMaxvalues = () =>
d3.hierarchy(data)
.leaves()
.map(d => d.data.value)
.filter(v => v !== undefined)
Insert cell
//define circle sizes by using getMinMaxvalues(). It is also possible to use the values obtained from python. In this case, it is not needed to define getMinMaxvalues().
CirSize = d3.scaleSqrt()
.domain([d3.min(getMinMaxvalues()), d3.max(getMinMaxvalues())])
.range([1, 8])
Insert cell
//define circle opacities by using getMinMaxvalues()
CircleAlpha = d3.scaleLinear().domain([d3.min(getMinMaxvalues()),d3.max(getMinMaxvalues())]).range([0.3,1])
Insert cell
width = 975
Insert cell
radius = width / 2
Insert cell
tree = d3.cluster().size([2 * Math.PI, radius - 100])
Insert cell
d3 = require("d3@6")
Insert cell
// Title
svg.append("text")
.attr("x", 0)
.attr("y", -root.y - 40) // Place above the top of the chart
.attr("text-anchor", "middle")
.attr("font-size", 16)
.attr("font-weight", "bold")
.text("Top-Rated Restaurants in Seattle's U District by Category");

// Author, Date
svg.append("text")
.attr("x", 0)
.attr("y", root.y + 60) // Below the chart
.attr("text-anchor", "middle")
.attr("font-size", 10)
.text("By Emina Garcia | Created April 28, 2025");

// Data Source
svg.append("text")
.attr("x", 0)
.attr("y", root.y + 75) // Just below the author/date
.attr("text-anchor", "middle")
.attr("font-size", 9)
.attr("fill", "gray")
.text("Data source: Yelp Fusion API (https://www.yelp.com/developers)");

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