use sankey::{Sankey, SankeyStyle}; #[test] fn tax() { let mut sankey = Sankey::new(); const INCOME_NODE_COLOR: &str = "#F66F"; const INCOME_EDGE_COLOR: &str = "#F665"; const TAX_NODE_COLOR: &str = "#777F"; const TAX_EDGE_COLOR: &str = "#7775"; const BILLS_NODE_COLOR: &str = "#974F"; const BILLS_EDGE_COLOR: &str = "#9845"; const SPENDING_NODE_COLOR: &str = "#EC5F"; const SPENDING_EDGE_COLOR: &str = "#EC55"; const SAVING_NODE_COLOR: &str = "#C5EF"; const SAVING_EDGE_COLOR: &str = "#C5E5"; const EMPLOYER_NODE_COLOR: &str = "#6B7F"; const EMPLOYER_EDGE_COLOR: &str = "#6B75"; const GOVERNMENT_NODE_COLOR: &str = "#27BF"; const GOVERNMENT_EDGE_COLOR: &str = "#27B5"; let salary = sankey.node(Some(50000.0), Some("Salary".into()), Some(INCOME_NODE_COLOR.into())); let bonus = sankey.node(Some(5000.0), Some("Bonus".into()), Some(INCOME_NODE_COLOR.into())); let employer = sankey.node(None, Some("Employer".into()), Some(EMPLOYER_NODE_COLOR.into())); let government = sankey.node(None, Some("Government".into()), Some(GOVERNMENT_NODE_COLOR.into())); let income = sankey.node(None, Some("Income".into()), Some(INCOME_NODE_COLOR.into())); sankey.edge(salary, income, sankey.remaining_output(salary), None, Some(INCOME_EDGE_COLOR.into())); sankey.edge(bonus, income, sankey.remaining_output(bonus), None, Some(INCOME_EDGE_COLOR.into())); let pension = sankey.node(None, Some("Pension".into()), Some(SAVING_NODE_COLOR.into())); sankey.edge(income, pension, sankey.required_output(income) * 0.1, None, Some(SAVING_EDGE_COLOR.into())); sankey.edge(employer, pension, sankey.current_input(pension), None, Some(EMPLOYER_EDGE_COLOR.into())); let taxable = sankey.node(None, Some("Taxable".into()), Some(INCOME_NODE_COLOR.into())); sankey.edge(income, taxable, sankey.remaining_output(income), None, Some(INCOME_EDGE_COLOR.into())); let tax = sankey.node(None, Some("Tax".into()), Some(TAX_NODE_COLOR.into())); sankey.edge(taxable, tax, apply_tax(sankey.required_output(taxable), &TAX_BANDS), None, Some(TAX_EDGE_COLOR.into())); let national_insurance = sankey.node(Some(apply_tax(sankey.required_output(taxable), &NATIONAL_INSURANCE_BANDS)), Some("National Insurance".into()), Some(TAX_NODE_COLOR.into())); sankey.edge(taxable, national_insurance, apply_tax(sankey.required_output(taxable), &NATIONAL_INSURANCE_CONTRIBUTION_BANDS), None, Some(TAX_EDGE_COLOR.into())); sankey.edge(employer, national_insurance, sankey.remaining_input(national_insurance), None, Some(EMPLOYER_EDGE_COLOR.into())); let student_loan = sankey.node(None, Some("Student Loan".into()), Some(TAX_NODE_COLOR.into())); sankey.edge(taxable, student_loan, apply_tax(sankey.required_output(taxable), &STUDENT_LOAN_BANDS), None, Some(TAX_EDGE_COLOR.into())); let rent = sankey.node(Some(8400.0), Some("Rent".into()), Some(BILLS_NODE_COLOR.into())); sankey.edge(taxable, rent, sankey.required_input(rent), None, Some(BILLS_EDGE_COLOR.into())); let bills = sankey.node(Some(1000.0), Some("Bills".into()), Some(BILLS_NODE_COLOR.into())); sankey.edge(taxable, bills, sankey.required_input(bills), None, Some(BILLS_EDGE_COLOR.into())); let food = sankey.node(Some(2500.0), Some("Food".into()), Some(SPENDING_NODE_COLOR.into())); sankey.edge(taxable, food, sankey.required_input(food), None, Some(SPENDING_EDGE_COLOR.into())); let travel = sankey.node(Some(5000.0), Some("Travel".into()), Some(SPENDING_NODE_COLOR.into())); sankey.edge(taxable, travel, sankey.required_input(travel), None, Some(SPENDING_EDGE_COLOR.into())); let other = sankey.node(Some(2000.0), Some("Other".into()), Some(SPENDING_NODE_COLOR.into())); sankey.edge(taxable, other, sankey.required_input(other), None, Some(SPENDING_EDGE_COLOR.into())); if sankey.remaining_output(taxable) > 0.0 { let lisa = sankey.node(None, Some("LISA".into()), Some(SAVING_NODE_COLOR.into())); sankey.edge(taxable, lisa, f64::min(sankey.remaining_output(taxable), 4000.0), None, Some(SAVING_EDGE_COLOR.into())); sankey.edge(government, lisa, sankey.current_input(lisa) * 0.25, None, Some(GOVERNMENT_EDGE_COLOR.into())); } if sankey.remaining_output(taxable) > 0.0 { let isa = sankey.node(None, Some("ISA".into()), Some(SAVING_NODE_COLOR.into())); sankey.edge(taxable, isa, f64::min(sankey.remaining_output(taxable), 16000.0), None, Some(SAVING_EDGE_COLOR.into())); } if sankey.remaining_output(taxable) > 0.0 { let savings = sankey.node(None, Some("Savings".into()), Some(SAVING_NODE_COLOR.into())); sankey.edge(taxable, savings, sankey.remaining_output(taxable), None, Some(SAVING_EDGE_COLOR.into())); } let style = SankeyStyle { number_format: Some(|x| format!("£{x:.2}")), ..SankeyStyle::default() }; let svg = sankey.draw(512.0, 512.0, style); svg::save("./example.svg", &svg).unwrap(); } struct Band { max: f64, rate: f64, } const TAX_BANDS: [Band; 6] = [ Band { max: 12570.0, rate: 0.0 }, Band { max: 50270.0, rate: 0.2 }, Band { max: 100000.0, rate: 0.4 }, Band { max: 125140.0, rate: 0.6 }, Band { max: 150000.0, rate: 0.4 }, Band { max: f64::INFINITY, rate: 0.45 }, ]; const NATIONAL_INSURANCE_CONTRIBUTION_BANDS: [Band; 3] = [ Band { max: 12570.0, rate: 0.0 }, Band { max: 50270.0, rate: 0.1325 }, Band { max: f64::INFINITY, rate: 0.0325 }, ]; const NATIONAL_INSURANCE_BANDS: [Band; 2] = [ Band { max: 9100.0, rate: 0.0 }, Band { max: f64::INFINITY, rate: 0.1505 }, ]; const STUDENT_LOAN_BANDS: [Band; 2] = [ Band { max: 27295.0, rate: 0.0 }, Band { max: f64::INFINITY, rate: 0.09 }, ]; fn apply_tax(mut income: f64, bands: &[Band]) -> f64 { let mut tax = 0.0; for &Band { max, rate } in bands { tax += rate * f64::min(income, max); income -= max; if income <= 0.0 { break; } } tax }