default_example_id = q. get ('id' ) ?? ''
show_hidden = q. get ('hidden' ) == 'hidden' ?? false
a_thing2 = html `<select style="font-size: 1.5em">
${ examples. map (category => `<optgroup label=" ${ category. category } ">
${ category. tiles . filter (tile => ((! tile. disabled ) || show_hidden)). map (d => `<option value=" ${ d. id } " ${ d. id == default_example_id ? ' selected="selected"' : '' } }> ${ d. title } </option>` )}
${ category. tiles . filter (d => d. id == default_example_id && d. disabled && (! show_hidden)). map (d => `<option value=" ${ d. id } " ${ d. id == default_example_id ? ' selected="selected"' : '' } }> ${ d. title } </option>` )}
</optgroup>
` )} </select>`
md `* ${ example_details. subtitle } *`
cul_resources = {
if (cul_js_cul)
return Object . values (introspection_nomemo. cul_scope_ids_to_resource ). map (loc => loc. slice (2 ). slice (0 , loc. indexOf ('?' ) != - 1 ? loc. indexOf ('?' )- 2 : 999 )). map (d => d + ((d. slice (- 3 ) != '.js' ) ? '.js' : '' )). map (loc => dir+ loc)
else return [... Array (Object . keys (introspection_nomemo. cul_scope_ids_to_resource ). length )]. map ((_, i) => entrypoint_no_cul_js /* this is not modelname! this should be related to entrypoit, not id */ + (calculang_source_nomemo. length ? '-nomemo' : '' ) + '_esm/' + 'cul_scope_' + i+ '.mjs' )
}
//cul_resources
output = {
let o = [];
cul_resources. forEach (loc => {
o. push (fetch (loc). then (d => d. text ()))
})
return o
}
// TODO replace duplicate (source) code by a message
calculang = [await output[0 ], cul_resources. length > 1 ? await output[1 ] : ''
, cul_resources. length > 2 ? await output[2 ] : ''
, cul_resources. length > 3 ? await output[3 ] : ''
, cul_resources. length > 4 ? await output[4 ] : ''
, cul_resources. length > 5 ? await output[5 ] : ''
, cul_resources. length > 6 ? await output[6 ] : '' // TODO warn when overflows
, cul_resources. length > 7 ? await output[7 ] : '' ]. filter (d => d != '' )
//calculang
// "x.cul.js formulae?"
fff = calculang. map (d => d. split (' \n ' )). map (d => [d. findIndex (e => e. indexOf ('inputs:' + (options. includes ('inputs' ) ? 'BLAH' : '' )) != - 1 ) ?? 999 , ... d]). map (d => d. filter ((e, i) => i <= d[0 ] || d[0 ] == - 1 )). map (d => d. slice (1 )). map (d => d. join (' \n ' )). map ((d, i) => `***[ ${ introspection_nomemo. cul_scope_ids_to_resource [i]. split ('?' )[0 ]. replaceAll ('-nomemo' , '' )} ]( ${ cul_resources[i]. replaceAll ('-nomemo' , '' )} ) ${ cul_js_cul ? `-> [js]( ${ entrypoint_no_cul_js} _esm/cul_scope_ ${ i} .mjs)` : `` } ***
~~~js
${ d. replaceAll ('export const ' + (options. includes ('export consts' )
? 'BLAH' : '' ), '' )}
~~~
`
)
md `<br/>**inputs โ๏ธ** \`${ inputs. filter (d => fns0. includes (d)). join (' \` , \` ' )}\`${ not_inputs. length ? ' | ~~ \` ' : '' }${ not_inputs. map ((d, i) => ` ${ d}\` ~~ ${ i != not_inputs. length - 1 ? ', ~~ \` ' : '' } ` ). join ('' )} `
not_inputs = inputs. filter (d => ! fns0. includes (d))
dot ` ${ scope_id_graph} `
md ` ${ fff. join (' \n\n ' )} `
viewof options = Inputs. checkbox (["export consts" , "inputs" /*, "lists (todo)"*/ ], {label : "unhide?" , value : ["" ]})
//md`---`
viewof cul_js = Inputs. radio (["๐ calculang source ๐ฌ" , "โจ JS/ESM output โจ" ], {label : "๐ซถ" , value : "๐ calculang source ๐ฌ" })
cul_js_cul = cul_js == "๐ calculang source ๐ฌ" ? true : false
viewof calculang_source_nomemo = Inputs. checkbox (["nomemo" ], {value : ["nomemo" ], label : 'see memo? ๐' , disabled : cul_js== "๐ calculang source ๐ฌ" })
import {Tangle} from "@declann/colored-tangle"
viewof i_start = Inputs. input (1 )
md `*starting i*: ${ Inputs. bind (Inputs. range ([- 10 , 150 ], {step : 1 , width : 50 }), viewof i_start)} `
embed (
calcuvizspec ({
models : modelname == 'fizzbuzz' ? [model] : [],
input_cursors : [{}],
mark : {type : 'text' , fontSize : 15 , fontWeight : 'bold' },
encodings : {
x : {name : 'formula' , type : 'nominal' , domain : ['fizz' , 'buzz' , 'fizzbuzz' ], axis : {labelFontSize : 60 }, sort : ['fizzbuzz' ]},
y : {name : 'i_in' , type : 'nominal' , domain : _. range (i_start, i_start+ 20 ), axis : {grid : true }},
text : {name : 'value' , type : 'nominal' },
color : {name : 'value' , type : 'nominal' , legend : false },
},
width : 250 , // height:50,
spec_post_process : spec => { spec. background = "#fff" ; spec. encoding . y . axis = {grid : true }; return spec; }
}))
viewof leap_year_start = Inputs. input (1990 )
md `*starting year*: ${ Inputs. bind (Tangle ({min : - 1000 , max : 5000 , step : 1 , power : 1.8 , color : 'darkblue' }), viewof leap_year_start)} `
import { calcuvegadata } from "baafa4b071a5b66a" // draft API!
leap_year_spec = ({
background : 'white' ,
"data" : {"name" : "data" },
"datasets" : { "data" : leap_year_data0},
"vconcat" : [{
"width" : 480 ,
"mark" : {type : "line" , point : true },
"encoding" : {
"x" : {
"field" : "year_in" ,
"type" : "quantitative" ,
"scale" : {"domain" : {"param" : "brush" }, zero : false , nice : false },
"axis" : {"title" : "" , format : ".0f" , grid : false , format : '.0f' , ticks : true }
},
"y" : {"field" : "is_leap_year" , "type" : "quantitative" ,
scale : {domain : [- 0.1 , 1.1 ]},
"axis" : {format : ".0f" , grid : false , values : [0 , 1 ], format : '.0f' , ticks : true }
},
"order" : {"field" : "year_in" },
"tooltip" : {field : "year_in" }
}
}, {
"width" : 480 ,
"height" : 60 ,
"mark" : {type : "line" , size : 0.1 },
"params" : [{
"name" : "brush" ,
value : { x : [leap_year_start, leap_year_start+ 50 ]}, // initial value here fails whenever I switch to this model after loading a different one ... understand
//"value": {"year_in": [2000, 2010]},
"select" : {"type" : "interval" , "encodings" : ["x" ]}
}],
"encoding" : {
"x" : {
"field" : "year_in" ,
"type" : "quantitative" ,
scale : {zero : false , nice : false },
axis : {format : ".0f" , grid : false , tickCount : 12 , ticks : true }
},
"y" : {
"field" : "is_leap_year" ,
"type" : "quantitative" ,
"axis" : { "grid" : false , values : [0 , 1 ], format : '.0f' }
}
}
}]
})
leap_year_data = calcuvegadata ({
models : modelname == 'leap-year' ? [model] : [],
spec : leap_year_spec,
domains : { year_in : _. range (leap_year_start, leap_year_start+ 1001 ) },
input_cursors : [{}]
})
viewof leap_year_viz = embed (leap_year_spec)
{
leap_year_viz. data ("data" , leap_year_data). resize (). run ();
}
mutable leap_year_data0 = [] // includes data in spec for when I Open in Vega Editor
htl. html `<button onclick= ${ () => (mutable leap_year_data0 = leap_year_data)} >insert data -> vega editor (dev)</button>`
viewof frame_in = Scrubber (_. range (0 , 60 * 5 ), {autoplay : false , delay : 50 , label : 'frame' , initial : 2 })
viewof speed_in = Inputs. range ([- 3 , 15 ], {value : 3 , label : 'speed' , step : 0.01 })
starfield_spec = ({
background : 'black' , //'lightblue',
"data" : {"name" : "data" },
"datasets" : { "data" : starfield_data0},
"height" : 300 , width : 300 ,
layer : [{
"mark" : {type : "point" , tooltip : true , clip : false },
/*"params": [{
"name": "brush",
value: { x: [leap_year_start, leap_year_start+50]}, // initial value here fails whenever I switch to this model after loading a different one ... understand
//"value": {"year_in": [2000, 2010]},
"select": {"type": "interval", "encodings": ["x"]}
}],*/
"encoding" : {
"x" : {
"field" : "x" ,
"type" : "quantitative" ,
scale : {zero : false , nice : false , domain : [- 50 , 50 ]},
//axis: {format:".0f", grid: false, tickCount:12, ticks:true }
},
"y" : {
"field" : "y" ,
"type" : "quantitative" ,
scale : {zero : false , nice : false , domain : [- 50 , 50 ]},
//"axis": { "grid": false, values: [0,1], format: '.0f'}
},
"size" : {
"field" : "z" ,
"type" : "quantitative" ,
scale : {reverse : true },
legend : false
//scale: {zero:false, nice: false, domain:[-50,50]},
//"axis": { "grid": false, values: [0,1], format: '.0f'}
},
color : {value : 'white' }, //opacity: {value:1}
}
},
{
mark : 'rule' ,
encoding : {
"x" : {
"field" : "x" ,
"type" : "quantitative" ,
scale : {zero : false , nice : false , domain : [- 50 , 50 ]},
//axis: {format:".0f", grid: false, tickCount:12, ticks:true }
},
"y" : {
"field" : "y" ,
"type" : "quantitative" ,
scale : {zero : false , nice : false , domain : [- 50 , 50 ]},
//"axis": { "grid": false, values: [0,1], format: '.0f'}
},
"x2" : {
"field" : "px" ,
"type" : "quantitative" ,
scale : {zero : false , nice : false , domain : [- 50 , 50 ]},
//axis: {format:".0f", grid: false, tickCount:12, ticks:true }
},
"y2" : {
"field" : "py" ,
"type" : "quantitative" ,
scale : {zero : false , nice : false , domain : [- 50 , 50 ]},
//"axis": { "grid": false, values: [0,1], format: '.0f'}
},
color : {value : 'white' }
}
}]
})
starfield_data = calcuvegadata ({
models : modelname == 'starfield' ? [model] : [],
spec : starfield_spec,
domains : { star_in : _. range (0 , 100 ) },
input_cursors : [{speed_in, frame_in, random_seed_in : 'hello' }]
})
viewof starfield_viz = embed (starfield_spec)
{
starfield_viz. data ("data" , starfield_data)/*.resize()*/ . run ();
}
mutable starfield_data0 = [] // includes data in spec for when I Open in Vega Editor
htl. html `<button onclick= ${ () => (mutable starfield_data0 = starfield_data)} >insert data -> vega editor (dev)</button>`
maze_in = [
[33 , 59 , 92 , 97 , 80 , 25 , 71 , 67 , 29 , 36 , 76 , 22 , 58 , 14 , 42 ],
[26 , 2 , 87 , 64 , 40 , 76 , 70 , 75 , 24 , 19 , 94 , 86 , 87 , 40 , 25 ],
[26 , 21 , 57 , 63 , 100 , 96 , 21 , 82 , 22 , 93 , 15 , 57 , 49 , 81 , 73 ],
[68 , 55 , 11 , 67 , 46 , 73 , 44 , 32 , 84 , 100 , 51 , 87 , 18 , 75 , 82 ],
[80 , 41 , 36 , 11 , 43 , 74 , 39 , 93 , 35 , 53 , 73 , 59 , 27 , 8 , 30 ],
[28 , 86 , 21 , 75 , 69 , 27 , 41 , 88 , 84 , 83 , 75 , 90 , 60 , 11 , 12 ],
[77 , 87 , 0 , 59 , 35 , 5 , 79 , 37 , 45 , 2 , 29 , 4 , 99 , 4 , 43 ],
[55 , 91 , 69 , 24 , 9 , 67 , 84 , 100 , 99 , 41 , 29 , 62 , 65 , 27 , 87 ],
[30 , 27 , 12 , 86 , 21 , 8 , 40 , 81 , 81 , 39 , 56 , 82 , 40 , 36 , 79 ],
[72 , 6 , 94 , 64 , 51 , 42 , 97 , 93 , 74 , 1 , 70 , 97 , 68 , 42 , 72 ],
[0 , 16 , 20 , 11 , 1 , 59 , 48 , 41 , 87 , 51 , 53 , 35 , 25 , 46 , 21 ],
[34 , 57 , 0 , 98 , 86 , 30 , 69 , 25 , 82 , 84 , 86 , 2 , 17 , 67 , 25 ],
[88 , 100 , 23 , 72 , 12 , 73 , 51 , 47 , 4 , 61 , 44 , 44 , 49 , 53 , 27 ],
[95 , 31 , 31 , 39 , 55 , 64 , 61 , 52 , 41 , 33 , 86 , 0 , 40 , 48 , 90 ],
[79 , 3 , 62 , 88 , 27 , 93 , 29 , 0 , 91 , 24 , 17 , 49 , 2 , 68 , 33 ],
];
embed (
calcuvizspec ({
models : modelname == 'maze' ? [model] : [],
input_cursors : [{maze_in}],
mark : {type : 'text' , fontSize : 11 , fontWeight : 'bold' },
encodings : {
row : {name : 'formula' , type : 'nominal' , domain : ['value' , 'max' , 'arrow' , 'arrow_path' , 'arrow_path_pretty' ], axis : {labelFontSize : 60 }, sort : ['value' ]},
x : {name : 'x_in' , type : 'nominal' , domain : _. range (0 , 15 ), axis : {grid : false }},
y : {name : 'y_in' , type : 'nominal' , domain : _. range (0 , 15 ), axis : {grid : false }},
text : {name : 'value' , type : 'nominal' },
//color: {name: 'value', type: 'quantitative', independent:true, legend:false},
},
width : 400 , // height:50,
//spec_post_process: spec => { spec.background = 'white'; spec.encoding.color.scale = {scheme:'viridis'}; return spec;}
spec_post_process : spec => { spec. encoding . row . header = {labelLimit : 100 }; spec. background = 'white' ; spec. encoding . color = {value : 'darkblue' }; return spec; }
}))
viewof num_snowflakes = Inputs. range ([0 , 100 ], {label : '# snowflakes' , value : 20 , step : 1 })
viewof w_in = Inputs. range ([0 , 2 ], {label : 'angular speed (w_in)' , value : 0.6 , step : 0.01 })
viewof height_in = Inputs. range ([100 , 500 ], {label : 'height' , value : 200 , step : 20 })
viewof f_in = Scrubber (_. range (0 , 60 * 100 ), {autoplay : false , delay : 10 , label : 'frame' })
embed (
calcuvizspec ({
models : modelname == 'snowflakes' ? [model] : [],
input_cursors : [{f_in, w_in, width_in : 400 , height_in}],
mark : {type : 'text' , clip : true },
encodings : {
x : { name : 'x' , type : 'quantitative' , scale : { domain : [0 , 400 ] } },
y : { name : 'y' , type : 'quantitative' , sort : 'descending' , scale : { domain : [0 , height_in] } },
size : { name : 'size' , type : 'quantitative' , legend : false },
angle : {name : 'x' , type : 'quantitative' , scale : {range : [0 , 300 ] } },
// different snowflakes:
detail : {name : 'p_in' , type : 'nominal' , domain : _. range (0 , num_snowflakes, 1 ) },
},
width : 300 ,
height : 260 ,
// viz customizations:
spec_post_process : spec => {
spec. encoding . text = { value : 'โ๏ธ' }
spec. background = 'darkred' ;
return spec
}
})
, {theme : 'vox' , renderer : 'canvas' })
embed (
calcuvizspec ({
models : modelname == 'snowflakes' ? [model] : [],
input_cursors : [{ w_in, width_in : 400 , height_in}],
mark : {type : 'line' , point : true , strokeWidth : '1px' },
encodings : {
x : { name : 'f_in' , type : 'quantitative' , domain : _. range (0 , 501 , 5 ) },
y : { name : 'value' , type : 'quantitative' , independent : true },
color : {name : 'p_in' , type : 'nominal' , domain : [0 , 1 , snowflake_in] },
row : {name : 'formula' , type : 'nominal' , domain : ['x' , 'y' , 'angle' , 'size' , 'radius' , 'initial_angle' ], sort : 'descending' },
},
width : 200 ,
height : 30 ,
// viz customizations:
spec_post_process : spec => {
spec. config = { point : { size : 10 } }
spec. encoding . y . title = null ;
return spec
}
})
, {})
viewof snowflake_in = Inputs. range ([0 , num_snowflakes], {value : 10 , label : 'chosen extra โ๏ธ' , step : 1 })
viewof petal_num_in = Inputs. range ([0 , 50 ], {label : '# petals' , value : 7 , step : 1 })
viewof petal_size_in = Inputs. range ([0 , 50 ], {label : 'petal size' , value : 30 , step : 1 })
viewof petals_roundy_spikiness_in = Inputs. range ([0 , 2 ], {label : 'petals roundy/spikiness' , value : 0.5 , step : 0.1 })
viewof flower_big = Inputs. toggle ({label : 'big?' , value : false })
embed (
calcuvizspec ({
models : modelname == 'flower' ? [model] : [],
input_cursors : [{petal_num_in, petal_size_in, petals_roundy_spikiness_in}],
mark : 'rect' , // heatmap
encodings : {
x : { name : 'column_in' , type : 'nominal' , domain : _. range (0 , 101 , 1 ) },
y : { name : 'row_in' , type : 'nominal' , domain : _. range (0 , 101 , 1 ) },
color : {'name' : 'value' , type : 'quantitative' , scale : {scheme : 'brownbluegreen' , reverse : true }, legend : false , },
row : {
name : 'formula' , sort : 'descending' ,
domain : ['result' , 'petals' , 'head' , 'face' ]
}
},
width : 300 * (flower_big ? 1.5 : 0.5 ),
height : 250 * (flower_big ? 1.5 : 0.5 ),
// viz customizations:
spec_post_process : spec => {
spec. encoding . x . axis = { grid : false , domain : false , ticks : 0 , labels : false }
spec. encoding . y . axis = { grid : false , domain : false , ticks : 0 , labels : false }
return spec
}
})
)
viewof timing_in = Inputs. select (["start of today" , "start of tomorrow" ], {label : 'from' })
md `** ${ (d3. format ('.2%' )(model. fractionLeftInYear ({date_in : new Date (), timing_in})))} ** of year reamining, or visually:`
Plot. plot ({
//marginLeft: 90,
height : 80 ,
//width:5000,
color : { legend : true },
marks : [ Plot. axisX ({anchor : "top" }),
, Plot. barX (
[{sort : 1 , status : 'remaining' , percent : 100 * model. fractionLeftInYear ({date_in : new Date (), timing_in})},
{sort : 0 , status : 'complete' , percent : 100 * model. fractionIntoYear ({date_in : new Date (), timing_in})
}]
, { x : "percent" , fill : "status" , sort : 'sort' })]
})
embed (
calcuvizspec ({
models : modelname == 'monte-carlo-pension-calculator' ? [model] : [],
input_cursors : [{
random_seed_in : "something really random" ,
"age_0_in" : 30 ,
"fund_value_0_in" : 0 ,
"retirement_age_in" : 65 ,
"missed_contribution_age_in" : 0 ,
"salary_age_0_in" : 30 ,
"salary_0_in" : 30000 ,
"salary_inflation_rate_in" : 0.02 ,
"empee_contribution_rate_in" : 0.1 ,
"emper_matching_rate_in" : 1 ,
"contribution_charge_rate_in" : 0.1 ,
"management_charge_rate_in" : 0.01 }],
mark : {type : 'text' , fontSize : 20 , fontWeight : 'bold' },
encodings : {
x : {name : 'formula' , type : 'nominal' , domain : ['retirement_fund_value' ], axis : {labelFontSize : 60 }},
y : {name : 'simulation_in' , type : 'ordinal' , domain : _. range (0 , 10 , 1 )},
text : {name : 'value' , type : 'quantitative' , format : ',.0f' },
//color: {name: 'formula', type:'nominal', domain: formulae_not_inputs, legend:false},
},
width : 90 , // height:50,
}))
embed (
calcuvizspec ({
models : modelname == 'monte-carlo-pension-calculator' ? [model] : [],
input_cursors : [{
random_seed_in : "something really random" ,
"age_0_in" : 30 ,
"fund_value_0_in" : 0 ,
"retirement_age_in" : 65 ,
"missed_contribution_age_in" : 0 ,
"salary_age_0_in" : 30 ,
"salary_0_in" : 30000 ,
"salary_inflation_rate_in" : 0.02 ,
"empee_contribution_rate_in" : 0.1 ,
"emper_matching_rate_in" : 1 ,
"contribution_charge_rate_in" : 0.1 ,
"management_charge_rate_in" : 0.01 }],
mark : {type : 'line' , point : false , fontSize : 20 , fontWeight : 'bold' },
encodings : {
//x: {name: 'formula', type:'nominal', domain: ['retirement_fund_value'], axis: {labelFontSize: 60}},
x : {name : 'simulation_in' , type : 'quantitative' , domain : _. range (0 , 100 , 1 )},
y : {name : 'retirement_fund_value' , type : 'quantitative' , format : ',.0f' },
//color: {name: 'formula', type:'nominal', domain: formulae_not_inputs, legend:false},
},
width : 300 , height : 100 ,
}))
embed (
calcuvizspec ({
models : modelname == 'monte-carlo-pension-calculator' ? [model] : [],
input_cursors : [{
random_seed_in : "something really random" ,
"age_0_in" : 30 ,
"fund_value_0_in" : 0 ,
"retirement_age_in" : 65 ,
"missed_contribution_age_in" : 0 ,
"salary_age_0_in" : 30 ,
"salary_0_in" : 30000 ,
"salary_inflation_rate_in" : 0.02 ,
"empee_contribution_rate_in" : 0.1 ,
"emper_matching_rate_in" : 1 ,
"contribution_charge_rate_in" : 0.1 ,
"management_charge_rate_in" : 0.01 }],
mark : {type : 'line' , point : false , fontSize : 20 , fontWeight : 'bold' },
encodings : {
//x: {name: 'formula', type:'nominal', domain: ['retirement_fund_value'], axis: {labelFontSize: 60}},
color : {name : 'simulation_in' , type : 'nominal' , domain : _. range (0 , 100 , 1 )},
x : {name : 'age_in' , type : 'quantitative' , domain : _. range (20 , 70 , 1 )},
y : {name : 'fund_value' , type : 'quantitative' , format : ',.0f' },
//color: {name: 'formula', type:'nominal', domain: formulae_not_inputs, legend:false},
},
width : 300 , height : 400 ,
}))
viewof s = Inputs. toggle ({ value : false , label : "persist interaction ๐จ" })
viewof form = {
let form = Inputs. form ({
age_0_in : Inputs. range ([18 , 65 ], {label : 'starting age' , value : q. get ('age_0_in' ) ?? 50 , step: 1 }),
retirement_age_in : Inputs. range ([50 , 75 ], {label : 'retirement age' , value : 65 , step : 1 }),
fund_value_0_in : Inputs. range ([0 , 1000000 ], {label : 'starting fund value' , value : 0 , step : 1000 }),
empee_contribution_rate_in : Inputs. range ([0.00 , 0.30 ], {label : 'empee contribution as % of salary' , value : 0.1 , step : 0.01 }),
emper_matching_rate_in : Inputs. range ([0.00 , 2 ], {label : 'emper matching rate' , value : q. get ('emper_matching_rate_in' ) ?? 1 , step: 0.02 }),
salary_0_in : Inputs. range ([5000 , 300000 ], {label : 'salary' , value : q. get ('salary_0_in' ) ?? 50000 , step: 1000 }),
salary_age_0_in : Inputs. range ([18 , 65 ], {label : 'salary ref. age' , value : q. get ('salary_age_0_in' ) ?? 50 , step: 1 }),
salary_inflation_rate_in : Inputs. range ([- 0.02 , 0.10 ], {label : 'salary growth rate' , value : 0.02 , step : 0.002 }),
unit_growth_rate_in : Inputs. range ([- 0.02 * 0 /*banned (along with neg charges) for technical reasons with the visual !!*/ , 0.10 ], {label : 'investment growth rate' , value : q. get ('unit_growth_rate_in' ) ?? 0.05 , step: 0.002 }),
contribution_charge_rate_in : Inputs. range ([- 0.02 * 0 , 0.10 ], {label : 'contribution charge rate' , value : 0.04 , step : 0.01 }),
management_charge_rate_in : Inputs. range ([- 0.02 * 0 , 0.10 ], {label : 'management charge rate' , value : 0.01 , step : 0.002 }),
missed_contribution_age_in : Inputs. range ([15 , 75 ], {label : 'Missed contribution (scenario)' , value : 15 , step : 1 }),
}, {template});
let state = false ; // need to avoid circ. defn.
form. oninput = (d) => {console . log ('oninput' , d); if (state == false ) {mutable inputs_history = [... mutable inputs_history, form. value ]; state = true ; mutable statey = true ; mutable changing = d. srcElement . parentElement . previousElementSibling . textContent } mutable inputs_history = [... mutable inputs_history. slice (0 ,- 1 ), form. value ]}
form. onchange = () => { console . log ('onchange' ); state = false ; mutable statey = s /* false */ ; mutable inputs_history[mutable inputs_history. length - 1 ] = form. value };
return form;
}
md `**retirement fund projection = <span style="color:darkorange"><span style="font-size:2em">โฌ ${ f (new_value)} **</span></span> <span style="color: ${ good_guy ? 'green' : 'red' } ; font-size:13px">** ${ statey ? (good_guy ? 'โ' : 'โ' )+ f (Math . abs (new_value - old_value)) : ' ' } **</span>
*The retirement fund projection is the **pre-retirement area under inflows less the pre-retirement area under outflows** below.*`
{
if (! statey) return md `.................................................................................................................................................................... ....................................................................................................................................................................` ;
else return md `impact <span style="color: ${ good_guy ? 'green' : 'red' } ">โฌ** ${ statey ? (good_guy ? 'โ' : 'โ' )+ f (Math . abs (new_value - old_value)) : ' ' } **</span> due to **ฮ ${ changing} ** ( ${ d3. format (',.2f' )(old_in)} -> ${ d3. format (',.2f' )(new_in)} ) is visualised as **<span style="color:green">green boxes</span> - <span style="color:red">red boxes</span>** (inflows and outflows). ๐ฆ๐ฆ`
}
data_all2 = calcudata ({
models : modelname == 'pension-calculator' ? [model] : [],
input_domains : {age_in : _. range (Math . min (inputs_history[input_cursor_id]. age_0_in , inputs_history[input_cursor_id- 1 ]. age_0_in )- 1 , Math . max (inputs_history[input_cursor_id]. retirement_age_in , inputs_history[input_cursor_id- 1 ]. retirement_age_in )+ addl_years, 1 )},
input_cursors : inputs_history. map (d => ({... d, /*age_in:d.retirement_age_in*/ /* TODO hacky? */ })),
outputs : [... formulae, 'check_cfs_vs_fund_value' ],
pivot : false
})//.map(d => d.formula == 'fund_charges' ? ({...d, value:-d.value}) : d) // TEMP9
. filter (d => d. input_cursor_id == input_cursor_id || (d. input_cursor_id == input_cursor_id- 1 && statey))
// check_cfs_vs_fund_value is weakened as a check
embed ({
"$schema" : "https://vega.github.io/schema/vega/v5.json" ,
//"background": "#eef",
"padding" : 5 ,
"width" : 400 ,
//"height": 500,
"style" : "cell" ,
signals : [ {"name" : "offset" , "value" : 80 },
{"name" : "cellHeight" , "value" : 150 },
{"name" : "height" , "update" : "2 * (offset + cellHeight)" }
],
"data" : [
{
"name" : "data" ,
"values" : data_all2,
"transform" : [
{
type : 'formula' ,
//expr: 'datum.value >=0', This is bloody hard to debug and do correctly, and must be correct for posneg swaps etc. So just banning negative charges for now and will fix this later!
expr : 'indexof(["management_charge","contribution_charge"],datum.formula) == -1' ,
as : 'posneg'
},
{
type : 'formula' , expr : 'abs(datum.value)' , as : 'value'
},
{
"type" : "stack" ,
"groupby" : ["age_in" ],
"field" : "value" ,
"sort" : {"field" : ["formula" , "input_cursor_id" ], "order" : ["ascending" , "descending" ]},
"as" : ["value1_start" , "value1_end" ],
"offset" : "zero"
},
]
},
{
name : 'data_old' ,
source : 'data' ,
"transform" : [
{
type : 'filter' ,
expr : `datum.input_cursor_id == ${ input_cursor_id- 1 } ` , // eliminates chk
},
{
"type" : "stack" ,
"groupby" : ["age_in" ],
"field" : "value" ,
"sort" : {"field" : ["formula" , "input_cursor_id" ], "order" : ["ascending" , "descending" ]},
"as" : ["value1_start" , "value1_end" ],
"offset" : "zero"
},
]}
],
scales : [ {
"name" : "gscale" ,
"type" : "band" ,
"range" : [0 , {"signal" : "height" }],
"round" : true ,
"domain" : {
"data" : "data" ,
"field" : "posneg" ,
/*"sort": {
"field": "posneg",
"op": "median",
"order": "descending"
}*/
}
},
{
"name" : "y_shared" ,
"type" : "linear" ,
domainMin : '0' , // domainMax: '60000', // doesn't generalise
"domain" : {"data" : statey? "data_old" : 'data' , "fields" : ["value1_start" , "value1_end" ]}, // give this a cushion ??
"range" : [{"signal" : "cellHeight" }, 0 ],
"nice" : false ,
"zero" : true ,
},
{
"name" : "y_bad" , // how to share y scale on the stack total ?!
"type" : "linear" ,
domainMin : '0' , domainMax : {signal : "domain('yyy')[1]*2" }, // doesn't generalise. Can I not x2 or update when dragging?
"domain" : {"data" : "data" , "fields" : ["value1_start" , "value1_end" ]}, // give this a cushion ??
"range" : [{"signal" : "cellHeight" }, 0 ],
"nice" : false ,
"zero" : true ,
},
{
"name" : "color" ,
"type" : "ordinal" ,
"domain" : {"data" : "data" , "field" : "formula" , "sort" : ["empee_contribution_cost" , "check_cfs_vs_fund_value" ]},
"range" : "category"
},
],
"marks" : [
{
type : 'group' ,
name : 'posneg' ,
from : {
facet : {
data : 'data' ,
name : 'posnegs' ,
groupby : 'posneg'
}
},
data : [{name : 'data_windowed' , source : 'posnegs' , transform : [
{
"type" : "window" ,
"params" : [null ],
"as" : ["B_value" ],
"ops" : ["lag" ],
"fields" : ["value" ],
"sort" : {
"field" : ["input_cursor_id" ],
"order" : ["descending" ]
},
"groupby" : ["formula" , "age_in" ],
"frame" : [- 1 , 0 ]
},
{
"type" : "formula" ,
"expr" : "datum.value-datum.B_value" ,
"as" : "impact"
},
{"type" : "formula" , "expr" : `datum.value > datum.B_value ? 0 : datum.B_value - datum.value` , "as" : "adj" }, // I need to record the adj in id-1 for use in calc for id w. a follow-up window fn !
{"type" : "formula" , "expr" : `datum.input_cursor_id == ${ input_cursor_id- 1 } ? datum.value > datum.B_value ? datum.value - datum.B_value : datum.B_value - datum.value : datum.value` , "as" : "BplusAminusB" }, // I need to record the adj in id-1 for use in calc for id w. a follow-up window fn !
{"type" : "formula" , "expr" : `datum.adj` , "as" : "adjposneg" },
{
"type" : "window" ,
"params" : [null ],
"as" : ["B_adj" ],
"ops" : ["lag" ],
"fields" : ["adj" ],
"sort" : {
"field" : ["input_cursor_id" ],
"order" : ["ascending" ]
},
"groupby" : ["formula" , "age_in" ],
"frame" : [- 1 , 0 ]
},
{
"type" : "formula" ,
"expr" : "datum.BplusAminusB-datum.B_adj" ,
"as" : "BplusAminusB"
},
{"type" : "filter" , "expr" : "abs(datum.BplusAminusB)>=0.01" },
]},
{
"name" : "data_0" ,
"source" : "data_windowed" ,
"transform" : [
{
"type" : "stack" ,
"groupby" : ["age_in" ],
"field" : "BplusAminusB" ,
"sort" : {"field" : ["formula" , "input_cursor_id" ], "order" : ["ascending" , "descending" ]},
"as" : ["value_start" , "value_end" ],
"offset" : "zero"
},
{ type : 'formula' , 'expr' : 'abs(datum.value_end)' , as : 'abs_value_end' },
{ type : 'formula' , 'expr' : 'abs(datum.value_start)' , as : 'abs_value_start' },
/*{
"type": "filter",
"expr": "isValid(datum[\"age_in\"]) && isFinite(+datum[\"age_in\"]) && isValid(datum[\"value\"]) && isFinite(+datum[\"value\"])"
}*/
]
}],
"encode" : {
"enter" : {
"y" : {"scale" : "gscale" , "field" : "posneg" , "offset" : {"signal" : "offset" }},
"height" : {"signal" : "cellHeight" },
"width" : {"signal" : "width" },
"stroke" : {"value" : "#ccc" }
}
},
marks : [
{
"name" : "marks" ,
"type" : "rect" ,
"style" : ["bar" ],
"from" : {"data" : "data_0" },
"encode" : {
"update" : {
"stroke" : { "signal" : `datum.posneg ? (datum.impact > 0 ? 'red' : 'green') : (datum.impact > 0 ? 'green' : 'red')` },
//"stroke": { "signal": `datum.posneg ? (datum.input_cursor_id == ${input_cursor_id} ? 100 : -100) : (datum.input_cursor_id == ${input_cursor_id} ? -100 : 100)`},
"strokeOpacity" : {"value" : 1 },
"tooltip" : {
"signal" : "{formula: datum['formula'], value: format(datum.value, ',.2f'), Age: datum['age_in']}"
},
"fill" : {"scale" : "color" , "field" : "formula" },
"opacity" : {"scale" : "opacity" , "field" : "input_cursor_id" },
"strokeWidth" : {"scale" : "strokeWidth" , "field" : "input_cursor_id" },
"ariaRoleDescription" : {"value" : "bar" },
"description" : {
"signal" : " \" age_in: \" + (format(datum[ \" age_in \" ], \"\" )) + \" ; value: \" + (format(datum[ \" value \" ], \"\" )) + \" ; formula: \" + (isValid(datum[ \" formula \" ]) ? datum[ \" formula \" ] : \"\" +datum[ \" formula \" ])"
},
"x" : {"scale" : "x" , "field" : "age_in" },
"width" : {"signal" : "max(0.25, bandwidth('x'))" },
"y" : {"scale" : y, "field" : "abs_value_end" },
"y2" : {"scale" : y, "field" : "abs_value_start" }
}
}
}
],
"scales" : [ {
"name" : "y" ,
"type" : "linear" ,
domainMin : '0' , // domainMax: '60000', // doesn't generalise
"domain" : {"data" : 'data_0' , "fields" : ["abs_value_start" , "abs_value_end" ]}, // give this a cushion ??
"range" : [{"signal" : "cellHeight" }, 0 ],
"nice" : false ,
"zero" : true ,
},
{
"name" : "x" ,
"type" : "band" ,
"domain" : {"data" : "data_0" , "field" : "age_in" , "sort" : true },
"range" : [0 , {"signal" : "width" }],
"paddingInner" : 0.2 ,
"paddingOuter" : 0.1
},
{ name : 'opacity' , type : 'ordinal' , "domain" : [input_cursor_id- 1 , input_cursor_id] , range : [1 , statey ? 0.3 : 1 ]},
{ name : 'strokeWidth' , type : 'ordinal' , "domain" : [input_cursor_id, input_cursor_id- 1 ] , range : [0 , 2 ]},
{
"name" : "threshold" ,
"type" : "threshold" ,
"domain" : [- 99999999 , 1 ],
"range" : ["green" , "red" , "green" ]
}
],
"axes" : [
{
"scale" : "x" ,
"orient" : "bottom" ,
"grid" : false ,
//"title": "age_in",
//"labelAlign": "center",
//"labelAngle": -10,
"labelBaseline" : "top" ,
"zindex" : 0 ,
ticks : true ,
values : _. range (15 , 105 , 5 )
},
{
"scale" : y,
"orient" : "left" ,
"grid" : true ,
gridOpacity : 0.2 ,
"title" : ` ${ statey ? 'ฮ ' : '' } fund value` ,
"labelOverlap" : true ,
"tickCount" : 5 ,
"zindex" : 0
}
],
},
{
"type" : "text" ,
"from" : {"data" : "posneg" },
"encode" : {
"enter" : {
"x" : {"field" : "width" , "mult" : 0.5 },
"y" : {"field" : "y" , offset :- 25 },
"fontSize" : {"value" : 31 },
opacity : {value : 0.7 },
//"fontWeight": {"value": "bold"},
"text" : {"signal" : "datum.datum.posneg ? 'Inflows ๐ฐ' : 'Outflows ๐ธ' " },
"align" : {"value" : "center" },
"baseline" : {"value" : "top" },
"fill" : {"value" : "#000" }
}
}
}],
"legends" : [{"fill" : "color" , "symbolType" : "square" , "title" : "formula" }],
"config" : {
"legend" : {"orient" : "right" , "layout" : {"right" : {"anchor" : "middle" }}}
}
})
viewof shared_y_scale = Inputs. toggle ({label : 'shared y scale?' , value : true })
md `<br/>`
DOM. download (serialize (data_all2), "pension_savings_calculator.csv" , "csv download" )
md `<br/>`
DOM. download (new Blob ([unpivot. toCSV ()], {type : "text/csv" }), "pension_savings_calculator_unpivot.csv" , "csv download (pivoted)" )
old_in = inputs_history[inputs_history. length - 2 ][changing_i_in]
new_in = inputs_history[inputs_history. length - 1 ][changing_i_in]
mutable changing = ''
changing_i = Array . from (viewof form. children . item (0 ). children ). map ((d, i) => ({d, i})). find (e => e. d . textContent . includes (changing)). i
changing_i_in = Object . entries (viewof form. value )[changing_i][0 ]
formulae = ['empee_contribution_tax_relief' , 'empee_contribution_cost' , 'emper_contribution' , 'fund_growth' , 'contribution_charge' , 'management_charge' ]
y = shared_y_scale ? 'y_shared' : 'y'
f = d3. format (',.2f' )
mutable statey = false ;
mutable inputs_history = [JSON . parse (`{"age_0_in": ${ q. get ('age_0_in' ) ?? 50 } ,"fund_value_0_in":0,"unit_growth_rate_in": ${ q. get ('unit_growth_rate_in' ) ?? 0.05 } ,"retirement_age_in":65,"salary_0_in": ${ q. get ('salary_0_in' ) ?? 50000 } ,"salary_inflation_rate_in":0.02,"empee_contribution_rate_in":0.1,"emper_matching_rate_in": ${ q. get ('emper_matching_rate_in' ) ?? 1 } ,"contribution_charge_rate_in":0.04,"management_charge_rate_in":0.01,"salary_age_0_in": ${ q. get ('salary_age_0_in' ) ?? 50 } ,"missed_contribution_age_in":15}`
), JSON . parse (`{"age_0_in": ${ q. get ('age_0_in' ) ?? 50 } ,"fund_value_0_in":0,"unit_growth_rate_in": ${ q. get ('unit_growth_rate_in' ) ?? 0.05 } ,"retirement_age_in":65,"salary_0_in": ${ q. get ('salary_0_in' ) ?? 50000 } ,"salary_inflation_rate_in":0.02,"empee_contribution_rate_in":0.1,"emper_matching_rate_in": ${ q. get ('emper_matching_rate_in' ) ?? 1 } ,"contribution_charge_rate_in":0.04,"management_charge_rate_in":0.01,"salary_age_0_in": ${ q. get ('salary_age_0_in' ) ?? 50 } ,"missed_contribution_age_in":15}`
)]
input_cursor_id = inputs_history. length - 1
addl_years = 2 // 2
new_value = model. retirement_fund_value (inputs_history[inputs_history. length - 1 ])
old_value = model. retirement_fund_value (inputs_history[inputs_history. length - 2 ])
good_guy = new_value - old_value > 0
viewof shop_application = Inputs. select (['shop-sliders' , 'shop-x-sales-price' ], {value : 'shop-sliders' , label : 'Application โ' , width : 150 })
viewof purchase_price_in = Inputs. range ([5 , 7 ], {step : 0.05 , label : 'purchase_price' , value : 5 , width : 200 })
viewof sales_price_in = Inputs. range ([5 , 7 ], {step : 0.05 , label : 'sales_price' , value : 6 , width : 200 , disabled : shop_application == 'shop-sliders' ? false : true })
viewof expenses_in = Inputs. range ([0 , 20000 ], {step : 1000 , label : 'expenses' , value : 15000 , width : 200 })
viewof units_in = Inputs. range ([0 , 50000 ], {step : 1000 , label : 'units' , value : 20000 , width : 200 })
embed (
calcuvizspec ({
models : modelname == 'shop' ? [model] : [],
input_cursors : [{sales_price_in, purchase_price_in, expenses_in, units_in}],
mark : {type : 'text' , fontSize : 20 , fontWeight : 'bold' },
encodings : {
y : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, axis : {labelFontSize : 60 }},
text : {name : 'value' , type : 'quantitative' , format : ',.0f' },
color : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, legend : false },
},
width : 90 , // height:50,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . y . sort = ["profit" ]; spec. encoding . y . axis = {labelFontSize : 20 , labelFontWeight : 'bold' }; return spec; }
}))
embed (
calcuvizspec ({
models : modelname == 'shop' ? [model] : [],
input_cursors : [{purchase_price_in, expenses_in, units_in}],
mark : {type : 'text' , fontSize : 15 , fontWeight : 'bold' },
encodings : {
x : {name : 'sales_price_in' , type : 'quantitative' , domain : [5 , 6 , 7 ], scale : {domain : [4 , 7 ]}},
y : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, axis : {labelFontSize : 60 }},
text : {name : 'value' , type : 'quantitative' , format : ',.0f' },
color : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, legend : false },
},
width : 200 , // height:50,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . y . sort = ["profit" ]; spec. encoding . y . axis = {labelFontSize : 15 , labelFontWeight : 'bold' }; return spec; }
}))
embed (
calcuvizspec ({
models : modelname == 'shop' ? [model] : [],
input_cursors : [{purchase_price_in, expenses_in, units_in}],
mark : 'bar' ,
encodings : {
x : {name : 'sales_price_in' , type : 'nominal' , domain : _. range (5 , 7.01 ,. 1 ), format : ',.2f' },
y : {name : 'value' , type : 'quantitative' , format : ',.0f' , independent : true , zero : false },
color : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, legend : false },
row : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, axis : {labelFontSize : 60 }},
},
width : 230 , height : 50 ,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . row . sort = ["profit" ]; spec. encoding . row . title = null ; spec. encoding . row . header = {labelFontSize : 16 , labelFontWeight : 'bold' }; return spec; }
}))
embed (
calcuvizspec ({
models : modelname == 'shop' ? [model] : [],
input_cursors : [{purchase_price_in, expenses_in, units_in}],
mark : {type : 'line' , point : true },
encodings : {
x : {name : 'sales_price_in' , type : 'quantitative' , domain : _. range (5 , 7.01 ,. 2 ), format : ',.2f' },
y : {name : 'value' , type : 'quantitative' , format : ',.0f' , independent : true , zero : false },
color : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, legend : false },
row : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, axis : {labelFontSize : 60 }},
},
width : 230 , height : 50 ,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . row . sort = ["profit" ]; spec. encoding . row . title = null ; spec. encoding . row . header = {labelFontSize : 16 , labelFontWeight : 'bold' }; return spec; }
}))
Inputs. bind (Inputs. range ([5 , 7 ], {step : 0.20 , label : 'purchase_price_in' , value : 5 , width : 200 }), viewof purchase_price_in)
Inputs. bind (Inputs. range ([0 , 20000 ], {step : 1000 , label : 'expenses_in' , value : 15000 , width : 200 }), viewof expenses_in)
formulae_not_inputs = Object . values (introspection. cul_functions ). filter (d => d. reason == 'definition' && inputs. indexOf (d. name + '_in' ) == - 1 ). map (d => d. name )
//formulae_not_inputs = //['profit','sales','purchases','expenses']
import {radio} from "@jgrunik/inputs"
embed (
calcuvizspec ({
models : [modelname == 'shop-demand-curve' ? model : (modelname == 'shop-demand-curve-modular' ? model : {})],
input_cursors : [{purchase_price_in, expenses_in}],
mark : 'bar' ,
encodings : {
x : {name : 'sales_price_in' , type : 'nominal' , domain : _. range (5 , 7.01 ,. 1 ), format : ',.2f' },
y : {name : 'value' , type : 'quantitative' , format : ',.0f' , independent : true , zero : false },
color : {name : 'formula' , type : 'nominal' , domain : ff, legend : false },
row : {name : 'formula' , type : 'nominal' , domain : ff},
},
width : 230 , height : 50 ,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . row . sort = ["profit" ]; spec. encoding . row . title = null ; spec. encoding . row . header = {labelFontSize : 16 , labelFontWeight : 'bold' }; return spec; }
}))
ff = [... formulae_not_inputs, 'units' ]
embed (
calcuvizspec ({
models : [modelname == 'shop-demand-curve' ? model : (modelname == 'shop-demand-curve-modular' ? model : {})],
input_cursors : [{purchase_price_in, expenses_in}],
mark : {type : 'line' , point : true },
encodings : {
x : {name : 'sales_price_in' , type : 'quantitative' , domain : _. range (5 , 7.01 ,. 2 )},
y : {name : 'value' , type : 'quantitative' , format : ',.0f' , independent : true , zero : false },
color : {name : 'formula' , type : 'nominal' , domain : ff, legend : false },
row : {name : 'formula' , type : 'nominal' , domain : ff},
},
width : 230 , height : 50 ,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . row . sort = ["profit" ]; spec. encoding . row . title = null ; spec. encoding . row . header = {labelFontSize : 16 , labelFontWeight : 'bold' }; return spec; }
}))
/*viewof annual_payment_in = Inputs.range([0,1000], {step: 100, value:1000, label: 'annual_payment_in'})
viewof interest_rate_in = Inputs.range([0,0.5], {step:0.01, label: 'interest_rate_in', value: 0.02})
viewof duration_in = Inputs.range([0,10], {step:1, value:5, label:'duration_in'})*/
// trying out lil-gui controls
function r2 (){
let preset = {}
let savings_options = {
annual_payment_in : 1000 ,
interest_rate : 2 ,
duration_in : 5 ,
savePreset : () => {
// save current values to an object
preset = gui. save ();
loadButton. enable ();
},
loadPreset : () => {
gui. load ( preset );
}
}
const container = DOM. element ("div" );
const form= DOM. element ("form" )
//autoPlace is true by default, which will display outside cell,so change to false.
const gui = new dat. GUI ({ autoPlace : false , closeOnTop : true })
gui. add (savings_options, 'annual_payment_in' , 0 , 1000 ). onChange (v=> form. dispatchEvent (new CustomEvent ("input" , {bubbles : true })))
gui. add (savings_options, 'interest_rate' , - 1 , 10 , 1 ). onChange (v=> form. dispatchEvent (new CustomEvent ("input" , {bubbles : true })))
gui. add (savings_options, 'duration_in' , 0 , 10 , 1 ). onChange (v=> form. dispatchEvent (new CustomEvent ("input" , {bubbles : true })))
gui. add ( savings_options, 'savePreset' );
const loadButton = gui. add ( savings_options, 'loadPreset' );
loadButton. disable ();
form. append (gui. domElement )
container. value = savings_options
container. append (form)
return container
}
viewof c = r2 ()
embed (
calcuvizspec ({
models : modelname == 'savings' ? [model] : [],
input_cursors : [{... c, interest_rate_in : c. interest_rate / 100 }],
mark : {type : 'text' , fontSize : 15 , fontWeight : 'bold' },
encodings : {
x : {name : 'formula' , type : 'nominal' , domain : ['balance' , 'interest' , 'deposits' , 'interest_rate' ], axis : {labelFontSize : 60 }},
y : {name : 'year_in' , type : 'nominal' , domain : _. range (0 , c. duration_in + 1 , 1 ), sort : 'ascending' },
text : {name : 'value' , type : 'quantitative' , format : ',.2f' },
color : {name : 'formula' , type : 'nominal' , domain : ['balance' , 'interest' , 'deposits' , 'interest_rate' ], legend : false },
},
"width" : 340 , height : 160
}))
/*viewof annual_payment_in = Inputs.range([0,1000], {step: 100, value:1000, label: 'annual_payment_in'})
viewof interest_rate_in = Inputs.range([0,0.5], {step:0.01, label: 'interest_rate_in', value: 0.02})
viewof duration_in = Inputs.range([0,10], {step:1, value:5, label:'duration_in'})*/
function r3 (){
let preset = {}
let options = {
skewness_in : - 1 ,
step_size : 0.25 ,
savePreset : () => {
// save current values to an object
preset = gui. save ();
loadButton. enable ();
},
loadPreset : () => {
gui. load ( preset );
}
}
const container = DOM. element ("div" );
const form= DOM. element ("form" )
//autoPlace is true by default, which will display outside cell,so change to false.
const gui = new dat. GUI ({ autoPlace : false , closeOnTop : true })
/*
!!! IMPORTANT:
special handle for color,or observable will not observe the change imediately.
same with range control
so you can simplely add .onChange for all control to dispatchEvent again
*/
//gui.addColor(options,'color').onChange(v=>form.dispatchEvent(new CustomEvent("input", {bubbles: true})))
//gui.add(options,'types',['one','two','three'])
//gui.add(options,'speed',{slow:1,fast:40})
//gui.add(options,'age',1,40)
//gui.addFolder('inputs โ๏ธ')
gui. add (options, 'skewness_in' , - 2 , 2 ). onChange (v=> form. dispatchEvent (new CustomEvent ("input" , {bubbles : true })))
gui. add (options, 'step_size' , 0.15 , 0.3 ). onChange (v=> form. dispatchEvent (new CustomEvent ("input" , {bubbles : true })))
gui. add ( options, 'savePreset' );
preset = gui. save ();
const loadButton = gui. add ( options, 'loadPreset' );
//loadButton.disable();
form. append (gui. domElement )
container. value = options
container. append (form)
return container
}
viewof sp_padrao_options = r3 ()
sp_padrao_data = calcudata ({
models : modelname == 'sp-padrao' ? [model] : [],
input_domains : {x_in : _. range (9 , 21 , sp_padrao_options. step_size ), y_in : _. range (- 4 , 7 , sp_padrao_options. step_size )},
input_cursors : [{... sp_padrao_options}],
outputs : ['x' , 'y' , 'color' ],
pivot : true
})
//Inputs.table(sp_padrao_data)
embed ({
"data" : {
"name" : "projection"
},
"mark" : {
"type" : "point" ,
"tooltip" : true ,
"strokeWidth" : 5 ,
"clip" : true
},
"encoding" : {
"x" : {
"field" : "x_in" ,
"type" : "quantitative" ,
"scale" : {
"zero" : false ,
"domain" : [
9 ,
21
]
},
"axis" : {
"labelAngle" : 0 ,
"orient" : "top"
}
},
"y" : {
"field" : "y" ,
"type" : "quantitative" ,
"scale" : {
"zero" : false ,
"domain" : [
- 3.5 ,
6.7
]
}
},
"detail" : {
"field" : "x_in" ,
"type" : "nominal"
},
"shape" : {
"field" : "y_in" ,
"type" : "nominal" ,
"scale" : {
"range" : [
"square"
]
}
},
"color" : {
"field" : "color" ,
"type" : "nominal" ,
"scale" : {
"range" : (color_range. includes ("๐ข๐ก๐ต" ) ? ["green" , "yellow" , "blue" ]
: (color_range. includes ("xyz" ) ? ["black" , "#f0f0f0" ]
: [ "black" , "#f0f0f0" ]))
}
},
"size" : {
"value" : 100
},
"opacity" :
opacity. length ? { "field" : "y_in" } : {}
},
"config" : {
"legend" : {
"disable" : true
}
},
"width" : 300 * (sp_padrao_size+ 1 ),
"height" : 250 * (sp_padrao_size+ 1 ),
"datasets" : {'projection' : sp_padrao_data}
})
viewof color_range = Inputs. select (["classic โซโช" , "๐ข๐ก๐ต" ], {label : "color scheme" , value : "classic โซโช" })
viewof opacity = Inputs. checkbox (["opacity" ], {label : "effects" , value : ["opacity" ]})
viewof sp_padrao_size = Inputs. toggle ({label : 'large' })
// trying out lil-gui controls
function r5 (){
let preset = {}
let options = {
operation_in : 'union' ,
i_in : 10 ,
alpha_in : 69 ,
beta_in : 38 ,
dist_in : 1.88 ,
fov_in : 34 ,
screen_width_in : 30 ,
screen_height_in : 30 ,
shape_A_in : 'cube' , shape_x_A_in : 0 , shape_y_A_in : 0 , shape_z_A_in : 0 , shape_size_A_in : 0.4 ,
shape_B_in : 'sphere' , shape_x_B_in : 0 , shape_y_B_in : 0.3 , shape_z_B_in : 0 , shape_size_B_in : 0.4 ,
savePreset : () => {
// save current values to an object
preset = gui. save ();
loadButton. enable ();
},
loadPreset : () => {
gui. load ( preset );
}
}
const container = DOM. element ("div" );
const form= DOM. element ("form" )
//autoPlace is true by default, which will display outside cell,so change to false.
const gui = new dat. GUI ({ autoPlace : false , closeOnTop : true })
gui. add (options, 'i_in' , 0 , 50 , 1 ). name ('# raymarching iterations' );
gui. add (options, 'operation_in' , ['union' , 'intersection' , 'B-A' , 'A-B' ]). name ('operation?' );
const shape_A = gui. addFolder ('shape A' );
shape_A. add (options, 'shape_A_in' , ['sphere' , 'cube' , 'teapot' ]);
shape_A. add (options, 'shape_x_A_in' , - 1 , 1 , 0.1 );
shape_A. add (options, 'shape_y_A_in' , - 1 , 1 , 0.1 );
shape_A. add (options, 'shape_z_A_in' , - 1 , 1 , 0.1 );
shape_A. add (options, 'shape_size_A_in' , 0 , 1 , 0.1 );
const shape_B = gui. addFolder ('shape B' );
shape_B. add (options, 'shape_B_in' , ['sphere' , 'cube' /*, 'teapot'*/ ]);
shape_B. add (options, 'shape_x_B_in' , - 1 , 1 , 0.1 );
shape_B. add (options, 'shape_y_B_in' , - 1 , 1 , 0.1 );
shape_B. add (options, 'shape_z_B_in' , - 1 , 1 , 0.1 );
shape_B. add (options, 'shape_size_B_in' , 0 , 1 , 0.1 );
gui. onChange (v=> form. dispatchEvent (new CustomEvent ("input" , {bubbles : true })));
gui. add ( options, 'savePreset' );
preset = gui. save ();
const loadButton = gui. add ( options, 'loadPreset' );
//loadButton.disable();
form. append (gui. domElement )
container. value = options
container. append (form)
return container
}
viewof raymarching_inputs = r5 ()
viewof alpha_in = Inputs. range ([- 180 , 180 ], {value : 69 , step : 1 , label : "alpha_in" })
viewof beta_in = Inputs. range ([- 180 , 180 ], {value : 38 , step : 1 , label : "beta_in" })
viewof dist_in = Inputs. range ([1 , 3 ], {value : 1.88 , step : 0.01 , label : "dist_in" })
viewof fov_in = Inputs. range ([1 , 180 ], {value : 34 , step : 1 , label : "fov_in" })
viewof sdf_shape_in = Inputs. select ([
'sphere' ,
'cube' ,
'cube_with_holes' ,
'cube_with_holes2' ,
'cube_with_holes3' ,
'cube_with_holes4' ,
'cube_and_torus' ,
'teapot' ,
], {value : 'cube_with_holes4' , label : "sdf_shape" , disabled : true })
viewof screen_width_in = Inputs. range ([0 , 150 ], {value : 30 , step : 10 , label : "screen_width_in" })
viewof screen_height_in = Inputs. range ([0 , 150 ], {value : 30 , step : 10 , label : "screen_height_in" })
embed (
calcuvizspec ({
models : modelname == 'raymarching' ? [model] : [],
input_cursors : [{alpha_in, beta_in, dist_in, fov_in, shape_A_in : 'cube' , screen_width_in, screen_height_in,... raymarching_inputs}],
mark : 'rect' , // heatmap
encodings : {
x : {'name' : 'xq_in' , type : 'nominal' , domain : _. range (0 , screen_width_in, 1 )},
y : {'name' : 'yq_in' , type : 'nominal' , domain : _. range (0 , screen_height_in, 1 )},
color : {'name' : 'value' , type : 'quantitative' },
row : {'name' : 'formula' , domain : ['brightness_2' /* can put more formulae here */ ]}
},
spec_post_process : spec => {
spec. width = 300 ;
spec. height = 250 ;
spec. encoding . y . sort = 'descending'
spec. encoding . x . axis = { grid : false , domain : false , ticks : 0 , labels : false }
spec. encoding . y . axis = { grid : false , domain : false , ticks : 0 , labels : false }
spec. config = {"legend" : {"disable" : true }}
spec. resolve = {scale : {color : 'independent' }}
spec. mark . clip = true ;
return spec;
}
})
)
sdf = require ('/models/raymarching/sdf_composed.js' )
data_sdf = calcudata ({
models : modelname == 'raymarching' ? [sdf] : [],
input_cursors : [{z_in : 0 , alpha_in, beta_in, dist_in, fov_in, shape_A_in : 'cube' , screen_width_in, screen_height_in,... raymarching_inputs, i_in : 100 }],
input_domains : { x_in : _. range (- 2 , 2.001 , 0.1 / 4 ),
y_in : _. range (- 2.5 , 2.001 , 0.1 / 4 ) }
,
outputs : ['sdf' ]
})
md `<br/>Plot of sdf_composed sdf @ z=0, iteration 100:`
viewof plot_sdf = Plot. plot ({
//width:400,
//height:260,
style : {
background : 'rgba(0,0,0,0)' // not applied to legend
},
color : {legend : true , reverse : true , label : `sdf` },
x : { label : 'x_in' , tickFormat : d3. format ('.2f' ), ticks : _. uniq (data_sdf. map (d => d. x_in ))},
y : { label : 'y_in' , tickFormat : d3. format ('.2f' ), ticks : _. uniq (data_sdf. map (d => d. y_in ))},
marks : [
Plot. contour (data_sdf, { // https://observablehq.com/plot/marks/contour
x : "x_in" ,
y : "y_in" ,
fill : "value" , interpolate : Plot. interpolateNearest , blur : false ,
interval : 0.15 ,
stroke : "black"
})
]
})
viewof saas_form = {
let form = Inputs. form ({
mrr_in : Inputs. range ([0 , 500000 ], {label : "monthly revenue (mrr)" , step : 5000 , value : 155000 }),
mrr_growth_in : Inputs. range ([- 0.3 , . 8 ], {label : "mrr growth factor" , step : 0.01 , value : 0.15 }),
vc_1_in : Inputs. range ([0 , 1e7 ], {label : "venture capital R1 (vc)" , step : 100000 , value : 1000000 }),
vc_2_in : Inputs. range ([0 , 1e7 ], {label : "venture capital R2 (vc)" , step : 100000 , value : 2000000 }),
salary_per_employee_in : Inputs. range ([0 , 30000 ], {label : "salary/employee" , step : 1000 }),
employees_0_in : Inputs. range ([0 , 50 ], {label : "employees @ start" , step : 1 , value : 26 }),
new_employees_per_month_in : Inputs. range ([0 , 10 ], {label : "new employees per mth" , step : 1 , value : 2 }),
rent_in : Inputs. range ([0 , 300000 ], {label : "rent" , step : 10000 , value : 200000 }),
}, /*{template}*/ );
return form
}
viewof last_month_in = Inputs. range ([0 , 36 ], {label : "last month (0-index)" , step : 1 , value : 17 })
viewof npv_i_in = Inputs. range ([- 0.1 , . 2 ], {label : "npv interest rate (monthly!)" , step : 0.001 , value : 0.0 })
viewof grid = Inputs. checkbox (["โฐ gridlines โฐ" ], {label : "" , value : ["" /*"โฐ gridlines โฐ"*/ ]})
saas_data = calcudata ({
models : modelname == 'saas-cashflows' ? [model] : [],
input_domains : {month_in : _. range (- 1 , last_month_in+ 1 , 1 )},
input_cursors : [{... saas_form, npv_i_in, last_month_in}],
outputs : formulae_not_inputs,
//pivot:true
})
md `The NPV over the ${ last_month_in+ 1 } months is **โฌ ${ d3. format (',.2f' )(model. npv ({... saas_form, last_month_in, npv_i_in, month_in :- 1 }))} **`
embed ({
"$schema" : "https://vega.github.io/schema/vega-lite/v5.json" ,
"data" : {"name" : "projection" },
"datasets" : {projection : saas_data},
"height" : 200 , "width" : 230 ,
"transform" : [{'filter' : 'indexof(datum.formula, "_cf") != -1' }],
"encoding" : {
"x" : {"field" : "month_in" },
"y" : {"field" : "value" , "type" : "quantitative" , "axis" : {"grid" : grid. length , labelAngle : 90 }},
},
"layer" : [
{
"transform" : [{"filter" : "datum.formula != 'total_cf'" }],
"mark" : {"type" : "bar" , "tooltip" : true },
"encoding" : {
"color" : {"field" : "formula" , "type" : "nominal" }
}
},
{
"transform" : [{"filter" : "datum.formula == 'total_cf'" }],
"mark" : {"type" : "line" , "point" : true , size : 3 , "tooltip" : true },
"encoding" : {
"color" : {"value" : "black" },
"opacity" : {"value" : 1 },
"strokeWidth" : {"value" : 2 },
"y" : {"field" : "value" , "type" : "quantitative" , "title" : "total_cf" }
}
}]
}, {theme : 'fivethirtyeight' })
embed (
calcuvizspec ({
models : modelname == 'saas-cashflows' ? [model] : [],
input_cursors : [{... saas_form, npv_i_in, last_month_in}],
mark : 'bar' ,
encodings : {
x : {name : 'month_in' , type : 'nominal' , domain : _. range (- 1 , last_month_in+ 1 , 1 )},
y : {name : 'value' , type : 'quantitative' , format : ',.0f' , independent : true , zero : false },
color : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, legend : false },
row : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, axis : {labelFontSize : 60 }},
},
width : 230 , height : 50 ,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . row . sort = ["profit" ]; spec. encoding . row . title = null ; spec. encoding . row . header = {labelFontSize : 16 , labelFontWeight : 'bold' }; return spec; }
}))
embed (
calcuvizspec ({
models : modelname == 'saas-cashflows' ? [model] : [],
input_cursors : [{... saas_form, npv_i_in, last_month_in}],
mark : {type : 'line' , point : true /*, interpolate: 'step-after'*/ },
encodings : {
x : {name : 'month_in' , type : 'nominal' , domain : _. range (- 1 , last_month_in+ 1 , 1 )},
y : {name : 'value' , type : 'quantitative' , format : ',.0f' , independent : true , zero : false },
color : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, legend : false },
row : {name : 'formula' , type : 'nominal' , domain : formulae_not_inputs, axis : {labelFontSize : 60 }},
},
width : 230 , height : 50 ,
spec_post_process : spec => { spec. encoding . y . title = null ; spec. encoding . row . sort = ["profit" ]; spec. encoding . row . title = null ; spec. encoding . row . header = {labelFontSize : 16 , labelFontWeight : 'bold' }; return spec; }
}))
viewof point = location_ui ({value : [- 6.475492 , 53.694712 ], label : "Co-ords" })
viewof clip1 = Inputs. toggle ({label : "clip1" , value : true })
viewof clip2 = Inputs. toggle ({label : "clip2" , value : true })
import { location as location_ui } from "@roelandschoukens/inputs"
viewof date_in = Inputs. date ({label : "start date" , value : new Date (2023 , 11 , 22 , 18 , 0 , 0 )})
md `**offset days:**`
d = date_fns. addDays (date_in, offset)
viewof fast = Inputs. toggle ({label : 'fast' , value : false })
viewof offset = Scrubber (_. range (0 , 365 , fast? 2 : 1 ), {autoplay : false , delay : fast ? 3 : 60 , label : 'offset' })
title = d3. timeFormat ('%B' )(d)
embed (calcuvizspec ({
models : modelname == 'sunsets' ? [model] : [],
input_cursors : [{
lat_in : point[1 ], lng_in : point[0 ], obj_in : 'sun' ,
}],
mark : {type : 'point' , size : 2000 , filled : true , tooltip : true , clip : clip1},
encodings : {
y : {grid : false , name : 'altitude_obj' , type : 'quantitative' , scale : {zero : false , domain : [0 , 1 ]}},
x : {grid : false , name : 'azimuth_obj' , type : 'quantitative' , scale : {zero : false , domain : [0 , 2 ]}},
color : {name : 'date_in' , sort : 'ascending' , type : 'quantitative' , legend : true , domain :
d3. timeHour . range (
new Date (d). setHours (clip2 ? 13 : 0 ),
new Date (d). setHours (clip2? 22 : 24 ),
1
), }//_.range(-60,24*60,30).map(d => date_fns.addMinutes(new Date(date_in), d))},
// row: additional months ..?
},
width : 500 *. 6 * (clip1 ? 1 : 0.5 ),
height : 400 *. 6 * (clip1 ? 1 : 0.5 ),
spec_post_process : s => { s. encoding . color . timeUnit = 'hours' ; s. title = title; s. titleFontSize = 40 ;
s. encoding . color . scale = {"scheme" : "lightmulti" }; return s }
}), {actions : true , config : {view : {fill : '#fdfaff' }}})
/*viewof annual_payment_in = Inputs.range([0,1000], {step: 100, value:1000, label: 'annual_payment_in'})
viewof interest_rate_in = Inputs.range([0,0.5], {step:0.01, label: 'interest_rate_in', value: 0.02})
viewof duration_in = Inputs.range([0,10], {step:1, value:5, label:'duration_in'})*/
function r4 (){
let preset = {}
let options = {no_points : 10000 ,
savePreset : () => {
// save current values to an object
preset = gui. save ();
loadButton. enable ();
},
loadPreset : () => {
gui. load ( preset );
}
}
const container = DOM. element ("div" );
const form= DOM. element ("form" )
//autoPlace is true by default, which will display outside cell,so change to false.
const gui = new dat. GUI ({ autoPlace : false , closeOnTop : true })
/*
!!! IMPORTANT:
special handle for color,or observable will not observe the change imediately.
same with range control
so you can simplely add .onChange for all control to dispatchEvent again
*/
//gui.addColor(options,'color').onChange(v=>form.dispatchEvent(new CustomEvent("input", {bubbles: true})))
//gui.add(options,'types',['one','two','three'])
//gui.add(options,'speed',{slow:1,fast:40})
//gui.add(options,'age',1,40)
gui. add (options, 'no_points' , 0 , 20000 ). onChange (v=> form. dispatchEvent (new CustomEvent ("input" , {bubbles : true })))
gui. add ( options, 'savePreset' );
preset = gui. save ();
const loadButton = gui. add ( options, 'loadPreset' );
//loadButton.disable();
form. append (gui. domElement )
container. value = options
container. append (form)
return container
}
viewof copacabana_options = r4 ()
copacabana_data = calcudata ({
models : modelname == 'copacabana' ? [model] : [],
input_domains : {p_in : _. range (0 , copacabana_options. no_points )},
input_cursors : [{}],
outputs : ['x' , 'y' , 'color' ],
pivot : true
})
Inputs. table (copacabana_data)
embed ({
"data" : {"name" : "projection" },
"mark" : {"type" : "point" , "tooltip" : true , "opacity" : 0.7 , "strokeWidth" : 5 },
"encoding" : {
"x" : {
"field" : "x" ,
"type" : "quantitative" ,
"scale" : {"zero" : false },
"axis" : {"labelAngle" : 0 , "orient" : "top" }
},
"y" : {"field" : "y" , "type" : "quantitative" , "scale" : {"zero" : false }},
"color" : {"field" : "color" , "type" : "nominal" , scale},
"shape" : {"field" : "p_in" , "type" : "nominal" },
"size" : {"value" : 50 }
},
"config" : {"legend" : {"disable" : true }},
"width" : 300 * (copacabana_size+ 1 ),
"height" : 250 * (copacabana_size+ 1 ),
"datasets" : {projection : copacabana_data}})
scale = {
if (copacabana_color_range == 'classic โซโช' )
return ({"range" : ["#dddde9" , "black" ]})
else if (copacabana_color_range == 'colorful ๐จ' )
return ({scheme : 'category10' })
else if (copacabana_color_range == '๐ข๐ก๐ต' )
return ({range : ["green" , "yellow" , "blue" ]})
else
return ({scheme : copacabana_color_range})
}
viewof copacabana_color_range = Inputs. select (["classic โซโช" , "colorful ๐จ" , "๐ข๐ก๐ต" , "blues" , "browns" ], {label : "color scheme" , value : "classic โซโช" })
viewof copacabana_size = Inputs. toggle ({label : 'large' })
viewof actual_interest_rate_co_in = Inputs. range ([- 1 , 6 ], {value : 2 , label : 'actual_interest_rate_rate_co_in' , step : 1 })
savings_rec_data = calcudata ({
models : modelname == 'savings-rec' ? [model] : [],
input_domains : {year_in : _. range (0 , 6 ), actual_interest_rate_co_in : [actual_interest_rate_co_in- 1 , actual_interest_rate_co_in]},
input_cursors : [{annual_payment_in : 1000 }],
outputs : ['balance' , 'interest' , 'deposits' , 'interest_rate' ]
})
viewof savings_actualator = embed (
{
"data" : {"name" : "data" },
"transform" : [
{
"window" : [{"op" : "lag" , "field" : "value" , "as" : "A_value" }],
"groupby" : ["formula" , "year_in" ],
"sort" : [{"field" : "actual_interest_rate_co_in" }],
"frame" : [- 1 , 0 ]
},
{"calculate" : "datum.value-datum.A_value" , "as" : "impact" },
],
"encoding" : {
"y" : {"field" : "year_in" , "type" : "nominal" , "sort" : "ascending" },
"color" : {"field" : "formula" , "type" : "nominal" },
"x" : {
"field" : "formula" ,
"type" : "nominal" ,
"axis" : {"labelAngle" : 0 , "orient" : "top" , "labelLimit" : 90 }
}
},
layer : [{
"params" : [{"name" : "y" , "select" : { "type" : "point" , "on" : "mouseover" , "nearest" : true , "encodings" : ["y" ], "toggle" : false }}],
"mark" : {
"type" : "text" ,
"fontSize" : 15 ,
"fontWeight" : "bold" ,
"tooltip" : false
},
"transform" : [{"filter" : `datum.actual_interest_rate_co_in != ${ actual_interest_rate_co_in- 1 } ` },
//{"calculate": "datum.year_in > datum.actual_interest_rate_co_in", "as": "fut"}
],
encoding : {
"opacity" : {"condition" : {"test" : "datum.year_in > datum.actual_interest_rate_co_in" , "value" : 0.5 }, "value" : 1 },
// {"field": "fut", "type": "nominal", "sort": "descending"},
"text" : {
"field" : "value" ,
"type" : "quantitative" ,
"format" : ",.2f" ,
"axis" : {"format" : ",.2f" }
},
}
}, {
"mark" : {
"type" : "text" , dx : 20 , dy : - 10 ,
"fontSize" : 12 ,
"fontWeight" : "bold" , //
"tooltip" : false
},
"transform" : [{"filter" : `abs(datum.impact) >= 0.00001 && datum.actual_interest_rate_co_in != ${ actual_interest_rate_co_in- 1 } ` }],
encoding : {
"color" : {"value" : "green" , "condition" : {"test" : "datum.impact < 0" , "value" : "red" }},
"opacity" : {"condition" : {"test" : "datum.year_in > datum.actual_interest_rate_co_in" , "value" : 0.5 }, "value" : 1 },
"text" : {
"field" : "impact" ,
"type" : "quantitative" ,
"format" : ",.2f" ,
"axis" : {"format" : ",.2f" }
},
}
}],
"config" : {"legend" : {"disable" : true }},
"datasets" : { data : savings_rec_data
},
"width" : 340 , height : 160
})
savings_actualator. addSignalListener ("y" , (_, r) => { // this didn't improve perf a lot vs event listener: nearly every mousemove is a signal change, I suppose
//if (observable_world == 0) return;
viewof actual_interest_rate_co_in. value = r. year_in [0 ];
viewof actual_interest_rate_co_in. dispatchEvent (new CustomEvent ('input' ), {bubbles : true });
}) && 1
input_domains = ({
x_in : _. range (- 3 , 3.001 , 0.25 ),
y_in : _. range (- 3.5 , 5.001 , 0.25 ),
})
data = calcudata ({
models : modelname == 'heart-contour' ? [model] : [],
input_cursors : [{}],
input_domains,
outputs : ['f' ]
})
viewof plot = Plot. plot ({
//width:400,
//height:260,
style : {
background : 'rgba(0,0,0,0)' // not applied to legend
},
color : {legend, reverse : true , label : `f(x_in,y_in)` },
x : { label : 'x_in' , tickFormat : d3. format ('.2f' ), ticks : _. uniq (data. map (d => d. x_in ))},
y : { label : 'y_in' , tickFormat : d3. format ('.2f' ), ticks : _. uniq (data. map (d => d. y_in ))},
marks : [
Plot. contour (data, { // https://observablehq.com/plot/marks/contour
x : "x_in" ,
y : "y_in" ,
fill : "value" , interpolate : Plot. interpolateNearest , blur,
interval : intervals
})
]
})
viewof legend = Inputs. toggle ({value : false , label : 'legend' })
viewof intervals = Inputs. range ([1 , 10 ], {value : 4 , label : 'contour intervals' , step : 0.1 , width : 100 })
viewof blur = Inputs. range ([0 , 50 ], {value : 4 , label : 'blur' , step : 0.5 , width : 100 })
import {interval} from '@mootari/range-slider'
viewof form_projectile = {
let form = Inputs. form ({
angle_in : Inputs. range ([- 2 , 2 ], {value : 1 , step : 0.002 , label : "Angle" }),
power_in : Inputs. range ([0 , 100 ], {value : 30 , step : 0.01 , label : "Power" }),
g_in : Inputs. range ([0 , 3 ], {value : 1 , step : 0.01 , label : "Gravity factor" }),
drag_coefficient_in : Inputs. range ([- 0.1 , 0.1 ], {value : 0.01 , step : 0.001 , label : "Air resistance" }),
t_interval : interval ([0 , 100 ], {step : 1 , label : 'time โ๏ธ' , color : 'skyblue' , value : [0 , 60 ], width : 200 }),
});
let state = false ;
form. oninput = () => {console . log ('oninput' ); if (state == false ) {mutable inputs_history_projectile = [... mutable inputs_history_projectile, form. value ]; state = true ; } mutable inputs_history_projectile = [... mutable inputs_history_projectile. slice (0 ,- 1 ), form. value ]}
form. onchange = () => { console . log ('onchange' ); state = false ; mutable inputs_history_projectile[mutable inputs_history_projectile. length - 1 ] = form. value };
return form;
}
mutable inputs_history_projectile = [JSON . parse (`{"angle_in":1,"power_in":30,"g_in":1,"drag_coefficient_in":0.01,"t_interval":[0,60]}`
)]
clip = true
// NOTE : adding some hurried customisation and experimentation for fireworks effect! should be cleaner
embed (
calcuvizspec ({
models : modelname == 'projectile' ? [model] : [],
input_cursors : inputs_history_projectile,
mark : {type : 'point' , clip},
encodings : {
detail : {name : 't_in' , type : 'quantitative' , domain : _. range (form_projectile. t_interval [0 ], form_projectile. t_interval [1 ]+ 0.1 , 1 )},
//opacity: {name: 't_in', type: 'quantitative', sort:'descending', scale:{domain:[0,100]}, legend:null/*not working => spec_post_process*/, domain: _.range(form_projectile.t_interval[0],form_projectile.t_interval[1]+0.1,1)},
y : {name : 'y' , type : 'quantitative' , sort : 'ascending' , scale : {domain : [- 1200 , 800 ], sort : 'descending' }},
x : {name : 'x' , grid : false , type : 'quantitative' , scale : {domain : [- 30 , 40 ]}},
color : {name : 'input_cursor_id' , type : 'nominal' },
},
width : 300 , height : 220 ,
spec_post_process : (spec) => {spec. encoding . y . axis = {tickCount : 1 , grid : true }; spec. datasets . tags = tags;
spec. encoding . x . axis = {tickCount : 3 , grid : false };
spec. transform = [
{"lookup" : "input_cursor_id" , "from" : {"data" : {"name" : "tags" }, "key" : "i" , "fields" : ["tag" ]}, "as" : ["tag" ]}
];
spec. encoding . color = {"field" : "tag" , "type" : "nominal" , "sort" : {"field" : "input_cursor_id" }, "title" : "scenario" }
return spec}
}))
tags = [{i : 0 , tag : 'initial' },... inputs_history_projectile. slice (1 ). map ((d, i) => ({i : i+ 1 , tag : Object . entries (d). filter (([k, v]) => v != inputs_history_projectile[i- 1 + 1 ][k])[0 ]})). map (d => ({... d, tag : ` ${ d. tag [0 ]} ${ inputs_history_projectile[d. i - 1 ][d. tag [0 ]]} -> ${ d. tag [1 ]} ` }))]
viewof dampener_in = Inputs. range ([- 1 , 2 ], {value : 0.90 , step : 0.01 , label : "dampener factor" })//viewof sales_price_in = Inputs.range([5,7], {step:0.20, label:'sales_price_in', value:6, disabled: true})
viewof dx_in = Inputs. range ([- 5 , 5 ], {value : 3 , step : 0.5 , label : "dx" })
viewof t = interval ([0 , 120 ], {
step : 1 ,
value : [0 , 60 ],
label : 'time' ,
width : 200
})
viewof frame_rate = Inputs. range ([0 , 60 ], {step : 5 , value : 40 , label : 'frame rate' })
bounce_data = calcudata ({models : modelname == 'bounce-6' ? [model] : [],
input_domains : {t_in : _. range (t[0 ], t[1 ], 1 )},
input_cursors : [{dx_in, dampener_in}],
outputs : ['x' , 'y' , 'dy' , 'bouncing' ],
pivot : true
})
p5 (sketch => {
//sync;
sketch. setup = function () {
sketch. createCanvas (300 , 120 );
sketch. frameRate (frame_rate);
};
var t = 0 , w, h;
sketch. draw = function () {
sketch. background ("#fed" );
sketch. stroke ("black" );
if (bounce_data[t] == undefined ) t = 0 ;
w = 30 ; h = 30 ;
if (bounce_data[t]. bouncing ) { w = 36 ; h = 18 ; }
sketch. ellipse (100 + bounce_data[t]. x , 5 + 100 - bounce_data[t]. y , w, h); // poor on move to calcudata but works
// just for fun
//if(t>0) { sketch.stroke("red"); sketch.line(bounce_data[t].x, bounce_data[t].y, bounce_data[t-1].x, bounce_data[t-1].y); }
t++;
}
})
import {p5} from "@tmcw/p5"
Inputs. table (bounce_data)
viewof fov2 = interval ([- 3 , 3 ], {
step : 0.005 ,
value : [- 1 - Math . PI / 2 , 1 - Math . PI / 2 ],
label : 'field of view' ,
width : 200
})
viewof scene = embed (
calcuvizspec ({
models : modelname == 'raycasting' ? [model] : [],
input_cursors : [{player_x_in, player_y_in}],
mark : {type : 'bar' , point : false , clip : true , tooltip : false },
encodings : {
x : {grid : false , name : 'ray_angle_in' , type : 'quantitative' , domain : [clamped_ray_angle_in, ... _. range (fov2[0 ], fov2[1 ], ray_angle_in_step_size)], nice : false , ticks : 2 },
color : {name : 'ray_hit_color' , type : 'nominal' , legend : false },
y : {grid : false , name : 'inverse_ray_length' , type : 'quantitative' },
opacity : {name : 'inverse_ray_length' , type : 'quantitative' , legend : false },
},
width : 400 , height : 150 ,
spec_post_process : spec => {
spec. encoding . y . scale = {domain : [0 , 0.3 ], };
spec. encoding . x . axis = labels ? {tickCount : 2 , grid : false } : null
spec. encoding . y . axis = labels ? {grid : false } : null
spec. params = [{"name" : "ray_angle_in" , "value" : { "ray_angle_in" : 0 }, "select" : { "type" : "point" , "on" : "mouseover" , "nearest" : true , "encodings" : ["x" ], toggle : false }}];
spec. encoding . color . condition = {"test" : `datum.ray_angle_in== ${ clamped_ray_angle_in || 999 } ` , "value" : "red" }
spec. encoding . opacity . condition = {"test" : `datum.ray_angle_in== ${ clamped_ray_angle_in || 999 } ` , "value" : 1 }
return spec
}
}))
viewof labels = Inputs. toggle ({label : "labels" , value : true })
viewof level = embed (
calcuvizspec ({
models : modelname == 'raycasting' ? [model] : [],
input_cursors : [{player_x_in, player_y_in, ray_angle_in : clamped_ray_angle_in/*:(fov[0]+fov[1])/2*/
, fov_in : fov2}],
mark : {type : 'rect' , tooltip : false },
encodings : {
x : {grid : false , name : 'level_x_in' , type : 'nominal' , domain : _. range (0 , 63.1 , 1 )},
y : {grid : false , name : 'level_y_in' , type : 'nominal' , domain : _. range (0 , 63.1 , 1 )},
color : {name : (observable_world ? 'level_player_ray_fov' : 'level_player' ), type : 'quantitative' , legend : false },
//row: {name: 'formula', type:'nominal', domain: ['level', 'level_plus_player', 'level_plus_player_plus_ray']},
},
width : 280 , height : 280 ,
spec_post_process : spec => {
spec. params = [{"name" : "xy" , "select" : { "type" : "point" , "on" : "mouseover" , "nearest" : true , "encodings" : ["x" , "y" ], toggle : false }}];
let a = spec. encoding . x . axis ; spec. encoding . x . axis = {... a, ticks : false , labels : false }
a = spec. encoding . y . axis ; spec. encoding . y . axis = {... a, ticks : false , labels : false }
spec. encoding . color . scale = {scheme : 'turbo' }
return spec;
}
}))
viewof player_x_in = Inputs. range ([3 , 61 ], {step : 1 , value : 32 , label : 'player x' })
viewof player_y_in = Inputs. range ([3 , 61 ], {step : 1 , value : 32 , label : 'player y' })
//viewof observable_world_v = Inputs.checkbox(["overlay field of view"], {value: true})
observable_world = true
import {viewof keys} from 'd/867e1424e1d093ab'
viewof keys
viewof time_in = Inputs. range ([0 , keys. length ], {step : 1 , value : keys. length , label : 'time_in' })
//time_in = keys.length
fov = model. fov_calcd ({time_in, keys_in : keys. map (d => d. key ). filter ((d, i) => i < time_in), speed_in : 3 , fov_0_in : [- 1 - Math . PI / 2 , 1 - Math . PI / 2 ]})
fov
keys
md `keys: ${ keys. length == 0 ? 'โ' : keys. map (d => d. key ). reverse (). slice (0 , 10 ). map (d => d == 'ArrowUp' ? 'โฌ๏ธ' : d == 'ArrowDown' ? 'โฌ๏ธ' : d == 'ArrowLeft' ? 'โฌ
๏ธ' : d == 'ArrowRight' ? 'โก๏ธ' : d). join (' ' )} `
e = model. inverse_ray_length$m . cache
e
Object . keys (e). length
viewof scene2 = embed (
calcuvizspec ({
models : modelname == 'raycasting-playable' ? [model] : [],
input_cursors : [{time_in, keys_in : keys. map (d => d. key ). filter ((d, i) => i < time_in), speed_in : 3 , fov_0_in : [- 1 - Math . PI / 2 , 1 - Math . PI / 2 ], }],
mark : {type : 'bar' , point : false , clip : true , tooltip : false },
encodings : {
x : {grid : false , name : 'ray_angle_in' , type : 'quantitative' , domain :
[clamped_ray_angle_in2, ... _. range (fov[0 ], fov[1 ], ray_angle_in_step_size)]
//[1,1.1,1.2]
, nice : false , ticks : 2 },
//color: {name: 'ray_hit_color', type:'nominal', legend:false},
y : {grid : false , name : 'inverse_ray_length' , type : 'quantitative' },
opacity : {name : 'inverse_ray_length' , type : 'quantitative' , legend : false },
},
width : 400 , height : 150 ,
spec_post_process : spec => {
spec. encoding . y . scale = {domain : [0 , 0.3 ], };
spec. encoding . x . axis = labels2 ? {tickCount : 2 , grid : false } : null
spec. encoding . y . axis = labels2 ? {grid : false } : null
spec. params = [{"name" : "ray_angle_in" , "value" : { "ray_angle_in" : 0 }, "select" : { "type" : "point" , "on" : "mouseover" , "nearest" : true , "encodings" : ["x" ], toggle : false }}];
//spec.encoding.color.condition = {"test": `datum.ray_angle_in==${clamped_ray_angle_in2 || 999}`,"value": "red"}
spec. encoding . opacity . condition = {"test" : `datum.ray_angle_in== ${ clamped_ray_angle_in2 || 999 } ` , "value" : 1 }
return spec
}
}))
viewof labels2 = Inputs. toggle ({label : "labels" , value : true })
4 + 4
clamped_ray_angle_in2
raycasting_playable_level_data = calcuvegadata ({
models : modelname == 'raycasting-playable' ? [model] : [],
spec : raycasting_playable_level_spec. layer [0 ],
domains : { level_x_in : _. range (0 , 63.1 , 1 ), level_y_in : _. range (0 , 63.1 , 1 ) },
input_cursors : [{ }]
})
raycasting_playable_level_player_data = calcuvegadata ({
models : modelname == 'raycasting-playable' ? [model] : [],
spec : raycasting_playable_level_spec. layer [1 ],
domains : {},
input_cursors : [{time_in, keys_in : keys. map (d => d. key ), speed_in : 3 , fov_0_in : [- 1 - Math . PI / 2 , 1 - Math . PI / 2 ] }]
})
raycasting_playable_level_player_data
raycasting_playable_level_spec = ({
//"data": {"name": "data"},
"datasets" : { data : raycasting_playable_level_data0,
player : raycasting_playable_level_player_data0 },
layer : [{
"data" : {"name" : "data" },
"mark" : {"type" : "rect" , "tooltip" : false },
"encoding" : {
"x" : {
"field" : "level_x_in" ,
"type" : "nominal" ,
"scale" : {"rangeStep" : 12 },
"axis" : {"grid" : false , "ticks" : false , "labels" : false }
},
"y" : {
"field" : "level_y_in" ,
"type" : "nominal" ,
"scale" : {"rangeStep" : 12 },
"axis" : {"grid" : false , "ticks" : false , "labels" : false }
},
"color" : {
"field" : "level" ,
"type" : "quantitative" ,
"scale" : {"scheme" : "turbo" }
}
},
},
{
"data" : {"name" : "player" },
mark : 'text' ,
encoding : {
x : {field : 'player_x_calcd' },
y : {field : 'player_y_calcd' },
text : {value : '๐ง' },
}
}],
"config" : {"legend" : {"disable" : true }},
width : 280 , height : 280 ,
})
mutable raycasting_playable_level_data0 = [] // includes data in spec for when I Open in Vega Editor
mutable raycasting_playable_level_player_data0 = []
htl. html `<button onclick= ${ () => {mutable raycasting_playable_level_data0 = raycasting_playable_level_data; mutable raycasting_playable_level_player_data0 = raycasting_playable_level_player_data}} >LEVEL insert data -> vega editor (dev)</button>`
viewof raycasting_playable_level_viz = embed (raycasting_playable_level_spec)
{
raycasting_playable_level_viz. data ("data" , raycasting_playable_level_data). resize (). run ();
}
{
console . log (Object . entries (model. inverse_ray_length$m . cache ). length )
console . log (Object . entries (model. player_x_raw$m . cache ). length )
raycasting_playable_level_viz. data ("player" , raycasting_playable_level_player_data). resize (). run ();
}
viewof observable_world_v2 = Inputs. checkbox (["overlay field of view" ], {value : true })
observable_world2 = observable_world_v2. length
viewof climate_sensitivity_in = Inputs. range ([0 , 5 ], {step : 0.2 , value : 3 , label : 'climate_sensitivity_in' })
viewof drawdown_factor_in = Inputs. range ([0 , 0.001 * 2 ], {step : 0.001 / 5 , value : 0.001 , label : 'drawdown_factor_in' })
md `**45%** emissions absorption currently hardcoded`
viewof emissions = embed (
{
"$schema" : "https://vega.github.io/schema/vega/v5.json" ,
"width" : 350 ,
"height" : 200 , //+ 0* climate_sensitivity_in,
"padding" : 5 ,
"data" : [
// using climate_results makes this responsive but resets visual on refresh
{"name" : "results2" , "values" : [{"year_in" : 2100 , "temperature_delta" : 1.23 /*BAD*/ }]},
{
"name" : "table" ,
"values" : [
{"category" : 1960 , "amount" : 4 },
{"category" : 1990 , "amount" : 8 },
{"category" : 2020 , "amount" : 28 },
{"category" : 2025 , "amount" : 29 },
{"category" : 2030 , "amount" : 30 },
{"category" : 2035 , "amount" : 32 },
{"category" : 2040 , "amount" : 33 },
{"category" : 2045 , "amount" : 34 },
{"category" : 2050 , "amount" : 38 },
{"category" : 2100 , "amount" : 90 },
{"category" : 2150 , "amount" : 5 }
]
},
{
"name" : "seq" ,
"transform" : [
{"type" : "sequence" , "start" : 1960 , "stop" : 2200 , "step" : 5 },
{"type" : "filter" , "expr" : "datum.data >= bau.category" }
]
},
{
"name" : "seq2" ,
"transform" : [
{"type" : "sequence" , "start" : 1960 , "stop" : 2200 , "step" : 5 },
{
"type" : "lookup" ,
"from" : "table" ,
"key" : "category" ,
"fields" : ["data" ],
"as" : ["table" ]
},
{"type" : "filter" , "expr" : "datum.table != null" },
{"type" : "window" , "ops" : ["lag" ], "fields" : ["table" ]},
{"type" : "filter" , "expr" : "datum.lag_table != null" },
{
"type" : "formula" ,
"expr" : "datum.lag_table.amount+(datum.table.amount-datum.lag_table.amount)*(datum.table.category-datum.lag_table.category)" ,
"as" : "interpolated"
}
]
},
{
"name" : "interpolated" ,
"transform" : [
{"type" : "sequence" , "start" : 1960 , "stop" : 2200 , "step" : 1 },
{
"type" : "lookup" ,
"from" : "table" ,
"key" : "category" ,
"fields" : ["data" ],
"values" : ["category" ],
"as" : ["group" ]
},
{"type" : "window" , "ops" : ["next_value" ], "fields" : ["group" ]},
{
"type" : "lookup" ,
"from" : "seq2" ,
"key" : "data" ,
"fields" : ["next_value_group" ],
"as" : ["group" ]
},
{"type" : "filter" , "expr" : "datum.group != null" },
{
"type" : "formula" ,
"expr" : "max(0,datum.data <= bau.category ? datum.group.lag_table.amount+(datum.group.table.amount-datum.group.lag_table.amount)*(datum.data-datum.group.lag_table.category)/(datum.group.table.category-datum.group.lag_table.category) : bau.amount+(0-bau.amount)*(datum.data-bau.category)/(zero_category-bau.category))" ,
"as" : "interpolated"
},
{
"type" : "formula" ,
"expr" : "datum.data < 2020 ? 'old' : 'future'" ,
"as" : "old"
}
]
},
{
"name" : "interpolated_baseline" ,
"transform" : [
{"type" : "sequence" , "start" : 1960 , "stop" : 2200 , "step" : 1 },
{
"type" : "lookup" ,
"from" : "table" ,
"key" : "category" ,
"fields" : ["data" ],
"values" : ["category" ],
"as" : ["group" ]
},
{"type" : "window" , "ops" : ["next_value" ], "fields" : ["group" ]},
{
"type" : "lookup" ,
"from" : "seq2" ,
"key" : "data" ,
"fields" : ["next_value_group" ],
"as" : ["group" ]
},
{"type" : "filter" , "expr" : "datum.group != null" },
{
"type" : "formula" ,
"expr" : "datum.group.lag_table.amount+(datum.group.table.amount-datum.group.lag_table.amount)*(datum.data-datum.group.lag_table.category)/(datum.group.table.category-datum.group.lag_table.category)" ,
"as" : "interpolated"
}
]
},
{
"name" : "seq3" ,
"transform" : [
{"type" : "sequence" , "start" : 1960 , "stop" : 2200 , "step" : 5 },
{
"type" : "lookup" ,
"from" : "seq2" ,
"key" : "data" ,
"fields" : ["data" ],
"as" : ["table" ]
},
{
"type" : "window" ,
"ops" : ["first_value" ],
"sort" : {"field" : "table" },
"fields" : ["table" ]
},
{
"type" : "formula" ,
"as" : "aaa" ,
"expr" : "isValid(datum.table) ? 0 : datum.lag_table"
}
]
},
{
"name" : "table-first" ,
"source" : "table" ,
"transform" : [{"type" : "filter" , "expr" : "datum.category == 1960" }]
},
{
"name" : "table-baus" ,
"source" : "table" ,
"transform" : [
{"type" : "filter" , "expr" : "datum.category <= bau.category" }
]
}
],
"signals" : [
{
"name" : "bau" ,
"value" : {"category" : 2030 , "amount" : 30 },
"on" : [{"events" : "@baus:mouseover" , "update" : "datum" }]
},
{
"name" : "zero_category" ,
"update" : "max(zero_category,bau.category)" ,
"value" : 2050 ,
"on" : [
{"events" : "@zeros:mouseover" , "update" : "max(datum.data,bau.category)" }
]
},
{
"name" : "history" ,
"value" : "2030|2050" ,
"on" : [
{
"events" : "@baus:mouseover,@zeros:mouseover" ,
"update" : "history+','+bau.category+'|'+zero_category"
}
]
},
],
"scales" : [
{
"name" : "xscale" ,
"type" : "linear" ,
"zero" : false ,
"domain" : {"data" : "table" , "field" : "category" },
"range" : "width" ,
"padding" : 0.05 ,
"round" : true
},
{
"name" : "yscale" ,
"domain" : {"data" : "table" , "field" : "amount" },
"nice" : true ,
"range" : "height"
},
{
"name" : "emissions" ,
"type" : "ordinal" ,
"domain" : ["old" , "future" ],
"range" : ["darkgrey" , "steelblue" ]
},
{
"name" : "bemissions" ,
"type" : "ordinal" ,
"domain" : ["old" , "future" ],
"range" : ["brown" , "blue" ]
}
],
"axes" : [
{
"orient" : "bottom" ,
"scale" : "xscale" ,
"format" : ".0f" ,
"labelFontSize" : 20 ,
"tickCount" : 6
},
{"orient" : "left" , "scale" : "yscale" }
],
"marks" : [
{
"type" : "rect" ,
"from" : {"data" : "interpolated_baseline" },
"encode" : {
"enter" : {"fill" : {"value" : "pink" }, "opacity" : {"value" : 0.1 }},
"update" : {
"x" : {"scale" : "xscale" , "field" : "data" },
"width" : {"value" : 10 },
"y" : {"scale" : "yscale" , "field" : "interpolated" },
"y2" : {"scale" : "yscale" , "value" : 0 }
}
}
},
{
"type" : "rect" ,
"from" : {"data" : "interpolated" },
"encode" : {
"enter" : {
"fill" : {"scale" : "emissions" , "field" : "old" },
"opacity" : {"value" : 0.5 }
},
"update" : {
"x" : {"scale" : "xscale" , "field" : "data" },
"width" : {"value" : 9 },
"y" : {"scale" : "yscale" , "field" : "interpolated" },
"y2" : {"scale" : "yscale" , "value" : 0 }
}
}
},
{
"type" : "rect" ,
"from" : {"data" : "table" },
"encode" : {
"enter" : {
"x" : {"scale" : "xscale" , "field" : "category" },
"width" : {"scale" : "xscale" , "band" : 1 },
"y" : {"scale" : "yscale" , "field" : "amount" },
"y2" : {"scale" : "yscale" , "value" : 0 },
"opacity" : {"value" : 0.1 }
},
"update" : {"fill" : {"value" : "steelblue" }}
}
},
{
"type" : "line" ,
"from" : {"data" : "table-baus" },
"encode" : {
"enter" : {
"strokeWidth" : {"value" : 5 },
"stroke" : {"value" : "black" },
"interpolate" : {"value" : "linear" },
"strokeCap" : {"value" : "round" }
},
"update" : {
"x" : {"scale" : "xscale" , "field" : "category" , "band" : 1 },
"y" : {"scale" : "yscale" , "field" : "amount" }
}
}
},
{
"type" : "rule" ,
"encode" : {
"enter" : {"strokeWidth" : {"value" : 5 }, "strokeCap" : {"value" : "round" }},
"update" : {
"x" : {"scale" : "xscale" , "signal" : "bau.category" , "band" : 1 },
"y" : {"scale" : "yscale" , "signal" : "bau.amount" , "offset" : 0 },
"x2" : {"scale" : "xscale" , "signal" : "zero_category" , "band" : 1 },
"y2" : {"scale" : "yscale" , "value" : 0 , "offset" : 0 }
}
}
},
{
"type" : "rule" ,
"encode" : {
"enter" : {"strokeWidth" : {"value" : 5 }, "strokeCap" : {"value" : "round" }},
"update" : {
"x2" : {"scale" : "xscale" , "value" : 2200 , "band" : 1 },
"x" : {"scale" : "xscale" , "signal" : "zero_category" , "band" : 1 },
"y" : {"scale" : "yscale" , "value" : 0 , "offset" : 0 }
}
}
},
{
"type" : "rule" ,
"from" : {"data" : "table-first" },
"encode" : {
"enter" : {"strokeWidth" : {"value" : 5 }, "strokeCap" : {"value" : "round" }},
"update" : {
"x2" : {"scale" : "xscale" , "field" : "category" , "band" : 1 },
"x" : {"scale" : "xscale" , "field" : "category" , "band" : 0 },
"y" : {"scale" : "yscale" , "field" : "amount" , "offset" : 0 }
}
}
},
{
"type" : "symbol" ,
"name" : "zeros" ,
"from" : {"data" : "seq" },
"encode" : {
"enter" : {
"size" : {"value" : 500 },
"fill" : {"value" : "lightblue" },
"stroke" : {"value" : "black" },
"shape" : {"value" : "triangle" },
"opacity" : {"value" : 0.8 }
},
"update" : {
"x" : {"scale" : "xscale" , "field" : "data" , "band" : 1 },
"y" : {"scale" : "yscale" , "value" : "0" , "offset" : 0 }
}
}
},
{
"type" : "text" ,
"name" : "results" ,
"from" : {"data" : "results2" },
"encode" : {
"enter" : {
"fontSize" : {"value" : 50 },
"fill" : {"value" : "red" },
"stroke" : {"value" : "black" },
"align" : {"value" : "center" },
"fontWeight" : {"value" : "bold" },
"text" : {"signal" : "datum.temperature_delta == 999 ? '-' : 'max ฮ T โ ' + format(datum.temperature_delta, ',.2f')" },
},
"update" : {
//"x": {"scale": "xscale", "field": "year_in", "band": 1,},
"x" : {"scale" : "xscale" , "value" : 2075 },
"y" : {"scale" : "yscale" , "value" : "-30" , "offset" : 0 }
}
}
},
{
"type" : "symbol" ,
"name" : "baus" ,
"from" : {"data" : "table" },
"encode" : {
"enter" : {
"size" : {"value" : 500 },
"fill" : {"value" : "pink" },
"stroke" : {"value" : "black" },
"shape" : {"value" : "square" },
"opacity" : {"value" : 0.8 }
},
"update" : {
"x" : {"scale" : "xscale" , "field" : "category" , "band" : 1 },
"y" : {"scale" : "yscale" , "field" : "amount" , "offset" : 0 }
}
}
}
]
}
)
//viewof emissions = Inputs.input([])
mutable emissions_ = emissions. data ('interpolated' )
climate1 = calcudata ({
models : modelname == 'simple-climate' ? [model] : [],
input_cursors : [{emissions_table_in : emissions_. map (d => ({year_in : d. data , emissions_rate : d. interpolated })), climate_sensitivity_in, ppm_to_GtC_in : 2.3 , drawdown_factor_in}],
input_domains : {
year_in : [2015 , 2016 ,... _. range (2020 , 2151 , 10 )],
},
outputs : ['emissions_rate' , 'temperature_delta' , 'CO2_concentration' , 'concentration_factor' ]
})
// "gradual calculation"
// this should be calcd in-model
Tmax_year_1 = d3. greatest (climate1, d => d. value ). year_in
climate2 = calcudata ({
models : modelname == 'simple-climate' ? [model] : [],
input_cursors : [{emissions_table_in : emissions_. map (d => ({year_in : d. data , emissions_rate : d. interpolated })), climate_sensitivity_in, ppm_to_GtC_in : 2.3 , drawdown_factor_in}],
input_domains : {
year_in : _. range (Math . max (2015 , Tmax_year_1- 10 ), Tmax_year_1+ 11 , 1 ),
},
outputs : ['temperature_delta' ]
})
Tmax_year_2 = d3. greatest (climate2, d => d. value )//.year_in
// used in layer in table viz
climate_Tmax = calcudata ({
models : modelname == 'simple-climate' ? [model] : [],
input_cursors : [{emissions_table_in : emissions_. map (d => ({year_in : d. data , emissions_rate : d. interpolated })), climate_sensitivity_in, ppm_to_GtC_in : 2.3 , drawdown_factor_in,
year_in : Tmax_year_2. year_in }],
input_domains : {
//year_in: [2015,2016,..._.range(2020,2151,10)],
},
outputs : ['emissions_rate' , 'temperature_delta' , 'CO2_concentration' , 'concentration_factor' ]
})
embed ({
width : 380 , height : 340 ,
"data" : {"name" : "data" },
"encoding" : {
"y" : {
"field" : "year_in" ,
"type" : "quantitative" ,
"scale" : {"zero" : false },
"sort" : "ascending"
},
"x" : {
"field" : "formula" ,
"type" : "nominal" ,
"axis" : {"labelAngle" : 0 , "orient" : "top" , "labelLimit" : 90 }
},
"color" : {"field" : "formula" , "type" : "nominal" },
"opacity" : {"value" : 0.7 },
"text" : {
"field" : "value" ,
"type" : "quantitative" ,
"format" : ".2f" ,
"axis" : {"format" : ".2f" }
}
},
"layer" : [
{
"transform" : [{"filter" : "datum.year_in == " + Tmax_year_2. year_in }],
"mark" : {
"type" : "text" ,
"fontSize" : 15 ,
"fontWeight" : "bold" ,
"tooltip" : true ,
//"dx": 1, "dy": 1
},
"encoding" : {//"color": {"value": "red"},
"opacity" : {"value" : 1 }}
},
{
"transform" : [{"filter" : "datum.year_in <= 2100" }],
"mark" : {
"type" : "text" ,
"fontSize" : 16 ,
"fontWeight" : "bold" ,
"tooltip" : true
}
},
],
"config" : {"legend" : {"disable" : true }},
"datasets" : {
"data" : [... climate1,... climate_Tmax]
}
})
update_climate = (name, value) => {
if (value != undefined )
mutable emissions_ = value;
/*emissions.change('results2',emissions.insert('results2',calcudata({
models: modelname == 'simple-climate' ? [model] : [],
input_domains: {year_in: [2015,2100]},
input_cursors: [{emissions_table_in: emissions_.map(d => ({year_in:d.data, emissions_rate:d.interpolated})),climate_sensitivity_in,ppm_to_GtC_in:2.3,drawdown_factor_in}],
outputs: ['temperature_delta'],
pivot:true
}).map(({year_in,temperature_delta}) => ({year_in,temperature_delta})))).remove('results2',d => true).run()*/
emissions. data ('results2' ,
/*calcudata({
models: modelname == 'simple-climate' ? [model] : [],
input_domains: {year_in: [2100]}, // should show more, to demo drawdown
input_cursors: [{emissions_table_in: emissions_.map(d => ({year_in:d.data, emissions_rate:d.interpolated})),climate_sensitivity_in,ppm_to_GtC_in:2.3,drawdown_factor_in}],
outputs: ['temperature_delta'],
pivot:true
}).map(({year_in,temperature_delta}) => ({year_in,temperature_delta}))*/
[Tmax_year_2]. map (d => ({... d, temperature_delta : d. value }))
). run ()
}
{emissions. addDataListener ('interpolated' , update_climate) };
{
drawdown_factor_in;
climate_sensitivity_in; // force recalc/coordination on input change
update_climate ()
//emissions.insert('interpolated',[]).run()
}