{
// Generate moons-like data
const n = 200;
const data = [];
for (let i = 0; i < n; i++) {
const t = (i / n) * Math.PI;
if (i < n / 2) {
data.push({
x: Math.cos(t) + (Math.random() - 0.5) * 0.3,
y: Math.sin(t) + (Math.random() - 0.5) * 0.3,
cls: 0
});
} else {
data.push({
x: 1 - Math.cos(t - Math.PI) + (Math.random() - 0.5) * 0.3,
y: 1 - Math.sin(t - Math.PI) - 0.5 + (Math.random() - 0.5) * 0.3,
cls: 1
});
}
}
const w = 700, h = 400;
const svg = d3.create("svg").attr("width", w).attr("height", h)
.style("background", "#fafafa").style("border-radius", "8px")
.style("border", "1px solid #ddd");
const xScale = d3.scaleLinear().domain([-1.5, 2.5]).range([50, w - 30]);
const yScale = d3.scaleLinear().domain([-1.5, 2]).range([h - 30, 30]);
// Draw a linear decision boundary (rough best fit)
svg.append("line")
.attr("x1", xScale(-1.5)).attr("y1", yScale(1.2))
.attr("x2", xScale(2.5)).attr("y2", yScale(-0.8))
.attr("stroke", "#888").attr("stroke-width", 2)
.attr("stroke-dasharray", "8,4");
svg.append("text").attr("x", xScale(1.8)).attr("y", yScale(-0.5))
.text("Best linear boundary").attr("fill", "#888")
.attr("font-size", 13).attr("font-style", "italic");
svg.selectAll("circle").data(data).join("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 4)
.attr("fill", d => d.cls === 0 ? "#2E86AB" : "#D62828")
.attr("opacity", 0.7);
svg.append("text").attr("x", w / 2).attr("y", 20)
.text("No straight line can separate these classes")
.attr("text-anchor", "middle").attr("font-size", 15)
.attr("font-weight", "bold").attr("fill", "#333");
return svg.node();
}