// Observable JS module
export default function define(runtime, observer) {
const main = runtime.module();
// Define inputs and reactive values
main.variable(observer("viewof singleSampleSD")).define("viewof singleSampleSD", ["html"], html => {
return html`
`;
});
// Extract input values
main.variable(observer("singleSampleSD")).define("singleSampleSD", ["viewof singleSampleSD"], view => {
return {
sd: parseFloat(view.querySelector("#normalSD").value),
sampleSize: parseInt(view.querySelector("#sampleSize").value),
confidenceLevel: parseInt(view.querySelector("#confidenceLevel").value)
};
});
// Generate random sample
main.variable(observer("singleSample")).define("singleSample", ["singleSampleSD", "normrnd"], (params, normrnd) => {
return normrnd(0, params.sd, params.sampleSize);
});
// Define the plot
main.variable(observer("singleSamplePlot")).define("singleSamplePlot", ["d3", "singleSample", "singleSampleSD"], (d3, sample, params) => {
const width = 800;
const height = 400;
const margin = {top: 30, right: 30, bottom: 50, left: 50};
// Calculate statistics
const mean = d3.mean(sample);
const sd = d3.deviation(sample);
const se = sd / Math.sqrt(sample.length);
// Calculate CI
const alpha = 1 - (params.confidenceLevel / 100);
const zCrit = -d3.quantileNormal(alpha / 2);
const halfwidth = zCrit * se;
const ciLower = mean - halfwidth;
const ciUpper = mean + halfwidth;
// Create SVG
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Create scales
const x = d3.scaleLinear()
.domain([-7, 7])
.range([margin.left, width - margin.right]);
const y = d3.scaleLinear()
.domain([0, 0.5])
.range([height - margin.bottom, margin.top]);
// Generate histogram
const bins = d3.bin()
.domain(x.domain())
.thresholds(20)
(sample);
// Normalize bin heights
const binMax = d3.max(bins, d => d.length) / sample.length;
const yScale = d3.scaleLinear()
.domain([0, binMax])
.range([height - margin.bottom, margin.top]);
// Draw histogram
svg.append("g")
.selectAll("rect")
.data(bins)
.join("rect")
.attr("x", d => x(d.x0))
.attr("y", d => yScale(d.length / sample.length))
.attr("width", d => Math.max(0, x(d.x1) - x(d.x0) - 1))
.attr("height", d => yScale(0) - yScale(d.length / sample.length))
.attr("fill", "lightblue");
// Draw normal curve (population)
const curve = d3.line()
.x(d => x(d))
.y(d => y(d3.randomNormal.pdf(d, 0, params.sd)));
const points = d3.range(-7, 7, 0.1);
svg.append("path")
.attr("d", curve(points))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none");
// Draw axes
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x));
// Add vertical lines for population and sample means
svg.append("line")
.attr("x1", x(0))
.attr("x2", x(0))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom)
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "5,5");
svg.append("line")
.attr("x1", x(mean))
.attr("x2", x(mean))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom)
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "5,5");
// Add CI line
svg.append("line")
.attr("x1", x(ciLower))
.attr("x2", x(ciUpper))
.attr("y1", y(0.5))
.attr("y2", y(0.5))
.attr("stroke", "black")
.attr("stroke-width", 3);
// Add CI endpoints
svg.append("line")
.attr("x1", x(ciLower))
.attr("x2", x(ciLower))
.attr("y1", y(0.48))
.attr("y2", y(0.52))
.attr("stroke", "black")
.attr("stroke-width", 3);
svg.append("line")
.attr("x1", x(ciUpper))
.attr("x2", x(ciUpper))
.attr("y1", y(0.48))
.attr("y2", y(0.52))
.attr("stroke", "black")
.attr("stroke-width", 3);
// Add legend
const legend = svg.append("g")
.attr("transform", `translate(${width - margin.right - 200}, ${margin.top + 20})`);
const legendItems = [
{ label: "Population distribution", color: "red", type: "line" },
{ label: "Population mean", color: "red", type: "dashed" },
{ label: "Sample mean", color: "blue", type: "dashed" },
{ label: "95% CI", color: "black", type: "line" }
];
legendItems.forEach((item, i) => {
legend.append("line")
.attr("x1", 0)
.attr("x2", 30)
.attr("y1", i * 25)
.attr("y2", i * 25)
.attr("stroke", item.color)
.attr("stroke-width", 2)
.attr("stroke-dasharray", item.type === "dashed" ? "5,5" : null);
legend.append("text")
.attr("x", 40)
.attr("y", i * 25 + 5)
.text(item.label);
});
return svg.node();
});
// Helper function for normal random numbers
main.variable(observer("normrnd")).define("normrnd", ["d3"], d3 => {
return (mean, sd, n) => {
const normal = d3.randomNormal(mean, sd);
return Array.from({length: n}, normal);
};
});
// Add normal PDF calculation
d3.randomNormal.pdf = (x, mean, sd) => {
return (1 / (sd * Math.sqrt(2 * Math.PI))) *
Math.exp(-0.5 * Math.pow((x - mean) / sd, 2));
};
return main;
}