@decidables/discountable-elements 0.5.1 → 0.6.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.
@@ -249,54 +249,90 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
249
249
  /* shape-rendering: crispEdges; */
250
250
  }
251
251
 
252
- .curve {
253
- fill: none;
254
- stroke: var(---color-element-emphasis);
255
- stroke-width: 2;
252
+ .option .interactive {
253
+ filter: url("#shadow-2");
256
254
  }
257
255
 
258
- .curve.interactive {
259
- cursor: nwse-resize;
260
-
261
- filter: url("#shadow-2");
262
- outline: none;
256
+ .option .interactive:hover {
257
+ filter: url("#shadow-4");
263
258
  }
264
259
 
265
- .curve.interactive:hover {
260
+ .option .body.interactive:has(~ .point:hover) {
266
261
  filter: url("#shadow-4");
267
262
  }
268
263
 
269
- .curve.interactive:active {
264
+ .option .interactive:active {
270
265
  filter: url("#shadow-8");
271
266
  }
272
267
 
273
- :host(.keyboard) .curve.interactive:focus {
268
+ .option .body.interactive:has(~ .point:active) {
274
269
  filter: url("#shadow-8");
275
270
  }
276
271
 
277
- .bar {
278
- fill: none;
279
- stroke: var(---color-element-emphasis);
280
- stroke-width: 2;
272
+ :host(.keyboard) .option .interactive:focus-within {
273
+ filter: url("#shadow-8");
281
274
  }
282
275
 
283
- .bar.interactive {
284
- cursor: ew-resize;
285
-
286
- filter: url("#shadow-2");
276
+ :host(.keyboard) .option .body.interactive:has(~ .point:focus-within) {
277
+ filter: url("#shadow-8");
278
+ }
279
+
280
+ .gradient.sooner stop {
281
+ stop-color: var(---color-sooner);
282
+ }
283
+
284
+ .gradient.later stop {
285
+ stop-color: var(---color-later);
286
+ }
287
+
288
+ .stop-0,
289
+ .stop-before {
290
+ stop-opacity: 0;
291
+ }
292
+
293
+ .stop-after,
294
+ .stop-100 {
295
+ stop-opacity: 1;
296
+ }
297
+
298
+ .fill {
299
+ fill: var(---color-element-enabled);
300
+ fill-opacity: 0.5;
301
+ stroke: none;
302
+ }
303
+
304
+ .interactive .fill {
305
+ cursor: move;
306
+
287
307
  outline: none;
288
308
  }
289
309
 
290
- .bar.interactive:hover {
291
- filter: url("#shadow-4");
310
+ .sooner .fill {
311
+ fill: var(---color-sooner);
292
312
  }
293
313
 
294
- .bar.interactive:active {
295
- filter: url("#shadow-8");
314
+ .later .fill {
315
+ fill: var(---color-later);
296
316
  }
297
317
 
298
- :host(.keyboard) .bar.interactive:focus {
299
- filter: url("#shadow-8");
318
+ .trial.sooner .fill {
319
+ fill: url("#sooner-gradient");
320
+ }
321
+
322
+ .trial.later .fill {
323
+ fill: url("#later-gradient");
324
+ }
325
+
326
+ .bar {
327
+ fill: none;
328
+ stroke: var(---color-element-emphasis);
329
+ stroke-width: 2;
330
+ }
331
+
332
+ .interactive .bar {
333
+ cursor: ew-resize;
334
+
335
+ outline: none;
300
336
  }
301
337
 
302
338
  .point .mark {
@@ -314,38 +350,22 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
314
350
  fill: var(---color-text-inverse);
315
351
  }
316
352
 
317
- .point.interactive {
353
+ .point.interact {
318
354
  cursor: ns-resize;
319
355
 
320
- filter: url("#shadow-2");
321
356
  outline: none;
322
-
323
- /* HACK: This gets Safari to correctly apply the filter! */
324
- /* https://github.com/emilbjorklund/svg-weirdness/issues/27 */
325
- stroke: #000000;
326
- stroke-opacity: 0;
327
- stroke-width: 0;
328
357
  }
329
358
 
330
- .point.interactive:hover {
331
- filter: url("#shadow-4");
332
-
333
- /* HACK: This gets Safari to correctly apply the filter! */
334
- stroke: #ff0000;
335
- }
336
-
337
- .point.interactive:active {
338
- filter: url("#shadow-8");
339
-
340
- /* HACK: This gets Safari to correctly apply the filter! */
341
- stroke: #00ff00;
359
+ .curve {
360
+ fill: none;
361
+ stroke: var(---color-element-emphasis);
362
+ stroke-width: 2;
342
363
  }
343
364
 
344
- :host(.keyboard) .point.interactive:focus {
345
- filter: url("#shadow-8");
365
+ .curve.interactive {
366
+ cursor: nwse-resize;
346
367
 
347
- /* HACK: This gets Safari to correctly apply the filter! */
348
- stroke: #0000ff;
368
+ outline: none;
349
369
  }
350
370
 
351
371
  /* Make larger targets for touch users */
@@ -434,6 +454,38 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
434
454
  const svgEnter = svgUpdate.enter().append('svg')
435
455
  .classed('main', true);
436
456
  svgEnter.html(DiscountableElement.svgDefs);
457
+ // Gradients for fill animations
458
+ const svgDefs = svgEnter.append('defs');
459
+ const soonerGradient = svgDefs.append('linearGradient')
460
+ .classed('gradient sooner', true)
461
+ .attr('id', 'sooner-gradient');
462
+ soonerGradient.append('stop')
463
+ .classed('stop-0', true)
464
+ .attr('offset', '0');
465
+ soonerGradient.append('stop')
466
+ .classed('stop-before animation', true)
467
+ .attr('offset', '1');
468
+ soonerGradient.append('stop')
469
+ .classed('stop-after animation', true)
470
+ .attr('offset', '1');
471
+ soonerGradient.append('stop')
472
+ .classed('stop-100', true)
473
+ .attr('offset', '1');
474
+ const laterGradient = svgDefs.append('linearGradient')
475
+ .classed('gradient later', true)
476
+ .attr('id', 'later-gradient');
477
+ laterGradient.append('stop')
478
+ .classed('stop-0', true)
479
+ .attr('offset', '0');
480
+ laterGradient.append('stop')
481
+ .classed('stop-before animation', true)
482
+ .attr('offset', '1');
483
+ laterGradient.append('stop')
484
+ .classed('stop-after animation', true)
485
+ .attr('offset', '1');
486
+ laterGradient.append('stop')
487
+ .classed('stop-100', true)
488
+ .attr('offset', '1');
437
489
  // MERGE
438
490
  const svgMerge = svgEnter.merge(svgUpdate)
439
491
  .attr('viewBox', `0 0 ${elementWidth} ${elementHeight}`);
@@ -554,13 +606,27 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
554
606
  );
555
607
  // ENTER
556
608
  const optionEnter = optionUpdate.enter().append('g')
557
- .classed('option', true);
558
- // Curve
559
- const curveEnter = optionEnter.append('g')
560
- .classed('curve', true)
561
- .attr('clip-path', 'url(#clip-htd-curves)');
562
- curveEnter.append('path')
563
- .classed('path', true)
609
+ .attr('class', (datum) => {
610
+ const labelClass = datum.label === 's' ? 'sooner' : datum.label === 'l' ? 'later' : '';
611
+ const trialClass = datum.trial ? 'trial' : '';
612
+ return `option ${labelClass} ${trialClass}`;
613
+ });
614
+ // Body (Fill, Bar, Point)
615
+ const bodyEnter = optionEnter.append('g')
616
+ .classed('body', true);
617
+ // Fill
618
+ const fillEnter = bodyEnter.append('g')
619
+ .classed('fill', true)
620
+ .attr('clip-path', 'url(#clip-htd-curves)')
621
+ .each((datum) => {
622
+ if (datum.trial) {
623
+ svgMerge
624
+ .selectAll('.gradient .animation')
625
+ .attr('offset', 1);
626
+ }
627
+ });
628
+ fillEnter.append('path')
629
+ .classed('region', true)
564
630
  .attr('d', (datum) => {
565
631
  const curve = d3.range(xScale(datum.d), xScale(0), -1).map((range) => {
566
632
  return {
@@ -572,8 +638,20 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
572
638
  ),
573
639
  };
574
640
  });
575
- return line(curve);
576
- })
641
+ return line([...curve, {d: 0, v: 0}, {d: datum.d, v: 0}]);
642
+ });
643
+ // Bar
644
+ const barEnter = bodyEnter.append('g')
645
+ .classed('bar', true);
646
+ barEnter.append('line')
647
+ .classed('line', true);
648
+ barEnter.append('line')
649
+ .classed('line touch', true);
650
+ barEnter.selectAll('.line')
651
+ .attr('x1', (datum) => { return xScale(datum.d); })
652
+ .attr('x2', (datum) => { return xScale(datum.d); })
653
+ .attr('y1', yScale(0))
654
+ .attr('y2', (datum) => { return yScale(datum.a); })
577
655
  .attr('stroke-dasharray', (datum, index, nodes) => {
578
656
  if (datum.trial) {
579
657
  const length = nodes[index].getTotalLength();
@@ -581,8 +659,20 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
581
659
  }
582
660
  return 'none';
583
661
  });
662
+ // Point
663
+ const pointEnter = bodyEnter.append('g')
664
+ .classed('point', true);
665
+ pointEnter.append('circle')
666
+ .classed('mark touch', true);
667
+ // Curve
668
+ const curveEnter = optionEnter.append('g')
669
+ .classed('curve', true)
670
+ .attr('clip-path', 'url(#clip-htd-curves)');
584
671
  curveEnter.append('path')
585
- .classed('path touch', true)
672
+ .classed('path', true);
673
+ curveEnter.append('path')
674
+ .classed('path touch', true);
675
+ curveEnter.selectAll('.path')
586
676
  .attr('d', (datum) => {
587
677
  const curve = d3.range(xScale(datum.d), xScale(0), -1).map((range) => {
588
678
  return {
@@ -603,38 +693,14 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
603
693
  }
604
694
  return 'none';
605
695
  });
606
- // Bar
607
- const barEnter = optionEnter.append('g')
608
- .classed('bar', true);
609
- barEnter.append('line')
610
- .classed('line', true)
611
- .attr('x1', (datum) => { return xScale(datum.d); })
612
- .attr('x2', (datum) => { return xScale(datum.d); })
613
- .attr('y1', yScale(0))
614
- .attr('y2', (datum) => { return yScale(datum.a); })
615
- .attr('stroke-dasharray', (datum, index, nodes) => {
616
- if (datum.trial) {
617
- const length = nodes[index].getTotalLength();
618
- return `0,${length}`;
619
- }
620
- return 'none';
621
- });
622
- barEnter.append('line')
623
- .classed('line touch', true)
624
- .attr('x1', (datum) => { return xScale(datum.d); })
625
- .attr('x2', (datum) => { return xScale(datum.d); })
626
- .attr('y1', yScale(0))
627
- .attr('y2', (datum) => { return yScale(datum.a); })
628
- .attr('stroke-dasharray', (datum, index, nodes) => {
629
- if (datum.trial) {
630
- const length = nodes[index].getTotalLength();
631
- return `0,${length}`;
632
- }
633
- return 'none';
634
- });
635
- // Point
636
- const pointEnter = optionEnter.append('g')
637
- .classed('point', true)
696
+ // Point (again)
697
+ const topPointEnter = optionEnter.append('g')
698
+ .classed('point top-point', true);
699
+ topPointEnter.append('circle')
700
+ .classed('mark touch', true);
701
+ topPointEnter.append('text')
702
+ .classed('label', true);
703
+ optionEnter.selectAll('.point')
638
704
  .attr('transform', (datum) => {
639
705
  return `translate(${xScale(datum.d)}, ${yScale(datum.a)})`;
640
706
  })
@@ -644,28 +710,27 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
644
710
  }
645
711
  return 1;
646
712
  });
647
- pointEnter.append('circle')
648
- .classed('mark touch', true);
649
- pointEnter.append('text')
650
- .classed('label', true);
713
+
651
714
  // MERGE
652
715
  const optionMerge = optionEnter.merge(optionUpdate);
653
716
 
654
717
  // Interactive options
655
- // Curve
656
- optionMerge
718
+ // Body (Fill, Bar, Point)
719
+ const bodyMergeInteractive = optionMerge
657
720
  .filter((datum, index, nodes) => {
658
- return (this.interactive && !d3.select(nodes[index]).select('.curve').classed('interactive'));
721
+ return (this.interactive && !datum.trial && !d3.select(nodes[index]).select('.body').classed('interactive'));
659
722
  })
660
- .select('.curve')
661
- .classed('interactive', true)
723
+ .select('.body');
724
+ bodyMergeInteractive.classed('interactive', true);
725
+ // Fill
726
+ bodyMergeInteractive.select('.fill')
662
727
  .attr('tabindex', 0)
663
728
  // Drag interaction
664
729
  .call(d3.drag()
665
- .subject((event) => {
730
+ .subject((event, datum) => {
666
731
  return {
667
- x: event.x,
668
- y: event.y,
732
+ x: xScale(datum.d),
733
+ y: yScale(datum.a),
669
734
  };
670
735
  })
671
736
  .on('start', (event) => {
@@ -674,24 +739,22 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
674
739
  })
675
740
  .on('drag', (event, datum) => {
676
741
  this.drag = true;
677
- const dragD = datum.d - xScale.invert(event.x);
678
- const d = (dragD < 0)
679
- ? 0
680
- : (dragD > datum.d)
681
- ? datum.d
682
- : dragD;
683
- const dragV = yScale.invert(event.y);
684
- const v = (dragV <= 0)
685
- ? 0.001
686
- : (dragV > datum.a)
687
- ? datum.a
688
- : dragV;
689
- const k = HTDMath.adv2k(datum.a, d, v);
690
- this.k = (k < HTDMath.k.MIN)
691
- ? HTDMath.k.MIN
692
- : (k > HTDMath.k.MAX)
693
- ? HTDMath.k.MAX
694
- : k;
742
+ const d = xScale.invert(event.x);
743
+ const a = yScale.invert(event.y);
744
+ datum.d = (d < this.scale.time.min)
745
+ ? this.scale.time.min
746
+ : (d > this.scale.time.max)
747
+ ? this.scale.time.max
748
+ : this.scale.time.round(d);
749
+ datum.a = (a < this.scale.value.min)
750
+ ? this.scale.value.min
751
+ : (a > this.scale.value.max)
752
+ ? this.scale.value.max
753
+ : this.scale.value.round(a);
754
+ if (datum.name === 'default') {
755
+ this.d = datum.d;
756
+ this.a = datum.a;
757
+ }
695
758
  this.alignState();
696
759
  this.requestUpdate();
697
760
  this.dispatchEvent(new CustomEvent('htd-curves-change', {
@@ -711,27 +774,42 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
711
774
  }))
712
775
  // Keyboard interaction
713
776
  .on('keydown', (event, datum) => {
714
- if (['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft'].includes(event.key)) {
715
- let {k} = this;
777
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
778
+ let keyA = datum.a;
779
+ let keyD = datum.d;
716
780
  switch (event.key) {
717
781
  case 'ArrowUp':
718
- case 'ArrowLeft':
719
- k *= event.shiftKey ? 0.95 : 0.85;
782
+ keyA += event.shiftKey ? 1 : 5;
720
783
  break;
721
784
  case 'ArrowDown':
785
+ keyA -= event.shiftKey ? 1 : 5;
786
+ break;
722
787
  case 'ArrowRight':
723
- k *= event.shiftKey ? (1 / 0.95) : (1 / 0.85);
788
+ keyD += event.shiftKey ? 1 : 5;
789
+ break;
790
+ case 'ArrowLeft':
791
+ keyD -= event.shiftKey ? 1 : 5;
724
792
  break;
725
793
  default:
726
794
  // no-op
727
795
  }
728
- k = (k < HTDMath.k.MIN)
729
- ? HTDMath.k.MIN
730
- : (k > HTDMath.k.MAX)
731
- ? HTDMath.k.MAX
732
- : k;
733
- if (k !== this.k) {
734
- this.k = k;
796
+ keyD = (keyD < this.scale.time.min)
797
+ ? this.scale.time.min
798
+ : ((keyD > this.scale.time.max)
799
+ ? this.scale.time.max
800
+ : keyD);
801
+ keyA = (keyA < this.scale.value.min)
802
+ ? this.scale.value.min
803
+ : ((keyA > this.scale.value.max)
804
+ ? this.scale.value.max
805
+ : keyA);
806
+ if ((keyD !== datum.d) || (keyA !== datum.a)) {
807
+ datum.d = keyD;
808
+ datum.a = keyA;
809
+ if (datum.name === 'default') {
810
+ this.d = datum.d;
811
+ this.a = datum.a;
812
+ }
735
813
  this.alignState();
736
814
  this.requestUpdate();
737
815
  this.dispatchEvent(new CustomEvent('htd-curves-change', {
@@ -749,13 +827,7 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
749
827
  }
750
828
  });
751
829
  // Bar
752
- optionMerge
753
- .filter((datum, index, nodes) => {
754
- return (this.interactive && !datum.trial && !d3.select(nodes[index]).select('.bar').classed('interactive'));
755
- })
756
- .select('.bar')
757
- .classed('interactive', true)
758
- .attr('tabindex', 0)
830
+ bodyMergeInteractive.select('.bar')
759
831
  // Drag interaction
760
832
  .call(d3.drag()
761
833
  .subject((event, datum) => {
@@ -839,11 +911,10 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
839
911
  // Point
840
912
  optionMerge
841
913
  .filter((datum, index, nodes) => {
842
- return (this.interactive && !datum.trial && !d3.select(nodes[index]).select('.point').classed('interactive'));
914
+ return (this.interactive && !datum.trial && !d3.select(nodes[index]).select('.top-point').classed('interact'));
843
915
  })
844
- .select('.point')
845
- .classed('interactive', true)
846
- .attr('tabindex', 0)
916
+ .select('.top-point')
917
+ .classed('interact', true)
847
918
  // Drag interaction
848
919
  .call(d3.drag()
849
920
  .subject((event, datum) => {
@@ -924,148 +995,212 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
924
995
  event.preventDefault();
925
996
  }
926
997
  });
927
-
928
- // Non-interactive options
929
998
  // Curve
930
999
  optionMerge
931
1000
  .filter((datum, index, nodes) => {
932
- return (!this.interactive && d3.select(nodes[index]).select('.curve').classed('interactive'));
1001
+ return (this.interactive && !d3.select(nodes[index]).select('.curve').classed('interactive'));
933
1002
  })
934
1003
  .select('.curve')
935
- .classed('interactive', false)
1004
+ .classed('interactive', true)
1005
+ .attr('tabindex', 0)
1006
+ // Drag interaction
1007
+ .call(d3.drag()
1008
+ .subject((event) => {
1009
+ return {
1010
+ x: event.x,
1011
+ y: event.y,
1012
+ };
1013
+ })
1014
+ .on('start', (event) => {
1015
+ const element = event.currentTarget;
1016
+ d3.select(element).classed('dragging', true);
1017
+ })
1018
+ .on('drag', (event, datum) => {
1019
+ this.drag = true;
1020
+ const dragD = datum.d - xScale.invert(event.x);
1021
+ const d = (dragD < 0)
1022
+ ? 0
1023
+ : (dragD > datum.d)
1024
+ ? datum.d
1025
+ : dragD;
1026
+ const dragV = yScale.invert(event.y);
1027
+ const v = (dragV <= 0)
1028
+ ? 0.001
1029
+ : (dragV > datum.a)
1030
+ ? datum.a
1031
+ : dragV;
1032
+ const k = HTDMath.adv2k(datum.a, d, v);
1033
+ this.k = (k < HTDMath.k.MIN)
1034
+ ? HTDMath.k.MIN
1035
+ : (k > HTDMath.k.MAX)
1036
+ ? HTDMath.k.MAX
1037
+ : k;
1038
+ this.alignState();
1039
+ this.requestUpdate();
1040
+ this.dispatchEvent(new CustomEvent('htd-curves-change', {
1041
+ detail: {
1042
+ name: datum.name,
1043
+ a: datum.a,
1044
+ d: datum.d,
1045
+ k: this.k,
1046
+ label: datum.label,
1047
+ },
1048
+ bubbles: true,
1049
+ }));
1050
+ })
1051
+ .on('end', (event) => {
1052
+ const element = event.currentTarget;
1053
+ d3.select(element).classed('dragging', false);
1054
+ }))
1055
+ // Keyboard interaction
1056
+ .on('keydown', (event, datum) => {
1057
+ if (['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft'].includes(event.key)) {
1058
+ let {k} = this;
1059
+ switch (event.key) {
1060
+ case 'ArrowUp':
1061
+ case 'ArrowLeft':
1062
+ k *= event.shiftKey ? 0.95 : 0.85;
1063
+ break;
1064
+ case 'ArrowDown':
1065
+ case 'ArrowRight':
1066
+ k *= event.shiftKey ? (1 / 0.95) : (1 / 0.85);
1067
+ break;
1068
+ default:
1069
+ // no-op
1070
+ }
1071
+ k = (k < HTDMath.k.MIN)
1072
+ ? HTDMath.k.MIN
1073
+ : (k > HTDMath.k.MAX)
1074
+ ? HTDMath.k.MAX
1075
+ : k;
1076
+ if (k !== this.k) {
1077
+ this.k = k;
1078
+ this.alignState();
1079
+ this.requestUpdate();
1080
+ this.dispatchEvent(new CustomEvent('htd-curves-change', {
1081
+ detail: {
1082
+ name: datum.name,
1083
+ a: datum.a,
1084
+ d: datum.d,
1085
+ k: this.k,
1086
+ label: datum.label,
1087
+ },
1088
+ bubbles: true,
1089
+ }));
1090
+ }
1091
+ event.preventDefault();
1092
+ }
1093
+ });
1094
+
1095
+ // Non-interactive options
1096
+ // Body (Fill, Bar, Point)
1097
+ const bodyMergeNoninteractive = optionMerge
1098
+ .filter((datum, index, nodes) => {
1099
+ return ((!this.interactive || datum.trial) && d3.select(nodes[index]).select('.body').classed('interactive'));
1100
+ });
1101
+ bodyMergeNoninteractive.classed('interactive', false);
1102
+ // Fill
1103
+ bodyMergeNoninteractive
1104
+ .select('.fill')
936
1105
  .attr('tabindex', null)
937
1106
  .on('drag', null)
938
1107
  .on('keydown', null);
939
1108
  // Bar
1109
+ bodyMergeNoninteractive
1110
+ .select('.bar')
1111
+ .on('drag', null)
1112
+ .on('keydown', null);
1113
+ // Point
940
1114
  optionMerge
941
1115
  .filter((datum, index, nodes) => {
942
- return ((!this.interactive || datum.trial) && d3.select(nodes[index]).select('.bar').classed('interactive'));
1116
+ return ((!this.interactive || datum.trial) && d3.select(nodes[index]).select('.top-point').classed('interact'));
943
1117
  })
944
- .select('.bar')
945
- .classed('interactive', false)
946
- .attr('tabindex', null)
1118
+ .select('.top-point')
1119
+ .classed('interact', false)
947
1120
  .on('drag', null)
948
1121
  .on('keydown', null);
949
- // Point
1122
+ // Curve
950
1123
  optionMerge
951
1124
  .filter((datum, index, nodes) => {
952
- return ((!this.interactive || datum.trial) && d3.select(nodes[index]).select('.point').classed('interactive'));
1125
+ return (!this.interactive && d3.select(nodes[index]).select('.curve').classed('interactive'));
953
1126
  })
954
- .select('.point')
1127
+ .select('.curve')
955
1128
  .classed('interactive', false)
956
1129
  .attr('tabindex', null)
957
1130
  .on('drag', null)
958
1131
  .on('keydown', null);
959
1132
 
960
1133
  // Trial Animation
961
- // Curve
1134
+ // Fill
962
1135
  optionMerge
963
1136
  .filter((datum) => {
964
1137
  return (datum.new);
965
1138
  })
966
- .select('.curve .path').transition()
967
- .duration(transitionDuration)
968
- .delay(transitionDuration + transitionDuration / 10)
969
- .ease(d3.easeLinear)
970
- .attrTween('stroke-dasharray', (datum, index, nodes) => {
971
- const length = nodes[index].getTotalLength();
972
- return d3.interpolate(`0,${length}`, `${length},${0}`);
973
- })
974
- .on('end', (datum) => {
975
- datum.new = false;
976
- this.dispatchEvent(new CustomEvent('discountable-response', {
977
- detail: {
978
- trial: this.trialCount,
979
- as: this.as,
980
- ds: this.ds,
981
- al: this.al,
982
- dl: this.dl,
983
- response: this.response,
984
- },
985
- bubbles: true,
986
- }));
1139
+ .each(() => {
1140
+ svgMerge
1141
+ .selectAll('.gradient .animation').transition()
1142
+ .duration(transitionDuration)
1143
+ .delay(transitionDuration + transitionDuration / 10)
1144
+ .ease(d3.easeLinear)
1145
+ .attrTween('offset', () => { return d3.interpolate(1, 0); });
987
1146
  });
1147
+ // Bar
988
1148
  optionMerge
989
1149
  .filter((datum) => {
990
1150
  return (datum.new);
991
1151
  })
992
- .select('.curve .path.touch').transition()
1152
+ .selectAll('.bar .line').transition()
993
1153
  .duration(transitionDuration)
994
- .delay(transitionDuration + transitionDuration / 10)
995
1154
  .ease(d3.easeLinear)
996
1155
  .attrTween('stroke-dasharray', (datum, index, nodes) => {
997
1156
  const length = nodes[index].getTotalLength();
998
- return d3.interpolate(`0,${length}`, `${length},${0}`);
1157
+ return d3.interpolate(`0,${length}`, `${length},${length}`);
999
1158
  });
1000
- // Bar
1159
+ // Point
1001
1160
  optionMerge
1002
1161
  .filter((datum) => {
1003
1162
  return (datum.new);
1004
1163
  })
1005
- .select('.bar .line').transition()
1006
- .duration(transitionDuration)
1164
+ .selectAll('.point').transition()
1165
+ .duration(transitionDuration / 10)
1166
+ .delay(transitionDuration)
1007
1167
  .ease(d3.easeLinear)
1008
- .attrTween('stroke-dasharray', (datum, index, nodes) => {
1009
- const length = nodes[index].getTotalLength();
1010
- return d3.interpolate(`0,${length}`, `${length},${length}`);
1011
- });
1168
+ .attrTween('opacity', () => { return d3.interpolate(0, 1); });
1169
+ // Curve
1012
1170
  optionMerge
1013
1171
  .filter((datum) => {
1014
1172
  return (datum.new);
1015
1173
  })
1016
- .select('.bar .line.touch').transition()
1174
+ .selectAll('.curve .path').transition()
1017
1175
  .duration(transitionDuration)
1176
+ .delay(transitionDuration + transitionDuration / 10)
1018
1177
  .ease(d3.easeLinear)
1019
1178
  .attrTween('stroke-dasharray', (datum, index, nodes) => {
1020
1179
  const length = nodes[index].getTotalLength();
1021
- return d3.interpolate(`0,${length}`, `${length},${length}`);
1022
- });
1023
- // Point
1024
- optionMerge
1025
- .filter((datum) => {
1026
- return (datum.new);
1180
+ return d3.interpolate(`0,${length}`, `${length},${0}`);
1027
1181
  })
1028
- .select('.point').transition()
1029
- .duration(transitionDuration / 10)
1030
- .delay(transitionDuration)
1031
- .ease(d3.easeLinear)
1032
- .attrTween('opacity', () => { return d3.interpolate(0, 1); });
1182
+ .on('end', (datum) => {
1183
+ datum.new = false;
1184
+ this.dispatchEvent(new CustomEvent('discountable-response', {
1185
+ detail: {
1186
+ trial: this.trialCount,
1187
+ as: this.as,
1188
+ ds: this.ds,
1189
+ al: this.al,
1190
+ dl: this.dl,
1191
+ response: this.response,
1192
+ },
1193
+ bubbles: true,
1194
+ }));
1195
+ });
1033
1196
 
1034
1197
  // All options
1035
- optionUpdate.select('.curve .path').transition()
1036
- .duration(this.drag
1037
- ? 0
1038
- : (this.firstUpdate
1039
- ? (transitionDuration * 2)
1040
- : transitionDuration))
1041
- .ease(d3.easeCubicOut)
1042
- .attrTween('d', (datum, index, elements) => {
1043
- const element = elements[index];
1044
- const interpolateA = d3.interpolate(
1045
- (element.a !== undefined) ? element.a : datum.a,
1046
- datum.a,
1047
- );
1048
- const interpolateD = d3.interpolate(
1049
- (element.d !== undefined) ? element.d : datum.d,
1050
- datum.d,
1051
- );
1052
- return (time) => {
1053
- element.a = interpolateA(time);
1054
- element.d = interpolateD(time);
1055
- const curve = d3.range(xScale(element.d), xScale(0), -1).map((range) => {
1056
- return {
1057
- d: xScale.invert(range),
1058
- v: HTDMath.adk2v(
1059
- element.a,
1060
- element.d - xScale.invert(range),
1061
- this.k,
1062
- ),
1063
- };
1064
- });
1065
- return line(curve);
1066
- };
1067
- });
1068
- optionUpdate.select('.curve .path.touch').transition()
1198
+ optionMerge.filter((datum) => { return datum.label === 's'; })
1199
+ .raise();
1200
+ optionMerge.filter((datum) => { return datum.label === 'l'; })
1201
+ .lower();
1202
+ // Fill
1203
+ optionUpdate.select('.fill .region').transition()
1069
1204
  .duration(this.drag
1070
1205
  ? 0
1071
1206
  : (this.firstUpdate
@@ -1095,10 +1230,11 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
1095
1230
  ),
1096
1231
  };
1097
1232
  });
1098
- return line(curve);
1233
+ return line([...curve, {d: 0, v: 0}, {d: element.d, v: 0}]);
1099
1234
  };
1100
1235
  });
1101
- optionUpdate.select('.bar .line').transition()
1236
+ // Bar
1237
+ optionUpdate.selectAll('.bar .line').transition()
1102
1238
  .duration(this.drag
1103
1239
  ? 0
1104
1240
  : (this.firstUpdate
@@ -1138,71 +1274,66 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
1138
1274
  return `${yScale(element.a)}`;
1139
1275
  };
1140
1276
  });
1141
- optionUpdate.select('.bar .line.touch').transition()
1277
+ // Point
1278
+ optionUpdate.selectAll('.point').transition()
1142
1279
  .duration(this.drag
1143
1280
  ? 0
1144
1281
  : (this.firstUpdate
1145
1282
  ? (transitionDuration * 2)
1146
1283
  : transitionDuration))
1147
1284
  .ease(d3.easeCubicOut)
1148
- .attrTween('x1', (datum, index, elements) => {
1149
- const element = elements[index];
1150
- const interpolateD = d3.interpolate(
1151
- (element.d !== undefined) ? element.d : datum.d,
1152
- datum.d,
1153
- );
1154
- return (time) => {
1155
- element.d = interpolateD(time);
1156
- return `${xScale(element.d)}`;
1157
- };
1158
- })
1159
- .attrTween('x2', (datum, index, elements) => {
1285
+ .attrTween('transform', (datum, index, elements) => {
1160
1286
  const element = elements[index];
1161
1287
  const interpolateD = d3.interpolate(
1162
1288
  (element.d !== undefined) ? element.d : datum.d,
1163
1289
  datum.d,
1164
1290
  );
1165
- return (time) => {
1166
- element.d = interpolateD(time);
1167
- return `${xScale(element.d)}`;
1168
- };
1169
- })
1170
- .attrTween('y2', (datum, index, elements) => {
1171
- const element = elements[index];
1172
1291
  const interpolateA = d3.interpolate(
1173
1292
  (element.a !== undefined) ? element.a : datum.a,
1174
1293
  datum.a,
1175
1294
  );
1176
1295
  return (time) => {
1296
+ element.d = interpolateD(time);
1177
1297
  element.a = interpolateA(time);
1178
- return `${yScale(element.a)}`;
1298
+ return `translate(${xScale(element.d)}, ${yScale(element.a)})`;
1179
1299
  };
1180
1300
  });
1181
- optionUpdate.select('.point').transition()
1301
+ optionMerge.select('.point .label')
1302
+ .text((datum) => { return datum.label; });
1303
+ // Curve
1304
+ optionUpdate.selectAll('.curve .path').transition()
1182
1305
  .duration(this.drag
1183
1306
  ? 0
1184
1307
  : (this.firstUpdate
1185
1308
  ? (transitionDuration * 2)
1186
1309
  : transitionDuration))
1187
1310
  .ease(d3.easeCubicOut)
1188
- .attrTween('transform', (datum, index, elements) => {
1311
+ .attrTween('d', (datum, index, elements) => {
1189
1312
  const element = elements[index];
1190
- const interpolateD = d3.interpolate(
1191
- (element.d !== undefined) ? element.d : datum.d,
1192
- datum.d,
1193
- );
1194
1313
  const interpolateA = d3.interpolate(
1195
1314
  (element.a !== undefined) ? element.a : datum.a,
1196
1315
  datum.a,
1197
1316
  );
1317
+ const interpolateD = d3.interpolate(
1318
+ (element.d !== undefined) ? element.d : datum.d,
1319
+ datum.d,
1320
+ );
1198
1321
  return (time) => {
1199
- element.d = interpolateD(time);
1200
1322
  element.a = interpolateA(time);
1201
- return `translate(${xScale(element.d)}, ${yScale(element.a)})`;
1323
+ element.d = interpolateD(time);
1324
+ const curve = d3.range(xScale(element.d), xScale(0), -1).map((range) => {
1325
+ return {
1326
+ d: xScale.invert(range),
1327
+ v: HTDMath.adk2v(
1328
+ element.a,
1329
+ element.d - xScale.invert(range),
1330
+ this.k,
1331
+ ),
1332
+ };
1333
+ });
1334
+ return line(curve);
1202
1335
  };
1203
1336
  });
1204
- optionMerge.select('.point .label')
1205
- .text((datum) => { return datum.label; });
1206
1337
  // EXIT
1207
1338
  // NOTE: Could add a transition here
1208
1339
  optionUpdate.exit().remove();