-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Click anywhere
feature
#5443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Click anywhere
feature
#5443
Changes from all commits
6a07bc9
0ab1e75
603cfa1
279a0d1
fb506ba
4d221db
461b695
0956827
a9c0c7b
3506f04
babdf89
9b5c237
a406275
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,12 +14,30 @@ module.exports = function click(gd, evt, subplot) { | |
hover(gd, evt, subplot, true); | ||
} | ||
|
||
function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, event: evt}); } | ||
function emitClick(data) { gd.emit('plotly_click', {points: data, event: evt}); } | ||
|
||
if(gd._hoverdata && evt && evt.target) { | ||
if(annotationsDone && annotationsDone.then) { | ||
annotationsDone.then(emitClick); | ||
} else emitClick(); | ||
var clickmode = gd._fullLayout.clickmode; | ||
var data; | ||
if(evt && evt.target) { | ||
if(gd._hoverdata) { | ||
data = gd._hoverdata; | ||
} else if(clickmode.indexOf('anywhere') > -1) { | ||
if(gd._fullLayout.geo) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic means that if the plot has a geo subplot, we're assuming the click was on that, even if the plot has other subplots too. But we can do a lot better than that, and there are more subplot types than just geo and 2D cartesian. And fortunately we already have a mechanism to detect this: the third arg to 2D cartesian, ternary, and polar subplots report this correctly (eg Pie, sankey, and funnelarea also all reach this point but don't give a subplot. We could have them give the trace number I guess, but is there anything useful to report for them? Just the raw coordinates within the plot? 3D, parcoords, and parcats don't even get here, I'm happy to ignore those for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that something that you intend to do in a second round or within this PR. If it is something I should fix, then I'd like a short example ideally for each plot type as I am not familiar with most of the ones you listed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please see https://rreusser.github.io/plotly-mock-viewer/#plot_types as well as https://rreusser.github.io/plotly-mock-viewer/#polar_subplots which render https://github.com/plotly/plotly.js/blob/master/test/image/mocks/plot_types.json and |
||
var lat = gd._fullLayout.geo._subplot.xaxis.p2c(); | ||
|
||
var lon = gd._fullLayout.geo._subplot.yaxis.p2c(); | ||
data = [{lat: lat, lon: lon}]; | ||
} else { | ||
var bb = evt.target.getBoundingClientRect(); | ||
var x = gd._fullLayout.xaxis.p2d(evt.clientX - bb.left); | ||
var y = gd._fullLayout.yaxis.p2d(evt.clientY - bb.top); | ||
data = [{x: x, y: y}]; | ||
} | ||
} | ||
if(data) { | ||
if(annotationsDone && annotationsDone.then) { | ||
annotationsDone.then(function() { emitClick(data); }); | ||
} else emitClick(data); | ||
} | ||
|
||
// why do we get a double event without this??? | ||
if(evt.stopImmediatePropagation) evt.stopImmediatePropagation(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
var Plotly = require('@lib/index'); | ||
var Lib = require('@src/lib'); | ||
var click = require('../assets/click'); | ||
|
||
var createGraphDiv = require('../assets/create_graph_div'); | ||
var destroyGraphDiv = require('../assets/destroy_graph_div'); | ||
var DBLCLICKDELAY = require('@src/plot_api/plot_config').dfltConfig.doubleClickDelay; | ||
|
||
var clickEvent; | ||
var clickedPromise; | ||
|
||
function resetEvents(gd) { | ||
clickEvent = null; | ||
|
||
gd.removeAllListeners(); | ||
|
||
clickedPromise = new Promise(function(resolve) { | ||
gd.on('plotly_click', function(data) { | ||
clickEvent = data.points[0]; | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
|
||
describe('Click-to-select', function() { | ||
var mock14PtsScatter = { | ||
'in-margin': { x: 28, y: 28 }, | ||
'point-0': { x: 92, y: 102 }, | ||
'between-point-0-and-1': { x: 117, y: 110 }, | ||
'point-11': { x: 339, y: 214 }, | ||
}; | ||
var expectedEventsScatter = { | ||
'in-margin': false, | ||
'point-0': { | ||
curveNumber: 0, | ||
pointIndex: 0, | ||
pointNumber: 0, | ||
x: 0.002, | ||
y: 16.25 | ||
}, | ||
'between-point-0-and-1': { x: 0.002990379231567056, y: 14.169142943944111 }, | ||
'point-11': { | ||
curveNumber: 0, | ||
pointIndex: 11, | ||
pointNumber: 11, | ||
x: 0.125, | ||
y: 2.125 | ||
}, | ||
}; | ||
|
||
var mockPtsGeoscatter = { | ||
'start': {lat: 40.7127, lon: -74.0059}, | ||
'end': {lat: 51.5072, lon: 0.1275}, | ||
}; | ||
var mockPtsGeoscatterClick = { | ||
'in-margin': { x: 28, y: 28 }, | ||
'start': {x: 239, y: 174}, | ||
'end': {x: 426, y: 157}, | ||
'iceland': {x: 322, y: 150}, | ||
}; | ||
var expectedEventsGeoscatter = { | ||
'in-margin': false, | ||
'start': { | ||
curveNumber: 0, | ||
pointIndex: 0, | ||
pointNumber: 0, | ||
lat: 40.7127, | ||
lon: -74.0059, | ||
}, | ||
'end': { | ||
curveNumber: 0, | ||
pointIndex: 1, | ||
pointNumber: 1, | ||
lat: 51.5072, | ||
lon: 51.5072, | ||
}, | ||
'iceland': {lat: -18.666562962962963, lon: 56.66635185185185}, | ||
}; | ||
|
||
var gd; | ||
|
||
beforeEach(function() { | ||
gd = createGraphDiv(); | ||
}); | ||
|
||
afterEach(function() { | ||
resetEvents(gd); | ||
destroyGraphDiv(); | ||
}); | ||
|
||
function plotMock14Anywhere(layoutOpts) { | ||
var mock = require('@mocks/14.json'); | ||
var defaultLayoutOpts = { | ||
layout: { | ||
clickmode: 'event+anywhere', | ||
hoverdistance: 1 | ||
} | ||
}; | ||
var mockCopy = Lib.extendDeep( | ||
{}, | ||
mock, | ||
defaultLayoutOpts, | ||
{ layout: layoutOpts }); | ||
|
||
return Plotly.newPlot(gd, mockCopy.data, mockCopy.layout); | ||
} | ||
|
||
function plotMock14AnywhereSelect(layoutOpts) { | ||
var mock = require('@mocks/14.json'); | ||
var defaultLayoutOpts = { | ||
layout: { | ||
clickmode: 'select+event+anywhere', | ||
hoverdistance: 1 | ||
} | ||
}; | ||
var mockCopy = Lib.extendDeep( | ||
{}, | ||
mock, | ||
defaultLayoutOpts, | ||
{ layout: layoutOpts }); | ||
|
||
return Plotly.newPlot(gd, mockCopy.data, mockCopy.layout); | ||
} | ||
|
||
function plotGeoscatterAnywhere() { | ||
var layout = { | ||
clickmode: 'event+anywhere', | ||
hoverdistance: 1 | ||
}; | ||
var data = [{ | ||
type: 'scattergeo', | ||
lat: [ mockPtsGeoscatter.start.lat, mockPtsGeoscatter.end.lat ], | ||
lon: [ mockPtsGeoscatter.start.lon, mockPtsGeoscatter.end.lat ], | ||
mode: 'lines', | ||
line: { | ||
width: 2, | ||
color: 'blue' | ||
} | ||
}]; | ||
return Plotly.newPlot(gd, data, layout); | ||
} | ||
|
||
function isSubset(superObj, subObj) { | ||
return superObj === subObj || | ||
typeof superObj === 'object' && | ||
typeof subObj === 'object' && ( | ||
subObj.valueOf() === superObj.valueOf() || | ||
Object.keys(subObj).every(function(k) { return isSubset(superObj[k], subObj[k]); }) | ||
); | ||
} | ||
|
||
/** | ||
* Executes a click and before resets event handlers. | ||
* Returns the `clickedPromise` for convenience. | ||
*/ | ||
function _click(x, y, clickOpts) { | ||
resetEvents(gd); | ||
setTimeout(function() { | ||
click(x, y, clickOpts); | ||
}, DBLCLICKDELAY * 1.03); | ||
return clickedPromise; | ||
} | ||
|
||
function clickAndTestPoint(mockPts, expectedEvents, pointKey, clickOpts) { | ||
var x = mockPts[pointKey].x; | ||
var y = mockPts[pointKey].y; | ||
var expectedEvent = expectedEvents[pointKey]; | ||
var result = _click(x, y, clickOpts); | ||
if(expectedEvent) { | ||
result.then(function() { | ||
expect(isSubset(clickEvent, expectedEvent)).toBe(true); | ||
}); | ||
} else { | ||
expect(clickEvent).toBe(null); | ||
result = null; | ||
} | ||
return result; | ||
} | ||
|
||
it('selects point and/or coordinate when clicked - scatter - event+anywhere', function(done) { | ||
plotMock14Anywhere() | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'in-margin'); }) | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'point-0'); }) | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'between-point-0-and-1'); }) | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'point-11'); }) | ||
.then(done, done.fail); | ||
}); | ||
|
||
it('selects point and/or coordinate when clicked - scatter - select+event+anywhere', function(done) { | ||
plotMock14AnywhereSelect() | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'in-margin'); }) | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'point-0'); }) | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'between-point-0-and-1'); }) | ||
.then(function() { return clickAndTestPoint(mock14PtsScatter, expectedEventsScatter, 'point-11'); }) | ||
.then(done, done.fail); | ||
}); | ||
|
||
it('selects point and/or coordinate when clicked - geoscatter - event+anywhere', function(done) { | ||
plotGeoscatterAnywhere() | ||
.then(function() { return clickAndTestPoint(mockPtsGeoscatterClick, expectedEventsGeoscatter, 'in-margin'); }) | ||
.then(function() { return clickAndTestPoint(mockPtsGeoscatterClick, expectedEventsGeoscatter, 'start'); }) | ||
.then(function() { return clickAndTestPoint(mockPtsGeoscatterClick, expectedEventsGeoscatter, 'end'); }) | ||
.then(function() { return clickAndTestPoint(mockPtsGeoscatterClick, expectedEventsGeoscatter, 'iceland'); }) | ||
.then(done, done.fail); | ||
}); | ||
}); |