Skip to content

Instantly share code, notes, and snippets.

@manzt
Last active May 18, 2023 16:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manzt/cdc408333b926a4ca91ae4d5d5ddd033 to your computer and use it in GitHub Desktop.
Save manzt/cdc408333b926a4ca91ae4d5d5ddd033 to your computer and use it in GitHub Desktop.
An animated observable line plot
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Interactive Observable Plot</title>
<script type="module" src="/main.js"></script>
</head>
<style>
:root {
font-family: sans-serif;
}
.inputs {
display: flex;
flex-direction: column;
max-width: 20rem;
margin-top: 1rem;
}
.inputs > * {
margin-bottom: 1rem;
}
</style>
<body>
<div id="vis"></div>
<div class="inputs">
<label for="amplitude">Amplitude</label>
<input type="range" id="amplitude" min="0" max="100" value="50" step="1">
<label for="frequency">Frequency</label>
<input type="range" id="frequency" min="0" max="100" value="50" step="1">
<button id="button">Random</button>
</div>
</body>
</html>
import * as d3 from "https://esm.sh/d3@7";
import * as Plot from "https://esm.sh/@observablehq/plot@0.6.6";
/**
* Creates a `Plot.line` with a transition method that animates the line.
*
* @param {Array} data - the data to plot
* @param {Object} options - the options to pass to `Plot.line`
*/
function lineY(data, options) {
const base = Plot.lineY(data, options);
const { render } = base;
let renderGroups = [];
let plotConfig;
// overrides the render method to store the plot config and the rendered groups
// so we can access them later in the transition method
base.render = function (I, scales, channels, dimensions) {
plotConfig = { scales, dimensions, channels };
let el = render.apply(this, arguments);
renderGroups.push(el);
return el;
};
let line = d3.line();
base.transition = (update, { delay = 0, duration = 0 } = {}) => {
let data = update.map((d, i) => [plotConfig.scales.x(i), plotConfig.scales.y(d)]);
let paths = d3
.selectAll(renderGroups)
.selectAll("path");
// if delay or duration is set, transition the path, otherwise set the path immediately
if (delay > 0 || duration > 0) {
paths = paths.transition().delay(delay).duration(duration);
}
paths.attr("d", line(data));
};
return base;
}
function main() {
let amplitude = document.querySelector("#amplitude");
let frequency = document.querySelector("#frequency");
let button = document.querySelector("#button");
amplitude.value = 100;
frequency.value = 100;
// sine wave
function y() {
let freq = +frequency.value;
let amp = +amplitude.value;
return Array.from({ length: 500 }, (_, i) => amp * Math.sin(i / freq));
}
let plot = lineY(y());
amplitude.addEventListener("input", () => plot.transition(y()));
frequency.addEventListener("input", () => plot.transition(y()));
button.addEventListener("click", () => {
amplitude.value = Math.random() * 100;
frequency.value = Math.random() * 100;
plot.transition(y(), { delay: 200, duration: 1000 });
});
document.querySelector("#vis").appendChild(plot.plot());
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment