Public
Edited
Dec 3, 2023
Insert cell
Insert cell
chart_2 = {
// Set up the SVG container
const width = 650,
height = 600;
const legendHeight = 10; // Height of the legend
const legendWidth = 300;
const svg = d3
.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);

// Add the image to the SVG, centering it
const imageWidth = 300; // Increased image width for better visibility
const imageHeight = 300; // Adjust the image height as needed
const imageX = -imageWidth / 1.7; // X position of the image
const imageY = -imageHeight / 1.055; // Y position of the image

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) // Center the image on the X axis
.attr("y", -imageHeight / 1); // Center the image on the Y axis

const textLines = ["Doctor", "Who"]; // Text split into lines

const textElement = svg
.append("text")
.attr("x", imageX - 10) // Position the text to the left of the image
.attr("y", imageY + imageHeight / 2) // Start vertically centered relative to the image
.attr("text-anchor", "middle") // Right-align the text
.style("font-size", "60px") // Adjust the font size as needed
.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();
}
Insert cell
df.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more