---
title: "Projectile Simulation ðŠĻ"
order: 2
#author:
# - name: "Declan Naughton ð§ŪðĻâðŧ"
# url: "https://calcwithdec.dev/about.html"
#description: "projectile code"
format:
html:
echo: false
resources:
- '../../models/projectile/*.js'
- '../../models/projectile/*.js.map'
- '../../models/projectile/*.json'
---
<details open><summary>inputs âïļ</summary>
```{ojs}
//| panel: input
////| layout-ncol: 2 // doesn't work
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:360}),
});
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]}`
)]
```
</details>
### Projectile Path ð§
```{ojs}
//| echo: false
// TODO value, impact, color code?
//viewof clip = Inputs.toggle({label: "clip", value: true})
clip = true
// NOTE: adding some hurried customisation and experimentation for fireworks effect! should be cleaner
embed(
calcuvizspec({
models: [main],
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: 400, 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}
}, {renderer: 'svg'}))
```
```{ojs}
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]}`}))]
// also done in calcuvizspec-presentation
//Inputs.table(tags)
```
<br/>
<details><summary>workings ð</summary>
::: {.panel-tabset}
## ð grid
```{ojs}
projectile_formulae = ['x','y','dx','dy','drag_x','drag_y']
width = 600
viewof grid = embed(
calcuvizspec({
models: [main],
input_cursors: [form_projectile],
mark: {type: 'text', tooltip: false, size:16, fontWeight: 'bold'},
encodings: {
x: {name: 'formula', type:'nominal', domain: projectile_formulae},
y: {name: 't_in', type: 'nominal', domain: _.range(form_projectile.t_interval[0],form_projectile.t_interval[1]+0.1, 5)},
text: {name: 'value', type: 'quantitative', format:',.2f'},
color: {name: 'formula', type:'nominal', domain: projectile_formulae, legend:false},
},
width, height:220,
spec_post_process: spec => {
spec.encoding.x.axis = {labelFontSize:20, titleFontSize:20, labelAngle:0, orient:'top'};
spec.encoding.y.axis = {labelFontSize:15, titleFontSize:20 };
return spec
}
}), {renderer: 'svg'})
```
## ð row facet bars
```{ojs}
viewof bars = embed(
calcuvizspec({
models: [main],
input_cursors: [form_projectile],
mark: 'bar',
encodings: {
x: {name: 't_in', type: 'quantitative', domain: _.range(form_projectile.t_interval[0],form_projectile.t_interval[1]+0.1,1)},
y: {name: 'value', type: 'quantitative', independent:true},
row: {name: 'formula', type:'nominal', domain: projectile_formulae},
color: {name: 'formula', type:'nominal', domain: projectile_formulae,legend:false},
},
width, height:40,
spec_post_process: spec => {
spec.encoding.x.axis = {labelFontSize:20, titleFontSize:20, labelAngle:0, orient:'top'};
spec.encoding.y.axis = {title:null, labelFontSize:20};
spec.encoding.row.header.labelFontSize=14;
return spec
}
}), {renderer: 'svg'})
```
## ð row facet lines
```{ojs}
viewof lines = embed(
calcuvizspec({
models: [main],
input_cursors: inputs_history_projectile,
mark: {type: 'line', point: true, strokeWidth: 0.5},
encodings: {
x: {name: 't_in', type: 'quantitative', domain: _.range(form_projectile.t_interval[0],form_projectile.t_interval[1]+0.1,2)},
y: {name: 'value', type: 'quantitative', independent:true},
row: {name: 'formula', type:'nominal', domain: projectile_formulae},
color: {name: 'input_cursor_id', type: 'nominal'},
},
width, height:40,
spec_post_process: spec => {
spec.encoding.x.axis = {labelFontSize:20, titleFontSize:20, labelAngle:0, orient:'top'};
spec.encoding.y.axis = {title:null, labelFontSize:20};
spec.encoding.row.header.labelFontSize=14;
return spec
}
}), {renderer: 'svg'})
```
:::
<!-- code viewer -->
<details open><summary>*calculang formulae* ðŠ behind the workings</summary>
```{ojs}
//| echo: false
// todo here: pull introspection-no-memo, use entrypoint.cul.js, publish as a helper? -> test in calcuvizspec
// ALSO will formula UI sit inside/outside? Inside is better, how to maintain interactivity?
code_viewer = async (entrypoint, formula) => {
const cul_fetch = await fetch(`https://calcy-quarty-vizys-online.pages.dev/models/projectile/projectile${1 ? '-nomemo_esm' : '_esm'}/cul_scope_${0}.${code_opt_fv.indexOf('source') != -1 ? 'cul.js' : 'mjs'}`)
const cul = await cul_fetch.text()
return md`
~~~js
${
formulae_objs.filter(f => f.name == formula_select).map(f => cul.split('\n').filter((d,i) => i >= f.loc.start.line-1 && i < f.loc.end.line).join('\n').slice(13)).join('\n\n')
}
~~~
`
}
code_viewer('',formula_select)
```
</details>
<!-- end code viewer -->
<br/>
<br/>
<br/>
<br/>
```{ojs}
import { calcuvizspec } from "@declann/little-calcu-helpers"
embed = require('vega-embed');
main = require('https://calcy-quarty-vizys-online.pages.dev/models/projectile/projectile.js')
introspection_fetch = await fetch(`https://calcy-quarty-vizys-online.pages.dev/models/projectile/projectile.introspection.json`)
introspection = introspection_fetch.json({typed:true})
introspection_nomemo_fetch = await fetch(`https://calcy-quarty-vizys-online.pages.dev/models/projectile/projectile-nomemo.introspection.json`)
introspection_nomemo = introspection_nomemo_fetch.json({typed:true})
inputs = Object.values(introspection.cul_functions).filter(d => d.reason == 'input definition').map(d => d.name).sort()
formulae = Object.values(introspection.cul_functions).filter(d => d.reason == 'definition').map(d => d.name)
// formulae excluding pure inputs
formulae_not_inputs = Object.values(introspection.cul_functions).filter(d => d.reason == 'definition' && inputs.indexOf(d.name+'_in') == -1).map(d => d.name)
```
<details><summary>appendix</summary>
```{ojs}
//| echo: false
// code viewer
formulae_objs = Object.values(introspection_nomemo.cul_functions).filter(d => d.reason == 'definition' && inputs.indexOf(d.name+'_in') == -1)
viewof code_opt_fv = Inputs.select(["ð calculang source ðŽ", "âĻ calculang output âĻ for ðĨïļ"], {label: "ð§ via ðēs", value: "ð calculang source ðŽ"})
viewof formula_select = Inputs.select(/*Object.entries(introspection_nomemo.cul_functions).filter(([a,b]) => b.reason == 'definition' && b.cul_source_scope_id==0).map(([a,b]) => b.name)*/formulae_not_inputs, {label: "formula"})
// force nomemo here
//cs = (code_opt_fv.indexOf("source") != -1 ? calcuin_fv : calcuout).split('\n')
```
```{ojs}
//| echo: false
grid.addEventListener('mousemove', (event, item) => {
viewof formula_select.value = item.datum.formula;
viewof formula_select.dispatchEvent(new CustomEvent("input"))
})
bars.addEventListener('mousemove', (event, item) => {
viewof formula_select.value = item.datum.formula;
viewof formula_select.dispatchEvent(new CustomEvent("input"))
})
lines.addEventListener('mousemove', (event, item) => {
viewof formula_select.value = item.datum.formula;
viewof formula_select.dispatchEvent(new CustomEvent("input"))
})
```
</details>