Public
Edited
Feb 25
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof chart = {
// Set chart dimensions
const width = 800, height = 500;
const margin = { top: 50, right: 30, bottom: 30, left: 100 };
// Create SVG element
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Sample data (Time-series format)
const data = [{'year': '2008-2011', 'category': 'Domestic Affairs', 'value': 9.0},
{'year': '2008-2011', 'category': 'Economic Policy', 'value': 9.0},
{'year': '2008-2011', 'category': 'International Affairs', 'value': 34.0},
{'year': '2008-2011', 'category': 'Social Justice', 'value': 49.0},
{'year': '2012-2015', 'category': 'Domestic Affairs', 'value': 26.0},
{'year': '2012-2015', 'category': 'Economic Policy', 'value': 11.0},
{'year': '2012-2015', 'category': 'International Affairs', 'value': 21.0},
{'year': '2012-2015', 'category': 'Social Justice', 'value': 42.0},
{'year': '2016-2019', 'category': 'Domestic Affairs', 'value': 12.0},
{'year': '2016-2019', 'category': 'Economic Policy', 'value': 4.0},
{'year': '2016-2019', 'category': 'International Affairs', 'value': 46.0},
{'year': '2016-2019', 'category': 'Social Justice', 'value': 38.0},
{'year': '2020-2023', 'category': 'Domestic Affairs', 'value': 20.0},
{'year': '2020-2023', 'category': 'Economic Policy', 'value': 5.0},
{'year': '2020-2023', 'category': 'International Affairs', 'value': 39.0},
{'year': '2020-2023', 'category': 'Social Justice', 'value': 36.0}];
// Extract unique years
const years = [...new Set(data.map(d => d.year))];

// Create scales
const x = d3.scaleLinear().range([margin.left, width - margin.right]);
const y = d3.scaleBand().range([margin.top, height - margin.bottom]).padding(0.1);
const color = d3.scaleOrdinal(d3.schemeTableau10);

const g = svg.append("g");
const legend = svg.append("g")
.attr("transform", `translate(${width - 150}, ${margin.top})`);


// Function to update the chart for a given year
function update(year) {
const yearLabel = svg.selectAll(".year-label")
.data([year]);

yearLabel.enter()
.append("text")
.attr("class", "year-label")
.attr("x", width - 50) // Position on the top right
.attr("y", margin.top - 20)
.attr("font-size", "24px")
.attr("font-weight", "bold")
.attr("text-anchor", "end") // Align text to the right
.merge(yearLabel)
.text(`Year ${year}`);

const filteredData = data.filter(d => d.year === year);

// Sort data in descending order based on value
filteredData.sort((a, b) => b.value - a.value);

x.domain([0, d3.max(filteredData, d => d.value)]);
y.domain(filteredData.map(d => d.category));
const bars = g.selectAll("rect").data(filteredData, d => d.category);

bars.enter()
.append("rect")
.attr("y", d => y(d.category))
.attr("height", y.bandwidth())
.attr("x", margin.left)
.attr("width", 0)
.attr("fill", d => color(d.category))
.merge(bars)
.transition().duration(4000)
.attr("y", d => y(d.category)) // Re-sort bars dynamically
.attr("x", margin.left)
.attr("width", d => x(d.value) - margin.left);
bars.exit().transition().duration(4000).attr("width", 0).remove();

// Update text labels
const labels = g.selectAll("text").data(filteredData, d => d.category);
const threshold = 180; // Define the threshold in pixels

labels.enter()
.append("text")
.attr("y", d => y(d.category) + y.bandwidth() / 2)
.attr("x", margin.left)
.attr("dy", "0.35em")
.attr("fill", "black")
.merge(labels)
.transition().duration(4000)
.attr("y", d => y(d.category) + y.bandwidth() / 2) // Re-sort labels dynamically
.attr("x", d => {
// Compute the end position of the bar
const barEnd = x(d.value);
// If the bar width (barEnd - margin.left) is less than the threshold,
// position the label just outside (to the right) of the bar
if (barEnd - margin.left < threshold) {
return barEnd + 5; // adjust offset as needed
}
// Otherwise, position the label inside the bar
return barEnd - 20;
})
.attr("text-anchor",d => {
const barEnd = x(d.value);
// If bar is too short, align the label from the left (outside the bar)
if (barEnd - margin.left < threshold) {
return "start";
}
// Otherwise, align the label to the end (inside the bar)
return "end";
}) // Align text from the left
.text(d => `(${d.value}%) ${d.category}`); // Append category name

labels.exit().transition().duration(4000).attr("x", margin.left).remove();
}

// Animate through years
let i = 0, animation;
function animate() {
if (i < years.length) {
update(years[i]);
i++;
animation = setTimeout(animate, 4000);
}
}

// Function to restart animation
function replay() {
clearTimeout(animation); // Stop current animation
i = 0; // Reset counter
animate(); // Start from beginning
}

// Create Replay Button
const button = document.createElement("button");
button.textContent = "Replay";
button.style.margin = "10px";
button.style.padding = "8px 12px";
button.style.fontSize = "16px";
button.style.cursor = "pointer";
button.onclick = replay;

// Start animation initially
animate();

// Create container to hold both SVG and button
const container = document.createElement("div");
container.appendChild(button);
container.appendChild(svg.node());

return container;
}

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