@decidables/accumulable-elements 0.3.3 → 0.3.5

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 CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.3.5](https://github.com/decidables/decidables/compare/@decidables/accumulable-elements@0.3.4...@decidables/accumulable-elements@0.3.5) (2025-11-21)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **accumulable-elements:** in `ddm-model`, improved arrows on dimension lines for measures ([a3b8ac4](https://github.com/decidables/decidables/commit/a3b8ac462223960c9caa72a4c1f399e008b1c439))
12
+
13
+
14
+
15
+ ## [0.3.4](https://github.com/decidables/decidables/compare/@decidables/accumulable-elements@0.3.3...@decidables/accumulable-elements@0.3.4) (2025-11-13)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **accumulable-elements:** fix ddm-model line markers for Safari and add dimension lines ([3269ea1](https://github.com/decidables/decidables/commit/3269ea1066759376ec5fd3d1c88fef1065f83461))
21
+
22
+
23
+
6
24
  ## [0.3.3](https://github.com/decidables/decidables/compare/@decidables/accumulable-elements@0.3.2...@decidables/accumulable-elements@0.3.3) (2025-11-03)
7
25
 
8
26
  **Note:** Version bump only for package @decidables/accumulable-elements
@@ -8105,8 +8105,13 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
8105
8105
  }
8106
8106
  }
8107
8107
 
8108
- .measure {
8109
- stroke-width: 2;
8108
+ .measure .line.short {
8109
+ stroke-width: 0;
8110
+ }
8111
+
8112
+ .measure .markers {
8113
+ fill: none;
8114
+ stroke-width: 0;
8110
8115
  }
8111
8116
 
8112
8117
  .measure .label {
@@ -8128,7 +8133,9 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
8128
8133
  stroke: var(---color-z);
8129
8134
  }
8130
8135
 
8131
- .measure.z .label {
8136
+ .measure.z .label,
8137
+ /* Hack to avoid Safari weirdness */
8138
+ .measure.z .label tspan {
8132
8139
  dominant-baseline: hanging;
8133
8140
  text-anchor: start;
8134
8141
  }
@@ -8151,6 +8158,43 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
8151
8158
  text-anchor: middle;
8152
8159
  }
8153
8160
 
8161
+ .measure-arrow {
8162
+ fill: context-stroke;
8163
+ stroke: context-stroke;
8164
+ }
8165
+
8166
+ .measure-arrow .arrow {
8167
+ stroke-width: 1;
8168
+ }
8169
+
8170
+ .measure-arrow.capped .cap {
8171
+ stroke-width: 2;
8172
+ }
8173
+
8174
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8175
+ .measure-arrow.a {
8176
+ fill: var(---color-a);
8177
+ stroke: var(---color-a);
8178
+ }
8179
+
8180
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8181
+ .measure-arrow.z {
8182
+ fill: var(---color-z);
8183
+ stroke: var(---color-z);
8184
+ }
8185
+
8186
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8187
+ .measure-arrow.v {
8188
+ fill: var(---color-v);
8189
+ stroke: var(---color-v);
8190
+ }
8191
+
8192
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8193
+ .measure-arrow.t0 {
8194
+ fill: var(---color-t0);
8195
+ stroke: var(---color-t0);
8196
+ }
8197
+
8154
8198
  .sd .indicator,
8155
8199
  .mean .indicator {
8156
8200
  stroke-width: 2;
@@ -8176,6 +8220,20 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
8176
8220
  stroke: var(---color-error-dark);
8177
8221
  }
8178
8222
 
8223
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8224
+ .cap.correct {
8225
+ fill: var(---color-correct-dark);
8226
+ stroke: var(---color-correct-dark);
8227
+ stroke-dasharray: 100%;
8228
+ }
8229
+
8230
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8231
+ .cap.error {
8232
+ fill: var(---color-error-dark);
8233
+ stroke: var(---color-error-dark);
8234
+ stroke-dasharray: 100%;
8235
+ }
8236
+
8179
8237
  .rt-label rect {
8180
8238
  filter: url("#shadow-2");
8181
8239
 
@@ -8395,10 +8453,29 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
8395
8453
  const svgEnter = svgUpdate.enter().append('svg').classed('main', true).html(AccumulableElement.svgDefs);
8396
8454
  const svgDefs = svgEnter.append('defs');
8397
8455
  // Arrowhead marker for measures
8398
- svgDefs.append('marker').attr('id', 'measure-arrow').attr('orient', 'auto-start-reverse').attr('markerUnits', 'userSpaceOnUse').attr('viewBox', '-5 -5 10 10').attr('refX', '2').attr('refY', '0').attr('markerWidth', '10').attr('markerHeight', '10').append('path').attr('stroke', 'context-stroke').attr('fill', 'context-stroke').attr('d', 'M -3 -3 l 6 3 l -6 3 z');
8456
+ const measureArrow = parameter => {
8457
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8458
+ svgDefs.append('marker').attr('id', `measure-arrow-${parameter}`).attr('class', `measure-arrow ${parameter}`).attr('orient', 'auto-start-reverse').attr('markerUnits', 'userSpaceOnUse').attr('viewBox', '-10 -6 12 12').attr('refX', '0').attr('refY', '0').attr('markerWidth', '12').attr('markerHeight', '12').append('path').attr('class', 'arrow').attr('d', 'M -7 -3 l 6 3 l -6 3 z');
8459
+ };
8460
+ const measureCappedArrow = parameter => {
8461
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8462
+ const marker = svgDefs.append('marker').attr('id', `measure-capped-arrow-${parameter}`).attr('class', `measure-arrow capped ${parameter}`).attr('orient', 'auto-start-reverse').attr('markerUnits', 'userSpaceOnUse').attr('viewBox', '-10 -6 12 12').attr('refX', '0').attr('refY', '0').attr('markerWidth', '12').attr('markerHeight', '12');
8463
+ marker.append('path').attr('class', 'arrow').attr('d', 'M -7 -3 l 6 3 l -6 3 z');
8464
+ marker.append('path').attr('class', 'cap').attr('d', 'M 0 -4 l 0 8');
8465
+ };
8466
+ measureArrow('a');
8467
+ measureArrow('z');
8468
+ measureCappedArrow('v');
8469
+ measureArrow('t0');
8470
+ measureCappedArrow('t0');
8399
8471
  // Flat markers for SDs
8400
- svgDefs.append('marker').attr('id', 'model-sd-cap').attr('orient', 'auto-start-reverse').attr('markerUnits', 'userSpaceOnUse').attr('viewBox', '-5 -5 10 10').attr('refX', '0').attr('refY', '0').attr('markerWidth', '10').attr('markerHeight', '10').append('path').attr('stroke', 'context-stroke').attr('fill', 'context-stroke').attr('stroke-width', '2').attr('d', 'M 0 -4 l 0 8');
8401
- svgDefs.append('marker').attr('id', 'data-sd-cap').attr('orient', 'auto-start-reverse').attr('markerUnits', 'userSpaceOnUse').attr('viewBox', '-5 -5 10 10').attr('refX', '0').attr('refY', '0').attr('markerWidth', '10').attr('markerHeight', '10').append('path').attr('stroke', 'context-stroke').attr('fill', 'context-stroke').attr('stroke-width', '2').attr('d', 'M 0 -3 l 0 6');
8472
+ const sdCap = outcome => {
8473
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */
8474
+ svgDefs.append('marker').attr('id', `model-sd-cap-${outcome}`).attr('class', `model-sd cap ${outcome}`).attr('orient', 'auto-start-reverse').attr('markerUnits', 'userSpaceOnUse').attr('viewBox', '-5 -5 10 10').attr('refX', '0').attr('refY', '0').attr('markerWidth', '10').attr('markerHeight', '10').append('path').attr('stroke', 'context-stroke').attr('fill', 'context-stroke').attr('stroke-width', '2').attr('d', 'M 0 -4 l 0 8');
8475
+ svgDefs.append('marker').attr('id', `data-sd-cap-${outcome}`).attr('class', `data-sd cap ${outcome}`).attr('orient', 'auto-start-reverse').attr('markerUnits', 'userSpaceOnUse').attr('viewBox', '-5 -5 10 10').attr('refX', '0').attr('refY', '0').attr('markerWidth', '10').attr('markerHeight', '10').append('path').attr('stroke', 'context-stroke').attr('fill', 'context-stroke').attr('stroke-width', '2').attr('d', 'M 0 -3 l 0 6');
8476
+ };
8477
+ sdCap('error');
8478
+ sdCap('correct');
8402
8479
  const gradient = svgDefs.append('linearGradient').attr('id', 'path-animate').attr('gradientUnits', 'userSpaceOnUse').attr('color-interpolation', 'linearRGB').attr('x1', '0').attr('x2', '0').attr('y1', evidenceScale(this.bounds.upper)).attr('y2', evidenceScale(this.bounds.lower));
8403
8480
  gradient.append('stop').classed('stop-0', true).attr('offset', '0%');
8404
8481
  gradient.append('stop').classed('stop-100', true).attr('offset', '100%');
@@ -8986,19 +9063,27 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
8986
9063
  // EXIT
8987
9064
  t0zUpdate.exit().remove();
8988
9065
 
9066
+ // Measures
9067
+ const markerCorrection = 2;
9068
+
8989
9069
  // a Measure
8990
9070
  // DATA-JOIN
8991
9071
  const aUpdate = evidenceOverlayerMerge.selectAll('.measure.a').data(this.measures ? [this.a] : []);
8992
9072
  // ENTER
8993
9073
  const aEnter = aUpdate.enter().append('g').classed('measure a', true);
8994
- aEnter.append('line').classed('line', true).attr('marker-start', 'url(#measure-arrow)').attr('marker-end', 'url(#measure-arrow)');
9074
+ aEnter.append('line').classed('line', true);
9075
+ aEnter.append('line').classed('markers', true)
9076
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */.attr('marker-start', 'url(#measure-arrow-a)').attr('marker-end', 'url(#measure-arrow-a)');
8995
9077
  const aLabel = aEnter.append('text').classed('label', true);
8996
9078
  aLabel.append('tspan').classed('a math-var', true).text('a');
8997
9079
  aLabel.append('tspan').classed('equals', true).text(' = ');
8998
9080
  aLabel.append('tspan').classed('value', true);
8999
9081
  // MERGE
9082
+ const aLength = Math.abs(evidenceScale(this.bounds.upper) - evidenceScale(this.bounds.lower));
9083
+ const aShort = aLength <= markerCorrection * 2;
9000
9084
  const aMerge = aEnter.merge(aUpdate);
9001
- aMerge.select('.line').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(this.scale.time.max) - this.rem * 0.75).attr('y1', evidenceScale(this.bounds.upper) + 2).attr('x2', timeScale(this.scale.time.max) - this.rem * 0.75).attr('y2', evidenceScale(this.bounds.lower) - 2);
9085
+ aMerge.select('.line').classed('short', aShort).transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(this.scale.time.max) - this.rem * 0.75).attr('y1', evidenceScale(this.bounds.upper) + markerCorrection).attr('x2', timeScale(this.scale.time.max) - this.rem * 0.75).attr('y2', evidenceScale(this.bounds.lower) - markerCorrection);
9086
+ aMerge.select('.markers').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(this.scale.time.max) - this.rem * 0.75).attr('y1', evidenceScale(this.bounds.upper)).attr('x2', timeScale(this.scale.time.max) - this.rem * 0.75).attr('y2', evidenceScale(this.bounds.lower));
9002
9087
  const aLabelMerge = aMerge.select('.label').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x', timeScale(this.scale.time.max)).attr('y', evidenceScale(this.bounds.upper) - this.rem * 0.25);
9003
9088
  aLabelMerge.select('.value').text(format('.2f')(this.a));
9004
9089
  // EXIT
@@ -9009,14 +9094,19 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
9009
9094
  const zUpdate = evidenceOverlayerMerge.selectAll('.measure.z').data(this.measures ? [this.z] : []);
9010
9095
  // ENTER
9011
9096
  const zEnter = zUpdate.enter().append('g').classed('measure z', true);
9012
- zEnter.append('line').classed('line', true).attr('marker-start', 'url(#measure-arrow)').attr('marker-end', 'url(#measure-arrow)');
9097
+ zEnter.append('line').classed('line', true);
9098
+ zEnter.append('line').classed('markers', true)
9099
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */.attr('marker-start', 'url(#measure-arrow-z)').attr('marker-end', 'url(#measure-arrow-z)');
9013
9100
  const zLabel = zEnter.append('text').classed('label', true);
9014
9101
  zLabel.append('tspan').classed('z math-var', true).text('z');
9015
9102
  zLabel.append('tspan').classed('equals', true).text(' = ');
9016
9103
  zLabel.append('tspan').classed('value', true);
9017
9104
  // MERGE
9105
+ const zLength = Math.abs(evidenceScale(this.startingPoint) - evidenceScale(this.bounds.lower));
9106
+ const zShort = zLength <= markerCorrection * 2;
9018
9107
  const zMerge = zEnter.merge(zUpdate);
9019
- zMerge.select('.line').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(this.scale.time.min) + this.rem * 0.75).attr('y1', evidenceScale(this.startingPoint) + 2).attr('x2', timeScale(this.scale.time.min) + this.rem * 0.75).attr('y2', evidenceScale(this.bounds.lower) - 2);
9108
+ zMerge.select('.line').classed('short', zShort).transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(this.scale.time.min) + this.rem * 0.75).attr('y1', evidenceScale(this.startingPoint) + markerCorrection).attr('x2', timeScale(this.scale.time.min) + this.rem * 0.75).attr('y2', evidenceScale(this.bounds.lower) - markerCorrection);
9109
+ zMerge.select('.markers').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(this.scale.time.min) + this.rem * 0.75).attr('y1', evidenceScale(this.startingPoint)).attr('x2', timeScale(this.scale.time.min) + this.rem * 0.75).attr('y2', evidenceScale(this.bounds.lower));
9020
9110
  const zLabelMerge = zMerge.select('.label').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x', timeScale(this.scale.time.min)).attr('y', evidenceScale(this.bounds.lower) + this.rem * 0.125);
9021
9111
  zLabelMerge.select('.value').text(format('.0%')(this.z));
9022
9112
  // EXIT
@@ -9027,20 +9117,37 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
9027
9117
  const vUpdate = evidenceOverlayerMerge.selectAll('.measure.v').data(this.measures ? [this.v] : []);
9028
9118
  // ENTER
9029
9119
  const vEnter = vUpdate.enter().append('g').classed('measure v', true);
9030
- vEnter.append('path').classed('line', true).attr('marker-start', 'url(#measure-arrow)').attr('marker-end', 'url(#measure-arrow)');
9120
+ vEnter.append('path').classed('line', true);
9121
+ vEnter.append('path').classed('markers', true)
9122
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */.attr('marker-start', 'url(#measure-capped-arrow-v)').attr('marker-end', 'url(#measure-capped-arrow-v)');
9031
9123
  const vLabel = vEnter.append('text').classed('label', true);
9032
9124
  vLabel.append('tspan').classed('v math-var', true).text('v');
9033
9125
  vLabel.append('tspan').classed('equals', true).text(' = ');
9034
9126
  vLabel.append('tspan').classed('value', true);
9035
9127
  // MERGE
9036
- const driftAngle = Math.atan(this.v / 1000 * scaleRatio);
9128
+ // Full path
9037
9129
  const driftHypotenuse = timeScale(200) - timeScale(0) + this.rem * 0.75;
9130
+ const driftAngle = Math.atan(this.v / 1000 * scaleRatio);
9038
9131
  const driftX = Math.cos(driftAngle) * driftHypotenuse;
9039
9132
  const driftY = Math.sin(driftAngle) * driftHypotenuse;
9133
+ // Corrected path
9134
+ const driftCorrection = markerCorrection / driftHypotenuse;
9135
+ const driftAngleCorrected = Math.atan(this.v / 1000 * scaleRatio) - driftCorrection;
9136
+ const driftStartX = Math.cos(driftCorrection) * driftHypotenuse;
9137
+ const driftStartY = Math.sin(driftCorrection) * driftHypotenuse;
9138
+ const driftEndX = Math.cos(driftAngleCorrected) * driftHypotenuse;
9139
+ const driftEndY = Math.sin(driftAngleCorrected) * driftHypotenuse;
9140
+ // Short path?
9141
+ const vLength = driftAngleCorrected * driftHypotenuse;
9142
+ const vShort = vLength <= markerCorrection * 2;
9040
9143
  const vMerge = vEnter.merge(vUpdate);
9041
- vMerge.select('.line').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('d', `
9144
+ vMerge.select('.line').classed('short', vShort).transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('d', `
9145
+ M ${timeScale(this.t0) + driftStartX}, ${evidenceScale(this.startingPoint) - driftStartY}
9146
+ A ${timeScale(200) - timeScale(0) + this.rem * 0.75} ${timeScale(200) - timeScale(0) + this.rem * 0.75} 0 0 0 ${timeScale(this.t0) + driftEndX} ${evidenceScale(this.startingPoint) - driftEndY}
9147
+ `);
9148
+ vMerge.select('.markers').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('d', `
9042
9149
  M ${timeScale(this.t0 + 200) + this.rem * 0.75}, ${evidenceScale(this.startingPoint)}
9043
- A ${timeScale(200) - timeScale(0)} ${timeScale(200) - timeScale(0)} 0 0 0 ${timeScale(this.t0) + driftX} ${evidenceScale(this.startingPoint) - driftY}
9150
+ A ${timeScale(200) - timeScale(0) + this.rem * 0.75} ${timeScale(200) - timeScale(0) + this.rem * 0.75} 0 0 0 ${timeScale(this.t0) + driftX} ${evidenceScale(this.startingPoint) - driftY}
9044
9151
  `);
9045
9152
  const vLabelMerge = vMerge.select('.label').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x', timeScale(this.t0 + 200) + this.rem * 0.5).attr('y', evidenceScale(this.bounds.upper) - this.rem * 0.25);
9046
9153
  vLabelMerge.select('.value').text(format('.2f')(this.v));
@@ -9052,14 +9159,19 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
9052
9159
  const t0Update = evidenceOverlayerMerge.selectAll('.measure.t0').data(this.measures ? [this.t0] : []);
9053
9160
  // ENTER
9054
9161
  const t0Enter = t0Update.enter().append('g').classed('measure t0', true);
9055
- t0Enter.append('line').classed('line', true).attr('marker-start', 'url(#measure-arrow)').attr('marker-end', 'url(#measure-arrow)');
9162
+ t0Enter.append('line').classed('line', true);
9163
+ t0Enter.append('line').classed('markers', true)
9164
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */.attr('marker-start', 'url(#measure-arrow-t0)').attr('marker-end', 'url(#measure-capped-arrow-t0)');
9056
9165
  const t0Label = t0Enter.append('text').classed('label', true);
9057
9166
  t0Label.append('tspan').classed('t0 math-var', true).text('t₀');
9058
9167
  t0Label.append('tspan').classed('equals', true).text(' = ');
9059
9168
  t0Label.append('tspan').classed('value', true);
9060
9169
  // MERGE
9170
+ const t0Length = Math.abs(timeScale(0) - timeScale(this.t0));
9171
+ const t0Short = t0Length <= markerCorrection * 2;
9061
9172
  const t0Merge = t0Enter.merge(t0Update);
9062
- t0Merge.select('.line').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(0) + 2).attr('y1', evidenceScale(this.startingPoint) - this.rem * 0.75).attr('x2', timeScale(this.t0) - 2).attr('y2', evidenceScale(this.startingPoint) - this.rem * 0.75);
9173
+ t0Merge.select('.line').classed('short', t0Short).transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(0) + markerCorrection).attr('y1', evidenceScale(this.startingPoint) - this.rem * 0.75).attr('x2', timeScale(this.t0) - markerCorrection).attr('y2', evidenceScale(this.startingPoint) - this.rem * 0.75);
9174
+ t0Merge.select('.markers').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', timeScale(0)).attr('y1', evidenceScale(this.startingPoint) - this.rem * 0.75).attr('x2', timeScale(this.t0)).attr('y2', evidenceScale(this.startingPoint) - this.rem * 0.75);
9063
9175
  const t0LabelMerge = t0Merge.select('.label').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x', timeScale(this.t0) + this.rem * 0.25).attr('y', evidenceScale(this.bounds.upper) - this.rem * 0.25);
9064
9176
  t0LabelMerge.select('.value').text(format('d')(this.t0));
9065
9177
  // EXIT
@@ -9128,7 +9240,12 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
9128
9240
  const sdEnter = sdUpdate.enter().append('g').attr('class', datum => {
9129
9241
  return `model sd ${datum.outcome}`;
9130
9242
  });
9131
- sdEnter.append('line').classed('indicator', true).attr('marker-start', 'url(#model-sd-cap)').attr('marker-end', 'url(#model-sd-cap)');
9243
+ sdEnter.append('line').classed('indicator', true)
9244
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */.attr('marker-start', datum => {
9245
+ return `url(#model-sd-cap-${datum.outcome})`;
9246
+ }).attr('marker-end', datum => {
9247
+ return `url(#model-sd-cap-${datum.outcome})`;
9248
+ });
9132
9249
  // MERGE
9133
9250
  const sdMerge = sdEnter.merge(sdUpdate);
9134
9251
  sdMerge.select('.indicator').transition().duration(this.drag ? 0 : transitionDuration).ease(cubicOut).attr('x1', datum => {
@@ -9152,7 +9269,12 @@ class DDMModel extends DecidablesMixinResizeable(AccumulableElement) {
9152
9269
  const dataSDEnter = dataSDUpdate.enter().append('g').attr('class', datum => {
9153
9270
  return `data sd ${datum.outcome}`;
9154
9271
  });
9155
- dataSDEnter.append('line').classed('indicator', true).attr('marker-start', 'url(#data-sd-cap)').attr('marker-end', 'url(#data-sd-cap)').attr('y1', datum => {
9272
+ dataSDEnter.append('line').classed('indicator', true)
9273
+ /* Hack to avoid lack of context-stroke and context-fill in Safari */.attr('marker-start', datum => {
9274
+ return `url(#data-sd-cap-${datum.outcome})`;
9275
+ }).attr('marker-end', datum => {
9276
+ return `url(#data-sd-cap-${datum.outcome})`;
9277
+ }).attr('y1', datum => {
9156
9278
  return datum.densityScale(0) + (datum.outcome === 'correct' ? 0.375 : -0.375) * this.rem;
9157
9279
  }).attr('y2', datum => {
9158
9280
  return datum.densityScale(0) + (datum.outcome === 'correct' ? 0.375 : -0.375) * this.rem;