function plot(f, {
tdomain = [-2.75, +2.75],
xdomain,
ydomain,
margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 640,
height = 240,
n = width
} = {}) {
const X = d3.range(n).map(d3.scaleLinear([0, n - 1], tdomain));
const Y = X.map(f);
const x = d3.scaleLinear().range([margin.left, width - margin.right]);
const y = d3.scaleLinear().range([height - margin.bottom, margin.top]);
if (xdomain === undefined) x.domain(tdomain).nice(20);
else x.domain(xdomain);
if (ydomain === undefined) y.domain(d3.extent(Y)).nice().domain();
else y.domain(ydomain);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("max-width", `${width}px`);
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom + 12})`)
.call(d3.axisBottom(x.copy().interpolate(d3.interpolateRound)))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("stroke-opacity", d => d ? 0.1 : 0.5)
.attr("y1", -height));
svg.append("g")
.attr("transform", `translate(${margin.left - 12},0)`)
.call(d3.axisLeft(y.copy().interpolate(d3.interpolateRound)).ticks(5))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("stroke-opacity", d => d ? 0.1 : 0.5)
.attr("x1", width));
svg.append("path")
.attr("fill", "none")
.attr("stroke", "#c4238f")
.attr("stroke-width", 2)
.attr("d", d3.line().x(d => x(d)).y((d, i) => y(Y[i]))(X));
return svg.node();
}