Loan Checker
important disclaimers â
â ī¸ The assumptions, methodology and limitations of a model should be carefully considered for any purpose you apply it to. I haven't even listed these here, nor otherwise documented the model fully/clearly. Proceed with caution đ
â ī¸ This is not a complete model/narrative and you should not rely on it.
â ī¸ Not recommended to enter real personal or sensitive details. Contact me with real-world calculation or model requirements instead. đŖâ
â ī¸ This validator/scenario tool isn't/won't be financial advice etc.
đĄ Click/drag to see workings (buggy suspected due to limitations in vega-lite; todo move to vega)
const spec = ({
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": 400,
datasets: {'source': [] },
"encoding": {
"x": {"field": "year_in"},
"y": {"field": "value", "type": "quantitative"},
},
"layer": [
{
"transform": [{"filter": "datum.formula != 'balance'"}],
"mark": {"type": "bar", "tooltip": true},
"encoding": {
"color": {"field": "formula", "type": "nominal", legend: {orient: 'top'}}
},
params: [
{
name: "formula",
//value: { ray_angle_in: 0 },
select: {
type: "point",
//on: "mousemove{0,50}",
on: "[touchdown, touchup] > touchmove, mouseup, touchup, [mousedown, mouseup] > mousemove",
//on: "[touchdown, touchup] > touchmove, [mousedown, mouseup] > mousemove", //{10, 100}",
// on: "[mousedown, mouseup] > mousemove",
nearest: true,
toggle: false,
clear: false,
encodings: ["x", "color"]
}
}
],
},
{
"transform": [{"filter": "datum.formula == 'balance'"}],
"mark": {"type": "line", "tooltip": true, "point": false},
"encoding": {
"color": {"value": "red"},
"y": {"field": "value", "type": "quantitative", "title": "balance"}
}
},
{
"transform": [{"filter": "datum.formula == 'balance'"}],
"mark": {"type": "point", "tooltip": true, filled:true, opacity: 0.7},
"encoding": {
"color": {"value": "red", legend: true},
size: {value: 200},
"x": {"field": "year_in"},
"y": {"field": "value", "type": "quantitative", "title": "balance", zero: true}
},
/*params: [
{
name: "balance",
//value: { ray_angle_in: 0 },
select: {
type: "point",
//on: "mousemove{0,50}",
on: "[touchdown, touchup] > touchmove, mouseup, touchup, [mousedown, mouseup] > mousemove",
//on: "[touchdown, touchup] > touchmove, [mousedown, mouseup] > mousemove", //{10, 100}",
// on: "[mousedown, mouseup] > mousemove",
nearest: true,
toggle: false,
clear: false,
encodings: ["x", "y"]
}
}
],*/
}
],
"resolve": {"scale": {"y": "independent"}}
})
// ({
// // vega-lite
// encoding: {
// x: { field: 'formula', type: 'nominal', "axis": {"labelAngle": -30, "orient": "top", title:null, "labelLimit": 300, labelFontSize: 16} },
// y: { field: 'i_in' },
// color: {field: 'value', legend: false},
// text: {field: 'value', },
// size: {value: 12}
// },
// layer: [
// {
// mark: {type:'text', fontWeight:'bold', dx:2, dy:2},
// transform: [{filter: "datum.highlight"}],
// encoding: {
// size: {value: 15},
// color: {value:'yellow'}
// }
// },
// {
// mark: {type:'text', tooltip:false, from: 'data'},
// //transform: [{filter: "!datum.current"}], // messes selection; better out or using conditional size
// encoding: {
// size: { value: 16, condition: {test: 'datum.current', value: 1} }
// },
// params: [
// {
// name: "formula",
// //value: { ray_angle_in: 0 },
// select: {
// type: "point",
// //on: "mousemove{0,50}",
// on: "[touchdown, touchup] > touchmove, mouseup, touchup, [mousedown, mouseup] > mousemove",
// //on: "[touchdown, touchup] > touchmove, [mousedown, mouseup] > mousemove", //{10, 100}",
// // on: "[mousedown, mouseup] > mousemove",
// nearest: true,
// toggle: false,
// clear: false,
// encodings: ["x", "y"]
// }
// }
// ]
// },
// {
// mark: {type:'text', fontWeight:'bold', dx:0, dy:0},
// transform: [{filter: "datum.current"}],
// encoding: {
// color: {value:'black'},
// size: {value: 20}
// }
// },
// ],
// data: { name: "data" },
// autosize: { "type": "fit", "contains": "padding"},
// // no reactive w/h due to https://github.com/observablehq/framework/issues/1194
// width: 450,//Math.min(500,content_width-30),//Math.min(400,content_width),
// height: 600,//Math.min(500,content_width-30)/1.2,//Math.min(400,content_width-30),
// background:'rgba(0,0,0,0)',
// })
// interactivity via vega signals and listeners
const viz = embed('#viz', spec)
// is this data_source_with_highlights data flow ok ... ?
viz.view.data("source", data_source_with_highlights).resize().run(); // turn off resize
const data_source = calcudata({
models: [model],
input_domains: {year_in:_.range(-1,cursor.term_in+0.1)},
input_cursors: [cursor],
outputs: ["capital_repayment", "interest_repayment", "balance"],
pivot: false
});
display(data_source)
viz.view.addSignalListener("formula", (a, b) => {
console.log('formula signal', a, b)
//alert(a, b)
// no modularity here:
//const newScope = +(fns_fromDefinition.find(d => (d.cul_scope_id == 0 && d.name == b.formula[0])).fromDefinition.split('_')[0])
//if (newScope !== editor.scope)
//editor.setScope(+(fns_fromDefinition.find(d => (d.cul_scope_id == 0 && d.name == b.formula[0])).fromDefinition.split('_')[0]))
setCursor('year_in', b.year_in[0])
setFormula(/*b.formula[0]*/'repayment')
document.querySelector('.calculang_f_'+/*b.formula[0]*/'repayment').scrollIntoView(scrollIntoViewOpts)
set_formulae_visible([/*b.formula[0]*/'repayment'])
});
/* not going to work: I need to use Vega
viz.view.addSignalListener("balance", (a, b) => {
console.log('formula signal', a, b)
alert('ih')
setCursor('year_in', b.year_in[0])
setFormula('balance')
document.querySelector('.calculang_f_'+'balance').scrollIntoView(scrollIntoViewOpts)
set_formulae_visible(['balance'])
})*/