UNPKG

@decidables/discountable-elements

Version:

discountable-elements: Web Components for visualizing Hyperbolic Temporal Discounting

226 lines (192 loc) 4.72 kB
import {html, css} from 'lit'; import * as Plot from '@observablehq/plot'; import HTDMath from '@decidables/discountable-math'; // Special Web Worker import for rollup-plugin-web-worker-loader import HTDFitWorker from 'web-worker:./htd-fit-worker'; /* eslint-disable-line import/no-unresolved */ import DiscountableElement from '../discountable-element'; /* HTDFit element <htd-fit> Attributes: interactive: true/false */ export default class HTDFit extends DiscountableElement { static get properties() { return { }; } constructor() { super(); this.k = HTDMath.k.DEFAULT; this.choices = []; this.samples = null; this.working = false; this.queued = false; this.worker = new HTDFitWorker(); this.worker.onmessage = (event) => { this.working = false; this.samples = event.data.samples; this.k = event.data.results.k; this.requestUpdate(); this.dispatchEvent(new CustomEvent('htd-fit-update', { detail: { k: this.k, }, bubbles: true, })); if (this.queued) { this.fit(); } }; this.fit(); } fit() { if (!this.working) { this.worker.postMessage(this.choices); this.working = true; this.queued = false; } else { this.queued = true; } } clear() { this.choices = []; this.fit(); } get(name = 'default') { const choice = this.choices.find((item) => { return (item.name === name); }); return (choice === undefined) ? null : choice; } set(as, ds, al, dl, response, name = '', label = '') { const choice = this.choices.find((item) => { return (item.name === name); }); if (choice === undefined) { this.choices.push({ as: as, ds: ds, al: al, dl: dl, response: response, name: name, label: label, }); } else { choice.as = as; choice.ds = ds; choice.al = al; choice.dl = dl; choice.response = response; choice.label = label; } this.fit(); } static get styles() { return [ super.styles, css` /* :host { display: inline-block; } */ figure { margin: 0.625rem; } figure h2 { margin: 0.25rem 0; font-size: 1.125rem; font-weight: 600; } .trace, .hist { display: inline-block; } `, ]; } render() { return html` <div> <div>After ${this.choices.length} trials:</div> <div>Current: <var class="math-var k">k</var> = ${this.k.toFixed(3)} </div> <div class="param"> <div class="trace k"></div> <div class="hist k"></div> </div> <div class="param"> <div class="trace luce"></div> <div class="hist luce"></div> </div> </div> `; } plotParam(param) { this.shadowRoot.querySelector(`.hist.${param}`).replaceChildren( Plot.plot({ title: `Posterior of ${param}`, x: {label: `${param}`}, width: 320, height: 240, style: 'font-size: 0.75rem; font-family: var(---font-family-base);', marks: [ Plot.rectY( this.samples[param], Plot.binX( {y: 'count'}, {x: Plot.identity}, ), ), Plot.rectY( this.samples[param], Plot.pointerX(Plot.binX( {y: 'count'}, { x: Plot.identity, stroke: 'black', fill: 'white', tip: true, }, )), ), ], }), ); this.shadowRoot.querySelector(`.trace.${param}`).replaceChildren( Plot.plot({ title: `Traceplot of ${param}`, x: {label: 'Sample'}, y: {label: `${param}`}, width: 320, height: 240, style: 'font-size: 0.75rem; font-family: var(---font-family-base);', marks: [ Plot.lineY( this.samples[param], ), Plot.dot( this.samples[param], Plot.pointer({ x: Plot.indexOf, y: Plot.identity, stroke: 'black', fill: 'white', r: 4, tip: true, }), ), ], }), ); } update(changedProperties) { super.update(changedProperties); if (this.samples !== null) { this.plotParam('k'); this.plotParam('luce'); } } } customElements.define('htd-fit', HTDFit);