feat: add binomial distribution calc #1
18 changed files with 1092 additions and 614 deletions
938
Cargo.lock
generated
938
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
22
Cargo.toml
22
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hypergeometric-calc"
|
name = "distribution-calc"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["Felipe Contreras Salinas <felipe@bstr.cl>"]
|
authors = ["Felipe Contreras Salinas <felipe@bstr.cl>"]
|
||||||
|
|
||||||
|
|
@ -9,18 +9,18 @@ authors = ["Felipe Contreras Salinas <felipe@bstr.cl>"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
console_log = "1.0.0"
|
console_log = "1.0.0"
|
||||||
fluent-templates = "0.13.0"
|
fluent-templates = "0.13.2"
|
||||||
leptos = { version = "0.8.2", features = ["csr", "tracing"] }
|
leptos = { version = "0.8.14", features = ["csr", "tracing"] }
|
||||||
leptos-fluent = "0.2.15"
|
leptos-fluent = "0.2.20"
|
||||||
leptos_meta = { version = "0.8.2" }
|
leptos_meta = { version = "0.8.5" }
|
||||||
leptos_router = { version = "0.8.2" }
|
leptos_router = { version = "0.8.10" }
|
||||||
log = "0.4.26"
|
log = "0.4.29"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen = "0.2.100"
|
wasm-bindgen = "0.2.106"
|
||||||
wasm-bindgen-test = "0.3.50"
|
wasm-bindgen-test = "0.3.56"
|
||||||
web-sys = { version = "0.3.77", features = ["Document", "Window"] }
|
web-sys = { version = "0.3.83", features = ["Document", "Window"] }
|
||||||
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -219,7 +219,7 @@ If you develop a new program, and you want it to be of the greatest possible use
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
hypergeometric-calc
|
distribution-calc
|
||||||
Copyright (C) 2025 LuckyMeowth
|
Copyright (C) 2025 LuckyMeowth
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# hypergeometric-calc
|
# distribution-calc
|
||||||
Web calculator for Hypergeometric Distribution built using [Leptos].
|
Web calculator for Probability Distributions built using [Leptos].
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
You will need to install the Rust toolchain. We recommend to do using [Rustup]. Once
|
You will need to install the Rust toolchain. We recommend to do using [Rustup]. Once
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
not-found = We couldn't find that page.
|
not-found = We couldn't find that page.
|
||||||
title = Hypergeometric Distribution Calculator
|
title-home = Calculators
|
||||||
|
title-binom = Binomial Distribution Calculator
|
||||||
|
title-hyper = Hypergeometric Distribution Calculator
|
||||||
population = Population Size
|
population = Population Size
|
||||||
successes = Successes in Population
|
successes = Successes in Population
|
||||||
sample = Sample Size
|
sample = Sample Size
|
||||||
sample-successes = Successes in Sample
|
sample-successes = Successes in Sample
|
||||||
|
hyper-description = Hypergeometric distribution measures the probability of getting a given amount of a certain type of elements from a sample on a population. Think on drawing a hand of 7 cards (sample size = 7) from a 52 cards deck (population size = 52) and wanting to know the probability of getting a given number of aces (successes in sample = X, successes in population = 4).
|
||||||
|
success-probability = Success probability
|
||||||
|
trials-number = Number of trials
|
||||||
|
successes-number = Number of successes
|
||||||
|
binom-description = Binomial distribution measures the probability of getting a given amount of successes in a sequence of experiments. For example, if you flip 5 (number of trials = 5) balanced coins (success probability = 0.5), the distribution describes the probability of having a given number of heads (successes number = X).
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
not-found = No pudimos encontrar esta página.
|
not-found = No pudimos encontrar esta página.
|
||||||
title = Calculadora Distribución Hipergeométrica
|
title-home = Calculadoras
|
||||||
|
title-binom = Calculadora Distribución Binomial
|
||||||
|
title-hyper = Calculadora Distribución Hipergeométrica
|
||||||
population = Tamaño población
|
population = Tamaño población
|
||||||
successes = Éxitos en la población
|
successes = Éxitos en la población
|
||||||
sample = Tamaño de la muestra
|
sample = Tamaño de la muestra
|
||||||
sample-successes = Éxitos en la muestra
|
sample-successes = Éxitos en la muestra
|
||||||
|
hyper-description = La distribución hipergeométrica mide la probabilidad de obtener una cierta cantidad de elementos de cierto tipo en una muestra de una población. Por ejemplo, si tomamos una mano de 7 cartas (tamaño de la muestra = 7) de un mazo de 52 cartas (tamaño población = 52) y queremos saber la probabilidad de tener cierta cantidad de ases (éxitos en la muestra = X, éxitos en la población = 4).
|
||||||
|
success-probability = Probabilidad de éxito
|
||||||
|
trials-number = Número de intentos
|
||||||
|
successes-number = Número de éxitos
|
||||||
|
binom-description = La distribución binomial mide la probabilidad de obtener un cierto número de éxitos en una secuencia de experimentos. Por ejemplo, si lanzas 5 (número de intentos = 5) monedas justas (probabilidad de éxito = 0.5), la distribución describe la probabilidad de obtener un cierto número de monedas (número de éxitos = X).
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,11 @@ body {
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
justify-content: space-between;
|
||||||
|
padding: 0.1em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
header > select {
|
header select {
|
||||||
margin: 0 1em;
|
margin: 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,3 +93,10 @@ div.results > span.right {
|
||||||
grid-column: 4 / 5;
|
grid-column: 4 / 5;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.calc-description {
|
||||||
|
max-width: 55em;
|
||||||
|
text-align: left;
|
||||||
|
hyphens: auto;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
|
||||||
420
src/calc.rs
420
src/calc.rs
|
|
@ -2,56 +2,179 @@
|
||||||
|
|
||||||
use std::{collections::HashMap, iter::repeat};
|
use std::{collections::HashMap, iter::repeat};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug)]
|
||||||
|
pub struct HyperGeometricInput {
|
||||||
|
population_size: u8,
|
||||||
|
successes: u8,
|
||||||
|
sample_size: u8,
|
||||||
|
sample_successes: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HyperGeometricInput {
|
||||||
|
pub fn new(
|
||||||
|
population_size: u8,
|
||||||
|
successes: u8,
|
||||||
|
sample_size: u8,
|
||||||
|
sample_successes: u8,
|
||||||
|
) -> Option<Self> {
|
||||||
|
if successes > population_size
|
||||||
|
|| sample_size > population_size
|
||||||
|
|| sample_successes > sample_size
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self {
|
||||||
|
population_size,
|
||||||
|
successes,
|
||||||
|
sample_size,
|
||||||
|
sample_successes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of hypergeometric probability calculation.
|
||||||
|
#[derive(Default, Debug, PartialEq)]
|
||||||
pub struct HyperGeometricProb {
|
pub struct HyperGeometricProb {
|
||||||
|
/// Probability of getting exactly X successes in the sample.
|
||||||
pub exactly: f64,
|
pub exactly: f64,
|
||||||
|
/// Probability of getting strictly less than X successes in the sample.
|
||||||
pub less_than: f64,
|
pub less_than: f64,
|
||||||
|
/// Probability of getting less than or exactly X successes in the sample.
|
||||||
pub less_or_equal: f64,
|
pub less_or_equal: f64,
|
||||||
|
/// Probability of getting strictly more X successes in the sample.
|
||||||
pub greater_than: f64,
|
pub greater_than: f64,
|
||||||
|
/// Probability of getting more than or exactly X successes in the sample.
|
||||||
pub greater_or_equal: f64,
|
pub greater_or_equal: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hyper_geometric(
|
pub fn hyper_geometric(input: HyperGeometricInput) -> HyperGeometricProb {
|
||||||
population_size: u8,
|
let exactly = hyper_geometric_exactly(&input);
|
||||||
successes: u8,
|
let (less_than, less_or_equal, greater_or_equal, greater_than) = if input.sample_successes
|
||||||
sample_size: u8,
|
< input.sample_size / 2
|
||||||
sample_successes: u8,
|
|
||||||
) -> Option<HyperGeometricProb> {
|
|
||||||
if successes > population_size
|
|
||||||
|| sample_size > population_size
|
|
||||||
|| sample_successes > sample_size
|
|
||||||
{
|
{
|
||||||
None
|
let less_than = (0..input.sample_successes)
|
||||||
|
.map(|i| {
|
||||||
|
hyper_geometric_exactly(&HyperGeometricInput {
|
||||||
|
population_size: input.population_size,
|
||||||
|
successes: input.successes,
|
||||||
|
sample_size: input.sample_size,
|
||||||
|
sample_successes: i,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.sum::<f64>()
|
||||||
|
.abs();
|
||||||
|
let less_or_equal = less_than + exactly;
|
||||||
|
let greater_or_equal = (1.0 - less_than).abs();
|
||||||
|
let greater_than = (1.0 - less_or_equal).abs();
|
||||||
|
(less_than, less_or_equal, greater_or_equal, greater_than)
|
||||||
} else {
|
} else {
|
||||||
let exactly =
|
let greater_than = (input.sample_successes + 1..=input.sample_size.min(input.successes))
|
||||||
hyper_geometric_exactly(population_size, successes, sample_size, sample_successes);
|
.map(|i| {
|
||||||
let (less_than, less_or_equal, greater_or_equal, greater_than) =
|
hyper_geometric_exactly(&HyperGeometricInput {
|
||||||
if sample_successes < sample_size / 2 {
|
population_size: input.population_size,
|
||||||
let less_than = (0..sample_successes)
|
successes: input.successes,
|
||||||
.map(|i| hyper_geometric_exactly(population_size, successes, sample_size, i))
|
sample_size: input.sample_size,
|
||||||
.sum::<f64>()
|
sample_successes: i,
|
||||||
.abs();
|
})
|
||||||
let less_or_equal = less_than + exactly;
|
})
|
||||||
let greater_or_equal = (1.0 - less_than).abs();
|
.sum::<f64>()
|
||||||
let greater_than = (1.0 - less_or_equal).abs();
|
.abs();
|
||||||
(less_than, less_or_equal, greater_or_equal, greater_than)
|
let greater_or_equal = greater_than + exactly;
|
||||||
} else {
|
let less_or_equal = (1.0 - greater_than).abs();
|
||||||
let greater_than = (sample_successes + 1..=sample_size)
|
let less_than = (1.0 - greater_or_equal).abs();
|
||||||
.map(|i| hyper_geometric_exactly(population_size, successes, sample_size, i))
|
(less_than, less_or_equal, greater_or_equal, greater_than)
|
||||||
.sum::<f64>()
|
};
|
||||||
.abs();
|
HyperGeometricProb {
|
||||||
let greater_or_equal = greater_than + exactly;
|
exactly,
|
||||||
let less_or_equal = (1.0 - greater_than).abs();
|
less_than,
|
||||||
let less_than = (1.0 - greater_or_equal).abs();
|
less_or_equal,
|
||||||
(less_than, less_or_equal, greater_or_equal, greater_than)
|
greater_than,
|
||||||
};
|
greater_or_equal,
|
||||||
Some(HyperGeometricProb {
|
}
|
||||||
exactly,
|
}
|
||||||
less_than,
|
|
||||||
less_or_equal,
|
pub struct BinomialInput {
|
||||||
greater_than,
|
success_probability: f64,
|
||||||
greater_or_equal,
|
trials_number: u8,
|
||||||
})
|
successes_number: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinomialInput {
|
||||||
|
pub fn new(success_probability: f64, trials_number: u8, successes_number: u8) -> Option<Self> {
|
||||||
|
if success_probability < 0.0
|
||||||
|
|| success_probability > 1.0
|
||||||
|
|| successes_number > trials_number
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self {
|
||||||
|
success_probability,
|
||||||
|
trials_number,
|
||||||
|
successes_number,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq)]
|
||||||
|
pub struct BinomialProb {
|
||||||
|
pub exactly: f64,
|
||||||
|
pub less_than: f64,
|
||||||
|
pub less_or_equal: f64,
|
||||||
|
pub greater_than: f64,
|
||||||
|
pub greater_or_equal: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn binomial(input: BinomialInput) -> BinomialProb {
|
||||||
|
let (p_powers, pc_powers) = powers(input.success_probability, input.trials_number);
|
||||||
|
let exactly = binomial_exactly(&input, &p_powers, &pc_powers);
|
||||||
|
let (less_than, less_or_equal, greater_or_equal, greater_than) =
|
||||||
|
if input.successes_number < input.trials_number / 2 {
|
||||||
|
let less_than = (0..input.successes_number)
|
||||||
|
.map(|i| {
|
||||||
|
binomial_exactly(
|
||||||
|
&BinomialInput {
|
||||||
|
success_probability: input.success_probability,
|
||||||
|
trials_number: input.trials_number,
|
||||||
|
successes_number: i,
|
||||||
|
},
|
||||||
|
&p_powers,
|
||||||
|
&pc_powers,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sum::<f64>()
|
||||||
|
.abs();
|
||||||
|
let less_or_equal = less_than + exactly;
|
||||||
|
let greater_or_equal = (1.0 - less_than).abs();
|
||||||
|
let greater_than = (1.0 - less_or_equal).abs();
|
||||||
|
(less_than, less_or_equal, greater_or_equal, greater_than)
|
||||||
|
} else {
|
||||||
|
let greater_than = (input.successes_number + 1..=input.trials_number)
|
||||||
|
.map(|i| {
|
||||||
|
binomial_exactly(
|
||||||
|
&BinomialInput {
|
||||||
|
success_probability: input.success_probability,
|
||||||
|
trials_number: input.trials_number,
|
||||||
|
successes_number: i,
|
||||||
|
},
|
||||||
|
&p_powers,
|
||||||
|
&pc_powers,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sum::<f64>()
|
||||||
|
.abs();
|
||||||
|
let greater_or_equal = greater_than + exactly;
|
||||||
|
let less_or_equal = (1.0 - greater_than).abs();
|
||||||
|
let less_than = (1.0 - greater_or_equal).abs();
|
||||||
|
(less_than, less_or_equal, greater_or_equal, greater_than)
|
||||||
|
};
|
||||||
|
BinomialProb {
|
||||||
|
exactly,
|
||||||
|
less_than,
|
||||||
|
less_or_equal,
|
||||||
|
greater_than,
|
||||||
|
greater_or_equal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,69 +183,129 @@ pub fn hyper_geometric(
|
||||||
///
|
///
|
||||||
/// The formula is choose(successes, sample_successes) * choose(population_size - successes,
|
/// The formula is choose(successes, sample_successes) * choose(population_size - successes,
|
||||||
/// sample_size - sample_successes) / choose(population_size, sample_size)
|
/// sample_size - sample_successes) / choose(population_size, sample_size)
|
||||||
fn hyper_geometric_exactly(
|
fn hyper_geometric_exactly(input: &HyperGeometricInput) -> f64 {
|
||||||
population_size: u8,
|
if input.population_size == input.successes {
|
||||||
successes: u8,
|
return if input.sample_successes == input.sample_size {
|
||||||
sample_size: u8,
|
|
||||||
sample_successes: u8,
|
|
||||||
) -> f64 {
|
|
||||||
if population_size == successes {
|
|
||||||
return if sample_successes == sample_size {
|
|
||||||
1.0
|
1.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if successes == 0 {
|
if input.successes == 0 {
|
||||||
return if sample_successes == 0 { 1.0 } else { 0.0 };
|
return if input.sample_successes == 0 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// On top we have: successes!, (population_size - successes)!, sample_size! and
|
// On top we have: successes!, (population_size - successes)!, sample_size! and
|
||||||
// (population_size - sample_size)!
|
// (population_size - sample_size)!
|
||||||
let top_factors = (1..=successes)
|
let top_factors = (1..=input.successes)
|
||||||
.chain(1..=(population_size - successes))
|
.chain(1..=(input.population_size - input.successes))
|
||||||
.chain(1..=sample_size)
|
.chain(1..=input.sample_size)
|
||||||
.chain(1..=(population_size - sample_size))
|
.chain(1..=(input.population_size - input.sample_size))
|
||||||
.flat_map(|n| factorize(n))
|
.flat_map(|n| factorize(n))
|
||||||
.fold(HashMap::<u8, u8>::new(), |mut counts, i| {
|
.fold(HashMap::<u8, u8>::new(), group_factors);
|
||||||
*counts.entry(i).or_default() += 1;
|
|
||||||
counts
|
|
||||||
});
|
|
||||||
|
|
||||||
// On bottom we have: sample_successes!, (successes - sample_successes)!
|
// On bottom we have: sample_successes!, (successes - sample_successes)!
|
||||||
// (sample_size - sample_successes)!, (population_size - successes - sample_size + sample_successes)!
|
// (sample_size - sample_successes)!, (population_size - successes - sample_size + sample_successes)!
|
||||||
// and population_size!
|
// and population_size!
|
||||||
let bot_factors = (1..=sample_successes)
|
let bot_factors = (1..=input.sample_successes)
|
||||||
.chain(1..=(successes - sample_successes))
|
.chain(1..=(input.successes - input.sample_successes))
|
||||||
.chain(1..=(sample_size - sample_successes))
|
.chain(1..=(input.sample_size - input.sample_successes))
|
||||||
.chain(
|
.chain(
|
||||||
1..=((population_size as u16 + sample_successes as u16
|
1..=((input.population_size as u16 + input.sample_successes as u16
|
||||||
- successes as u16
|
- input.successes as u16
|
||||||
- sample_size as u16) as u8),
|
- input.sample_size as u16) as u8),
|
||||||
)
|
)
|
||||||
.chain(1..=population_size)
|
.chain(1..=input.population_size)
|
||||||
.flat_map(|n| factorize(n))
|
.flat_map(|n| factorize(n))
|
||||||
.fold(HashMap::<u8, u8>::new(), |mut counts, i| {
|
.fold(HashMap::<u8, u8>::new(), group_factors);
|
||||||
counts.entry(i).and_modify(|count| *count += 1).or_insert(1);
|
|
||||||
counts
|
|
||||||
});
|
|
||||||
|
|
||||||
let (top_factors, bot_factors) = simplify(top_factors, bot_factors);
|
let (top_factors, bot_factors) = simplify(top_factors, bot_factors);
|
||||||
|
|
||||||
let top_product: f64 = top_factors
|
let top_product = product(top_factors);
|
||||||
.into_iter()
|
let bot_product = product(bot_factors);
|
||||||
.flat_map(|(f, count)| repeat(f).take(count as usize))
|
|
||||||
.map(|f| f as f64)
|
|
||||||
.product();
|
|
||||||
|
|
||||||
let bot_product: f64 = bot_factors
|
top_product / bot_product
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the probability of getting exactly `successes_number` within `trials_number` given
|
||||||
|
/// that the success probability is `success_probability`.
|
||||||
|
///
|
||||||
|
/// The formula is choose(trials_number, successes_number) * (success_probability)^successes_number
|
||||||
|
/// * (1 - success_probability)^(trials_number - successes_number)
|
||||||
|
fn binomial_exactly(input: &BinomialInput, p_powers: &[f64], pc_powers: &[f64]) -> f64 {
|
||||||
|
if input.success_probability == 0.0 {
|
||||||
|
return if input.successes_number == 0 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if input.success_probability == 1.0 {
|
||||||
|
return if input.successes_number == input.trials_number {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
choose(input.trials_number, input.successes_number)
|
||||||
|
* p_powers[input.successes_number as usize]
|
||||||
|
* pc_powers[(input.trials_number - input.successes_number) as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose(n: u8, k: u8) -> f64 {
|
||||||
|
// On top we have: n!
|
||||||
|
let top_factors = (1..=n)
|
||||||
|
.flat_map(|n| factorize(n))
|
||||||
|
.fold(HashMap::<u8, u8>::new(), group_factors);
|
||||||
|
|
||||||
|
// On bottom we have: k!, (n - k)!
|
||||||
|
let bot_factors = (1..=k)
|
||||||
|
.chain(1..=(n - k))
|
||||||
|
.flat_map(|n| factorize(n))
|
||||||
|
.fold(HashMap::<u8, u8>::new(), group_factors);
|
||||||
|
|
||||||
|
let (top_factors, bot_factors) = simplify(top_factors, bot_factors);
|
||||||
|
|
||||||
|
let top_product = product(top_factors);
|
||||||
|
let bot_product = product(bot_factors);
|
||||||
|
|
||||||
|
top_product / bot_product
|
||||||
|
}
|
||||||
|
|
||||||
|
fn powers(p: f64, n: u8) -> (Vec<f64>, Vec<f64>) {
|
||||||
|
let mut p_powers = Vec::with_capacity((n + 1) as usize);
|
||||||
|
let mut pc_powers = Vec::with_capacity((n + 1) as usize);
|
||||||
|
|
||||||
|
let mut p_power = 1.0;
|
||||||
|
let mut pc_power = 1.0;
|
||||||
|
for _ in 0..n + 1 {
|
||||||
|
p_powers.push(p_power);
|
||||||
|
pc_powers.push(pc_power);
|
||||||
|
p_power = p_power * p;
|
||||||
|
pc_power = pc_power * (1.0 - p)
|
||||||
|
}
|
||||||
|
(p_powers, pc_powers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_factors(mut counts: HashMap<u8, u8>, i: u8) -> HashMap<u8, u8> {
|
||||||
|
*counts.entry(i).or_default() += 1;
|
||||||
|
counts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn product(factors: HashMap<u8, u8>) -> f64 {
|
||||||
|
factors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|(f, count)| repeat(f).take(count as usize))
|
.flat_map(|(f, count)| repeat(f).take(count as usize))
|
||||||
.map(|f| f as f64)
|
.map(|f| f as f64)
|
||||||
.product();
|
.product()
|
||||||
|
|
||||||
top_product / bot_product
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simplify factors for a fraction.
|
||||||
|
///
|
||||||
|
/// This assumes factors are already prime factors.
|
||||||
fn simplify(
|
fn simplify(
|
||||||
mut top_factors: HashMap<u8, u8>,
|
mut top_factors: HashMap<u8, u8>,
|
||||||
mut bot_factors: HashMap<u8, u8>,
|
mut bot_factors: HashMap<u8, u8>,
|
||||||
|
|
@ -158,6 +341,8 @@ fn simplify(
|
||||||
const PRIMES: &[u8] = &[2, 3, 5, 7, 11, 13];
|
const PRIMES: &[u8] = &[2, 3, 5, 7, 11, 13];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// Iterator for the prime factors of a number. We obtain this iterator using the `factorize`
|
||||||
|
/// function.
|
||||||
struct FactorIter<'a> {
|
struct FactorIter<'a> {
|
||||||
/// remainder
|
/// remainder
|
||||||
n: u8,
|
n: u8,
|
||||||
|
|
@ -189,7 +374,7 @@ impl Iterator for FactorIter<'_> {
|
||||||
}
|
}
|
||||||
// If we stopped at a factor, we return it.
|
// If we stopped at a factor, we return it.
|
||||||
// If we exhausted the factors, we return the remainder, as it will be a prime (we
|
// If we exhausted the factors, we return the remainder, as it will be a prime (we
|
||||||
// checked primes less than sqrt(MAX).
|
// checked primes less than sqrt(u8::MAX).
|
||||||
match self.f {
|
match self.f {
|
||||||
Some(f) => Some(f),
|
Some(f) => Some(f),
|
||||||
None => Some(self.n),
|
None => Some(self.n),
|
||||||
|
|
@ -207,7 +392,10 @@ fn factorize(n: u8) -> FactorIter<'static> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::calc::hyper_geometric_exactly;
|
use crate::calc::{
|
||||||
|
BinomialInput, BinomialProb, HyperGeometricInput, HyperGeometricProb, binomial,
|
||||||
|
binomial_exactly, hyper_geometric, hyper_geometric_exactly, powers,
|
||||||
|
};
|
||||||
|
|
||||||
use super::factorize;
|
use super::factorize;
|
||||||
|
|
||||||
|
|
@ -228,18 +416,80 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hypergeometric_exact_all_successes() {
|
fn test_hypergeometric_exact_all_successes() {
|
||||||
assert_eq!(hyper_geometric_exactly(10, 10, 5, 5), 1.0);
|
let input = &HyperGeometricInput::new(10, 10, 5, 5).unwrap();
|
||||||
assert_eq!(hyper_geometric_exactly(10, 10, 5, 4), 0.0);
|
assert_eq!(hyper_geometric_exactly(input), 1.0);
|
||||||
|
let input = &HyperGeometricInput::new(10, 10, 5, 4).unwrap();
|
||||||
|
assert_eq!(hyper_geometric_exactly(input), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hypergeometric_exact_no_successes() {
|
fn test_hypergeometric_exact_no_successes() {
|
||||||
assert_eq!(hyper_geometric_exactly(10, 0, 5, 0), 1.0);
|
let input = &HyperGeometricInput::new(10, 0, 5, 0).unwrap();
|
||||||
assert_eq!(hyper_geometric_exactly(10, 0, 5, 1), 0.0);
|
assert_eq!(hyper_geometric_exactly(input), 1.0);
|
||||||
|
let input = &HyperGeometricInput::new(10, 0, 5, 1).unwrap();
|
||||||
|
assert_eq!(hyper_geometric_exactly(input), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hypergeometric_exact() {
|
fn test_hypergeometric_exact() {
|
||||||
assert_eq!(hyper_geometric_exactly(10, 3, 5, 2), 5.0 / 12.0);
|
let input = &HyperGeometricInput::new(10, 3, 5, 2).unwrap();
|
||||||
|
assert_eq!(hyper_geometric_exactly(input), 5.0 / 12.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hypergeometric_aces_poker() {
|
||||||
|
let input = HyperGeometricInput::new(52, 4, 5, 4).unwrap();
|
||||||
|
let exact = 1.846892603195124e-5;
|
||||||
|
assert_eq!(
|
||||||
|
hyper_geometric(input),
|
||||||
|
HyperGeometricProb {
|
||||||
|
exactly: exact,
|
||||||
|
less_than: 1.0 - exact,
|
||||||
|
less_or_equal: 1.0,
|
||||||
|
greater_than: 0.0,
|
||||||
|
greater_or_equal: exact
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binom_exact_all_success() {
|
||||||
|
let (p_powers, pc_powers) = powers(1.0, 5);
|
||||||
|
let input = &BinomialInput::new(1.0, 5, 5).unwrap();
|
||||||
|
assert_eq!(binomial_exactly(input, &p_powers, &pc_powers), 1.0);
|
||||||
|
let input = &BinomialInput::new(1.0, 5, 4).unwrap();
|
||||||
|
assert_eq!(binomial_exactly(input, &p_powers, &pc_powers), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binom_exact_no_success() {
|
||||||
|
let (p_powers, pc_powers) = powers(0.0, 5);
|
||||||
|
let input = &BinomialInput::new(0.0, 5, 0).unwrap();
|
||||||
|
assert_eq!(binomial_exactly(input, &p_powers, &pc_powers), 1.0);
|
||||||
|
let input = &BinomialInput::new(0.0, 5, 1).unwrap();
|
||||||
|
assert_eq!(binomial_exactly(input, &p_powers, &pc_powers), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binomial_exact() {
|
||||||
|
let (p_powers, pc_powers) = powers(0.5, 5);
|
||||||
|
let input = &BinomialInput::new(0.5, 5, 3).unwrap();
|
||||||
|
assert_eq!(binomial_exactly(input, &p_powers, &pc_powers), 10.0 / 32.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binomial() {
|
||||||
|
// 10.0 / 32.0
|
||||||
|
let input = BinomialInput::new(0.5, 5, 3).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
binomial(input),
|
||||||
|
BinomialProb {
|
||||||
|
exactly: 10.0 / 32.0,
|
||||||
|
less_than: 16.0 / 32.0,
|
||||||
|
less_or_equal: 26.0 / 32.0,
|
||||||
|
greater_than: 6.0 / 32.0,
|
||||||
|
greater_or_equal: 16.0 / 32.0,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,22 @@ use leptos::prelude::{
|
||||||
use leptos::{IntoView, component, view};
|
use leptos::{IntoView, component, view};
|
||||||
use leptos_fluent::move_tr;
|
use leptos_fluent::move_tr;
|
||||||
|
|
||||||
use crate::calc::hyper_geometric;
|
use crate::calc::{BinomialInput, HyperGeometricInput, binomial, hyper_geometric};
|
||||||
|
|
||||||
/// A parameterized incrementing button
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Calculator() -> impl IntoView {
|
pub fn HyperCalculator() -> impl IntoView {
|
||||||
let (population, set_population) = signal(0u8);
|
let (population, set_population) = signal(0u8);
|
||||||
let (successes, set_successes) = signal(0u8);
|
let (successes, set_successes) = signal(0u8);
|
||||||
let (sample, set_sample) = signal(0u8);
|
let (sample, set_sample) = signal(0u8);
|
||||||
let (sample_successes, set_sample_successes) = signal(0u8);
|
let (sample_successes, set_sample_successes) = signal(0u8);
|
||||||
let result = move || {
|
let result = move || {
|
||||||
hyper_geometric(
|
HyperGeometricInput::new(
|
||||||
population.get(),
|
population.get(),
|
||||||
successes.get(),
|
successes.get(),
|
||||||
sample.get(),
|
sample.get(),
|
||||||
sample_successes.get(),
|
sample_successes.get(),
|
||||||
)
|
)
|
||||||
|
.map(hyper_geometric)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
|
|
@ -110,6 +110,99 @@ pub fn Calculator() -> impl IntoView {
|
||||||
</span>
|
</span>
|
||||||
<span class="right">{move || display_rounded(result().greater_or_equal)}</span>
|
<span class="right">{move || display_rounded(result().greater_or_equal)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="calc-description">{move_tr!("hyper-description")}</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn BinomCalculator() -> impl IntoView {
|
||||||
|
let (success_probability, set_success_probability) = signal(0f64);
|
||||||
|
let (trials_number, set_trials_number) = signal(0u8);
|
||||||
|
let (successes_number, set_successes_number) = signal(0u8);
|
||||||
|
let result = move || {
|
||||||
|
BinomialInput::new(
|
||||||
|
success_probability.get(),
|
||||||
|
trials_number.get(),
|
||||||
|
successes_number.get(),
|
||||||
|
)
|
||||||
|
.map(binomial)
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<form>
|
||||||
|
<p>
|
||||||
|
<label for="success_probability">{move_tr!("success-probability")}</label>
|
||||||
|
<input
|
||||||
|
id="success_probability"
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
max=1
|
||||||
|
prop:step=0.1
|
||||||
|
prop:value=success_probability
|
||||||
|
on:input:target=move |ev| {
|
||||||
|
set_success_probability.set(ev.target().value().parse().unwrap_or_default())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="trials_number">{move_tr!("trials-number")}</label>
|
||||||
|
<input
|
||||||
|
id="trials_number"
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
prop:value=trials_number
|
||||||
|
on:input:target=move |ev| {
|
||||||
|
set_trials_number.set(ev.target().value().parse().unwrap_or_default())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="successes_number">{move_tr!("successes-number")}</label>
|
||||||
|
<input
|
||||||
|
id="successes_number"
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
prop:max=trials_number
|
||||||
|
prop:value=successes_number
|
||||||
|
on:input:target=move |ev| {
|
||||||
|
set_successes_number.set(ev.target().value().parse().unwrap_or_default())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<div class="results">
|
||||||
|
<span class="left">
|
||||||
|
<span>"P(X = "</span>
|
||||||
|
<span>{successes_number}</span>
|
||||||
|
<span>"): "</span>
|
||||||
|
</span>
|
||||||
|
<span class="right">{move || display_rounded(result().exactly)}</span>
|
||||||
|
<span class="left">
|
||||||
|
<span>"P(X < "</span>
|
||||||
|
<span>{successes_number}</span>
|
||||||
|
<span>"): "</span>
|
||||||
|
</span>
|
||||||
|
<span class="right">{move || display_rounded(result().less_than)}</span>
|
||||||
|
<span class="left">
|
||||||
|
<span>"P(X ≤ "</span>
|
||||||
|
<span>{successes_number}</span>
|
||||||
|
<span>"): "</span>
|
||||||
|
</span>
|
||||||
|
<span class="right">{move || display_rounded(result().less_or_equal)}</span>
|
||||||
|
<span class="left">
|
||||||
|
<span>"P(X > "</span>
|
||||||
|
<span>{successes_number}</span>
|
||||||
|
<span>"): "</span>
|
||||||
|
</span>
|
||||||
|
<span class="right">{move || display_rounded(result().greater_than)}</span>
|
||||||
|
<span class="left">
|
||||||
|
<span>"P(X ≥ "</span>
|
||||||
|
<span>{successes_number}</span>
|
||||||
|
<span>"): "</span>
|
||||||
|
</span>
|
||||||
|
<span class="right">{move || display_rounded(result().greater_or_equal)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="calc-description">{move_tr!("binom-description")}</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
18
src/components/common.rs
Normal file
18
src/components/common.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use leptos::html::ElementChild;
|
||||||
|
use leptos::{IntoView, component, view};
|
||||||
|
use leptos_router::nested_router::Outlet;
|
||||||
|
|
||||||
|
use crate::components::localization::{I18n, LanguageSelector};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Common() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<I18n>
|
||||||
|
<header>
|
||||||
|
<a href="/">"🏡"</a>
|
||||||
|
<LanguageSelector />
|
||||||
|
</header>
|
||||||
|
<Outlet />
|
||||||
|
</I18n>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
use fluent_templates::static_loader;
|
use fluent_templates::static_loader;
|
||||||
use leptos::html::ElementChild;
|
use leptos::html::ElementChild;
|
||||||
use leptos::prelude::{
|
use leptos::prelude::{
|
||||||
AddAnyAttr, Children, Get, GlobalAttributes, IntoAttribute, OnAttribute, PropAttribute, Set,
|
AddAnyAttr, Children, Get, GlobalAttributes, OnAttribute, PropAttribute, Set, expect_context,
|
||||||
};
|
};
|
||||||
use leptos::{IntoView, component, view};
|
use leptos::{IntoView, component, view};
|
||||||
use leptos_fluent::{Language, expect_i18n, leptos_fluent};
|
use leptos_fluent::{Language, leptos_fluent};
|
||||||
|
|
||||||
static_loader! {
|
static_loader! {
|
||||||
pub static TRANSLATIONS = {
|
pub static TRANSLATIONS = {
|
||||||
|
|
@ -23,36 +23,39 @@ pub fn I18n(children: Children) -> impl IntoView {
|
||||||
translations: [TRANSLATIONS],
|
translations: [TRANSLATIONS],
|
||||||
locales: "./locales",
|
locales: "./locales",
|
||||||
check_translations: "./src/**/*.rs",
|
check_translations: "./src/**/*.rs",
|
||||||
initial_language_from_localstorage: true,
|
initial_language_from_local_storage: true,
|
||||||
initial_language_from_navigator_to_localstorage: true,
|
initial_language_from_navigator_to_local_storage: true,
|
||||||
set_language_to_localstorage: true,
|
set_language_to_local_storage: true,
|
||||||
sync_html_tag_lang: true,
|
sync_html_tag_lang: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Selector for languages
|
||||||
#[component]
|
#[component]
|
||||||
pub fn LanguageSelector() -> impl IntoView {
|
pub fn LanguageSelector() -> impl IntoView {
|
||||||
// Use `expect_i18n()` to get the current i18n context:
|
let i18n = expect_context::<leptos_fluent::I18n>();
|
||||||
let i18n = expect_i18n();
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<label for="language">"A/文:"</label>
|
<span>
|
||||||
<select id="language">
|
<label for="language">"A/文:"</label>
|
||||||
{move || {
|
<select id="language">
|
||||||
i18n.languages.iter().map(|lang| render_language(lang)).collect::<Vec<_>>()
|
{move || {
|
||||||
}}
|
i18n.languages.iter().map(|lang| render_language(lang)).collect::<Vec<_>>()
|
||||||
</select>
|
}}
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_language(lang: &'static Language) -> impl IntoView {
|
fn render_language(lang: &'static Language) -> impl IntoView {
|
||||||
// Passed as atrribute, `Language` is converted to their code,
|
// Passed as atrribute, `Language` is converted to their code,
|
||||||
// so `<input id=lang` becomes `<input id=lang.id.to_string()`
|
// so `<input id=lang` becomes `<input id=lang.id.to_string()`
|
||||||
let i18n = expect_i18n();
|
let i18n = expect_context::<leptos_fluent::I18n>();
|
||||||
view! {
|
view! {
|
||||||
<option
|
<option
|
||||||
id=lang
|
id=lang
|
||||||
value=lang
|
value=lang
|
||||||
prop:selected=lang.is_active()
|
prop:selected=i18n.language.get() == lang
|
||||||
on:click=move |_| i18n.language.set(lang)
|
on:click=move |_| i18n.language.set(lang)
|
||||||
>
|
>
|
||||||
{lang.name}
|
{lang.name}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod calculator;
|
pub mod calculator;
|
||||||
|
pub mod common;
|
||||||
pub mod localization;
|
pub mod localization;
|
||||||
|
|
|
||||||
47
src/lib.rs
47
src/lib.rs
|
|
@ -1,15 +1,20 @@
|
||||||
use leptos::prelude::{AddAnyAttr, IntoAttribute};
|
use leptos::prelude::{AddAnyAttr, Get, expect_context};
|
||||||
use leptos::{IntoView, component, view};
|
use leptos::{IntoView, component, view};
|
||||||
use leptos_meta::*;
|
use leptos_meta::{Html, Meta, Title, provide_meta_context};
|
||||||
use leptos_router::{components::*, path};
|
use leptos_router::components::{ParentRoute, Route, Router, Routes};
|
||||||
|
use leptos_router::path;
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
mod calc;
|
mod calc;
|
||||||
mod components;
|
mod components;
|
||||||
mod pages;
|
mod pages;
|
||||||
|
|
||||||
// Top-Level pages
|
use crate::components::common::Common;
|
||||||
|
use crate::components::localization::I18n;
|
||||||
|
use crate::pages::binom::Binom;
|
||||||
use crate::pages::home::Home;
|
use crate::pages::home::Home;
|
||||||
|
use crate::pages::hyper::Hyper;
|
||||||
|
use crate::pages::not_found::NotFound;
|
||||||
|
|
||||||
/// An app router which renders the homepage and handles 404's
|
/// An app router which renders the homepage and handles 404's
|
||||||
#[component]
|
#[component]
|
||||||
|
|
@ -18,19 +23,31 @@ pub fn App() -> impl IntoView {
|
||||||
provide_meta_context();
|
provide_meta_context();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Html attr:lang="en" attr:dir="ltr" attr:data-theme="light" />
|
<I18n>
|
||||||
|
<HtmlAttrs />
|
||||||
|
|
||||||
// sets the document title
|
// sets the document title
|
||||||
<Title text="Hypergeometric Calculator" />
|
<Title text="🧮" />
|
||||||
|
|
||||||
// injects metadata in the <head> of the page
|
// injects metadata in the <head> of the page
|
||||||
<Meta charset="UTF-8" />
|
<Meta charset="UTF-8" />
|
||||||
<Meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<Meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
<Router>
|
<Router>
|
||||||
<Routes fallback=|| view! { NotFound }>
|
<Routes fallback=NotFound>
|
||||||
<Route path=path!("/") view=Home />
|
<ParentRoute path=path!("/") view=Common>
|
||||||
</Routes>
|
<Route path=path!("") view=Home />
|
||||||
</Router>
|
<Route path=path!("/hyper") view=Hyper />
|
||||||
|
<Route path=path!("/binom") view=Binom />
|
||||||
|
</ParentRoute>
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
</I18n>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn HtmlAttrs() -> impl IntoView {
|
||||||
|
let i18n = expect_context::<leptos_fluent::I18n>();
|
||||||
|
view! { <Html attr:lang=move || i18n.language.get() attr:dir="ltr" attr:data-theme="light" /> }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use hypergeometric_calc::App;
|
use distribution_calc::App;
|
||||||
use leptos::{mount::mount_to_body, view};
|
use leptos::{mount::mount_to_body, view};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
||||||
17
src/pages/binom.rs
Normal file
17
src/pages/binom.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use leptos::attr::global::ClassAttribute;
|
||||||
|
use leptos::html::ElementChild;
|
||||||
|
use leptos::{IntoView, component, view};
|
||||||
|
use leptos_fluent::move_tr;
|
||||||
|
|
||||||
|
use crate::components::calculator::BinomCalculator;
|
||||||
|
|
||||||
|
///Binom Page
|
||||||
|
#[component]
|
||||||
|
pub fn Binom() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">{move_tr!("title-binom")}</h1>
|
||||||
|
<BinomCalculator />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +1,22 @@
|
||||||
use leptos::attr::global::ClassAttribute;
|
use leptos::attr::global::ClassAttribute;
|
||||||
use leptos::error::ErrorBoundary;
|
|
||||||
use leptos::html::ElementChild;
|
use leptos::html::ElementChild;
|
||||||
use leptos::prelude::{CollectView, Get};
|
|
||||||
use leptos::{IntoView, component, view};
|
use leptos::{IntoView, component, view};
|
||||||
use leptos_fluent::move_tr;
|
use leptos_fluent::move_tr;
|
||||||
|
|
||||||
use crate::components::calculator::Calculator;
|
///Home Page
|
||||||
use crate::components::localization::{I18n, LanguageSelector};
|
|
||||||
|
|
||||||
/// Default Home Page
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Home() -> impl IntoView {
|
pub fn Home() -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<ErrorBoundary fallback=|errors| {
|
<div class="container">
|
||||||
view! {
|
<h1 class="title">{move_tr!("title-home")}</h1>
|
||||||
<h1>"Uh oh! Something went wrong!"</h1>
|
<menu>
|
||||||
|
<li>
|
||||||
<p>"Errors: "</p>
|
<a href="/binom">{move_tr!("title-binom")}</a>
|
||||||
// Render a list of errors as strings - good for development purposes
|
</li>
|
||||||
<ul>
|
<li>
|
||||||
{move || {
|
<a href="/hyper">{move_tr!("title-hyper")}</a>
|
||||||
errors
|
</li>
|
||||||
.get()
|
</menu>
|
||||||
.into_iter()
|
</div>
|
||||||
.map(|(_, e)| view! { <li>{e.to_string()}</li> })
|
|
||||||
.collect_view()
|
|
||||||
}}
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
<I18n>
|
|
||||||
<header>
|
|
||||||
<LanguageSelector />
|
|
||||||
</header>
|
|
||||||
<div class="container">
|
|
||||||
<Title />
|
|
||||||
<Calculator />
|
|
||||||
</div>
|
|
||||||
</I18n>
|
|
||||||
</ErrorBoundary>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Title
|
|
||||||
#[component]
|
|
||||||
pub fn Title() -> impl IntoView {
|
|
||||||
view! {<h1 class="title">{move_tr!("title")}</h1>}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
17
src/pages/hyper.rs
Normal file
17
src/pages/hyper.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use leptos::attr::global::ClassAttribute;
|
||||||
|
use leptos::html::ElementChild;
|
||||||
|
use leptos::{IntoView, component, view};
|
||||||
|
use leptos_fluent::move_tr;
|
||||||
|
|
||||||
|
use crate::components::calculator::HyperCalculator;
|
||||||
|
|
||||||
|
///Hyper Page
|
||||||
|
#[component]
|
||||||
|
pub fn Hyper() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">{move_tr!("title-hyper")}</h1>
|
||||||
|
<HyperCalculator />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
|
pub mod binom;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
|
pub mod hyper;
|
||||||
pub mod not_found;
|
pub mod not_found;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue