@decidables/discountable-elements 0.5.0 → 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.
- package/CHANGELOG.md +17 -0
- package/lib/discountableElements.esm.js +308 -188
- package/lib/discountableElements.esm.js.map +1 -1
- package/lib/discountableElements.esm.min.js +75 -55
- package/lib/discountableElements.esm.min.js.map +1 -1
- package/lib/discountableElements.umd.js +308 -188
- package/lib/discountableElements.umd.js.map +1 -1
- package/lib/discountableElements.umd.min.js +76 -56
- package/lib/discountableElements.umd.min.js.map +1 -1
- package/package.json +4 -4
- package/src/colors.yml +2 -2
- package/src/components/htd-curves.js +398 -267
|
@@ -249,54 +249,90 @@ export default class HTDCurves extends DecidablesMixinResizeable(DiscountableEle
|
|
|
249
249
|
/* shape-rendering: crispEdges; */
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
stroke: var(---color-element-emphasis);
|
|
255
|
-
stroke-width: 2;
|
|
252
|
+
.option .interactive {
|
|
253
|
+
filter: url("#shadow-2");
|
|
256
254
|
}
|
|
257
255
|
|
|
258
|
-
.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
filter: url("#shadow-2");
|
|
262
|
-
outline: none;
|
|
256
|
+
.option .interactive:hover {
|
|
257
|
+
filter: url("#shadow-4");
|
|
263
258
|
}
|
|
264
259
|
|
|
265
|
-
.
|
|
260
|
+
.option .body.interactive:has(~ .point:hover) {
|
|
266
261
|
filter: url("#shadow-4");
|
|
267
262
|
}
|
|
268
263
|
|
|
269
|
-
.
|
|
264
|
+
.option .interactive:active {
|
|
270
265
|
filter: url("#shadow-8");
|
|
271
266
|
}
|
|
272
267
|
|
|
273
|
-
|
|
268
|
+
.option .body.interactive:has(~ .point:active) {
|
|
274
269
|
filter: url("#shadow-8");
|
|
275
270
|
}
|
|
276
271
|
|
|
277
|
-
.
|
|
278
|
-
|
|
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
|
-
.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
.
|
|
291
|
-
|
|
310
|
+
.sooner .fill {
|
|
311
|
+
fill: var(---color-sooner);
|
|
292
312
|
}
|
|
293
313
|
|
|
294
|
-
.
|
|
295
|
-
|
|
314
|
+
.later .fill {
|
|
315
|
+
fill: var(---color-later);
|
|
296
316
|
}
|
|
297
317
|
|
|
298
|
-
|
|
299
|
-
|
|
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.
|
|
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
|
-
.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
345
|
-
|
|
365
|
+
.curve.interactive {
|
|
366
|
+
cursor: nwse-resize;
|
|
346
367
|
|
|
347
|
-
|
|
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
|
-
.
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
|
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
|
-
//
|
|
607
|
-
const
|
|
608
|
-
.classed('
|
|
609
|
-
|
|
610
|
-
.classed('
|
|
611
|
-
|
|
612
|
-
.
|
|
613
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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('.
|
|
721
|
+
return (this.interactive && !datum.trial && !d3.select(nodes[index]).select('.body').classed('interactive'));
|
|
659
722
|
})
|
|
660
|
-
.select('.
|
|
661
|
-
|
|
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:
|
|
668
|
-
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
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
?
|
|
686
|
-
: (
|
|
687
|
-
?
|
|
688
|
-
:
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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', '
|
|
715
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
729
|
-
?
|
|
730
|
-
: (
|
|
731
|
-
?
|
|
732
|
-
:
|
|
733
|
-
|
|
734
|
-
this.
|
|
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
|
-
|
|
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('
|
|
914
|
+
return (this.interactive && !datum.trial && !d3.select(nodes[index]).select('.top-point').classed('interact'));
|
|
843
915
|
})
|
|
844
|
-
.select('.point')
|
|
845
|
-
.classed('
|
|
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 (
|
|
1001
|
+
return (this.interactive && !d3.select(nodes[index]).select('.curve').classed('interactive'));
|
|
933
1002
|
})
|
|
934
1003
|
.select('.curve')
|
|
935
|
-
.classed('interactive',
|
|
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('.
|
|
1116
|
+
return ((!this.interactive || datum.trial) && d3.select(nodes[index]).select('.top-point').classed('interact'));
|
|
943
1117
|
})
|
|
944
|
-
.select('.
|
|
945
|
-
.classed('
|
|
946
|
-
.attr('tabindex', null)
|
|
1118
|
+
.select('.top-point')
|
|
1119
|
+
.classed('interact', false)
|
|
947
1120
|
.on('drag', null)
|
|
948
1121
|
.on('keydown', null);
|
|
949
|
-
//
|
|
1122
|
+
// Curve
|
|
950
1123
|
optionMerge
|
|
951
1124
|
.filter((datum, index, nodes) => {
|
|
952
|
-
return (
|
|
1125
|
+
return (!this.interactive && d3.select(nodes[index]).select('.curve').classed('interactive'));
|
|
953
1126
|
})
|
|
954
|
-
.select('.
|
|
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
|
-
//
|
|
1134
|
+
// Fill
|
|
962
1135
|
optionMerge
|
|
963
1136
|
.filter((datum) => {
|
|
964
1137
|
return (datum.new);
|
|
965
1138
|
})
|
|
966
|
-
.
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
.
|
|
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},${
|
|
1157
|
+
return d3.interpolate(`0,${length}`, `${length},${length}`);
|
|
999
1158
|
});
|
|
1000
|
-
//
|
|
1159
|
+
// Point
|
|
1001
1160
|
optionMerge
|
|
1002
1161
|
.filter((datum) => {
|
|
1003
1162
|
return (datum.new);
|
|
1004
1163
|
})
|
|
1005
|
-
.
|
|
1006
|
-
.duration(transitionDuration)
|
|
1164
|
+
.selectAll('.point').transition()
|
|
1165
|
+
.duration(transitionDuration / 10)
|
|
1166
|
+
.delay(transitionDuration)
|
|
1007
1167
|
.ease(d3.easeLinear)
|
|
1008
|
-
.attrTween('
|
|
1009
|
-
|
|
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
|
-
.
|
|
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},${
|
|
1022
|
-
});
|
|
1023
|
-
// Point
|
|
1024
|
-
optionMerge
|
|
1025
|
-
.filter((datum) => {
|
|
1026
|
-
return (datum.new);
|
|
1180
|
+
return d3.interpolate(`0,${length}`, `${length},${0}`);
|
|
1027
1181
|
})
|
|
1028
|
-
.
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1036
|
-
.
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
|
1298
|
+
return `translate(${xScale(element.d)}, ${yScale(element.a)})`;
|
|
1179
1299
|
};
|
|
1180
1300
|
});
|
|
1181
|
-
|
|
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('
|
|
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
|
-
|
|
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();
|