chart_2 = {
const width = 650,
height = 600;
const legendHeight = 10;
const legendWidth = 300;
const svg = d3
.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);
const imageWidth = 300;
const imageHeight = 300;
const imageX = -imageWidth / 1.7;
const imageY = -imageHeight / 1.055;
svg
.append("image")
.attr(
"xlink:href",
"https://shopstands.com/cdn/shop/files/PoliceBoxDoor1_1445x.webp?v=1698161934"
)
.attr("width", imageWidth)
.attr("height", imageHeight)
.attr("x", -imageWidth / 2)
.attr("y", -imageHeight / 1);
const textLines = ["Doctor", "Who"];
const textElement = svg
.append("text")
.attr("x", imageX - 10)
.attr("y", imageY + imageHeight / 2)
.attr("text-anchor", "middle")
.style("font-size", "60px")
.style("fill", "#104E8B");
textLines.forEach((line, index) => {
textElement
.append("tspan")
.attr("x", imageX - 10) // Align each line to the same x position
.attr("dy", `${index * 1.2}em`) // Adjust vertical space between lines
.text(line);
});
// Manually split the right side text into lines
const imageX1 = -imageWidth / 2.5; // X position of the image
const imageY1 = -imageHeight / 0.9; // Y position of the image
const rightTextLines = [
"Doctor Who is an extremely",
"long-running British television",
"program. The show was revived",
"in 2005, and has proven very",
"popular since then. Rating of",
"the episodes in revived era."
];
// Create a new text element for the right side text
const rightTextElement = svg
.append("text")
.attr("x", imageX1 + imageWidth - 20) // Position to the right of the image
.attr("y", imageY1 + imageHeight / 2) // Start at the top of the image
.style("font-size", "14px") // Adjust the font size as needed
.attr("text-anchor", "middle") // Right-align the text
.style("fill", "Black");
// Append tspans for each line of the right side text
rightTextLines.forEach((line, index) => {
rightTextElement
.append("tspan")
.attr("x", imageX1 + imageWidth + 10) // Align each line to the right of the image
.attr("dy", `${index > 0 ? 2 : 0}em`) // Adjust vertical space between lines
.text(line);
});
// Group episodes by season
const seasons = Array.from(d3.group(df, (d) => d.season_number).values());
// Define color scale for episode ratings
const colorScale = d3
.scaleSequential(d3.interpolateBlues)
.domain(d3.extent(df, (d) => d.rating));
// Create a tooltip div
const tooltip = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.style("position", "absolute")
.style("text-align", "center")
.style("width", "120px")
.style("height", "auto")
.style("padding", "2px")
.style("font", "15px Lato")
.style("background", "white")
.style("border", "1px solid black")
.style("border-radius", "8px")
.style("pointer-events", "none");
// Define the maximum radius for the outermost circle
const maxRadius = (Math.min(width, height) / 1.2) * 0.5; // 80% of half the min dimension
// Calculate the radius increment for each season's circle
const ringIncrement = maxRadius / seasons.length;
// Add circles for each episode
seasons.forEach((season, sIndex) => {
// Calculate the radius for the current season's circle
const seasonRadius = ringIncrement * (seasons.length - sIndex); // Outermost circle for the first season
// Calculate the angle for each episode in the season
const angleIncrement = (1.085 * Math.PI) / season.length;
season.forEach((episode, eIndex) => {
svg
.append("circle")
.attr("cx", Math.cos(angleIncrement * eIndex) * seasonRadius)
.attr("cy", Math.sin(angleIncrement * eIndex) * seasonRadius)
.attr("r", 5) // Radius of dots
.attr("fill", colorScale(episode.rating))
.attr("stroke", "#00688B")
.on("mouseover", function (event) {
tooltip
.style("opacity", 0.9)
.html(`${episode.episode_title}<br/>Rating: ${episode.rating}`)
.style("left", event.pageX + 10 + "px") // Adjust the X position
.style("top", event.pageY - 28 + "px"); // Adjust the Y position
})
.on("mouseout", function () {
tooltip.style("opacity", 0);
});
});
});
// Create a group for the legend at the bottom
const legendGroup = svg
.append("g")
.attr(
"transform",
`translate(-${legendWidth / 2}, ${height / 2 - legendHeight - 20})`
);
// Create a gradient for the legend
const gradient = legendGroup
.append("defs")
.append("linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("x2", "100%")
.attr("y1", "0%")
.attr("y2", "0%");
// Define the gradient stops
const numStops = 10; // Number of stops in the gradient legend
d3.range(numStops).forEach((i) => {
gradient
.append("stop")
.attr("offset", `${(i / (numStops - 1)) * 100}%`)
.attr(
"stop-color",
colorScale(
colorScale.domain()[0] +
(i / (numStops - 1)) *
(colorScale.domain()[1] - colorScale.domain()[0])
)
);
});
// Add the gradient bar
legendGroup
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", legendHeight)
.style("fill", "url(#gradient)");
// Add legend text
legendGroup
.append("text")
.attr("class", "legend-text")
.attr("x", 0)
.attr("y", -5)
.style("text-anchor", "start")
.text(`${colorScale.domain()[0]}`);
legendGroup
.append("text")
.attr("class", "legend-text")
.attr("x", legendWidth)
.attr("y", -5)
.style("text-anchor", "end")
.text(`${colorScale.domain()[1]}`);
// Append the SVG container to the DOM (ObservableHQ specific)
return svg.node();
}