Skip to content

Commit 9112759

Browse files
authored
Merge pull request #4810 from plotly/fix4808-shape-pointer-events
Apply and ensure stroke event only to activate editable shapes
2 parents 245eeec + bb3f858 commit 9112759

File tree

2 files changed

+247
-13
lines changed

2 files changed

+247
-13
lines changed

src/components/shapes/draw.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ function drawOne(gd, index) {
119119
var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)';
120120
var lineWidth = options.line.width;
121121
var lineDash = options.line.dash;
122+
if(!lineWidth && options.editable === true) {
123+
// ensure invisible border to activate the shape
124+
lineWidth = 5;
125+
lineDash = 'solid';
126+
}
122127

123128
var isOpen = d[d.length - 1] !== 'Z';
124129

@@ -163,15 +168,11 @@ function drawOne(gd, index) {
163168
} else {
164169
if(gd._context.edits.shapePosition) {
165170
setupDragElement(gd, path, options, index, shapeLayer, editHelpers);
171+
} else if(options.editable === true) {
172+
path.style('pointer-events',
173+
(isOpen || Color.opacity(fillColor) * opacity <= 0.5) ? 'stroke' : 'all'
174+
);
166175
}
167-
168-
path.style('pointer-events',
169-
!couldHaveActiveShape(gd) || (
170-
lineWidth < 2 || ( // not has a remarkable border
171-
!isOpen && Color.opacity(fillColor) * opacity > 0.5 // not too transparent
172-
)
173-
) ? 'all' : 'stroke'
174-
);
175176
}
176177

177178
path.node().addEventListener('click', function() { return activateShape(gd, path); });

test/jasmine/tests/draw_newshape_test.js

Lines changed: 238 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,7 @@ describe('Activate and edit editable shapes', function() {
10891089
afterEach(destroyGraphDiv);
10901090

10911091
['mouse'].forEach(function(device) {
1092-
it('@flaky reactangle using' + device, function(done) {
1092+
it('@flaky reactangle using ' + device, function(done) {
10931093
var i = 0; // shape index
10941094

10951095
Plotly.newPlot(gd, {
@@ -1224,7 +1224,7 @@ describe('Activate and edit editable shapes', function() {
12241224
.then(done);
12251225
});
12261226

1227-
it('@flaky circle using' + device, function(done) {
1227+
it('@flaky circle using ' + device, function(done) {
12281228
var i = 1; // shape index
12291229

12301230
Plotly.newPlot(gd, {
@@ -1281,7 +1281,7 @@ describe('Activate and edit editable shapes', function() {
12811281
.then(done);
12821282
});
12831283

1284-
it('@flaky closed-path using' + device, function(done) {
1284+
it('@flaky closed-path using ' + device, function(done) {
12851285
var i = 2; // shape index
12861286

12871287
Plotly.newPlot(gd, {
@@ -1326,7 +1326,7 @@ describe('Activate and edit editable shapes', function() {
13261326
.then(done);
13271327
});
13281328

1329-
it('@flaky bezier curves using' + device, function(done) {
1329+
it('@flaky bezier curves using ' + device, function(done) {
13301330
var i = 5; // shape index
13311331

13321332
Plotly.newPlot(gd, {
@@ -1371,7 +1371,7 @@ describe('Activate and edit editable shapes', function() {
13711371
.then(done);
13721372
});
13731373

1374-
it('@flaky multi-cell path using' + device, function(done) {
1374+
it('@flaky multi-cell path using ' + device, function(done) {
13751375
var i = 6; // shape index
13761376

13771377
Plotly.newPlot(gd, {
@@ -1426,3 +1426,236 @@ describe('Activate and edit editable shapes', function() {
14261426
});
14271427
});
14281428
});
1429+
1430+
1431+
describe('Activate and edit editable shapes', function() {
1432+
var gd;
1433+
1434+
beforeEach(function() {
1435+
gd = createGraphDiv();
1436+
});
1437+
1438+
afterEach(destroyGraphDiv);
1439+
1440+
it('should not set pointer-events for non-editable shapes i.e. to allow pan/zoom/hover work inside shapes', function(done) {
1441+
Plotly.newPlot(gd, {
1442+
data: [{
1443+
mode: 'markers',
1444+
x: [1, 3, 3],
1445+
y: [2, 1, 3]
1446+
}],
1447+
layout: {
1448+
shapes: [
1449+
{
1450+
editable: false,
1451+
x0: 1.5,
1452+
x1: 2,
1453+
y0: 1.5,
1454+
y1: 2,
1455+
opacity: 0.5,
1456+
fillcolor: 'blue',
1457+
line: {
1458+
width: 0,
1459+
dash: 'dash',
1460+
color: 'black'
1461+
}
1462+
}, {
1463+
editable: false,
1464+
x0: 2,
1465+
x1: 2.5,
1466+
y0: 1.5,
1467+
y1: 2,
1468+
opacity: 0.7,
1469+
fillcolor: 'rgba(255,0,0,0.7)', // N.B. 0.7 * 0.7 = 0.49 <= 0.5 is quite transparent
1470+
line: {
1471+
width: 0,
1472+
dash: 'dash',
1473+
color: 'black'
1474+
}
1475+
}, {
1476+
editable: false,
1477+
x0: 1.5,
1478+
x1: 2,
1479+
y0: 2,
1480+
y1: 2.5,
1481+
fillcolor: 'red',
1482+
line: {
1483+
width: 3,
1484+
dash: 'dash',
1485+
color: 'green'
1486+
}
1487+
}, {
1488+
editable: false,
1489+
path: 'M2,2H2.5,V2.5', // open path
1490+
fillcolor: 'rgba(0,0,0,0)',
1491+
line: {
1492+
width: 3,
1493+
dash: 'dash',
1494+
color: 'green'
1495+
}
1496+
}
1497+
]
1498+
}
1499+
})
1500+
1501+
.then(function() {
1502+
var el = Plotly.d3.selectAll('.shapelayer path')[0][0];
1503+
expect(el.style['pointer-events']).toBe('');
1504+
expect(el.style.stroke).toBe('rgb(0, 0, 0)'); // no color
1505+
expect(el.style['stroke-opacity']).toBe('0'); // invisible
1506+
expect(el.style['stroke-width']).toBe('0px'); // no pixel
1507+
1508+
el = Plotly.d3.selectAll('.shapelayer path')[0][1];
1509+
expect(el.style['pointer-events']).toBe('');
1510+
expect(el.style.stroke).toBe('rgb(0, 0, 0)'); // no color
1511+
expect(el.style['stroke-opacity']).toBe('0'); // invisible
1512+
expect(el.style['stroke-width']).toBe('0px'); // no pixel
1513+
1514+
el = Plotly.d3.selectAll('.shapelayer path')[0][2];
1515+
expect(el.style['pointer-events']).toBe('');
1516+
expect(el.style.stroke).toBe('rgb(0, 128, 0)'); // custom color
1517+
expect(el.style['stroke-opacity']).toBe('1'); // visible
1518+
expect(el.style['stroke-width']).toBe('3px'); // custom pixel
1519+
1520+
el = Plotly.d3.selectAll('.shapelayer path')[0][3];
1521+
expect(el.style['pointer-events']).toBe('');
1522+
expect(el.style.stroke).toBe('rgb(0, 128, 0)'); // custom color
1523+
expect(el.style['stroke-opacity']).toBe('1'); // visible
1524+
expect(el.style['stroke-width']).toBe('3px'); // custom pixel
1525+
})
1526+
1527+
.catch(failTest)
1528+
.then(done);
1529+
});
1530+
1531+
it('should provide invisible border & set pointer-events (depending on fill transparency) for editable shapes i.e. to allow shape activation', function(done) {
1532+
Plotly.newPlot(gd, {
1533+
data: [{
1534+
mode: 'markers',
1535+
x: [1, 3, 3],
1536+
y: [2, 1, 3]
1537+
}],
1538+
layout: {
1539+
shapes: [
1540+
{
1541+
editable: true,
1542+
x0: 1.5,
1543+
x1: 2,
1544+
y0: 1.5,
1545+
y1: 2,
1546+
opacity: 0.5,
1547+
fillcolor: 'blue',
1548+
line: {
1549+
width: 0,
1550+
dash: 'dash',
1551+
color: 'black'
1552+
}
1553+
}, {
1554+
editable: true,
1555+
x0: 2,
1556+
x1: 2.5,
1557+
y0: 1.5,
1558+
y1: 2,
1559+
opacity: 0.7,
1560+
fillcolor: 'rgba(255,0,0,0.7)', // N.B. 0.7 * 0.7 = 0.49 <= 0.5 is quite transparent
1561+
line: {
1562+
width: 0,
1563+
dash: 'dash',
1564+
color: 'black'
1565+
}
1566+
}, {
1567+
editable: true,
1568+
x0: 1.5,
1569+
x1: 2,
1570+
y0: 2,
1571+
y1: 2.5,
1572+
fillcolor: 'red',
1573+
line: {
1574+
width: 3,
1575+
dash: 'dash',
1576+
color: 'green'
1577+
}
1578+
}, {
1579+
editable: true,
1580+
path: 'M2,2H2.5,V2.5', // open path
1581+
fillcolor: 'rgba(0,0,0,0)',
1582+
line: {
1583+
width: 3,
1584+
dash: 'dash',
1585+
color: 'green'
1586+
}
1587+
}
1588+
]
1589+
}
1590+
})
1591+
1592+
.then(function() {
1593+
var el = Plotly.d3.selectAll('.shapelayer path')[0][0];
1594+
expect(el.style['pointer-events']).toBe('stroke');
1595+
expect(el.style.stroke).toBe('rgb(0, 0, 0)'); // no color
1596+
expect(el.style['stroke-opacity']).toBe('0'); // invisible
1597+
expect(el.style['stroke-width']).toBe('5px'); // some pixels to activate shape
1598+
1599+
el = Plotly.d3.selectAll('.shapelayer path')[0][1];
1600+
expect(el.style['pointer-events']).toBe('stroke');
1601+
expect(el.style.stroke).toBe('rgb(0, 0, 0)'); // no color
1602+
expect(el.style['stroke-opacity']).toBe('0'); // invisible
1603+
expect(el.style['stroke-width']).toBe('5px'); // some pixels to activate shape
1604+
1605+
el = Plotly.d3.selectAll('.shapelayer path')[0][2];
1606+
expect(el.style['pointer-events']).toBe('all');
1607+
expect(el.style.stroke).toBe('rgb(0, 128, 0)'); // custom color
1608+
expect(el.style['stroke-opacity']).toBe('1'); // visible
1609+
expect(el.style['stroke-width']).toBe('3px'); // custom pixel
1610+
1611+
el = Plotly.d3.selectAll('.shapelayer path')[0][3];
1612+
expect(el.style['pointer-events']).toBe('stroke');
1613+
expect(el.style.stroke).toBe('rgb(0, 128, 0)'); // custom color
1614+
expect(el.style['stroke-opacity']).toBe('1'); // visible
1615+
expect(el.style['stroke-width']).toBe('3px'); // custom pixel
1616+
})
1617+
1618+
.catch(failTest)
1619+
.then(done);
1620+
});
1621+
1622+
it('should not provide invisible border & set pointer-events to "stroke" for shapes made editable via config', function(done) {
1623+
Plotly.newPlot(gd, {
1624+
data: [{
1625+
mode: 'markers',
1626+
x: [1, 3, 3],
1627+
y: [2, 1, 3]
1628+
}],
1629+
layout: {
1630+
shapes: [
1631+
{
1632+
x0: 1.5,
1633+
x1: 2.5,
1634+
y0: 1.5,
1635+
y1: 2.5,
1636+
fillcolor: 'blue',
1637+
line: {
1638+
width: 0,
1639+
dash: 'dash',
1640+
color: 'black'
1641+
}
1642+
}
1643+
]
1644+
},
1645+
config: {
1646+
editable: true
1647+
}
1648+
})
1649+
1650+
.then(function() {
1651+
var el = Plotly.d3.selectAll('.shapelayer path')[0][0];
1652+
expect(el.style['pointer-events']).toBe('all');
1653+
expect(el.style.stroke).toBe('rgb(0, 0, 0)'); // no color
1654+
expect(el.style['stroke-opacity']).toBe('0'); // invisible
1655+
expect(el.style['stroke-width']).toBe('0px'); // no extra pixels
1656+
})
1657+
1658+
.catch(failTest)
1659+
.then(done);
1660+
});
1661+
});

0 commit comments

Comments
 (0)