-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Annotation additions: standoff, anchor with arrow, clicktoshow #1265
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
Changes from 1 commit
8518ee9
45bc0e3
d69ed98
8f0038d
83482fb
9efa9b7
9cf04b6
2533ec5
578ea87
d77ff15
13947ef
582adfd
b683903
ccbffd0
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 |
---|---|---|
|
@@ -310,6 +310,42 @@ module.exports = { | |
'corresponds to the closest side.' | ||
].join(' ') | ||
}, | ||
clicktoshow: { | ||
valType: 'enumerated', | ||
values: [false, 'onoff', 'onout'], | ||
dflt: false, | ||
role: 'style', | ||
description: [ | ||
'Makes this annotation respond to clicks on the plot.', | ||
'If you click a data point that exactly matches the `x` and `y`', | ||
'values of this annotation, and it is hidden (visible: false),', | ||
'it will appear. In *onoff* mode, you must click the same point', | ||
'again to make it disappear, so if you click multiple points,', | ||
'you can show multiple annotations. In *onout* mode, a click', | ||
'anywhere else in the plot (on another data point or not) will', | ||
'hide this annotation.', | ||
'If you need to show/hide this annotation in response to different', | ||
'`x` or `y` values, you can set `xclick` and/or `yclick`. This is', | ||
'useful for example to label the side of a bar. To label markers', | ||
'though, `standoff` is preferred over `xclick` and `yclick`.' | ||
].join(' ') | ||
}, | ||
xclick: { | ||
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.
That said, I'm a little afraid about how these attribute will scale when we add data-referenced to other plot types. I'll elaborate on those concerns in more details in #751 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. |
||
valType: 'any', | ||
role: 'info', | ||
description: [ | ||
'Toggle this annotation when clicking a data point whose `x` value', | ||
'is `xclick` rather than the annotation\'s `x` value.' | ||
].join(' ') | ||
}, | ||
yclick: { | ||
valType: 'any', | ||
role: 'info', | ||
description: [ | ||
'Toggle this annotation when clicking a data point whose `y` value', | ||
'is `yclick` rather than the annotation\'s `y` value.' | ||
].join(' ') | ||
}, | ||
|
||
_deprecated: { | ||
ref: { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/** | ||
* Copyright 2012-2016, Plotly, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
|
||
'use strict'; | ||
|
||
var Plotly = require('../../plotly'); | ||
|
||
|
||
module.exports = { | ||
hasClickToShow: hasClickToShow, | ||
onClick: onClick | ||
}; | ||
|
||
/* | ||
* hasClickToShow: does the given hoverData have ANY annotations which will | ||
* turn ON if we click here? (used by hover events to set cursor) | ||
* | ||
* gd: graphDiv | ||
* hoverData: a hoverData array, as included with the *plotly_hover* or | ||
* *plotly_click* events in the `points` attribute | ||
* | ||
* returns: boolean | ||
*/ | ||
function hasClickToShow(gd, hoverData) { | ||
var sets = getToggleSets(gd, hoverData); | ||
return sets.on.length > 0 || sets.explicitOff.length > 0; | ||
} | ||
|
||
/* | ||
* onClick: perform the toggling (via Plotly.update) implied by clicking | ||
* at this hoverData | ||
* | ||
* gd: graphDiv | ||
* hoverData: a hoverData array, as included with the *plotly_hover* or | ||
* *plotly_click* events in the `points` attribute | ||
* | ||
* returns: Promise that the update is complete | ||
*/ | ||
function onClick(gd, hoverData) { | ||
var toggleSets = getToggleSets(gd, hoverData), | ||
onSet = toggleSets.on, | ||
offSet = toggleSets.off.concat(toggleSets.explicitOff), | ||
update = {}, | ||
i; | ||
|
||
if(!(onSet.length || offSet.length)) return; | ||
|
||
for(i = 0; i < onSet.length; i++) { | ||
update['annotations[' + onSet[i] + '].visible'] = true; | ||
} | ||
|
||
for(i = 0; i < offSet.length; i++) { | ||
update['annotations[' + offSet[i] + '].visible'] = false; | ||
} | ||
|
||
return Plotly.update(gd, {}, update); | ||
} | ||
|
||
/* | ||
* getToggleSets: find the annotations which will turn on or off at this | ||
* hoverData | ||
* | ||
* gd: graphDiv | ||
* hoverData: a hoverData array, as included with the *plotly_hover* or | ||
* *plotly_click* events in the `points` attribute | ||
* | ||
* returns: { | ||
* on: Array (indices of annotations to turn on), | ||
* off: Array (indices to turn off because you're not hovering on them), | ||
* explicitOff: Array (indices to turn off because you *are* hovering on them) | ||
* } | ||
*/ | ||
function getToggleSets(gd, hoverData) { | ||
var annotations = gd._fullLayout.annotations, | ||
onSet = [], | ||
offSet = [], | ||
explicitOffSet = [], | ||
hoverLen = (hoverData || []).length; | ||
|
||
var i, j, anni, showMode, pointj, toggleType; | ||
|
||
for(i = 0; i < annotations.length; i++) { | ||
anni = annotations[i]; | ||
showMode = anni.clicktoshow; | ||
if(showMode) { | ||
for(j = 0; j < hoverLen; j++) { | ||
pointj = hoverData[j]; | ||
if(pointj.x === anni._xclick && pointj.y === anni._yclick && | ||
pointj.xaxis._id === anni.xref && | ||
pointj.yaxis._id === anni.yref) { | ||
// match! toggle this annotation | ||
// regardless of its clicktoshow mode | ||
// but if it's onout mode, off is implicit | ||
if(anni.visible) { | ||
if(showMode === 'onout') toggleType = offSet; | ||
else toggleType = explicitOffSet; | ||
} | ||
else { | ||
toggleType = onSet; | ||
} | ||
toggleType.push(i); | ||
break; | ||
} | ||
} | ||
|
||
if(j === hoverLen) { | ||
// no match - only turn this annotation OFF, and only if | ||
// showmode is 'onout' | ||
if(anni.visible && showMode === 'onout') offSet.push(i); | ||
} | ||
} | ||
} | ||
|
||
return {on: onSet, off: offSet, explicitOff: explicitOffSet}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/** | ||
* Copyright 2012-2016, Plotly, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
|
||
'use strict'; | ||
|
||
var setCursor = require('./setCursor'); | ||
|
||
var STASHATTR = 'data-savedcursor'; | ||
|
||
/* | ||
* works with our CSS cursor classes (see css/_cursor.scss) | ||
* to override a previous cursor set on d3 single-element selections, | ||
* by moving the name of the original cursor to the data-savedcursor attr. | ||
* omit cursor to revert to the previously set value. | ||
*/ | ||
module.exports = function overrideCursor(el3, csr) { | ||
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. It would be nice to a few test cases for 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. haha good call - 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. Thanks 🎉 |
||
var savedCursor = el3.attr(STASHATTR); | ||
if(csr) { | ||
if(savedCursor) { | ||
setCursor(el3, csr); | ||
} | ||
else { | ||
var classes = (el3.attr('class') || '').split(' '); | ||
for(var i = 0; i < classes.length; i++) { | ||
var cls = classes[i]; | ||
if(cls.indexOf('cursor-') === 0) { | ||
el3.attr(STASHATTR, cls.substr(7)) | ||
.classed(cls, false); | ||
} | ||
} | ||
} | ||
} | ||
else if(savedCursor) { | ||
el3.attr(STASHATTR, null); | ||
setCursor(el3, savedCursor); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,8 @@ var svgTextUtils = require('../../lib/svg_text_utils'); | |
var Color = require('../../components/color'); | ||
var Drawing = require('../../components/drawing'); | ||
var dragElement = require('../../components/dragelement'); | ||
var overrideCursor = require('../../lib/override_cursor'); | ||
var Registry = require('../../registry'); | ||
|
||
var Axes = require('./axes'); | ||
var constants = require('./constants'); | ||
|
@@ -596,6 +598,13 @@ function hover(gd, evt, subplot) { | |
|
||
gd._hoverdata = newhoverdata; | ||
|
||
// TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true | ||
// we should improve the "fx" API so other plots can use it without these hack. | ||
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. FYI @etpinard |
||
if(evt.target && evt.target.tagName) { | ||
var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata); | ||
overrideCursor(d3.select(evt.target), hasClickToShow ? 'pointer' : ''); | ||
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. 👍 for |
||
} | ||
|
||
if(!hoverChanged(gd, evt, oldhoverdata)) return; | ||
|
||
if(oldhoverdata) { | ||
|
@@ -1323,8 +1332,16 @@ function hoverChanged(gd, evt, oldhoverdata) { | |
|
||
// on click | ||
fx.click = function(gd, evt) { | ||
var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata); | ||
|
||
function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata}); } | ||
|
||
if(gd._hoverdata && evt && evt.target) { | ||
gd.emit('plotly_click', {points: gd._hoverdata}); | ||
if(annotationsDone && annotationsDone.then) { | ||
annotationsDone.then(emitClick); | ||
} | ||
else emitClick(); | ||
|
||
// why do we get a double event without this??? | ||
if(evt.stopImmediatePropagation) evt.stopImmediatePropagation(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for
clicktoshow
Thanks for that very clear description.