@decidables/detectable-elements 0.1.1 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decidables/detectable-elements",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "detectable-elements: Web Components for visualizing Signal Detection Theory",
5
5
  "keywords": [
6
6
  "web component",
@@ -56,11 +56,11 @@
56
56
  "gulp": "^4.0.2"
57
57
  },
58
58
  "dependencies": {
59
- "@decidables/decidables-elements": "^0.1.1",
59
+ "@decidables/decidables-elements": "^0.3.1",
60
60
  "@decidables/detectable-math": "^0.1.1",
61
61
  "d3": "^7.3.0",
62
62
  "jstat": "^1.9.5",
63
63
  "lit": "^2.2.1"
64
64
  },
65
- "gitHead": "9a364dd56fa4a8deff0fb0fc6ab6556ee8508321"
65
+ "gitHead": "0afa544a1711ac1d34594934ab6c03064481810f"
66
66
  }
@@ -81,7 +81,7 @@ export default class DetectableControl extends DetectableElement {
81
81
  this.duration = undefined;
82
82
  this.coherence = undefined;
83
83
  this.payoff = undefined;
84
- this.colors = ['none', 'accuracy', 'stimulus', 'response', 'outcome'];
84
+ this.colors = ['none', 'accuracy', 'stimulus', 'response', 'outcome', 'all'];
85
85
  this.color = undefined;
86
86
  this.zRoc = undefined;
87
87
  this.run = false;
@@ -234,11 +234,12 @@ export default class DetectableControl extends DetectableElement {
234
234
  ? html`
235
235
  <decidables-toggle @change=${this.chooseColor.bind(this)}>
236
236
  <span slot="label">Emphasis</span>
237
- <decidables-toggle-option name=${`${this.uniqueId}-color`} value="none" ?checked=${this.color === 'none'}>None</decidables-toggle-option>
238
- <decidables-toggle-option name=${`${this.uniqueId}-color`} value="accuracy" ?checked=${this.color === 'accuracy'}>Accuracy</decidables-toggle-option>
239
- <decidables-toggle-option name=${`${this.uniqueId}-color`} value="stimulus" ?checked=${this.color === 'stimulus'}>Stimulus</decidables-toggle-option>
240
- <decidables-toggle-option name=${`${this.uniqueId}-color`} value="response" ?checked=${this.color === 'response'}>Response</decidables-toggle-option>
241
- <decidables-toggle-option name=${`${this.uniqueId}-color`} value="outcome" ?checked=${this.color === 'outcome'}>Outcome</decidables-toggle-option>
237
+ <decidables-toggle-option name="toggle" value="none" ?checked=${this.color === 'none'}>None</decidables-toggle-option>
238
+ <decidables-toggle-option name="toggle" value="accuracy" ?checked=${this.color === 'accuracy'}>Accuracy</decidables-toggle-option>
239
+ <decidables-toggle-option name="toggle" value="stimulus" ?checked=${this.color === 'stimulus'}>Stimulus</decidables-toggle-option>
240
+ <decidables-toggle-option name="toggle" value="response" ?checked=${this.color === 'response'}>Response</decidables-toggle-option>
241
+ <decidables-toggle-option name="toggle" value="outcome" ?checked=${this.color === 'outcome'}>Outcome</decidables-toggle-option>
242
+ <decidables-toggle-option name="toggle" value="all" ?checked=${this.color === 'all'}>All</decidables-toggle-option>
242
243
  </decidables-toggle>
243
244
  `
244
245
  : html``}
@@ -302,6 +302,11 @@ export default class DetectableResponse extends DetectableElement {
302
302
  border: 1px solid var(---color-element-border);
303
303
  }
304
304
 
305
+ :host([payoff="trial"]) .feedback,
306
+ :host([payoff="total"]) .feedback {
307
+ height: 5rem;
308
+ }
309
+
305
310
  .feedback.h {
306
311
  background-color: var(---color-h-light);
307
312
  }
@@ -337,17 +342,12 @@ export default class DetectableResponse extends DetectableElement {
337
342
  line-height: 1.15;
338
343
  }
339
344
 
340
- :host([payoff="trial"]) .feedback,
341
- :host([payoff="total"]) .feedback {
342
- height: 4rem;
343
- }
344
-
345
345
  /* Payoff feedback */
346
- .payoff {
346
+ .total {
347
347
  text-align: center;
348
348
  }
349
349
 
350
- .payoff .label {
350
+ .total .label {
351
351
  font-weight: 600;
352
352
  }
353
353
  `,
@@ -355,6 +355,21 @@ export default class DetectableResponse extends DetectableElement {
355
355
  }
356
356
 
357
357
  render() {
358
+ const payoffFormatter = new Intl.NumberFormat('en-US', {
359
+ style: 'currency',
360
+ currency: 'USD',
361
+ minimumFractionDigits: 0,
362
+ maximumFractionDigits: 0,
363
+ });
364
+ const payoffFormat = (number) => {
365
+ return payoffFormatter.formatToParts(number).map(({type, value}) => {
366
+ if (type === 'minusSign') {
367
+ return '−';
368
+ }
369
+ return value;
370
+ }).reduce((string, part) => { return string + part; });
371
+ };
372
+
358
373
  return html`
359
374
  <div class="holder">
360
375
  <div class="responses">
@@ -394,15 +409,15 @@ export default class DetectableResponse extends DetectableElement {
394
409
  ? html`<span class="outcome">Error</span>`
395
410
  : html`<span class="outcome">No<br>Response</span>`
396
411
  : ''}
397
- ${(this.payoff === 'trial' || this.payoff === 'total')
398
- ? html`<span class="payoff">${this.trialPayoff}</span>`
412
+ ${((this.state === 'feedback') && (this.payoff === 'trial' || this.payoff === 'total'))
413
+ ? html`<span class="payoff">${payoffFormat(this.trialPayoff)}</span>`
399
414
  : html``}
400
415
  </div>`
401
416
  : html``}
402
417
  ${(this.payoff === 'total')
403
418
  ? html`
404
- <div class="payoff">
405
- <span class="label">Total: </span><span class="value">${this.totalPayoff}</span>
419
+ <div class="total">
420
+ <span class="label">Total: </span><span class="value">${payoffFormat(this.totalPayoff)}</span>
406
421
  </div>`
407
422
  : html``}
408
423
  </div>`
@@ -118,8 +118,8 @@ export default class DetectableTable extends DetectableElement {
118
118
  this.summaries = ['stimulusRates', 'responseRates', 'accuracy'];
119
119
  this.summary = new Set();
120
120
 
121
- this.colors = ['none', 'accuracy', 'stimulus', 'response', 'outcome'];
122
- this.color = 'outcome';
121
+ this.colors = ['none', 'accuracy', 'stimulus', 'response', 'outcome', 'all'];
122
+ this.color = 'all';
123
123
 
124
124
  this.h = 40;
125
125
  this.m = 60;
@@ -318,7 +318,7 @@ export default class DetectableTable extends DetectableElement {
318
318
 
319
319
  /* Color schemes & Table emphasis */
320
320
 
321
- /* (Default) Outcome color scheme */
321
+ /* (Default) All color scheme */
322
322
  .h {
323
323
  background: var(---color-h-light);
324
324
  border-top: 2px solid var(---color-element-emphasis);
@@ -417,6 +417,15 @@ export default class DetectableTable extends DetectableElement {
417
417
  background: var(---color-element-background);
418
418
  }
419
419
 
420
+ /* Outcome color scheme */
421
+ :host([color="outcome"]) .hr,
422
+ :host([color="outcome"]) .far,
423
+ :host([color="outcome"]) .ppv,
424
+ :host([color="outcome"]) .fomr,
425
+ :host([color="outcome"]) .acc {
426
+ background: var(---color-element-background);
427
+ }
428
+
420
429
  /* No color scheme */
421
430
  :host([color="none"]) .cr,
422
431
  :host([color="none"]) .fa,
@@ -440,6 +449,14 @@ export default class DetectableTable extends DetectableElement {
440
449
  minimumFractionDigits: 0,
441
450
  maximumFractionDigits: 0,
442
451
  });
452
+ const payoffFormat = (number) => {
453
+ return payoffFormatter.formatToParts(number).map(({type, value}) => {
454
+ if (type === 'minusSign') {
455
+ return '−';
456
+ }
457
+ return value;
458
+ }).reduce((string, part) => { return string + part; });
459
+ };
443
460
 
444
461
  this.alignState();
445
462
  let h;
@@ -455,25 +472,25 @@ export default class DetectableTable extends DetectableElement {
455
472
  h = html`
456
473
  <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${this.h}" @input=${this.hInput.bind(this)}>
457
474
  <span>Hits</span>
458
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.hPayoff)}</span>` : html``}
475
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.hPayoff)}</span>` : html``}
459
476
  </decidables-spinner>
460
477
  `;
461
478
  m = html`
462
479
  <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${this.m}" @input=${this.mInput.bind(this)}>
463
480
  <span>Misses</span>
464
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.mPayoff)}</span>` : html``}
481
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.mPayoff)}</span>` : html``}
465
482
  </decidables-spinner>
466
483
  `;
467
484
  fa = html`
468
485
  <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${this.fa}" @input=${this.faInput.bind(this)}>
469
486
  <span>False Alarms</span>
470
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.faPayoff)}</span>` : html``}
487
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.faPayoff)}</span>` : html``}
471
488
  </decidables-spinner>
472
489
  `;
473
490
  cr = html`
474
491
  <decidables-spinner ?disabled=${!this.interactive} min="0" .value="${this.cr}" @input=${this.crInput.bind(this)}>
475
492
  <span>Correct Rejections</span>
476
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.crPayoff)}</span>` : html``}
493
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.crPayoff)}</span>` : html``}
477
494
  </decidables-spinner>
478
495
  `;
479
496
  hr = html`
@@ -503,13 +520,13 @@ export default class DetectableTable extends DetectableElement {
503
520
  `;
504
521
  } else {
505
522
  h = html`<span>Hits</span>
506
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.hPayoff)}</span>` : html``}`;
523
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.hPayoff)}</span>` : html``}`;
507
524
  m = html`<span>Misses</span>
508
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.mPayoff)}</span>` : html``}`;
525
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.mPayoff)}</span>` : html``}`;
509
526
  fa = html`<span>False Alarms</span>
510
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.faPayoff)}</span>` : html``}`;
527
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.faPayoff)}</span>` : html``}`;
511
528
  cr = html`<span>Correct Rejections</span>
512
- ${this.payoff ? html`<span class="payoff">${payoffFormatter.format(this.crPayoff)}</span>` : html``}`;
529
+ ${this.payoff ? html`<span class="payoff">${payoffFormat(this.crPayoff)}</span>` : html``}`;
513
530
  hr = html`<span>Hit Rate</span>`;
514
531
  far = html`<span>False Alarm Rate</span>`;
515
532
  acc = html`<span>Accuracy</span>`;
@@ -527,10 +544,10 @@ export default class DetectableTable extends DetectableElement {
527
544
  </tr>
528
545
  <tr>
529
546
  <th class="th th-sub" scope="col">
530
- "Present"
547
+ Present
531
548
  </th>
532
549
  <th class="th th-sub" scope="col">
533
- "Absent"
550
+ Absent
534
551
  </th>
535
552
  </tr>
536
553
  </thead>
@@ -164,7 +164,7 @@ export default class RDKTask extends DetectableElement {
164
164
  }
165
165
 
166
166
  .dot {
167
- /* r: 2px; HACK: Firefox does not support CSS SVG Geometry Properties */
167
+ r: 2px;
168
168
  }
169
169
 
170
170
  .dots.coherent {
@@ -188,7 +188,7 @@ export default class RDKTask extends DetectableElement {
188
188
  ];
189
189
  }
190
190
 
191
- render() { // eslint-disable-line class-methods-use-this
191
+ render() { /* eslint-disable-line class-methods-use-this */
192
192
  return html``;
193
193
  }
194
194
 
@@ -213,8 +213,7 @@ export default class RDKTask extends DetectableElement {
213
213
  super.firstUpdated(changedProperties);
214
214
 
215
215
  // Get the width and height after initial render/update has occurred
216
- // HACK Edge: Edge doesn't have width/height until after a 0ms timeout
217
- window.setTimeout(this.getDimensions.bind(this), 0);
216
+ this.getDimensions();
218
217
  }
219
218
 
220
219
  update(changedProperties) {
@@ -556,8 +555,7 @@ export default class RDKTask extends DetectableElement {
556
555
  .data((datum) => { return datum; });
557
556
  // ENTER
558
557
  const dotEnter = dotUpdate.enter().append('circle')
559
- .classed('dot', true)
560
- .attr('r', 2); /* HACK: Firefox does not support CSS SVG Geometry Properties */
558
+ .classed('dot', true);
561
559
  // MERGE
562
560
  dotEnter.merge(dotUpdate)
563
561
  .attr('cx', (datum) => { return datum.x; })
@@ -361,13 +361,13 @@ export default class ROCSpace extends DetectableElement {
361
361
  .point .circle {
362
362
  fill: var(---color-element-emphasis);
363
363
 
364
- /* r: 6; HACK: Firefox does not support CSS SVG Geometry Properties */
364
+ r: 6px;
365
365
  }
366
366
 
367
367
  .point .label {
368
368
  font-size: 0.75rem;
369
369
 
370
- dominant-baseline: middle;
370
+ dominant-baseline: central;
371
371
  text-anchor: middle;
372
372
 
373
373
  fill: var(---color-text-inverse);
@@ -376,7 +376,7 @@ export default class ROCSpace extends DetectableElement {
376
376
  ];
377
377
  }
378
378
 
379
- render() { // eslint-disable-line class-methods-use-this
379
+ render() { /* eslint-disable-line class-methods-use-this */
380
380
  return html`
381
381
  ${DetectableElement.svgFilters}
382
382
  `;
@@ -403,8 +403,7 @@ export default class ROCSpace extends DetectableElement {
403
403
  super.firstUpdated(changedProperties);
404
404
 
405
405
  // Get the width and height after initial render/update has occurred
406
- // HACK Edge: Edge doesn't have width/height until after a 0ms timeout
407
- window.setTimeout(this.getDimensions.bind(this), 0);
406
+ this.getDimensions();
408
407
  }
409
408
 
410
409
  update(changedProperties) {
@@ -1054,8 +1053,7 @@ export default class ROCSpace extends DetectableElement {
1054
1053
  const pointEnter = pointUpdate.enter().append('g')
1055
1054
  .classed('point', true);
1056
1055
  pointEnter.append('circle')
1057
- .classed('circle', true)
1058
- .attr('r', 6); /* HACK: Firefox does not support CSS SVG Geometry Properties */
1056
+ .classed('circle', true);
1059
1057
  pointEnter.append('text')
1060
1058
  .classed('label', true);
1061
1059
  // MERGE
@@ -415,7 +415,7 @@ export default class SDTModel extends DetectableElement {
415
415
  .threshold .handle {
416
416
  fill: var(---color-element-emphasis);
417
417
 
418
- /* r: 6; HACK: Firefox does not support CSS SVG Geometry Properties */
418
+ r: 6px;
419
419
  }
420
420
 
421
421
  /* Make a larger target for touch users */
@@ -473,7 +473,7 @@ export default class SDTModel extends DetectableElement {
473
473
  ];
474
474
  }
475
475
 
476
- render() { // eslint-disable-line class-methods-use-this
476
+ render() { /* eslint-disable-line class-methods-use-this */
477
477
  return html`
478
478
  ${DetectableElement.svgFilters}
479
479
  `;
@@ -517,8 +517,7 @@ export default class SDTModel extends DetectableElement {
517
517
  super.firstUpdated(changedProperties);
518
518
 
519
519
  // Get the width and height after initial render/update has occurred
520
- // HACK Edge: Edge doesn't have width/height until after a 0ms timeout
521
- window.setTimeout(this.getDimensions.bind(this), 0);
520
+ this.getDimensions();
522
521
  }
523
522
 
524
523
  update(changedProperties) {
@@ -644,7 +643,7 @@ export default class SDTModel extends DetectableElement {
644
643
  })
645
644
  .on('drag', (event, datum) => {
646
645
  this.drag = true;
647
- let muS = this.muS; // eslint-disable-line prefer-destructuring
646
+ let muS = this.muS; /* eslint-disable-line prefer-destructuring */
648
647
  if (this.interactive) {
649
648
  muS = xScale.invert(event.x);
650
649
  // Clamp Signal Curve to stay visible
@@ -654,7 +653,7 @@ export default class SDTModel extends DetectableElement {
654
653
  ? xScale.domain()[1]
655
654
  : muS;
656
655
  }
657
- let hS = this.hS; // eslint-disable-line prefer-destructuring
656
+ let hS = this.hS; /* eslint-disable-line prefer-destructuring */
658
657
  if (this.unequal) {
659
658
  hS = yScale.invert(event.y);
660
659
  // Clamp Signal Curve to stay visible
@@ -850,7 +849,7 @@ export default class SDTModel extends DetectableElement {
850
849
  .on('keydown', this.interactive
851
850
  ? (event) => {
852
851
  if (['ArrowRight', 'ArrowLeft'].includes(event.key)) {
853
- let muN = this.muN; // eslint-disable-line prefer-destructuring
852
+ let muN = this.muN; /* eslint-disable-line prefer-destructuring */
854
853
  switch (event.key) {
855
854
  case 'ArrowRight':
856
855
  muN += event.shiftKey ? 0.01 : 0.1;
@@ -1045,7 +1044,7 @@ export default class SDTModel extends DetectableElement {
1045
1044
  .on('keydown.sensitivity', this.interactive
1046
1045
  ? (event) => {
1047
1046
  if (['ArrowRight', 'ArrowLeft'].includes(event.key)) {
1048
- let muS = this.muS; // eslint-disable-line prefer-destructuring
1047
+ let muS = this.muS; /* eslint-disable-line prefer-destructuring */
1049
1048
  switch (event.key) {
1050
1049
  case 'ArrowRight':
1051
1050
  muS += event.shiftKey ? 0.01 : 0.1;
@@ -1073,7 +1072,7 @@ export default class SDTModel extends DetectableElement {
1073
1072
  .on('keydown.variance', this.unequal
1074
1073
  ? (event) => {
1075
1074
  if (['ArrowUp', 'ArrowDown'].includes(event.key)) {
1076
- let hS = this.hS; // eslint-disable-line prefer-destructuring
1075
+ let hS = this.hS; /* eslint-disable-line prefer-destructuring */
1077
1076
  switch (event.key) {
1078
1077
  case 'ArrowUp':
1079
1078
  hS += event.shiftKey ? 0.002 : 0.02;
@@ -1327,7 +1326,7 @@ export default class SDTModel extends DetectableElement {
1327
1326
  );
1328
1327
  return (time) => {
1329
1328
  element.d = interpolateD(time);
1330
- d3.select(element).text(+(element.d).toFixed(3));
1329
+ d3.select(element).text(d3.format('.3')(element.d));
1331
1330
  };
1332
1331
  });
1333
1332
  // EXIT
@@ -1385,7 +1384,7 @@ export default class SDTModel extends DetectableElement {
1385
1384
  );
1386
1385
  return (time) => {
1387
1386
  element.c = interpolateC(time);
1388
- d3.select(element).text(+(element.c).toFixed(3));
1387
+ d3.select(element).text(d3.format('.3')(element.c));
1389
1388
  };
1390
1389
  });
1391
1390
  // EXIT
@@ -1451,7 +1450,7 @@ export default class SDTModel extends DetectableElement {
1451
1450
  );
1452
1451
  return (time) => {
1453
1452
  element.s = interpolateS(time);
1454
- d3.select(element).text(+(element.s).toFixed(3));
1453
+ d3.select(element).text(d3.format('.3')(element.s));
1455
1454
  };
1456
1455
  });
1457
1456
  // EXIT
@@ -1467,8 +1466,7 @@ export default class SDTModel extends DetectableElement {
1467
1466
  thresholdEnter.append('line')
1468
1467
  .classed('line', true);
1469
1468
  thresholdEnter.append('circle')
1470
- .classed('handle', true)
1471
- .attr('r', 6); /* HACK: Firefox does not support CSS SVG Geometry Properties */
1469
+ .classed('handle', true);
1472
1470
  // MERGE
1473
1471
  const thresholdMerge = thresholdEnter.merge(thresholdUpdate)
1474
1472
  .attr('tabindex', this.interactive ? 0 : null)
@@ -1482,7 +1480,7 @@ export default class SDTModel extends DetectableElement {
1482
1480
  .call(dragThreshold)
1483
1481
  .on('keydown', (event) => {
1484
1482
  if (['ArrowRight', 'ArrowLeft'].includes(event.key)) {
1485
- let l = this.l; // eslint-disable-line prefer-destructuring
1483
+ let l = this.l; /* eslint-disable-line prefer-destructuring */
1486
1484
  switch (event.key) {
1487
1485
  case 'ArrowRight':
1488
1486
  l += event.shiftKey ? 0.01 : 0.1;
@@ -0,0 +1,121 @@
1
+
2
+ import {html} from 'lit';
3
+
4
+ import '@decidables/decidables-elements/spinner';
5
+ import SDTMath from '@decidables/detectable-math';
6
+
7
+ import SDTEquation from './sdt-equation';
8
+
9
+ /*
10
+ SDTEquationHFa2Ppv element
11
+ <sdt-equation-hm2hr>
12
+
13
+ Attributes:
14
+ Hits; Misses; Hit Rate;
15
+ */
16
+ export default class SDTEquationHFa2Ppv extends SDTEquation {
17
+ static get properties() {
18
+ return {
19
+ h: {
20
+ attribute: 'hits',
21
+ type: Number,
22
+ reflect: true,
23
+ },
24
+ fa: {
25
+ attribute: 'false-alarms',
26
+ type: Number,
27
+ reflect: true,
28
+ },
29
+ ppv: {
30
+ attribute: false,
31
+ type: Number,
32
+ reflect: false,
33
+ },
34
+ };
35
+ }
36
+
37
+ constructor() {
38
+ super();
39
+ this.h = 0;
40
+ this.fa = 0;
41
+ this.alignState();
42
+ }
43
+
44
+ alignState() {
45
+ this.ppv = SDTMath.hFa2Ppv(this.h, this.fa);
46
+ }
47
+
48
+ sendEvent() {
49
+ this.dispatchEvent(new CustomEvent('sdt-equation-hfa2ppv-change', {
50
+ detail: {
51
+ h: this.h,
52
+ fa: this.fa,
53
+ ppv: this.ppv,
54
+ },
55
+ bubbles: true,
56
+ }));
57
+ }
58
+
59
+ hInput(event) {
60
+ this.h = parseInt(event.target.value, 10);
61
+ this.alignState();
62
+ this.sendEvent();
63
+ }
64
+
65
+ faInput(event) {
66
+ this.fa = parseInt(event.target.value, 10);
67
+ this.alignState();
68
+ this.sendEvent();
69
+ }
70
+
71
+ render() {
72
+ this.alignState();
73
+ let h;
74
+ let fa;
75
+ let ppv;
76
+ if (this.numeric) {
77
+ h = html`
78
+ <decidables-spinner class="h" ?disabled=${!this.interactive} min="0" .value="${this.h}" @input=${this.hInput.bind(this)}>
79
+ <var>Hits</var>
80
+ </decidables-spinner>
81
+ `;
82
+ fa = html`
83
+ <decidables-spinner class="fa" ?disabled=${!this.interactive} min="0" .value="${this.fa}" @input=${this.faInput.bind(this)}>
84
+ <var>False Alarms</var>
85
+ </decidables-spinner>
86
+ `;
87
+ ppv = html`
88
+ <decidables-spinner class="ppv" disabled min="0" max="1" step=".001" .value="${+this.ppv.toFixed(3)}">
89
+ <var>Positive Predictive Value</var>
90
+ </decidables-spinner>
91
+ `;
92
+ } else {
93
+ h = html`<var class="h">Hits</var>`;
94
+ fa = html`<var class="fa">False Alarms</var>`;
95
+ ppv = html`<var class="ppv">Positive Predictive Value</var>`;
96
+ }
97
+ return html`
98
+ <div class="holder">
99
+ <table class="equation">
100
+ <tbody>
101
+ <tr>
102
+ <td rowspan="2">
103
+ ${ppv}<span class="equals">=</span>
104
+ </td>
105
+ <td class="underline">
106
+ ${h}
107
+ </td>
108
+ </tr>
109
+ <tr>
110
+ <td>
111
+ ${h}<span class="plus">+</span>${fa}
112
+ </td>
113
+ </tr>
114
+ </tbody>
115
+ </table>
116
+ </div>
117
+ `;
118
+ }
119
+ }
120
+
121
+ customElements.define('sdt-equation-hfa2ppv', SDTEquationHFa2Ppv);
@@ -149,7 +149,7 @@ export default class SDTEquationHMFaCr2Acc extends SDTEquation {
149
149
  </tr>
150
150
  <tr>
151
151
  <td>
152
- ${h}<span class="plus">+</span>${m}<span class="plus">+</span>${fa}<span class="plus">+</span>${cr}
152
+ ${h}<span class="plus">+</span>${cr}<span class="plus">+</span>${m}<span class="plus">+</span>${fa}
153
153
  </td>
154
154
  </tr>
155
155
  </tbody>
@@ -2,7 +2,9 @@
2
2
  export {default as SDTEquationDC2Far} from './dc2far';
3
3
  export {default as SDTEquationDC2Hr} from './dc2hr';
4
4
  export {default as SDTEquationFaCr2Far} from './facr2far';
5
+ export {default as SDTEquationHFa2Ppv} from './hfa2ppv';
5
6
  export {default as SDTEquationHM2Hr} from './hm2hr';
6
7
  export {default as SDTEquationHMFaCr2Acc} from './hmfacr2acc';
7
8
  export {default as SDTEquationHrFar2C} from './hrfar2c';
8
9
  export {default as SDTEquationHrFar2D} from './hrfar2d';
10
+ export {default as SDTEquationMCr2Fomr} from './mcr2fomr';