A very visual Bayesian calculator.
import { App, Component, getPropertyValue, formField, inputRange, div, h1, span, strong, type HValues } from "domeleon";
class BayesCalculator extends Component {
prior = 0.1
sensitivity = 0.9
falsePositiveRate = 0.05
get marginalLikelihood() {
const truePositives = this.sensitivity * this.prior
return truePositives + this.falsePositiveRate * (1 - this.prior)
}
get posterior() {
const numerator = this.sensitivity * this.prior
const denominator = this.marginalLikelihood
return denominator === 0 ? 0 : numerator / denominator
}
slider (prop: () => number, labelText: string, notation: string, paramClass: string) {
return formField({
target: this,
prop,
inputFn: inputRange,
fieldAttrs: { class: "control-row" },
inputProps: { attrs: { min: "0", max: "1", step: "0.01" } },
label: div(
`${labelText} (`,
span({ class: paramClass }, notation),
"): ",
strong({ class: paramClass }, getPropertyValue(this, prop).toFixed(2))
)
})
}
private controls() {
return div({ class: "controls" },
this.slider(() => this.prior, "Prior Probability", "P(A)", "param-prior"),
this.slider(() => this.sensitivity, "Sensitivity", "P(B|A)", "param-sensitivity"),
this.slider(() => this.falsePositiveRate, "False Positive Rate", "P(B|¬A)", "param-fpr")
)
}
// Helper to create a region with subregions
private region(leftPercent: number, widthPercent: number, isA: boolean) {
const truePositiveHeightPercent = (isA ? this.sensitivity : this.falsePositiveRate) * 100
const complementHeightPercent = 100 - truePositiveHeightPercent
const regionClass = isA ? "region-a" : "region-nota"
const positiveClass = isA ? "subregion-ab" : "subregion-notab"
const negativeClass = isA ? "subregion-anotb" : "subregion-notanotb"
return div({
class: `region ${regionClass}`,
style: { left: `${leftPercent}%`, width: `${widthPercent}%` }
},
div({ class: `subregion ${positiveClass}`, style: { height: `${truePositiveHeightPercent}%` } }),
div({ class: `subregion ${negativeClass}`, style: { height: `${complementHeightPercent}%` } })
);
}
private visualization() {
const priorPercent = this.prior * 100
const notAPercent = (1 - this.prior) * 100
return div({ class: "bayes-rect" },
this.region(0, priorPercent, true),
this.region(priorPercent, notAPercent, false)
);
}
// Helper to create a fraction in an equation step
private fraction(numeratorContent: HValues[], denominatorContent: HValues[]) {
return div({ class: "fraction" },
div({ class: "numerator" }, ...numeratorContent),
div({ class: "denominator" }, ...denominatorContent)
);
}
// The famous Bayes' theorem formula: P(A|B) = P(B|A) × P(A) / P(B)
private bayesFormula() {
return this.fraction(
[
span({ class: "param-sensitivity" }, "P(B|A)"),
" × ",
span({ class: "param-prior" }, "P(A)")
],
[
span({ class: "param-marginal" }, "P(B)")
]
)
}
// Helper to create the full Bayes' theorem expansion (symbolic or numerical)
private bayesExpansion(useNumbers: boolean) {
const mult = " × "
const sens = useNumbers ? this.sensitivity.toFixed(2) : "P(B|A)"
const pri = useNumbers ? this.prior.toFixed(2) : "P(A)"
const fpr = useNumbers ? this.falsePositiveRate.toFixed(2) : "P(B|¬A)"
const notA = useNumbers ? (1 - this.prior).toFixed(2) : "P(¬A)"
const sensClass = "param-sensitivity"
const priClass = "param-prior"
const fprClass = "param-fpr"
const notAClass = "param-nota"
return this.fraction(
[
span({ class: sensClass }, sens),
mult,
span({ class: priClass }, pri)
],
[
span({ class: sensClass }, sens),
mult,
span({ class: priClass }, pri),
" + ",
span({ class: fprClass }, fpr),
mult,
span({ class: notAClass }, notA)
]
)
}
private equations() {
return div({ class: "equation-row" },
div({ class: "eq-lhs-container" },
div({ class: "eq-lhs" },
span({ class: "param-posterior" }, "P(A|B)"),
span({ class: "equals-inline" }, "=")
),
div({ class: "eq-label" }, "(Posterior)")
),
div({ class: "eq-rhs-stack" },
div({ class: "eq-step" }, this.bayesFormula()),
div({ class: "eq-step" }, this.bayesExpansion(false)),
div({ class: "eq-step" }, this.bayesExpansion(true)),
div({ class: "eq-step eq-result" },
div({ class: "result-value param-posterior" }, this.posterior.toFixed(3))
)
)
);
}
view() {
return div({ class: "app" },
h1("Bayes' Calculator"),
this.controls(),
this.visualization(),
this.equations()
)
}
}
new App({ root: new BayesCalculator(), id: "app" })