Public
Edited
Dec 31, 2023
1 fork
1 star
Insert cell
Insert cell
chart = {
// Specify the chart’s dimensions.
const width = 928;
const height = width;
const radius = width / 6;

// Create the color scale.
const color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, 11));
// Compute the layout.
const hierarchy = d3.hierarchy(data)
.sum(d => 1)
.sort((a, b) => b.value - a.value);
const root = d3.partition()
.size([2 * Math.PI, hierarchy.height + 1])
(hierarchy);
root.each(d => {d.current = d; d.data.name = d.data[0] || d.data.title
|| 'n/a'; d.value = d.data[1]
? d.data[1].length : 1});
console.log('heigharchy', hierarchy);

// Create the arc generator.
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1))

// Create the SVG container.
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, width])
.style("font", "10px sans-serif");

// Append the arcs.
const path = svg.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("fill", d => { while (d.depth > 1) d = d.parent; return color(d.data.name); })
.attr("fill-opacity", d => arcVisible(d.current) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.current) ? "auto" : "none")

.attr("d", d => arc(d.current));

// Make them clickable if they have children.
path.filter(d => d.children)
.style("cursor", "pointer")
.on("click", clicked);

const format = d3.format(",d");
path.append("title")
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join("/")}\n${format(d.value)}`);

const label = svg.append("g")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.selectAll("text")
.data(root.descendants().slice(1))
.join("text")
.attr("dy", "0.35em")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.text(d => d.data.name);

const parent = svg.append("circle")
.datum(root)
.attr("r", radius)
.attr("fill", "none")
.attr("pointer-events", "all")
.on("click", clicked);

// Handle zoom on click.
function clicked(event, p) {
parent.datum(p.parent || root);

root.each(d => d.target = {
x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
y0: Math.max(0, d.y0 - p.depth),
y1: Math.max(0, d.y1 - p.depth)
});

const t = svg.transition().duration(750);

// Transition the data on all arcs, even the ones that aren’t visible,
// so that if this transition is interrupted, entering arcs will start
// the next transition from the desired position.
path.transition(t)
.tween("data", d => {
const i = d3.interpolate(d.current, d.target);
return t => d.current = i(t);
})
.filter(function(d) {
return +this.getAttribute("fill-opacity") || arcVisible(d.target);
})
.attr("fill-opacity", d => arcVisible(d.target) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.target) ? "auto" : "none")

.attrTween("d", d => () => arc(d.current));

label.filter(function(d) {
return +this.getAttribute("fill-opacity") || labelVisible(d.target);
}).transition(t)
.attr("fill-opacity", d => +labelVisible(d.target))
.attrTween("transform", d => () => labelTransform(d.current));
}
function arcVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0;
}

function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}

function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}

return svg.node();
}
Insert cell
data = dataMoviesFlattened
Insert cell
dataMoviesFlattened = {
const raw = d3.group(videosBySeries, g => g[1][0].isMovie ? "Movie" : "Show");
console.log(JSON.stringify(raw), raw.get('Movie')[0][1])
raw.set('Movie', raw.get('Movie')[0][1]);
console.log(raw);
console.log("videos", videos);
return raw;
}

Insert cell
videosBySeries = d3.group(videos, v => v.series)

Insert cell
videos= dataCsv.map((row) => {
return {
title: row['Episode Name'],
season: row['Season'],
date: new Date(Date.parse(row['Last Played At'])),
series: row['Series Name'],
isMovie: row['Season'] == 'N/A',
}
})
.filter(v => !/Trailer/g.test(v.title))
.filter(v => isAllowed(v.title) || isAllowed(v.series))
.filter(v => v.date.getFullYear() >= new Date().getFullYear())
Insert cell
dataCsv= FileAttachment("hulu.csv").csv()
Insert cell
isAllowed = function(text) {
for (let d of allowedTitles) {
if (new RegExp(d).test(text)) {
return true;
}
}
return false;
}

Insert cell
allowedTitles = [
"Culprits",
"Percy Jackson and the Olympians",
"Primeval",
"Such Brave Girls",
"Abbott Elementary",
"Reservation Dogs",
"A Murder at the End of the World",
"Class of '09",
"For the People",
"Schitt's Creek",
"Moonlighting",
"The Other Black Girl",
"SurrealEstate",
"Alaska Daily",
"Superstore",
"The Orville",
"Animaniacs",
"What We Do in the Shadows",
"The Mick",
"The Unit",
"Little Demon",
"Just Shoot Me",
"Fleishman Is in Trouble",
"Extraordinary",
"History of the World, Part II",
"Party Down",
"Will Trent",
"Speechless",
"Animal Control",
"The Fresh Prince of Bel-Air",
"Not Dead Yet",
"Accused",
"Rick and Morty",
"Burden of Truth",
"Dating: No Filter",
"Kindred",
"Body of Proof",
"Lost",
"American Horror Story",
"The Wonder Years",
"The Rookie",
"Timeless",
"Station 19",
"How I Met Your Father",

"The Retirement Plan",
"The Retirement Plan",
"Quiz Lady",
"Robots",
"Glengarry Glen Ross",
"Rosaline",
"Edge of Tomorrow",
"Jumanji: Welcome to the Jungle"
]

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