{
// Define the four functions with their properties
const FUNCTIONS = {
"2x + 1": {
label: "f(x) = 2x + 1",
fn: x => 2 * x + 1,
domain: [-6, 6],
domainInterval: "(-∞, ∞)",
rangeInterval: "(-∞, ∞)",
isRestricted: false
},
"x²": {
label: "f(x) = x²",
fn: x => x * x,
domain: [-6, 6],
domainInterval: "(-∞, ∞)",
rangeInterval: "[0, ∞)",
isRestricted: false,
minOutput: 0
},
"|x|": {
label: "f(x) = |x|",
fn: x => Math.abs(x),
domain: [-6, 6],
domainInterval: "(-∞, ∞)",
rangeInterval: "[0, ∞)",
isRestricted: false,
minOutput: 0
},
"√(x + 5)": {
label: "f(x) = √(x + 5)",
fn: x => (x >= -5) ? Math.sqrt(x + 5) : NaN,
domain: [-5, 6],
domainStart: -5,
domainInterval: "[-5, ∞)",
rangeInterval: "[0, ∞)",
isRestricted: true,
minOutput: 0
}
};
function svgEl(tag) {
return document.createElementNS("http://www.w3.org/2000/svg", tag);
}
const container = document.createElement("div");
container.style.cssText = "max-width: 700px; margin: 1.25rem 0 1.5rem 0; font-family: inherit;";
// Controls
const controls = document.createElement("div");
controls.style.cssText = "display: grid; gap: 0.75rem; margin-bottom: 1rem;";
const funcWrap = document.createElement("label");
funcWrap.style.cssText = "display: grid; gap: 0.25rem; font-size: 0.9rem; color: #374151;";
funcWrap.appendChild(Object.assign(document.createElement("span"), { textContent: "Select function" }));
const funcSel = document.createElement("select");
funcSel.style.cssText = "padding: 0.35rem 0.55rem; border: 1px solid #d1d5db; border-radius: 8px; background: #fff; font: inherit;";
Object.keys(FUNCTIONS).forEach(fn => {
const opt = document.createElement("option");
opt.value = fn;
opt.textContent = FUNCTIONS[fn].label;
funcSel.appendChild(opt);
});
funcSel.value = "2x + 1";
funcWrap.appendChild(funcSel);
controls.appendChild(funcWrap);
const xWrap = document.createElement("label");
xWrap.style.cssText = "display: grid; gap: 0.25rem; font-size: 0.9rem; color: #374151;";
xWrap.appendChild(Object.assign(document.createElement("span"), { textContent: "Input x" }));
const xSlider = document.createElement("input");
xSlider.type = "range";
xSlider.min = "-6";
xSlider.max = "6";
xSlider.step = "0.25";
xSlider.value = "0";
const xVal = document.createElement("span");
xVal.style.cssText = "font-variant-numeric: tabular-nums; color: #6b7280;";
xWrap.append(xSlider, xVal);
controls.appendChild(xWrap);
// Machine visualization
const machineContainer = document.createElement("div");
machineContainer.style.cssText = "background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 1rem; margin-bottom: 1rem;";
const machineLabel = document.createElement("p");
machineLabel.style.cssText = "font-size: 0.9rem; color: #6b7280; margin: 0 0 0.75rem 0; font-weight: 500;";
machineLabel.textContent = "Function machine";
const machineSvg = svgEl("svg");
machineSvg.setAttribute("viewBox", "0 0 600 120");
machineSvg.setAttribute("width", "100%");
machineSvg.style.cssText = "display: block; background: #fff; border: 1px solid #e5e7eb; border-radius: 8px;";
const machineCaption = document.createElement("p");
machineCaption.style.cssText = "font-size: 0.85rem; color: #4b5563; margin: 0.5rem 0 0 0;";
machineContainer.append(machineLabel, machineSvg, machineCaption);
// Graph visualization
const graphContainer = document.createElement("div");
graphContainer.style.cssText = "background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 1rem; margin-bottom: 1rem;";
const graphLabel = document.createElement("p");
graphLabel.style.cssText = "font-size: 0.9rem; color: #6b7280; margin: 0 0 0.75rem 0; font-weight: 500;";
graphLabel.textContent = "Graph and domain/range";
const graphSvg = svgEl("svg");
graphSvg.setAttribute("viewBox", "0 0 580 360");
graphSvg.setAttribute("width", "100%");
graphSvg.style.cssText = "display: block; background: #fff; border: 1px solid #e5e7eb; border-radius: 8px;";
const graphCaption = document.createElement("p");
graphCaption.style.cssText = "font-size: 0.85rem; color: #4b5563; margin: 0.5rem 0 0 0;";
graphContainer.append(graphLabel, graphSvg, graphCaption);
function drawText(svg, x, y, text, fill = "#374151", size = 12, anchor = "middle", weight = "400") {
const t = svgEl("text");
t.setAttribute("x", x);
t.setAttribute("y", y);
t.setAttribute("fill", fill);
t.setAttribute("font-size", size);
t.setAttribute("text-anchor", anchor);
t.setAttribute("font-weight", weight);
t.textContent = text;
svg.appendChild(t);
}
function drawLine(svg, x1, y1, x2, y2, stroke = "#6b7280", width = "1.6", dasharray = null) {
const line = svgEl("line");
line.setAttribute("x1", x1);
line.setAttribute("y1", y1);
line.setAttribute("x2", x2);
line.setAttribute("y2", y2);
line.setAttribute("stroke", stroke);
line.setAttribute("stroke-width", width);
if (dasharray) line.setAttribute("stroke-dasharray", dasharray);
svg.appendChild(line);
}
function redraw() {
const fnKey = funcSel.value;
const fnDef = FUNCTIONS[fnKey];
const x = parseFloat(xSlider.value);
const y = fnDef.fn(x);
const isValidInput = !isNaN(y);
xVal.textContent = x.toFixed(2);
// ===== MACHINE SVG =====
while (machineSvg.firstChild) machineSvg.removeChild(machineSvg.firstChild);
const m_inputX = 60, m_boxX = 180, m_outputX = 420;
const m_centerY = 60;
// Input label and line
drawLine(machineSvg, 20, m_centerY, m_inputX - 20, m_centerY, "#6b7280", "2");
const inDot = svgEl("circle");
inDot.setAttribute("cx", m_inputX);
inDot.setAttribute("cy", m_centerY);
inDot.setAttribute("r", "6");
inDot.setAttribute("fill", isValidInput ? "#d97706" : "#ef4444");
machineSvg.appendChild(inDot);
drawText(machineSvg, m_inputX, m_centerY + 25, x.toFixed(2), "#374151", 12, "middle", "500");
drawText(machineSvg, 20, m_centerY + 8, "Input x", "#6b7280", 11, "start");
// Function box
const boxW = 120, boxH = 50;
const box = svgEl("rect");
box.setAttribute("x", m_boxX - boxW / 2);
box.setAttribute("y", m_centerY - boxH / 2);
box.setAttribute("width", boxW);
box.setAttribute("height", boxH);
box.setAttribute("fill", "#2563eb");
box.setAttribute("fill-opacity", "0.1");
box.setAttribute("stroke", "#2563eb");
box.setAttribute("stroke-width", "2");
box.setAttribute("rx", "4");
machineSvg.appendChild(box);
drawText(machineSvg, m_boxX, m_centerY - 8, fnDef.label.split("=")[0].trim(), "#2563eb", 11, "middle", "600");
drawText(machineSvg, m_boxX, m_centerY + 8, fnDef.label.split("=")[1].trim(), "#2563eb", 11, "middle", "400");
// Arrow and status
if (isValidInput) {
drawLine(machineSvg, m_inputX + 20, m_centerY, m_boxX - boxW / 2 - 10, m_centerY, "#9ca3af", "1.5");
// Arrow head
const arrowHead = svgEl("polygon");
arrowHead.setAttribute("points", `${m_boxX - boxW / 2 - 10},${m_centerY} ${m_boxX - boxW / 2 - 15},${m_centerY - 3} ${m_boxX - boxW / 2 - 15},${m_centerY + 3}`);
arrowHead.setAttribute("fill", "#9ca3af");
machineSvg.appendChild(arrowHead);
// Output line
drawLine(machineSvg, m_boxX + boxW / 2 + 10, m_centerY, m_outputX - 20, m_centerY, "#9ca3af", "1.5");
// Output arrow head
const outArrow = svgEl("polygon");
outArrow.setAttribute("points", `${m_outputX - 20},${m_centerY} ${m_outputX - 25},${m_centerY - 3} ${m_outputX - 25},${m_centerY + 3}`);
outArrow.setAttribute("fill", "#9ca3af");
machineSvg.appendChild(outArrow);
// Output value
const outDot = svgEl("circle");
outDot.setAttribute("cx", m_outputX);
outDot.setAttribute("cy", m_centerY);
outDot.setAttribute("r", "6");
outDot.setAttribute("fill", "#059669");
machineSvg.appendChild(outDot);
drawText(machineSvg, m_outputX, m_centerY + 25, y.toFixed(2), "#374151", 12, "middle", "500");
drawText(machineSvg, 520, m_centerY + 8, "Output y", "#6b7280", 11, "start");
} else {
// Input blocked
drawLine(machineSvg, m_inputX + 20, m_centerY, m_boxX - boxW / 2 - 5, m_centerY, "#ef4444", "2");
drawText(machineSvg, m_boxX, m_centerY + 25, "undefined", "#ef4444", 11, "middle", "500");
}
machineCaption.textContent = isValidInput
? `The input x = ${x.toFixed(2)} produces output y = ${y.toFixed(2)}.`
: `The input x = ${x.toFixed(2)} is outside the domain. The function is undefined.`;
// ===== GRAPH SVG =====
while (graphSvg.firstChild) graphSvg.removeChild(graphSvg.firstChild);
const gX0 = 40, gY0 = 320, gW = 480, gH = 260;
const gXScale = (v) => gX0 + (v + 6) / 12 * gW;
const gYScale = (v) => gY0 - (v + 2) / 10 * gH;
// Grid and axes
for (let i = -6; i <= 6; i += 2) {
drawLine(graphSvg, gXScale(i), gY0, gXScale(i), 40, "#f3f4f6", "1");
drawText(graphSvg, gXScale(i), gY0 + 20, i.toString(), "#9ca3af", 10, "middle");
}
for (let i = -2; i <= 8; i += 2) {
drawLine(graphSvg, gX0, gYScale(i), gX0 + gW, gYScale(i), "#f3f4f6", "1");
drawText(graphSvg, gX0 - 8, gYScale(i) + 4, i.toString(), "#9ca3af", 10, "end");
}
// Axes
drawLine(graphSvg, gX0, gY0, gX0 + gW, gY0, "#6b7280", "1.6"); // x-axis
drawLine(graphSvg, gX0, gY0, gX0, 40, "#6b7280", "1.6"); // y-axis
// Axis labels
drawText(graphSvg, gX0 + gW + 10, gY0 + 5, "x", "#374151", 13, "start", "500");
drawText(graphSvg, gX0 - 8, 30, "y", "#374151", 13, "end", "500");
// Draw function curve
const path = svgEl("path");
let pathD = "";
const step = 0.15;
let moved = false;
const [minX, maxX] = fnDef.domain;
for (let x_i = minX; x_i <= maxX; x_i += step) {
const y_i = fnDef.fn(x_i);
if (!isNaN(y_i)) {
const px = gXScale(x_i);
const py = gYScale(y_i);
pathD += moved ? ` L ${px} ${py}` : `M ${px} ${py}`;
moved = true;
} else {
moved = false;
}
}
path.setAttribute("d", pathD);
path.setAttribute("fill", "none");
path.setAttribute("stroke", "#2563eb");
path.setAttribute("stroke-width", "2.5");
path.setAttribute("stroke-linecap", "round");
path.setAttribute("stroke-linejoin", "round");
graphSvg.appendChild(path);
// Undefined region shading (for restricted domains)
if (fnDef.isRestricted && fnDef.domainStart !== undefined) {
const undefinedRect = svgEl("rect");
undefinedRect.setAttribute("x", gX0);
undefinedRect.setAttribute("y", 40);
undefinedRect.setAttribute("width", gXScale(fnDef.domainStart) - gX0);
undefinedRect.setAttribute("height", gH);
undefinedRect.setAttribute("fill", "#fecaca");
undefinedRect.setAttribute("fill-opacity", "0.15");
graphSvg.appendChild(undefinedRect);
}
// Domain highlighting (green on x-axis)
const domainLeft = fnDef.isRestricted ? fnDef.domainStart : -6;
const domainRight = 6;
const domainBar = svgEl("line");
domainBar.setAttribute("x1", gXScale(domainLeft));
domainBar.setAttribute("y1", gY0 + 6);
domainBar.setAttribute("x2", gXScale(domainRight));
domainBar.setAttribute("y2", gY0 + 6);
domainBar.setAttribute("stroke", "#059669");
domainBar.setAttribute("stroke-width", "3");
domainBar.setAttribute("stroke-linecap", "round");
graphSvg.appendChild(domainBar);
// Range highlighting (amber on y-axis)
let minY = Infinity, maxY = -Infinity;
for (let x_i = domainLeft; x_i <= domainRight; x_i += step) {
const y_i = fnDef.fn(x_i);
if (!isNaN(y_i)) {
minY = Math.min(minY, y_i);
maxY = Math.max(maxY, y_i);
}
}
if (minY !== Infinity) {
const rangeBar = svgEl("line");
rangeBar.setAttribute("x1", gX0 - 6);
rangeBar.setAttribute("y1", gYScale(minY));
rangeBar.setAttribute("x2", gX0 - 6);
rangeBar.setAttribute("y2", gYScale(maxY));
rangeBar.setAttribute("stroke", "#d97706");
rangeBar.setAttribute("stroke-width", "3");
rangeBar.setAttribute("stroke-linecap", "round");
graphSvg.appendChild(rangeBar);
}
// Current input-output point (amber)
if (isValidInput) {
const px = gXScale(x);
const py = gYScale(y);
// Dashed projections to axes
drawLine(graphSvg, px, py, px, gY0, "#9ca3af", "1.2", "4,3");
drawLine(graphSvg, px, py, gX0, py, "#9ca3af", "1.2", "4,3");
// Point
const dot = svgEl("circle");
dot.setAttribute("cx", px);
dot.setAttribute("cy", py);
dot.setAttribute("r", "6");
dot.setAttribute("fill", "#d97706");
dot.setAttribute("stroke", "#fff");
dot.setAttribute("stroke-width", "1.5");
graphSvg.appendChild(dot);
}
graphCaption.innerHTML = `<strong>Domain:</strong> ${fnDef.domainInterval} <strong>Range:</strong> ${fnDef.rangeInterval}`;
}
// Event listeners
[funcSel, xSlider].forEach(el => {
el.addEventListener("change", () => {
const fnDef = FUNCTIONS[funcSel.value];
const newX = Math.max(fnDef.domain[0], Math.min(fnDef.domain[1], parseFloat(xSlider.value)));
xSlider.value = newX;
redraw();
});
el.addEventListener("input", redraw);
});
container.append(controls, machineContainer, graphContainer);
redraw();
return container;
}