{
const width = 400;
const height = 150;
const svg = d3
.select("#exampleD3")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `-10 0 ${width} ${height}`);
const rivers = [
{ name: "Nile", lengthKm: 6650 },
{ name: "Amazon", lengthKm: 6400 },
{ name: "Yangtze", lengthKm: 6300 },
{ name: "Mississippi", lengthKm: 6275 },
{ name: "Yenisey", lengthKm: 5539 }
];
const maxLength = 7000;
const riverScale = d3.scaleLinear().domain([0, maxLength]).range([0, width]);
const barHeight = 20;
const barPadding = 15;
const riverColor = "hsl(200, 90%, 40%)";
let displayedRivers = [];
function yOffset(d, i) {
return i * barHeight + barPadding;
}
function update() {
// when binding data, second parameter should return a unique ID
// this is essential so that D3 can tell old elements from new
// (position is not enough since re-ordering is common)
svg
.selectAll("line")
.data(displayedRivers, (d) => d.name)
// Change #3 join can take separate functions for new/updating/removing items
.join(
// this function gets a selection of all entering elements
(entering) =>
entering
.append("line")
.attr("x1", 0)
.attr("x2", 0) // start at 0 to animate
.attr("y1", (d, i) => yOffset(d, i))
.attr("y2", (d, i) => yOffset(d, i))
.style("stroke", riverColor)
.transition()
.duration(1000)
.attr("x2", (d) => riverScale(d.lengthKm)),
// this function gets a selection of all updating elements
(updating) =>
updating
.transition()
.duration(500)
.attr("y1", (d, i) => yOffset(d, i))
.attr("y2", (d, i) => yOffset(d, i))
.style("stroke", "lightgrey") // fade all others out
.transition()
.duration(1500)
.style("stroke", riverColor)
);
// labels have mostly the same logic
svg
.selectAll("text")
.data(displayedRivers, (d) => d.name)
.join(
(entering) =>
entering
.append("text")
.text((d) => d.name)
.attr("x", 0)
.attr("y", (d, i) => yOffset(d, i))
.attr("dy", -3)
.attr("font-size", "0.7em")
.style("fill", riverColor)
.style("opacity", 0)
.transition()
.duration(1000)
.style("opacity", 1),
(updating) =>
updating
.transition()
.duration(500)
.attr("y", (d, i) => yOffset(d, i))
.style("fill", "lightgrey") // fade all others out
.transition()
.duration(1500)
.style("fill", riverColor)
);
}
// function to add a river to existing array
function addRiver() {
if (displayedRivers.length < rivers.length) {
// add elements from smallest to largest
/*displayedRivers.unshift(
rivers[rivers.length - displayedRivers.length - 1]
);*/
displayedRivers = rivers;
// trigger update function each time data is modified
update();
}
}
// button to add rivers
const button = d3
.select("#exampleD3")
.append("button")
.text("Add River")
.on("click", addRiver);
}