Skip to content

Commit 41a46d4

Browse files
committed
fix: match scatter error bar opacity to selected markers
1 parent 67cc7fa commit 41a46d4

4 files changed

Lines changed: 110 additions & 0 deletions

File tree

‎draftlogs/0_fix.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Make scatter error bars follow marker opacity during selection and deselection [[#0](https://github.com/plotly/plotly.js/pull/0)]

‎src/components/errorbars/style.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
var d3 = require('@plotly/d3');
44

55
var Color = require('../color');
6+
const Drawing = require('../drawing');
7+
const subTypes = require('../../traces/scatter/subtypes');
68

79

810
module.exports = function style(traces) {
@@ -12,14 +14,22 @@ module.exports = function style(traces) {
1214
var xObj = trace.error_x || {};
1315

1416
var s = d3.select(this);
17+
let opacity = 1;
18+
if (subTypes.hasMarkers(trace)) {
19+
const marker = trace.marker;
20+
const selectedOpacity = trace.selectedpoints && Drawing.makeSelectedPointStyleFns(trace).selectedOpacityFn;
21+
opacity = selectedOpacity || ((point) => (point.mo === undefined ? marker.opacity : point.mo));
22+
}
1523

1624
s.selectAll('path.yerror')
25+
.style('opacity', opacity)
1726
.style('stroke-width', yObj.thickness + 'px')
1827
.call(Color.stroke, yObj.color);
1928

2029
if(xObj.copy_ystyle) xObj = yObj;
2130

2231
s.selectAll('path.xerror')
32+
.style('opacity', opacity)
2333
.style('stroke-width', xObj.thickness + 'px')
2434
.call(Color.stroke, xObj.color);
2535
});

‎src/traces/scatter/style.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function styleOnSelect(gd, cd, sel) {
5050
stylePoints(sel, trace, gd);
5151
styleText(sel, trace, gd);
5252
}
53+
Registry.getComponentMethod('errorbars', 'style')(sel);
5354
}
5455

5556
module.exports = {

‎test/jasmine/tests/errorbars_test.js‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ var Plotly = require('../../../lib/index');
33
var d3Select = require('../../strict-d3').select;
44
var createGraphDiv = require('../assets/create_graph_div');
55
var destroyGraphDiv = require('../assets/destroy_graph_div');
6+
const drag = require('../assets/drag');
7+
const doubleClick = require('../assets/double_click');
68

79

810
describe('errorbar plotting', function() {
@@ -29,6 +31,102 @@ describe('errorbar plotting', function() {
2931
});
3032
}
3133

34+
function checkPointOpacities(expected) {
35+
for (const selector of ['.point', '.xerror', '.yerror']) {
36+
const values = Array.from(gd.querySelectorAll(selector), (node) => Number(getComputedStyle(node).opacity));
37+
expect(values).toBeCloseToArray(expected, 5);
38+
}
39+
}
40+
41+
it('applies marker opacity to error bars through selection and clear', async () => {
42+
await Plotly.newPlot(gd, [{
43+
x: [1, 2, 3],
44+
y: [1, 2, 3],
45+
mode: 'markers',
46+
marker: { opacity: 0.6 },
47+
error_x: { type: 'constant', value: 0.2 },
48+
error_y: { type: 'constant', value: 0.3 },
49+
selected: { marker: { opacity: 0.9 } },
50+
unselected: { marker: { opacity: 0.1 } }
51+
}]);
52+
checkPointOpacities([0.6, 0.6, 0.6]);
53+
54+
await Plotly.restyle(gd, { selectedpoints: [[1]] });
55+
checkPointOpacities([0.1, 0.9, 0.1]);
56+
57+
await Plotly.restyle(gd, { selectedpoints: [[]] });
58+
checkPointOpacities([0.1, 0.1, 0.1]);
59+
60+
await Plotly.restyle(gd, { selectedpoints: null });
61+
checkPointOpacities([0.6, 0.6, 0.6]);
62+
63+
await Plotly.restyle(gd, { 'marker.opacity': 0 });
64+
checkPointOpacities([0, 0, 0]);
65+
});
66+
67+
it('uses point opacity arrays across missing data', async () => {
68+
await Plotly.newPlot(gd, [{
69+
x: [1, null, 3],
70+
y: [1, 2, 3],
71+
mode: 'lines+markers',
72+
marker: { opacity: [0.8, 0.4, 0.6] },
73+
error_x: { type: 'constant', value: 0.2 },
74+
error_y: { type: 'constant', value: 0.3 },
75+
selectedpoints: [2]
76+
}]);
77+
checkPointOpacities([0.16, 0.6]);
78+
79+
await Plotly.restyle(gd, { selectedpoints: null });
80+
checkPointOpacities([0.8, 0.6]);
81+
});
82+
83+
for (const dragmode of ['select', 'lasso']) {
84+
it(`updates error bars after ${dragmode} and deselection`, async () => {
85+
await Plotly.newPlot(gd, [{
86+
x: [1, 2, 3],
87+
y: [1, 2, 3],
88+
mode: 'markers',
89+
selected: { marker: { opacity: 0.8 } },
90+
unselected: { marker: { opacity: 0.3 } },
91+
error_x: { type: 'constant', value: 0.2 },
92+
error_y: { type: 'constant', value: 0.3 }
93+
}], {
94+
width: 400,
95+
height: 400,
96+
margin: { l: 50, r: 50, t: 50, b: 50 },
97+
xaxis: { range: [0, 4] },
98+
yaxis: { range: [0, 4] },
99+
dragmode
100+
});
101+
await drag({
102+
path: dragmode === 'select'
103+
? [[100, 300], [150, 250]]
104+
: [[100, 300], [150, 300], [150, 250], [100, 250], [100, 300]]
105+
});
106+
expect(gd.data[0].selectedpoints).toEqual([0]);
107+
checkPointOpacities([0.8, 0.3, 0.3]);
108+
109+
await doubleClick(200, 200);
110+
checkPointOpacities([1, 1, 1]);
111+
});
112+
}
113+
114+
it('keeps line-only and bar error opacity independent of scatter marker styling', async () => {
115+
await Plotly.newPlot(gd, [
116+
{
117+
x: [1, 2], y: [1, 2], mode: 'lines', marker: { opacity: 0.2 },
118+
error_y: { type: 'constant', value: 0.2 }
119+
},
120+
{
121+
x: [1, 2], y: [1, 2], type: 'bar', marker: { opacity: 0.6 },
122+
error_y: { type: 'constant', value: 0.2 }, selectedpoints: [0]
123+
}
124+
]);
125+
for (const node of gd.querySelectorAll('.yerror')) {
126+
expect(Number(getComputedStyle(node).opacity)).toBe(1);
127+
}
128+
});
129+
32130
it('should autorange to the visible bars and remove invisible bars', function(done) {
33131
function check(xrange, yrange, xCount, yCount) {
34132
var xa = gd._fullLayout.xaxis;

0 commit comments

Comments
 (0)