A very visual Bayesian calculator.
---
format: typebulb/v1
name: "Bayes' Calculator"
---
**code.tsx**
```tsx
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" })
```
**styles.css**
```css
/* Theme: Light Mode */
html[data-theme="light"] {
color-scheme: light;
--bg-primary: #ffffff;
--text-primary: #333333;
--bg-container: #fafafa;
--border-container: #e5e5e5;
--border-color: #333333;
--color-prior: rgb(0, 69, 229); /* P(A) - also A∩¬B region */
--color-nota: rgb(79, 149, 255); /* P(¬A) - right side total */
--color-sensitivity: #059669; /* P(B|A) - also A∩B region */
--color-fpr: #dc2626; /* P(B|¬A) - also ¬A∩B region */
--color-marginal: #d97706; /* P(B) - marginal likelihood */
--color-posterior: #9333ea; /* P(A|B) */
--color-truenegative: #94a3b8; /* True negative: ¬A∩¬B */
--region-a-bg: #dbeafe; /* Light background for A region */
--region-nota-bg: #e6e6e6; /* Light background for ¬A region */
}
/* Theme: Dark Mode */
html[data-theme="dark"] {
color-scheme: dark;
--bg-primary: #1e1e1e;
--text-primary: #d4d4d4;
--bg-container: #252525;
--border-container: #3a3a3a;
--border-color: #666666;
--color-prior: #3b82f6; /* P(A) - also A∩¬B region - deeper blue for contrast with green */
--color-nota: #93c5fd; /* P(¬A) - right side total - lighter blue for contrast with bg */
--color-sensitivity: #10b981; /* P(B|A) - also A∩B region */
--color-fpr: #ef4444; /* P(B|¬A) - also ¬A∩B region */
--color-marginal: #f59e0b; /* P(B) - marginal likelihood */
--color-posterior: #a855f7; /* P(A|B) */
--color-truenegative: #475569; /* True negative: ¬A∩¬B */
--region-a-bg: #1a3a52; /* Dark background for A region */
--region-nota-bg: #2a2a2a; /* Dark background for ¬A region */
}
/* Base Styles */
body {
font-family: sans-serif;
margin: 0;
padding: 1rem 0.5rem;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
overflow-x: hidden;
}
.app {
max-width: 900px;
width: 100%;
margin: 0 auto;
padding: 2rem;
background: var(--bg-container);
border: 1px solid var(--border-container);
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
h1 {
text-align: center;
margin: 0.5em 0 1em 0;
}
/* Bayesian Visualization Rectangle */
.region { position: absolute; top: 0; height: 100%; }
.region-a { background: var(--region-a-bg); }
.region-nota { background: var(--region-nota-bg); }
.bayes-rect {
position: relative;
width: 300px;
height: 200px;
margin: 3em auto 0;
}
.subregion-ab { background: var(--color-sensitivity); }
.subregion-anotb { background: var(--color-prior); }
.subregion-notab { background: var(--color-fpr); }
.subregion-notanotb { background: var(--color-truenegative); }
/* Parameter Controls */
.controls {
margin: 0 0 3em 0;
}
.control-row { margin-bottom: 1.5em; }
.control-row > div {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.control-row label { text-align: right; }
input[type='range'] {
width: 100%;
}
@media (min-width: 640px) {
.control-row > div {
display: grid;
grid-template-columns: 300px 1fr;
gap: 1.5em;
align-items: center;
}
}
/* Equation Display */
.equation-row {
display: flex;
align-items: center;
justify-content: center;
gap: 1em;
margin: 1em 0 0 0;
padding: 1em;
}
.eq-lhs-container {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.25em;
}
.eq-label {
font-size: 1.1em;
font-style: normal;
font-family: sans-serif;
color: var(--text-primary);
opacity: 0.7;
}
.eq-lhs {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 0.2em;
font-size: 1.1em;
font-family: 'Georgia', 'Cambria Math', serif;
font-style: italic;
}
.eq-rhs-stack { display: flex; flex-direction: column; gap: 1em; }
.eq-step {
font-size: 1.1em;
font-family: 'Georgia', 'Cambria Math', serif;
font-style: italic;
display: flex;
justify-content: center;
align-items: center;
}
.eq-result { font-style: normal; }
.result-value {
font-weight: bold;
font-size: 1.8em;
font-family: 'Consolas', 'Monaco', monospace;
}
.equals-inline {
font-size: 1.3em;
font-weight: bold;
color: var(--text-primary);
font-style: normal;
margin: 0 0.25em;
}
.fraction {
display: inline-flex;
flex-direction: column;
align-items: center;
vertical-align: middle;
}
.numerator { padding: 0.25em 0.5em; border-bottom: 2px solid var(--text-primary); text-align: center; }
.denominator { padding: 0.25em 0.5em; text-align: center; }
/* Parameter Color Coding */
[class^="param-"] { font-weight: 600; }
.param-prior { color: var(--color-prior); }
.param-nota { color: var(--color-nota); }
.param-sensitivity { color: var(--color-sensitivity); }
.param-fpr { color: var(--color-fpr); }
.param-marginal { color: var(--color-marginal); }
.param-posterior { color: var(--color-posterior); }
```
**index.html**
```html
<div id="app"></div>
```
**config.json**
```json
{
"dependencies": {
"domeleon": "^0.5.2"
},
"description": "A very visual Bayesian calculator."
}
```