@decidables/prospectable-elements
Version:
prospectable-elements: Web Components for visualizing Cumulative Prospect Theory
233 lines (213 loc) • 6.39 kB
JavaScript
import {html} from 'lit';
import {animate, flyLeft} from '@lit-labs/motion';
import '@decidables/decidables-elements/spinner';
import DecidablesConverterArray from '@decidables/decidables-elements/converter-array';
import CPTEquation from './cpt-equation';
/*
CPTEquationVW2U element
<sdt-equation-vw2u>
Attributes:
Subjective Utility, Subjective Value, Subjective Decision Weight;
*/
export default class CPTEquationVW2U extends CPTEquation {
static get properties() {
return {
v: {
attribute: 'value',
converter: DecidablesConverterArray,
reflect: true,
},
w: {
attribute: 'weight',
converter: DecidablesConverterArray,
reflect: true,
},
n: {
attribute: 'outcomes',
type: Number,
reflect: true,
},
u: {
attribute: false,
type: Number,
reflect: false,
},
};
}
constructor() {
super();
this.v = [10, 0];
this.w = [0.75, 0.25];
this.n = 2;
this.nMax = 4;
this.vMax = new Array(this.nMax).fill(0);
this.wMax = new Array(this.nMax).fill(0);
this.alignState();
}
alignState() {
// Clean up v and w
this.v = (this.v.length < this.n)
? this.v.concat(this.vMax.slice(this.v.length, this.n))
: (this.v.length > this.n)
? this.v.slice(0, this.n)
: this.v;
this.w = (this.w.length < this.n)
? this.w.concat(this.wMax.slice(this.w.length, this.n))
: (this.w.length > this.n)
? this.w.slice(0, this.n)
: this.w;
// Update vMax and wMax to reflect current v and w
this.v.forEach((item, index) => {
this.vMax[index] = item;
});
this.w.forEach((item, index) => {
this.wMax[index] = item;
});
// Calculate u
this.u = this.v.reduce((sum, value, index) => { return sum + value * this.w[index]; }, 0);
}
sendEvent() {
this.dispatchEvent(new CustomEvent('cpt-equation-vw2u-change', {
detail: {
v: this.v,
w: this.w,
n: this.n,
u: this.u,
},
bubbles: true,
}));
}
vInput(index, event) {
this.v[index] = parseFloat(event.target.value);
this.alignState();
this.sendEvent();
}
wInput(index, event) {
this.w[index] = parseFloat(event.target.value);
this.alignState();
this.sendEvent();
}
nInput(event) {
this.n = parseFloat(event.target.value);
this.alignState();
this.sendEvent();
}
vTemplate(subscript = '', className = '', numeric = false) {
let v;
if (numeric) {
const index = Number.parseInt(subscript, 10) - 1;
v = html`<decidables-spinner class="v"
?disabled=${!this.interactive}
.value=${this.v[index]}
@input=${this.vInput.bind(this, index)}
>
<var class="math-var">v<sub class="subscript ${className}">${subscript}</sub></var>
</decidables-spinner>`;
} else {
v = html`<var class="math-var v">v<sub class="subscript ${className}">${subscript}</sub></var>`;
}
return v;
}
wTemplate(subscript = '', className = '', numeric = false) {
let w;
if (numeric) {
const index = Number.parseInt(subscript, 10) - 1;
w = html`<decidables-spinner class="w"
?disabled=${!this.interactive}
min="0"
max="1"
step=".001"
.value=${this.w[index]}
@input=${this.wInput.bind(this, index)}
>
<var class="math-var">w<sub class="subscript ${className}">${subscript}</sub></var>
</decidables-spinner>`;
} else {
w = html`<var class="math-var w">w<sub class="subscript ${className}">${subscript}</sub></var>`;
}
return w;
}
willUpdate() {
this.alignState();
}
render() {
let u;
let n;
if (this.numeric) {
u = html`<decidables-spinner class="u"
disabled
.value=${+this.u.toFixed(3)}
>
<var class="math-var">U</var>
</decidables-spinner>`;
n = html`<decidables-spinner class="n"
?disabled=${!this.interactive}
min="1"
max="4"
step="1"
.value=${this.n}
@input=${this.nInput.bind(this)}
>
<var class="math-var">n</var>
</decidables-spinner>`;
} else {
u = html`<var class="math-var u">U</var>`;
n = html`<var class="math-var subscript">n</var>`;
}
const equation = html`
<tr>
<td>
${u}<span class="equals">=</span>
</td>
<td>
<div class="summation">
<span class="tight">${n}</span>
<span class="tight"><var class="math-greek sigma">Σ</var></span>
<span class="tight"><var class="math-var subscript tight">i</var><span class="equals subscript">=</span><span class="subscript tight">1</span></span>
</div>
</td>
<td>
${
this.vTemplate('i', 'math-var', false)
} ${
this.wTemplate('i', 'math-var', false)
}<span class="equals">=</span>
</td>
<td>
${this.numeric
? Array(this.nMax).fill().map((_, index) => {
return (index < this.n)
? html`<span class="addend tight" ${animate({in: flyLeft, out: flyLeft})}>${
(index !== 0)
? html`<span class="plus">+</span>`
: html``
}${
this.vTemplate(index + 1, 'math-num', true)
} ${
this.wTemplate(index + 1, 'math-num', true)
}</span>`
: null;
})
: html`${
this.vTemplate('1', 'math-num', false)
} ${
this.wTemplate('1', 'math-num', false)
}<span class="plus">+</span><span class="ellipsis">…</span><span class="plus">+</span>${
this.vTemplate('n', 'math-var', false)
} ${
this.wTemplate('n', 'math-var', false)
}`
}
</td>
</tr>`;
return html`
<div class="holder">
<table class="equation">
<tbody>
${equation}
</tbody>
</table>
</div>`;
}
}
customElements.define('cpt-equation-vw2u', CPTEquationVW2U);