Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | 2x 2x 3018x 9x 15x 1x 14x 14x 14x 2x 16x 12x 7x 1x 6x 5x 5x 1x 4x 4x 4x 4x 2x 2x 9x 7x 2x 2x 2x 3x 3x 3x 3x 3x 3x 2997x 3x | /**
* EI-3152 — Calculus Visualization: pure function-plot config builders + math.
*
* These helpers are DOM-free and deterministic (mathjs) so they can be unit-tested
* in isolation. The React panel feeds their output straight into `functionPlot(...)`.
*
* - derivative mode → the curve + a tangent line at a chosen point x0
* (function-plot's native `derivative: { fn: <f'(x)>, x0 }`).
* - integral mode → the curve + the shaded definite-integral area on [a, b]
* (function-plot's native `closed: true` + `range: [a, b]`), bounds annotated.
*/
import { derivative as mathDerivative, evaluate as mathEvaluate, parse as mathParse } from "mathjs";
export const CALCULUS_MODES = Object.freeze({ DERIVATIVE: "derivative", INTEGRAL: "integral" });
export const DEFAULT_VIEWPORT = Object.freeze({ xMin: -10, xMax: 10, yMin: -10, yMax: 10 });
/** Evaluate f(x) at a single x. Throws if the expression is invalid. */
export function evaluateAt(fn, x) {
return mathEvaluate(fn, { x });
}
/** Symbolic f'(x) as a string (what function-plot's `derivative.fn` expects). */
export function derivativeExpression(fn) {
return mathDerivative(fn, "x").toString();
}
/**
* Validate calculus input. Returns { valid: boolean, error: string | null }.
* - `fn` must be a parseable expression in x.
* - derivative mode: `x0` must be a finite number.
* - integral mode: `a` and `b` must be finite numbers with a < b.
*/
export function validateCalculusInput({ mode, fn, x0, a, b } = {}) {
if (!fn || typeof fn !== "string" || !fn.trim()) {
return { valid: false, error: "Enter a function f(x)." };
}
try {
mathParse(fn);
// Probe-evaluate so obviously non-x / malformed expressions fail early.
evaluateAt(fn, 1);
} catch {
return { valid: false, error: "That function could not be parsed." };
}
// Number("") === 0 and Number(null) === 0, so reject blanks BEFORE coercion.
const isBlank = (v) => v === "" || v === null || v === undefined;
if (mode === CALCULUS_MODES.DERIVATIVE) {
if (isBlank(x0) || !Number.isFinite(Number(x0))) {
return { valid: false, error: "Enter a point x₀ for the tangent line." };
}
return { valid: true, error: null };
}
Eif (mode === CALCULUS_MODES.INTEGRAL) {
if (isBlank(a) || isBlank(b)) {
return { valid: false, error: "Enter both integration bounds a and b." };
}
const na = Number(a);
const nb = Number(b);
Iif (!Number.isFinite(na) || !Number.isFinite(nb)) {
return { valid: false, error: "Enter both integration bounds a and b." };
}
if (na >= nb) {
return { valid: false, error: "Lower bound a must be less than upper bound b." };
}
return { valid: true, error: null };
}
return { valid: false, error: "Unknown mode." };
}
function axes(viewport = DEFAULT_VIEWPORT) {
return {
grid: true,
xAxis: { domain: [viewport.xMin, viewport.xMax] },
yAxis: { domain: [viewport.yMin, viewport.yMax] },
};
}
/**
* function-plot options for derivative + tangent at x0.
* Returns a plain options object WITHOUT `target` (the caller adds it).
*/
export function buildDerivativeConfig({ fn, x0, viewport = DEFAULT_VIEWPORT } = {}) {
return {
...axes(viewport),
data: [
{ fn, color: "#0091DE" },
{ fn, derivative: { fn: derivativeExpression(fn), x0: Number(x0) }, color: "#e53935" },
],
};
}
/**
* function-plot options for the definite-integral area on [a, b].
* `closed: true` + `range` shades between the curve and the x-axis; the bounds
* are drawn as vertical annotations.
*/
export function buildIntegralConfig({ fn, a, b, viewport = DEFAULT_VIEWPORT } = {}) {
const lo = Number(a);
const hi = Number(b);
return {
...axes(viewport),
data: [
{ fn, closed: true, range: [lo, hi], color: "#0091DE" },
{ fn, color: "#0091DE" },
],
annotations: [
{ x: lo, text: `a = ${lo}` },
{ x: hi, text: `b = ${hi}` },
],
};
}
/**
* Numeric definite integral of fn on [a, b] via composite Simpson's rule.
* `n` (even) is the number of subintervals.
*/
export function definiteIntegral(fn, a, b, n = 1000) {
const lo = Number(a);
const hi = Number(b);
const m = n % 2 === 0 ? n : n + 1;
const h = (hi - lo) / m;
let sum = evaluateAt(fn, lo) + evaluateAt(fn, hi);
for (let i = 1; i < m; i += 1) {
sum += (i % 2 === 0 ? 2 : 4) * evaluateAt(fn, lo + i * h);
}
return (h / 3) * sum;
}
|