@decidables/accumulable-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,520 @@
1
+
2
+ import {html, css} from 'lit';
3
+
4
+ import '@decidables/decidables-elements/spinner';
5
+
6
+ import AccumulableElement from '../accumulable-element';
7
+
8
+ /*
9
+ AccumulableTable element
10
+ <accumulable-table>
11
+
12
+ Attributes:
13
+ ????Hit; Miss; FalseAlarm; CorrectRejection;
14
+ */
15
+ export default class AccumulableTable extends AccumulableElement {
16
+ static get properties() {
17
+ return {
18
+ numeric: {
19
+ attribute: 'numeric',
20
+ type: Boolean,
21
+ reflect: true,
22
+ },
23
+ summary: {
24
+ attribute: 'summary',
25
+ type: Boolean,
26
+ reflect: true,
27
+ },
28
+ color: {
29
+ attribute: 'color',
30
+ type: String,
31
+ reflect: true,
32
+ },
33
+
34
+ correctCount: {
35
+ attribute: 'correct-count',
36
+ type: Number,
37
+ reflect: true,
38
+ },
39
+ errorCount: {
40
+ attribute: 'error-count',
41
+ type: Number,
42
+ reflect: true,
43
+ },
44
+ nrCount: {
45
+ attribute: 'nr-count',
46
+ type: Number,
47
+ reflect: true,
48
+ },
49
+ accuracy: {
50
+ attribute: 'accuracy',
51
+ type: Number,
52
+ reflect: true,
53
+ },
54
+ correctMeanRT: {
55
+ attribute: 'correct-mean-rt',
56
+ type: Number,
57
+ reflect: true,
58
+ },
59
+ errorMeanRT: {
60
+ attribute: 'error-mean-rt',
61
+ type: Number,
62
+ reflect: true,
63
+ },
64
+ meanRT: {
65
+ attribute: 'mean-rt',
66
+ type: Number,
67
+ reflect: true,
68
+ },
69
+ correctSDRT: {
70
+ attribute: 'correct-sd-rt',
71
+ type: Number,
72
+ reflect: true,
73
+ },
74
+ errorSDRT: {
75
+ attribute: 'error-sd-rt',
76
+ type: Number,
77
+ reflect: true,
78
+ },
79
+ sdRT: {
80
+ attribute: 'sd-rt',
81
+ type: Number,
82
+ reflect: true,
83
+ },
84
+
85
+ payoff: {
86
+ attribute: 'payoff',
87
+ type: Boolean,
88
+ reflect: true,
89
+ },
90
+ correctPayoff: {
91
+ attribute: 'correct-payoff',
92
+ type: Number,
93
+ reflect: true,
94
+ },
95
+ errorPayoff: {
96
+ attribute: 'error-payoff',
97
+ type: Number,
98
+ reflect: true,
99
+ },
100
+ nrPayoff: {
101
+ attribute: 'no-response-payoff',
102
+ type: Number,
103
+ reflect: true,
104
+ },
105
+ };
106
+ }
107
+
108
+ constructor() {
109
+ super();
110
+
111
+ this.numeric = false;
112
+ this.summary = false;
113
+
114
+ this.colors = ['none', 'measure', 'outcome', 'all'];
115
+ this.color = 'all';
116
+
117
+ this.payoff = false;
118
+ this.correctPayoff = undefined; // Correct payoff
119
+ this.errorPayoff = undefined; // Error payoff
120
+ this.nrPayoff = undefined; // No Response payoff
121
+
122
+ this.correctCount = NaN;
123
+ this.errorCount = NaN;
124
+ this.nrCount = NaN;
125
+ this.accuracy = NaN;
126
+ this.correctMeanRT = NaN;
127
+ this.errorMeanRT = NaN;
128
+ this.meanRT = NaN;
129
+ this.correctSDRT = NaN;
130
+ this.errorSDRT = NaN;
131
+ this.sdRT = NaN;
132
+ }
133
+
134
+ sendEvent() {
135
+ this.dispatchEvent(new CustomEvent('accumulable-table-change', {
136
+ detail: {
137
+ correctCount: this.correctCount,
138
+ errorCount: this.errorCount,
139
+ nrCount: this.nrCount,
140
+ accuracy: this.accuracy,
141
+ correctMeanRT: this.correctMeanRT,
142
+ errorMeanRT: this.errorMeanRT,
143
+ meanRT: this.meanRT,
144
+ correctSDRT: this.correctSDRT,
145
+ errorSDRT: this.errorSDRT,
146
+ sdRT: this.sdRT,
147
+ },
148
+ bubbles: true,
149
+ }));
150
+ }
151
+
152
+ correctCountInput(e) {
153
+ this.correctCount = parseInt(e.target.value, 10);
154
+ this.sendEvent();
155
+ }
156
+
157
+ errorCountInput(e) {
158
+ this.errorCount = parseInt(e.target.value, 10);
159
+ this.sendEvent();
160
+ }
161
+
162
+ accuracyInput(e) {
163
+ this.accuracy = parseFloat(e.target.value);
164
+ this.sendEvent();
165
+ }
166
+
167
+ correctMeanRTInput(e) {
168
+ this.correctMeanRT = parseFloat(e.target.value);
169
+ this.sendEvent();
170
+ }
171
+
172
+ errorMeanRTInput(e) {
173
+ this.errorMeanRT = parseFloat(e.target.value);
174
+ this.sendEvent();
175
+ }
176
+
177
+ meanRTInput(e) {
178
+ this.meanRT = parseFloat(e.target.value);
179
+ this.sendEvent();
180
+ }
181
+
182
+ correctSDRTInput(e) {
183
+ this.correctSDRT = parseFloat(e.target.value);
184
+ this.sendEvent();
185
+ }
186
+
187
+ errorSDRTInput(e) {
188
+ this.errorSDRT = parseFloat(e.target.value);
189
+ this.sendEvent();
190
+ }
191
+
192
+ sdRTInput(e) {
193
+ this.sdRT = parseFloat(e.target.value);
194
+ this.sendEvent();
195
+ }
196
+
197
+ static get styles() {
198
+ return [
199
+ super.styles,
200
+ css`
201
+ :host {
202
+ display: inline-block;
203
+ }
204
+
205
+ /* Overall element */
206
+ table {
207
+ text-align: center;
208
+
209
+ border-collapse: collapse;
210
+
211
+ border: 0;
212
+ }
213
+
214
+ /* Headers */
215
+ .th-main {
216
+ padding: 0;
217
+
218
+ font-weight: bold;
219
+ }
220
+
221
+ .th-sub {
222
+ padding: 0 0.25rem;
223
+
224
+ font-weight: 600;
225
+ }
226
+
227
+ .th-left {
228
+ padding-left: 0;
229
+
230
+ text-align: right;
231
+ }
232
+
233
+ /* Cells */
234
+ .td {
235
+ width: 10rem;
236
+
237
+ padding: 0.25rem 0.25rem 0.375rem;
238
+
239
+ transition: all var(---transition-duration) ease;
240
+ }
241
+
242
+ .numeric .td {
243
+ width: 7rem;
244
+ }
245
+
246
+ /* Labels */
247
+ .payoff {
248
+ font-weight: 600;
249
+ line-height: 0.75rem;
250
+ }
251
+
252
+ /* User interaction <input> */
253
+ .td-data decidables-spinner {
254
+ --decidables-spinner-input-width: 3.5rem;
255
+ }
256
+
257
+ .td-summary decidables-spinner {
258
+ --decidables-spinner-input-width: 4.5rem;
259
+ }
260
+
261
+ /* Table emphasis */
262
+ .td-data.correct {
263
+ border-left: 2px solid var(---color-element-emphasis);
264
+ }
265
+
266
+ .td-data.error {
267
+ border-right: 2px solid var(---color-element-emphasis);
268
+ }
269
+
270
+ .td-data.count {
271
+ border-top: 2px solid var(---color-element-emphasis);
272
+ }
273
+
274
+ .td-data.sd-rt {
275
+ border-bottom: 2px solid var(---color-element-emphasis);
276
+ }
277
+
278
+ /* Color schemes */
279
+
280
+ /* (Default) All color scheme */
281
+ .correct.count {
282
+ background: var(---color-element-background); /* ###### */
283
+ }
284
+
285
+ .error.count {
286
+ background: var(---color-element-background); /* ###### */
287
+ }
288
+
289
+ .overall.proportion-correct {
290
+ background: var(---color-element-background); /* ###### */
291
+ }
292
+
293
+ .correct.mean-rt {
294
+ background: var(---color-element-background); /* ###### */
295
+ }
296
+
297
+ .error.mean-rt {
298
+ background: var(---color-element-background); /* ###### */
299
+ }
300
+
301
+ .overall.mean-rt {
302
+ background: var(---color-element-background); /* ###### */
303
+ }
304
+
305
+ .correct.sd-rt {
306
+ background: var(---color-element-background); /* ###### */
307
+ }
308
+
309
+ .error.sd-rt {
310
+ background: var(---color-element-background); /* ###### */
311
+ }
312
+
313
+ .overall.sd-rt {
314
+ background: var(---color-element-background); /* ###### */
315
+ }
316
+
317
+ /* Outcome color scheme */
318
+ :host([color="outcome"]) .correct {
319
+ background: var(---color-correct-light);
320
+ }
321
+
322
+ :host([color="outcome"]) .error {
323
+ background: var(---color-error-light);
324
+ }
325
+
326
+ :host([color="outcome"]) .overall {
327
+ background: var(---color-element-background);
328
+ }
329
+
330
+ /* Measure color scheme */
331
+ :host([color="measure"]) .count,
332
+ :host([color="measure"]) .proportion-correct {
333
+ background: var(---color-element-background); /* ###### */
334
+ }
335
+
336
+ :host([color="measure"]) .mean-rt {
337
+ background: var(---color-element-background); /* ###### */
338
+ }
339
+
340
+ :host([color="measure"]) .sd-rt {
341
+ background: var(---color-element-background); /* ###### */
342
+ }
343
+
344
+ /* No color scheme */
345
+ :host([color="none"]) .td-data,
346
+ :host([color="none"]) .td-summary {
347
+ background: var(---color-element-background);
348
+ }
349
+ `,
350
+ ];
351
+ }
352
+
353
+ render() {
354
+ const payoffFormatter = new Intl.NumberFormat('en-US', {
355
+ style: 'currency',
356
+ currency: 'USD',
357
+ minimumFractionDigits: 0,
358
+ maximumFractionDigits: 0,
359
+ });
360
+ const payoffFormat = (number) => {
361
+ return payoffFormatter.formatToParts(number).map(({type, value}) => {
362
+ if (type === 'minusSign') {
363
+ return '−';
364
+ }
365
+ return value;
366
+ }).reduce((string, part) => { return string + part; });
367
+ };
368
+
369
+ let correctCount;
370
+ let errorCount;
371
+ let accuracy;
372
+ let correctMeanRT;
373
+ let errorMeanRT;
374
+ let meanRT;
375
+ let correctSDRT;
376
+ let errorSDRT;
377
+ let sdRT;
378
+ if (this.numeric) {
379
+ correctCount = html`
380
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.correctCount}" @input=${this.correctCountInput.bind(this)}>
381
+ <span>Correct Count</span>
382
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.correctPayoff)}</span>` : html``}
383
+ </decidables-spinner>
384
+ `;
385
+ errorCount = html`
386
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.errorCount}" @input=${this.errorCountInput.bind(this)}>
387
+ <span>Error Count</span>
388
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.errorPayoff)}</span>` : html``}
389
+ </decidables-spinner>
390
+ `;
391
+ accuracy = html`
392
+ <decidables-spinner ?disabled=${!this.interactive} min="0" max="1" step=".01" .value="${+this.accuracy.toFixed(2)}" @input=${this.accuracyInput.bind(this)}>
393
+ <span>Accuracy</span>
394
+ </decidables-spinner>
395
+ `;
396
+ correctMeanRT = html`
397
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.correctMeanRT.toFixed(0)}" @input=${this.correctMeanRTInput.bind(this)}>
398
+ <span>Correct Mean RT</span>
399
+ </decidables-spinner>
400
+ `;
401
+ errorMeanRT = html`
402
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.errorMeanRT.toFixed(0)}" @input=${this.errorMeanRTInput.bind(this)}>
403
+ <span>Error Mean RT</span>
404
+ </decidables-spinner>
405
+ `;
406
+ meanRT = html`
407
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.meanRT.toFixed(0)}" @input=${this.meanRTInput.bind(this)}>
408
+ <span>Mean RT</span>
409
+ </decidables-spinner>
410
+ `;
411
+ correctSDRT = html`
412
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.correctSDRT.toFixed(0)}" @input=${this.correctSDRTInput.bind(this)}>
413
+ <span>Correct SD RT</span>
414
+ </decidables-spinner>
415
+ `;
416
+ errorSDRT = html`
417
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.errorSDRT.toFixed(0)}" @input=${this.errorSDRTInput.bind(this)}>
418
+ <span>Error SD RT</span>
419
+ </decidables-spinner>
420
+ `;
421
+ sdRT = html`
422
+ <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${+this.sdRT.toFixed(0)}" @input=${this.sdRTInput.bind(this)}>
423
+ <span>SD RT</span>
424
+ </decidables-spinner>
425
+ `;
426
+ } else {
427
+ correctCount = html`<span>Correct Count</span>
428
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.correctPayoff)}</span>` : html``}`;
429
+ errorCount = html`<span>Error Count</span>
430
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.errorPayoff)}</span>` : html``}`;
431
+ accuracy = html`<span>Accuracy</span>`;
432
+ correctMeanRT = html`<span>Correct Mean RT</span>`;
433
+ errorMeanRT = html`<span>Error Mean RT</span>`;
434
+ meanRT = html`<span>Mean RT</span>`;
435
+ correctSDRT = html`<span>Correct SD RT</span>`;
436
+ errorSDRT = html`<span>Error SD RT</span>`;
437
+ sdRT = html`<span>SD RT</span>`;
438
+ }
439
+ return html`
440
+ <table class=${this.numeric ? 'numeric' : ''}>
441
+ <thead>
442
+ <tr>
443
+ <th rowspan="2"></th>
444
+ <th class="th th-main" colspan="2" scope="col">
445
+ Outcome
446
+ </th>
447
+ </tr>
448
+ <tr>
449
+ <th class="th th-sub" scope="col">
450
+ Correct
451
+ </th>
452
+ <th class="th th-sub" scope="col">
453
+ Error
454
+ </th>
455
+ ${(this.summary)
456
+ ? html`
457
+ <th class="th th-main" scope="col">
458
+ Overall
459
+ </th>`
460
+ : html``}
461
+ </tr>
462
+ </thead>
463
+ <tbody>
464
+ <tr>
465
+ <th class="th th-sub th-left" scope="row">
466
+ Count
467
+ </th>
468
+ <td class="td td-data correct count">
469
+ ${correctCount}
470
+ </td>
471
+ <td class="td td-data error count">
472
+ ${errorCount}
473
+ </td>
474
+ ${(this.summary)
475
+ ? html`
476
+ <td class="td td-summary overall proportion-correct">
477
+ ${accuracy}
478
+ </td>`
479
+ : html``}
480
+ </tr>
481
+ <tr>
482
+ <th class="th th-sub th-left" scope="row">
483
+ Mean RT
484
+ </th>
485
+ <td class="td td-data correct mean-rt">
486
+ ${correctMeanRT}
487
+ </td>
488
+ <td class="td td-data error mean-rt">
489
+ ${errorMeanRT}
490
+ </td>
491
+ ${(this.summary)
492
+ ? html`
493
+ <td class="td td-summary overall mean-rt">
494
+ ${meanRT}
495
+ </td>`
496
+ : html``}
497
+ </tr>
498
+ <tr>
499
+ <th class="th th-sub th-left" scope="row">
500
+ SD RT
501
+ </th>
502
+ <td class="td td-data correct sd-rt">
503
+ ${correctSDRT}
504
+ </td>
505
+ <td class="td td-data error sd-rt">
506
+ ${errorSDRT}
507
+ </td>
508
+ ${(this.summary)
509
+ ? html`
510
+ <td class="td td-summary overall sd-rt">
511
+ ${sdRT}
512
+ </td>`
513
+ : html``}
514
+ </tr>
515
+ </tbody>
516
+ </table>`;
517
+ }
518
+ }
519
+
520
+ customElements.define('accumulable-table', AccumulableTable);
@@ -0,0 +1,31 @@
1
+ /* eslint no-restricted-globals: ["off", "self"] */
2
+
3
+ import DDMMath from '@decidables/accumulable-math';
4
+
5
+ self.onmessage = (event) => {
6
+ const params = DDMMath.data2ez({...event.data, s: DDMMath.s});
7
+
8
+ // ##### Arbitrary default values!!!
9
+ const a = !isNaN(params.a) ? params.a : 1.5;
10
+ const z = !isNaN(params.z) ? params.z : 0.5;
11
+ const v = !isNaN(params.v) ? params.v : 0.1;
12
+ const t0 = !isNaN(params.t0) ? params.t0 : 100;
13
+ const s = !isNaN(params.s) ? params.s : DDMMath.s;
14
+
15
+ const predicted = {
16
+ accuracy: DDMMath.azv2pC(a, z, v),
17
+ correctMeanRT: DDMMath.azvt02mC(a, z, v, t0),
18
+ errorMeanRT: DDMMath.azvt02mE(a, z, v, t0),
19
+ meanRT: DDMMath.azvt02m(a, z, v, t0),
20
+ correctSDRT: DDMMath.azv2sdC(a, z, v),
21
+ errorSDRT: DDMMath.azv2sdE(a, z, v),
22
+ sdRT: DDMMath.azv2sd(a, z, v),
23
+ };
24
+
25
+ self.postMessage({
26
+ params: {
27
+ a, z, v, t0, s,
28
+ },
29
+ predicted,
30
+ });
31
+ };
@@ -0,0 +1,130 @@
1
+
2
+ import {html, css} from 'lit';
3
+
4
+ // Special Web Worker import for rollup-plugin-web-worker-loader
5
+ import DDMFitWorker from 'web-worker:./ddm-fit-worker'; /* eslint-disable-line import/no-unresolved */
6
+
7
+ import AccumulableElement from '../accumulable-element';
8
+
9
+ /*
10
+ DDMFit element
11
+ <ddm-fit>
12
+
13
+ Attributes:
14
+ interactive: true/false
15
+ */
16
+ export default class DDMFit extends AccumulableElement {
17
+ static get properties() {
18
+ return {
19
+ };
20
+ }
21
+
22
+ constructor() {
23
+ super();
24
+
25
+ this.a = 1.2;
26
+ this.z = 0.35;
27
+ this.v = 1.5;
28
+ this.t0 = 150;
29
+
30
+ this.observed = {};
31
+ this.predicted = {};
32
+
33
+ this.working = false;
34
+ this.queued = false;
35
+ this.worker = new DDMFitWorker();
36
+
37
+ this.worker.onmessage = (event) => {
38
+ this.working = false;
39
+ this.predicted = event.data.predicted;
40
+ this.a = event.data.params.a;
41
+ this.z = event.data.params.z;
42
+ this.v = event.data.params.v;
43
+ this.t0 = event.data.params.t0;
44
+ this.requestUpdate();
45
+
46
+ this.dispatchEvent(new CustomEvent('ddm-fit-update', {
47
+ detail: {
48
+ a: this.a,
49
+ z: this.z,
50
+ v: this.v,
51
+ t0: this.t0,
52
+ },
53
+ bubbles: true,
54
+ }));
55
+
56
+ if (this.queued) {
57
+ this.fit();
58
+ }
59
+ };
60
+
61
+ this.fit();
62
+ }
63
+
64
+ fit() {
65
+ if (!this.working) {
66
+ this.worker.postMessage(this.observed);
67
+ this.working = true;
68
+ this.queued = false;
69
+ } else {
70
+ this.queued = true;
71
+ }
72
+ }
73
+
74
+ clear() {
75
+ this.observed = {};
76
+
77
+ this.fit();
78
+ }
79
+
80
+ set(data) {
81
+ // Deep copy
82
+ this.observed = structuredClone(data);
83
+
84
+ this.fit();
85
+ }
86
+
87
+ static get styles() {
88
+ return [
89
+ super.styles,
90
+ css`
91
+ :host {
92
+ display: inline-block;
93
+ }
94
+ `,
95
+ ];
96
+ }
97
+
98
+ render() {
99
+ return html`
100
+ <div>
101
+ <div><b>Observed:</b>
102
+ <br/>Accuracy = ${this.observed.accuracy?.toFixed(2)},
103
+ <br/>Correct Mean RT = ${this.observed.correctMeanRT?.toFixed(0)},
104
+ Error Mean RT = ${this.observed.errorMeanRT?.toFixed(0)},
105
+ Mean RT = ${this.observed.meanRT?.toFixed(0)},
106
+ <br/>Correct SD RT = ${this.observed.correctSDRT?.toFixed(0)},
107
+ Error SD RT = ${this.observed.errorSDRT?.toFixed(0)},
108
+ SD RT = ${this.observed.sdRT?.toFixed(0)},
109
+ </div>
110
+ <div><b>Parameters:</b>
111
+ <br/><var class="math-var a">a</var> = ${this.a.toFixed(2)},
112
+ <var class="math-var z">z</var> = ${this.z.toFixed(2)},
113
+ <var class="math-var v">v</var> = ${this.v.toFixed(2)},
114
+ <var class="math-var t0">t0</var> = ${this.t0.toFixed(0)}
115
+ </div>
116
+ <div><b>Predicted:</b>
117
+ <br/>Accuracy = ${this.predicted.accuracy?.toFixed(2)},
118
+ <br/>Correct Mean RT = ${this.predicted.correctMeanRT?.toFixed(0)},
119
+ Error Mean RT = ${this.predicted.errorMeanRT?.toFixed(0)},
120
+ Mean RT = ${this.predicted.meanRT?.toFixed(0)},
121
+ <br/>Correct SD RT = ${this.predicted.correctSDRT?.toFixed(0)},
122
+ Error SD RT = ${this.predicted.errorSDRT?.toFixed(0)},
123
+ SD RT = ${this.predicted.sdRT?.toFixed(0)},
124
+ </div>
125
+ </div>
126
+ `;
127
+ }
128
+ }
129
+
130
+ customElements.define('ddm-fit', DDMFit);