chart = {
const width = 800;
const height = 500;
const margin = {top: 40, right: 120, bottom: 60, left: 80};
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("max-width", "100%");
if (chartType === "scatter") {
const xScale = d3.scaleLinear()
.domain(d3.extent(filteredMovies, d => d.budget))
.range([margin.left, width - margin.right]);
const yScale = d3.scaleLinear()
.domain(d3.extent(filteredMovies, d => d.revenue))
.range([height - margin.bottom, margin.top]);
const colorScale = d3.scaleOrdinal()
.domain(["Action", "Comedy", "Drama", "Sci-Fi", "Horror", "Romance", "Thriller"])
.range(d3.schemeCategory10);
const sizeScale = d3.scaleSqrt()
.domain(d3.extent(filteredMovies, d => d.rating))
.range([4, 12]);
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale))
.append("text")
.attr("x", width / 2)
.attr("y", 35)
.attr("fill", "black")
.style("text-anchor", "middle")
.text("Budget ($ millions)");
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("x", -height / 2)
.attr("fill", "black")
.style("text-anchor", "middle")
.text("Revenue ($ millions)");
// Add title
svg.append("text")
.attr("x", width / 2)
.attr("y", 25)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Movie Budget vs Revenue");
// Create tooltip
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background", "rgba(0,0,0,0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("font-size", "12px")
.style("pointer-events", "none")
.style("z-index", "1000");
// Add circles with interactions
const circles = svg.selectAll("circle")
.data(filteredMovies)
.join("circle")
.attr("cx", d => xScale(d.budget))
.attr("cy", d => yScale(d.revenue))
.attr("r", d => sizeScale(d.rating))
.attr("fill", d => colorScale(d.genre))
.attr("stroke", "#333")
.attr("stroke-width", 0.5)
.style("opacity", 0.7)
.style("cursor", "pointer");
// Add hover interactions
circles
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("r", d => sizeScale(d.rating) * 1.5)
.style("opacity", 1)
.attr("stroke-width", 2);
tooltip
.style("visibility", "visible")
.html(`
<strong>${d.title}</strong><br/>
Genre: ${d.genre}<br/>
Year: ${d.year}<br/>
Budget: $${d.budget}M<br/>
Revenue: $${d.revenue}M<br/>
Rating: ${d.rating}/10<br/>
Duration: ${d.duration} min
`);
})
.on("mousemove", function(event) {
tooltip
.style("top", (event.pageY - 10) + "px")
.style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("r", d => sizeScale(d.rating))
.style("opacity", 0.7)
.attr("stroke-width", 0.5);
tooltip.style("visibility", "hidden");
})
.on("click", function(event, d) {
// Toggle selection - this now has access to mutable selectedMovies
const currentSelected = mutable selectedMovies;
const isSelected = currentSelected.some(movie => movie.id === d.id);
if (isSelected) {
mutable selectedMovies = currentSelected.filter(movie => movie.id !== d.id);
} else {
mutable selectedMovies = [...currentSelected, d];
}
// Update visual feedback
d3.select(this)
.attr("stroke", isSelected ? "#333" : "#ff0000")
.attr("stroke-width", isSelected ? 0.5 : 3);
});
// Add legend
const legend = svg.selectAll(".legend")
.data(colorScale.domain())
.join("g")
.attr("class", "legend")
.attr("transform", (d, i) => `translate(${width - 100}, ${50 + i * 20})`);
legend.append("circle")
.attr("r", 6)
.attr("fill", d => colorScale(d));
legend.append("text")
.attr("x", 12)
.attr("y", 4)
.style("font-size", "12px")
.text(d => d);
} else if (chartType === "bar") {
// BAR CHART IMPLEMENTATION
const genreData = d3.rollup(filteredMovies,
v => d3.sum(v, d => d.revenue),
d => d.genre
);
const barData = Array.from(genreData, ([genre, revenue]) => ({genre, revenue}))
.sort((a, b) => b.revenue - a.revenue);
const xScale = d3.scaleBand()
.domain(barData.map(d => d.genre))
.range([margin.left, width - margin.right])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, d3.max(barData, d => d.revenue)])
.range([height - margin.bottom, margin.top]);
const colorScale = d3.scaleOrdinal()
.domain(["Action", "Comedy", "Drama", "Sci-Fi", "Horror", "Romance", "Thriller"])
.range(d3.schemeCategory10);
// Add axes
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale));
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));
// Add title
svg.append("text")
.attr("x", width / 2)
.attr("y", 25)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Total Revenue by Genre");
// Add bars
svg.selectAll("rect")
.data(barData)
.join("rect")
.attr("x", d => xScale(d.genre))
.attr("y", d => yScale(d.revenue))
.attr("width", xScale.bandwidth())
.attr("height", d => yScale(0) - yScale(d.revenue))
.attr("fill", d => colorScale(d.genre))
.style("opacity", 0.8);
// Add legend for bar chart
const legend = svg.selectAll(".legend")
.data(barData)
.join("g")
.attr("class", "legend")
.attr("transform", (d, i) => `translate(${width - 100}, ${50 + i * 20})`);
legend.append("rect")
.attr("width", 12)
.attr("height", 12)
.attr("fill", d => colorScale(d.genre));
legend.append("text")
.attr("x", 16)
.attr("y", 9)
.style("font-size", "12px")
.text(d => d.genre);
} else {
// HISTOGRAM IMPLEMENTATION
const xScale = d3.scaleLinear()
.domain(d3.extent(filteredMovies, d => d.rating))
.range([margin.left, width - margin.right]);
const bins = d3.histogram()
.value(d => d.rating)
.domain(xScale.domain())
.thresholds(20)(filteredMovies);
const yScale = d3.scaleLinear()
.domain([0, d3.max(bins, d => d.length)])
.range([height - margin.bottom, margin.top]);
// Add axes
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale));
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));
// Add title
svg.append("text")
.attr("x", width / 2)
.attr("y", 25)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Distribution of Movie Ratings");
// Add bars
svg.selectAll("rect")
.data(bins)
.join("rect")
.attr("x", d => xScale(d.x0))
.attr("y", d => yScale(d.length))
.attr("width", d => Math.max(0, xScale(d.x1) - xScale(d.x0) - 1))
.attr("height", d => yScale(0) - yScale(d.length))
.attr("fill", "steelblue")
.style("opacity", 0.8);
}
return svg.node();
}