Public
Edited
Jan 4, 2024
12 stars
Also listed in…
Math
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function make_guitar_string() {
let xmin = 0;
let xmax = 1;
let ymin = -1 / 2;
let ymax = 1 / 2;
let w = width < 800 ? width : 800;
let h = w * 0.6;
let pad = 15;

let xScale = d3
.scaleLinear()
.domain([xmin, xmax])
.range([pad, w - pad]);
let yScale = d3
.scaleLinear()
.domain([ymin, ymax])
.range([h - pad, pad]);
let pts_to_path = d3
.line()
.x(function (d) {
return xScale(d[0]);
})
.y(function (d) {
return yScale(d[1]);
});

let svg = d3
.create("svg")
.attr("width", w)
.attr("height", h)
.style("background-color", "white");

svg
.append("path")
.attr("class", "string")
.attr(
"d",
pts_to_path([
[0, 0],
[1, 0]
])
)
.style("stroke", "black")
.style("stroke-width", 0.8)
.style("fill", "none");

svg
.append("circle")
.attr("class", "pick")
.attr("r", 5)
.attr("cx", 0)
.attr("cy", yScale(0))
.attr("fill", "lightgray")
.attr("stroke", "black")
.attr("opacity", 0);
svg
.append("circle")
.attr("class", "pin")
.attr("r", 5)
.attr("cx", xScale(0))
.attr("cy", yScale(0))
.attr("fill", "red")
.attr("stroke", "black");
svg
.append("circle")
.attr("class", "pin")
.attr("r", 5)
.attr("cx", xScale(1))
.attr("cy", yScale(0))
.attr("fill", "red")
.attr("stroke", "black");

let interval_id = 0;
let state = "stopped";
svg
.on("touchmove", (e) => e.preventDefault()) // prevent scrolling
.on("pointermove", function (evt) {
if (state == "stopped") {
if (
Math.abs(yScale.invert(evt.layerY)) <= 0.02 &&
0 < xScale.invert(evt.layerX) &&
xScale.invert(evt.layerX) < 1
) {
svg.select("circle.pick").attr("cx", evt.layerX).attr("opacity", 1);
} else if (
Math.abs(yScale.invert(evt.layerY)) > 0.02 ||
xScale.invert(evt.layerX) < 0 ||
xScale.invert(evt.layerX) > 1
) {
svg.select("circle.pick").attr("opacity", 0);
}
} else if (state == "pluck") {
svg
.select("circle.pick")
.attr("cx", evt.layerX)
.attr("cy", evt.layerY)
.attr("opacity", 1);
svg.select("path.string").attr(
"d",
pts_to_path([
[0, 0],
[xScale.invert(evt.layerX), yScale.invert(evt.layerY)],
[1, 0]
])
);
}
})
.on("pointerdown", function (evt) {
if (state == "stopped" && Math.abs(yScale.invert(evt.layerY)) < 0.02) {
state = "pluck";
}
})
.on("pointerup", function (evt) {
if (state == "pluck") {
state = "vibrating";
svg.select("circle.pick").attr("cy", yScale(0)).attr("opacity", 0);

clearInterval(interval_id);
let i = 0;
interval_id = setInterval(function () {
let pts = build_samples(
(x) =>
u(
yScale.invert(evt.layerY),
xScale.invert(evt.layerX),
x,
i++ / 3000
),
0,
1
);
svg.select("path.string").attr("d", pts_to_path(pts));

// Trails don't look as nice as I might have hoped
// if (i % 10 == 1) {
// svg
// .append("path")
// .attr("class", "trail")
// .attr("d", pts_to_path(pts))
// .style("stroke", "black")
// .style("stroke-width", 0.8)
// .style("fill", "none")
// .style("opacity", 0.2);
// }
if (i > 120000) {
clearInterval(interval_id);
state = "stopped";
}
});
}
});

return svg.node();
}
Insert cell
// The first twentyfive terms of the series solution described above.
function u(A, x0, x, t) {
return d3.sum(d3.range(32).map(n => term(A, x0, x, t, n)));
}
Insert cell
// The terms of the series
function term(A, x0, x, t, n) {
let c = 0.8;
let d = 0.3;
let n2 = n ** 2;
let sqrt4p2n2_1 = Math.sqrt(4 * pi2 * c ** 2 * n2 - d ** 2);
return (
(-2 *
A *
Math.exp((-d * t) / 2) *
(sqrt4p2n2_1 * Math.cos((sqrt4p2n2_1 * t) / 2) +
d * Math.sin((sqrt4p2n2_1 * t) / 2)) *
Math.sin(n * Math.PI * x) *
Math.sin(n * Math.PI * x0)) /
(n2 * pi2 * sqrt4p2n2_1 * (x0 - 1) * x0)
);
}
Insert cell
pi2 = Math.PI ** 2
Insert cell
d3 = require('d3-selection@2', 'd3-array@2', 'd3-scale@3', 'd3-shape@2')
Insert cell
import { build_samples } from '@mcmcclur/adaptive-plotter'
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