Public
Edited
Nov 14, 2024
Insert cell
Insert cell
<div class="exampleA">
<button id="buttonA" >this is a button</button>
<input id="rangeA" type="range" min="0" max="10">
<div id="output"></div>
</div>
Insert cell
{
let buttonA = document.getElementById("buttonA");
let rangeA = document.getElementById("rangeA");
let output = document.getElementById("output");
buttonA.addEventListener("click", (event) => {
output.innerHTML = "button clicked!";
});
rangeA.addEventListener("change", (event) => {
output.innerHTML = `changed to ${event.target.value}!`;
});
output.innerHTML = "do something!";
}
Insert cell
Insert cell
<div class="exampleB">
<button id="buttonB" >this is a button</button>
<input id="rangeB" type="range" min="50" max="300">
<div id="outputB">this is the output div</div>
</div>
<style>
#outputB { margin: 1rem; padding: 1rem; }
/* this property will be toggled by the event */
.clicked { background-color: hsl(80 80 80); }
</style>
Insert cell
{
let button = document.getElementById("buttonB");
let range = document.getElementById("rangeB");
let output = document.getElementById("outputB");

button.addEventListener("click", (event) => {
// turn clicked class on and off
output.classList.toggle("clicked");
});
range.addEventListener("change", (event) => {
// use value as percentage and change style directly
output.style.fontSize = `${event.target.value}%`;
});
}
Insert cell
<div class="exampleC">
<button id="buttonC" >this is a button</button>
<input id="rangeC" type="range" min="50" max="300">
<div id="outputC">this is the output div</div>
</div>
<style>
#outputC {
margin: 1rem; padding: 1rem;
/*
the transition property triggers interpolation
whenever these properties are changed

note that it works for the class change and the direct style change
*/
transition: background-color 2s 1s, font-size 2s 0s;
}
.clicked { background-color: hsl(80 80 80); }
</style>
Insert cell
{
// no real changes to the JS required for CSS animations
let button = document.getElementById("buttonC");
let range = document.getElementById("rangeC");
let output = document.getElementById("outputC");

button.addEventListener("click", (event) => {
output.classList.toggle("clicked");
});
range.addEventListener("change", (event) => {
output.style.fontSize = `${event.target.value}%`;
});
}
Insert cell
Insert cell
<div id="exampleD3">
</div>
Insert cell
{
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%)";

// initially empty dataset used for display
let displayedRivers = [];

function yOffset(d, i) {
return i * barHeight + barPadding;
}

// Change #1
// data binding now lives in an update() function so it can be called
// multiple times
function update() {
// Change #2
// 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);
}
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