// Create visualization
chart = {
const width = 700;
const height = 350;
const margin = {top: 30, right: 30, bottom: 50, left: 50};
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("max-width", "100%")
.style("font-family", "system-ui");
// Scales
const xScale = d3.scaleLinear()
.domain([0, 1])
.range([margin.left, width - margin.right]);
const yScale = d3.scaleLinear()
.domain([-2, 2])
.range([height - margin.bottom, margin.top]);
// Grid
svg.append("g")
.attr("stroke", "#E5E5E5")
.attr("stroke-width", 0.5)
.selectAll("line")
.data(d3.range(0, 1.1, 0.1))
.join("line")
.attr("x1", d => xScale(d))
.attr("x2", d => xScale(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom);
// Zero line
svg.append("line")
.attr("x1", margin.left)
.attr("x2", width - margin.right)
.attr("y1", yScale(0))
.attr("y2", yScale(0))
.attr("stroke", "#999")
.attr("stroke-dasharray", "4,4");
// Original signal
const line = d3.line()
.x(d => xScale(d.t))
.y(d => yScale(d.y));
svg.append("path")
.datum(signalData)
.attr("fill", "none")
.attr("stroke", "#3498DB")
.attr("stroke-width", 2)
.attr("d", line);
// Reconstructed signal
svg.append("path")
.datum(reconstructedData)
.attr("fill", "none")
.attr("stroke", "#E74C3C")
.attr("stroke-width", 1.5)
.attr("stroke-dasharray", "5,3")
.attr("d", line);
// Sample points
svg.selectAll(".sample")
.data(samplePoints)
.join("circle")
.attr("cx", d => xScale(d.t))
.attr("cy", d => yScale(d.y))
.attr("r", 5)
.attr("fill", "#E67E22")
.attr("stroke", "white")
.attr("stroke-width", 2);
// Vertical lines to samples
svg.selectAll(".sample-line")
.data(samplePoints)
.join("line")
.attr("x1", d => xScale(d.t))
.attr("x2", d => xScale(d.t))
.attr("y1", yScale(0))
.attr("y2", d => yScale(d.y))
.attr("stroke", "#E67E22")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "2,2")
.attr("opacity", 0.5);
// Axes
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale).ticks(10).tickFormat(d => d + "s"))
.selectAll("text")
.attr("font-size", 11);
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale).ticks(5))
.selectAll("text")
.attr("font-size", 11);
// Labels
svg.append("text")
.attr("x", width / 2)
.attr("y", height - 10)
.attr("text-anchor", "middle")
.attr("font-size", 12)
.text("Time (seconds)");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", 15)
.attr("text-anchor", "middle")
.attr("font-size", 12)
.text("Amplitude");
// Legend
const legend = svg.append("g")
.attr("transform", `translate(${width - 180}, 15)`);
legend.append("line")
.attr("x1", 0).attr("x2", 20)
.attr("y1", 5).attr("y2", 5)
.attr("stroke", "#3498DB")
.attr("stroke-width", 2);
legend.append("text")
.attr("x", 25).attr("y", 9)
.attr("font-size", 11)
.text("Original Signal");
legend.append("line")
.attr("x1", 0).attr("x2", 20)
.attr("y1", 25).attr("y2", 25)
.attr("stroke", "#E74C3C")
.attr("stroke-width", 1.5)
.attr("stroke-dasharray", "5,3");
legend.append("text")
.attr("x", 25).attr("y", 29)
.attr("font-size", 11)
.text("Reconstructed");
legend.append("circle")
.attr("cx", 10).attr("cy", 45)
.attr("r", 5)
.attr("fill", "#E67E22");
legend.append("text")
.attr("x", 25).attr("y", 49)
.attr("font-size", 11)
.text("Sample Points");
return svg.node();
}