@decidables/discountable-elements 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,253 @@
1
+
2
+ import {html, css} from 'lit';
3
+ import * as d3 from 'd3';
4
+
5
+ import DiscountableElement from '../discountable-element';
6
+ import './itc-choice';
7
+
8
+ /*
9
+ ITCTask element
10
+ <itc-task>
11
+
12
+ Attributes:
13
+ Dots; Coherence;
14
+ # Direction, Speed, Lifetime
15
+ */
16
+ export default class ITCTask extends DiscountableElement {
17
+ static get properties() {
18
+ return {
19
+ duration: {
20
+ attribute: 'duration',
21
+ type: Number,
22
+ reflect: true,
23
+ },
24
+ iti: {
25
+ attribute: 'iti',
26
+ type: Number,
27
+ reflect: true,
28
+ },
29
+ trials: {
30
+ attribute: 'trials',
31
+ type: Number,
32
+ reflect: true,
33
+ },
34
+ running: {
35
+ attribute: 'running',
36
+ type: Boolean,
37
+ reflect: true,
38
+ },
39
+
40
+ state: {
41
+ attribute: false,
42
+ type: String,
43
+ reflect: false,
44
+ },
45
+ };
46
+ }
47
+
48
+ constructor() {
49
+ super();
50
+
51
+ // Attributes
52
+ this.duration = 2000; // Duration of stimulus in milliseconds
53
+ this.iti = 2000; // Duration of inter-trial interval in milliseconds
54
+ this.trials = 5; // Number of trials per block
55
+ this.running = false; // Currently executing block of trials
56
+
57
+ // Properties
58
+ this.states = ['resetted', 'iti', 'stimulus', 'ended']; // Possible states of task
59
+ this.state = 'resetted'; // Current state of task
60
+
61
+ // Decision parameters
62
+ this.range = {};
63
+ this.range.as = {start: 10, stop: 20, step: 1}; // Amount SS
64
+ this.range.ds = {start: 10, stop: 40, step: 1}; // Delay SS
65
+ this.range.al = {start: 20, stop: 40, step: 1}; // Amount LL
66
+ this.range.dl = {start: 50, stop: 80, step: 1}; // Delay LL
67
+
68
+ this.range.as.values = d3.range(
69
+ this.range.as.start,
70
+ this.range.as.stop + 0.01,
71
+ this.range.as.step,
72
+ );
73
+ this.range.ds.values = d3.range(
74
+ this.range.ds.start,
75
+ this.range.ds.stop + 0.01,
76
+ this.range.ds.step,
77
+ );
78
+ this.range.al.values = d3.range(
79
+ this.range.al.start,
80
+ this.range.al.stop + 0.01,
81
+ this.range.al.step,
82
+ );
83
+ this.range.dl.values = d3.range(
84
+ this.range.dl.start,
85
+ this.range.dl.stop + 0.01,
86
+ this.range.dl.step,
87
+ );
88
+
89
+ // Private
90
+ this.firstUpdate = true;
91
+
92
+ this.as = 0;
93
+ this.ds = 0;
94
+ this.al = 0;
95
+ this.dl = 0;
96
+
97
+ this.trial = 0; // Count of current trial
98
+
99
+ this.baseTime = 0; // Real time, in milliseconds, that the current block started
100
+ this.pauseTime = 0; // Real time, in milliseconds, that block was paused at
101
+ this.startTime = 0; // Virtual time, in milliseconds, that current stage of trial started
102
+ this.lastTime = 0; // Virtual time, in milliseconds, of the most recent frame
103
+
104
+ this.runner = undefined; // D3 Interval for frame timing
105
+ }
106
+
107
+ static get styles() {
108
+ return [
109
+ super.styles,
110
+ css`
111
+ :host {
112
+ display: inline-block;
113
+ }
114
+ `,
115
+ ];
116
+ }
117
+
118
+ render() {
119
+ return html`
120
+ <div class="holder">
121
+ <itc-choice
122
+ state="${(this.state === 'stimulus')
123
+ ? 'choice'
124
+ : (this.state === 'iti')
125
+ ? 'fixation'
126
+ : 'blank'}"
127
+ amount-ss="${this.as}"
128
+ delay-ss="${this.ds}"
129
+ amount-ll="${this.al}"
130
+ delay-ll="${this.dl}">
131
+ </itc-choice>
132
+ </div>`;
133
+ }
134
+
135
+ update(changedProperties) {
136
+ super.update(changedProperties);
137
+
138
+ // Start or stop trial block
139
+ if (this.firstUpdate || changedProperties.has('running')) {
140
+ if (this.running) {
141
+ // (Re)Start
142
+ if (this.pauseTime) {
143
+ // Shift timeline forward as if paused time never happened
144
+ this.baseTime += (d3.now() - this.pauseTime);
145
+ this.pauseTime = 0;
146
+ }
147
+ this.runner = d3.interval(this.run.bind(this), 20); // FIXME??
148
+ } else if (this.runner !== undefined) {
149
+ // Pause
150
+ this.runner.stop();
151
+ this.pauseTime = d3.now();
152
+ }
153
+ }
154
+
155
+ this.firstUpdate = false;
156
+ }
157
+
158
+ reset() {
159
+ this.runner.stop();
160
+ this.running = false;
161
+ this.trial = 0;
162
+ this.state = 'resetted';
163
+
164
+ this.as = 0;
165
+ this.ds = 0;
166
+ this.al = 0;
167
+ this.dl = 0;
168
+
169
+ this.baseTime = 0;
170
+ this.pauseTime = 0;
171
+ this.startTime = 0;
172
+ this.lastTime = 0;
173
+ }
174
+
175
+ run(/* elapsed */) {
176
+ const realTime = d3.now();
177
+ const currentTime = (this.baseTime) ? (realTime - this.baseTime) : 0;
178
+ const elapsedTime = (this.baseTime) ? (currentTime - this.startTime) : 0;
179
+ this.lastTime = currentTime;
180
+ if (this.state === 'resetted') {
181
+ // Start block with an ITI
182
+ this.state = 'iti';
183
+ this.baseTime = realTime;
184
+ this.startTime = 0;
185
+ this.dispatchEvent(new CustomEvent('itc-block-start', {
186
+ detail: {
187
+ trials: this.trials,
188
+ },
189
+ bubbles: true,
190
+ }));
191
+ } else if ((this.state === 'iti') && (elapsedTime >= this.iti)) {
192
+ // Start new trial with a stimulus
193
+ this.trial += 1;
194
+ this.state = 'stimulus';
195
+ this.startTime = currentTime;
196
+ // Determine trial
197
+ this.as = this.range.as.values[Math.floor(Math.random() * this.range.as.values.length)];
198
+ this.ds = this.range.ds.values[Math.floor(Math.random() * this.range.ds.values.length)];
199
+ this.al = this.range.al.values[Math.floor(Math.random() * this.range.al.values.length)];
200
+ this.dl = this.range.dl.values[Math.floor(Math.random() * this.range.dl.values.length)];
201
+ this.dispatchEvent(new CustomEvent('itc-trial-start', {
202
+ detail: {
203
+ trials: this.trials,
204
+ duration: this.duration,
205
+ iti: this.iti,
206
+ trial: this.trial,
207
+ as: this.as,
208
+ ds: this.ds,
209
+ al: this.al,
210
+ dl: this.dl,
211
+ },
212
+ bubbles: true,
213
+ }));
214
+ } else if ((this.state === 'stimulus') && (elapsedTime >= this.duration)) {
215
+ // Stimulus is over, end of trial
216
+ this.dispatchEvent(new CustomEvent('itc-trial-end', {
217
+ detail: {
218
+ trials: this.trials,
219
+ duration: this.duration,
220
+ iti: this.iti,
221
+ trial: this.trial,
222
+ as: this.as,
223
+ ds: this.ds,
224
+ al: this.al,
225
+ dl: this.dl,
226
+ },
227
+ bubbles: true,
228
+ }));
229
+ if (this.trial >= this.trials) {
230
+ // End of block
231
+ this.runner.stop();
232
+ this.running = false;
233
+ this.state = 'ended';
234
+ this.baseTime = 0;
235
+ this.pauseTime = 0;
236
+ this.startTime = 0;
237
+ this.lastTime = 0;
238
+ this.dispatchEvent(new CustomEvent('itc-block-end', {
239
+ detail: {
240
+ trials: this.trial,
241
+ },
242
+ bubbles: true,
243
+ }));
244
+ } else {
245
+ // ITI
246
+ this.state = 'iti';
247
+ this.startTime = currentTime;
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ customElements.define('itc-task', ITCTask);
@@ -0,0 +1,105 @@
1
+
2
+ import {
3
+ css,
4
+ unsafeCSS,
5
+ } from 'lit';
6
+ import * as d3 from 'd3';
7
+
8
+ import {DecidablesElement} from '@decidables/decidables-elements';
9
+
10
+ /*
11
+ DiscountableElement Base Class - Not intended for instantiation!
12
+ <sdt-element>
13
+ */
14
+ export default class DiscountableElement extends DecidablesElement {
15
+ static get properties() {
16
+ return {
17
+ interactive: {
18
+ attribute: 'interactive',
19
+ type: Boolean,
20
+ reflect: true,
21
+ },
22
+ };
23
+ }
24
+
25
+ constructor() {
26
+ super();
27
+ this.interactive = false;
28
+ }
29
+
30
+ static get colors() {
31
+ return {
32
+ a: d3.schemeSet1[0],
33
+ d: d3.schemeSet1[1],
34
+ k: d3.schemeSet1[2],
35
+ v: d3.schemeSet1[3],
36
+ chosen: d3.schemeSet1[8],
37
+ better: '#4545d0',
38
+ worse: '#f032e6',
39
+ even: '#10dbc9',
40
+ correct: '#ffffff',
41
+ error: '#000000',
42
+ nr: '#cccccc',
43
+ };
44
+ }
45
+
46
+ static get lights() {
47
+ return Object.keys(DiscountableElement.colors).reduce((acc, cur) => {
48
+ acc[cur] = d3.interpolateRgb(DiscountableElement.colors[cur], '#ffffff')(0.5);
49
+ return acc;
50
+ }, {});
51
+ }
52
+
53
+ static get darks() {
54
+ return Object.keys(DiscountableElement.colors).reduce((acc, cur) => {
55
+ acc[cur] = d3.interpolateRgb(DiscountableElement.colors[cur], '#000000')(0.5);
56
+ return acc;
57
+ }, {});
58
+ }
59
+
60
+
61
+ static get styles() {
62
+ return [
63
+ super.styles,
64
+ css`
65
+ :host {
66
+ ---color-a: var(--color-a, ${unsafeCSS(this.colors.a)});
67
+ ---color-d: var(--color-d, ${unsafeCSS(this.colors.d)});
68
+ ---color-k: var(--color-k, ${unsafeCSS(this.colors.k)});
69
+ ---color-v: var(--color-v, ${unsafeCSS(this.colors.v)});
70
+ ---color-chosen: var(--color-chosen, ${unsafeCSS(this.colors.chosen)});
71
+ ---color-better: var(--color-better, ${unsafeCSS(this.colors.better)});
72
+ ---color-worse: var(--color-worse, ${unsafeCSS(this.colors.worse)});
73
+ ---color-even: var(--color-even, ${unsafeCSS(this.colors.even)});
74
+ ---color-correct: var(--color-correct, ${unsafeCSS(this.colors.correct)});
75
+ ---color-error: var(--color-error, ${unsafeCSS(this.colors.error)});
76
+ ---color-nr: var(--color-nr, ${unsafeCSS(this.colors.nr)});
77
+
78
+ ---color-a-light: var(--color-a-light, ${unsafeCSS(this.lights.a)});
79
+ ---color-d-light: var(--color-d-light, ${unsafeCSS(this.lights.d)});
80
+ ---color-k-light: var(--color-k-light, ${unsafeCSS(this.lights.k)});
81
+ ---color-v-light: var(--color-v-light, ${unsafeCSS(this.lights.v)});
82
+ ---color-chosen-light: var(--color-chosen-light, ${unsafeCSS(this.lights.chosen)});
83
+ ---color-better-light: var(--color-better-light, ${unsafeCSS(this.lights.better)});
84
+ ---color-worse-light: var(--color-worse-light, ${unsafeCSS(this.lights.worse)});
85
+ ---color-even-light: var(--color-even-light, ${unsafeCSS(this.lights.even)});
86
+ ---color-correct-light: var(--color-correct-light, ${unsafeCSS(this.lights.correct)});
87
+ ---color-error-light: var(--color-error-light, ${unsafeCSS(this.lights.error)});
88
+ ---color-nr-light: var(--color-nr-light, ${unsafeCSS(this.lights.nr)});
89
+
90
+ ---color-a-dark: var(--color-a-dark, ${unsafeCSS(this.darks.a)});
91
+ ---color-d-dark: var(--color-d-dark, ${unsafeCSS(this.darks.d)});
92
+ ---color-k-dark: var(--color-k-dark, ${unsafeCSS(this.darks.k)});
93
+ ---color-v-dark: var(--color-v-dark, ${unsafeCSS(this.darks.v)});
94
+ ---color-chosen-dark: var(--color-chosen-dark, ${unsafeCSS(this.darks.chosen)});
95
+ ---color-better-dark: var(--color-better-dark, ${unsafeCSS(this.darks.better)});
96
+ ---color-worse-dark: var(--color-worse-dark, ${unsafeCSS(this.darks.worse)});
97
+ ---color-even-dark: var(--color-even-dark, ${unsafeCSS(this.darks.even)});
98
+ ---color-correct-dark: var(--color-correct-dark, ${unsafeCSS(this.darks.correct)});
99
+ ---color-error-dark: var(--color-error-dark, ${unsafeCSS(this.darks.error)});
100
+ ---color-nr-dark: var(--color-nr-dark, ${unsafeCSS(this.darks.nr)});
101
+ }
102
+ `,
103
+ ];
104
+ }
105
+ }
@@ -0,0 +1,135 @@
1
+
2
+ import {html} from 'lit';
3
+
4
+ import '@decidables/decidables-elements/spinner';
5
+ import HTDMath from '@decidables/discountable-math';
6
+
7
+ import HTDEquation from './htd-equation';
8
+
9
+ /*
10
+ HTDEquationADK2V element
11
+ <htd-equation-adk2v>
12
+
13
+ Attributes:
14
+ amount, delay, k, value;
15
+ */
16
+ export default class HTDEquationADK2V extends HTDEquation {
17
+ static get properties() {
18
+ return {
19
+ a: {
20
+ attribute: 'amount',
21
+ type: Number,
22
+ reflect: true,
23
+ },
24
+ d: {
25
+ attribute: 'delay',
26
+ type: Number,
27
+ reflect: true,
28
+ },
29
+ k: {
30
+ attribute: 'k',
31
+ type: Number,
32
+ reflect: true,
33
+ },
34
+
35
+ v: {
36
+ attribute: false,
37
+ type: Number,
38
+ reflect: false,
39
+ },
40
+ };
41
+ }
42
+
43
+ constructor() {
44
+ super();
45
+ this.a = 100;
46
+ this.d = 30;
47
+ this.k = 0.05;
48
+ this.alignState();
49
+ }
50
+
51
+ alignState() {
52
+ this.v = HTDMath.adk2v(this.a, this.d, this.k);
53
+ }
54
+
55
+ sendEvent() {
56
+ this.dispatchEvent(new CustomEvent('htd-equation-adk2v-change', {
57
+ detail: {
58
+ a: this.a,
59
+ d: this.d,
60
+ k: this.k,
61
+ v: this.v,
62
+ },
63
+ bubbles: true,
64
+ }));
65
+ }
66
+
67
+ aInput(event) {
68
+ this.a = parseFloat(event.target.value);
69
+ this.alignState();
70
+ this.sendEvent();
71
+ }
72
+
73
+ dInput(event) {
74
+ this.d = parseFloat(event.target.value);
75
+ this.alignState();
76
+ this.sendEvent();
77
+ }
78
+
79
+ kInput(event) {
80
+ this.k = parseFloat(event.target.value);
81
+ this.alignState();
82
+ this.sendEvent();
83
+ }
84
+
85
+ render() {
86
+ this.alignState();
87
+ let a;
88
+ let d;
89
+ let k;
90
+ let v;
91
+ if (this.numeric) {
92
+ a = html`<decidables-spinner class="a bottom" ?disabled=${!this.interactive} step="1" .value="${this.a}" @input=${this.aInput.bind(this)}>
93
+ <var class="math-var">A</var>
94
+ </decidables-spinner>`;
95
+ d = html`<decidables-spinner class="d bottom" ?disabled=${!this.interactive} min="0" step="1" .value="${this.d}" @input=${this.dInput.bind(this)}>
96
+ <var class="math-var">D</var>
97
+ </decidables-spinner>`;
98
+ k = html`<decidables-spinner class="k bottom" ?disabled=${!this.interactive} min="0" max="100" step=".001" .value="${this.k}" @input=${this.kInput.bind(this)}>
99
+ <var class="math-var">k</var>
100
+ </decidables-spinner>`;
101
+ v = html`<decidables-spinner class="v bottom" disabled step=".001" .value="${+this.v.toFixed(3)}">
102
+ <var class="math-var">V</var>
103
+ </decidables-spinner>`;
104
+ } else {
105
+ a = html`<var class="math-var a">A</var>`;
106
+ d = html`<var class="math-var d">D</var>`;
107
+ k = html`<var class="math-var k">k</var>`;
108
+ v = html`<var class="math-var v">V</var>`;
109
+ }
110
+ const equation = html`
111
+ <tr>
112
+ <td rowspan="2">
113
+ ${v}<span class="equals">=</span>
114
+ </td>
115
+ <td class="underline">
116
+ ${a}
117
+ </td>
118
+ </tr>
119
+ <tr>
120
+ <td class="">
121
+ <span class="paren tight">(</span>1<span class="plus">+</span>${k}${d}<span class="paren tight">)</span>
122
+ </td>
123
+ </tr>`;
124
+ return html`
125
+ <div class="holder">
126
+ <table class="equation">
127
+ <tbody>
128
+ ${equation}
129
+ </tbody>
130
+ </table>
131
+ </div>`;
132
+ }
133
+ }
134
+
135
+ customElements.define('htd-equation-adk2v', HTDEquationADK2V);
@@ -0,0 +1,202 @@
1
+
2
+ import {css} from 'lit';
3
+
4
+ import DiscountableElement from '../discountable-element';
5
+
6
+ /*
7
+ HTDEquation Base Class - Not intended for instantiation!
8
+ <cpt-equation>
9
+ */
10
+ export default class HTDEquation extends DiscountableElement {
11
+ static get properties() {
12
+ return {
13
+ numeric: {
14
+ attribute: 'numeric',
15
+ type: Boolean,
16
+ reflect: true,
17
+ },
18
+ };
19
+ }
20
+
21
+ constructor() {
22
+ super();
23
+ this.numeric = false;
24
+ }
25
+
26
+ static get styles() {
27
+ return [
28
+ super.styles,
29
+ css`
30
+ :host {
31
+ display: block;
32
+
33
+ margin: 1rem;
34
+ }
35
+
36
+ /* Containing <div> */
37
+ .holder {
38
+ display: flex;
39
+
40
+ flex-direction: row;
41
+
42
+ justify-content: left;
43
+ }
44
+
45
+ /* Overall <table> */
46
+ .equation {
47
+ text-align: center;
48
+ white-space: nowrap;
49
+
50
+ border-collapse: collapse;
51
+
52
+ border: 0;
53
+ }
54
+
55
+ /* Modifies <td> */
56
+ .underline {
57
+ border-bottom: 1px solid var(---color-text);
58
+ }
59
+
60
+ /* Basic <span> and <var> w/modifiers */
61
+ span,
62
+ var {
63
+ padding: 0 0.25rem;
64
+
65
+ font-style: normal;
66
+ }
67
+
68
+ var {
69
+ border-radius: var(---border-radius);
70
+ }
71
+
72
+ .tight {
73
+ padding: 0;
74
+ }
75
+
76
+ .paren {
77
+ font-size: 150%;
78
+ }
79
+
80
+ .bracket {
81
+ font-size: 175%;
82
+ }
83
+
84
+ .brace {
85
+ font-size: 200%;
86
+ }
87
+
88
+ .addend {
89
+ position: relative;
90
+ display: inline-block;
91
+ }
92
+
93
+ .comparison {
94
+ position: relative;
95
+ display: inline-block;
96
+
97
+ font-size: 125%;
98
+ font-weight: 600;
99
+ }
100
+
101
+ .function {
102
+ display: inline-block;
103
+
104
+ border-radius: var(---border-radius);
105
+ }
106
+
107
+ :host([numeric]) .function {
108
+ padding: 0.25rem;
109
+ }
110
+
111
+ .exp {
112
+ display: inline-block;
113
+
114
+ font-size: 0.75rem;
115
+ }
116
+
117
+ .subscript {
118
+ display: inline-block;
119
+
120
+ font-size: 66.667%;
121
+ }
122
+
123
+ .summation {
124
+ display: flex;
125
+
126
+ flex-direction: column;
127
+
128
+ line-height: 0.8;
129
+ }
130
+
131
+ .sigma {
132
+ display: inline-block;
133
+
134
+ font-size: 200%;
135
+ }
136
+
137
+ /* Input wrapping <label> */
138
+ decidables-spinner {
139
+ --decidables-spinner-input-width: 4rem;
140
+
141
+ display: inline-block;
142
+
143
+ padding: 0.125rem 0.375rem 0.375rem;
144
+
145
+ line-height: 1.5;
146
+ vertical-align: middle;
147
+
148
+ border-radius: var(---border-radius);
149
+ }
150
+
151
+ .n {
152
+ --decidables-spinner-input-width: 2rem;
153
+ }
154
+
155
+ .left {
156
+ text-align: left;
157
+ }
158
+
159
+ .right {
160
+ text-align: right;
161
+ }
162
+
163
+ .bottom {
164
+ vertical-align: bottom;
165
+ }
166
+
167
+ .top {
168
+ vertical-align: top;
169
+ }
170
+
171
+ /* Color scheme */
172
+ /* .win {
173
+ background: var(---color-better);
174
+ }
175
+
176
+ .loss {
177
+ background: var(---color-worse);
178
+ }
179
+
180
+ .sure {
181
+ background: var(---color-even);
182
+ } */
183
+
184
+ .a {
185
+ background: var(---color-a-light);
186
+ }
187
+
188
+ .d {
189
+ background: var(---color-d-light);
190
+ }
191
+
192
+ .k {
193
+ background: var(---color-k-light);
194
+ }
195
+
196
+ .v {
197
+ background: var(---color-v-light);
198
+ }
199
+ `,
200
+ ];
201
+ }
202
+ }
@@ -0,0 +1,3 @@
1
+
2
+ /* eslint-disable-next-line import/prefer-default-export */
3
+ export {default as HTDEquationADK2V} from './adk2v';