-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Add "arrowanchor" property to annotations #2164
Conversation
Ha, nice. That functionality was halfway built years ago but never finished. A couple of comments:
|
Ok, no problem to add it. Let's agree about new name for
Yes, we do. See my screenshot above - "start+end" example |
I'd vote for:
That probably means we'll need add a few more attributes to complement |
Agreed - standoff is specific to the item you're pointing at, so a different item will in general want a different standoff, and inheriting this would more often hurt than help. @apalchys check out some of the other |
I like the sounds of that |
… 'arrowanchor' to 'arrowside'
I made the following changes:
Here is the updated demo: https://codepen.io/plotly-demo/pen/aVBZBq |
var arrowsize = coerce('arrowsize'); | ||
|
||
if(arrowside.indexOf('start') !== -1) { | ||
coerce('startarrowhead', arrowhead); |
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.
coerce('startarrowsize', arrowsize);
and
coerce('startstandoff');
should be inside this block as well - as they have no effect w/o start arrow.
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.
There was possibility to use standoff
with arrowhead: 0
(no arrow) before my changes and I decided to keep this functionality for ‘startstandoff’ -> when we have arrowside: none
, we still can set both properties of standoff and see the result.
Please just confirm that it is not expected behaviour and I will apply suggested changes.
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.
Ha good point. I think you have it right here.
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.
Right, so (start)standoff
always needs to be coerced - but doesn't startarrowsize
go in here?
Also if we have no 'end'
we don't need arrowhead
and arrowsize
do we?
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.
Seems like @apalchys posted and deleted a comment, so maybe he noticed what I'm about to say... (incidentally, if that's the case, let me encourage you to edit comments, using strikethrough if necessary, rather than deleting them, as it makes the conversation flow clearer)
(start)arrowhead
and (start)arrowsize
are not needed logically when we're not showing a head on that end, so that's a warning sign that providing them could lead to bugs. Indeed, I think there is a subtle one: if you choose an arrowhead that has a backoff
(including the default arrowhead: 1
! backoff
is how much you need to move the end of the line backward so the arrowhead can point to the right place without the line jutting through it) but you don't show that arrowhead, you'll get an excessive standoff.
options.arrowhead || 0
ensures an un-coerced arrowhead
is treated as no arrow, and we may need options.arrowsize || 0
(this value probably doesn't matter as long as it's numeric) to make the math well-defined.
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.
sorry, I recognized that my answer is wrong right after I submit it. I will commit an update soon.
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.
Please check my commit - 191d5d2
Is it what you expected?
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.
Perfect, thanks! Looks like this tweaked one more test image (gl2d_annotations
) but once that's fixed I think we're ready to go 🎉
arrowwidth: annAtts.arrowwidth, | ||
standoff: annAtts.standoff, | ||
startstandoff: annAtts.startstandoff, |
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.
Does this really work in 3D or are these attributes there just to make the supply-default function not error out?
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.
Yes, it works fine
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.
Great. Would you mind adding an annotations item showing off the new attributes to gl3d_annotations.json
?
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.
Done
test/image/mocks/annotations.json
Outdated
@@ -45,6 +45,8 @@ | |||
"arrowcolor":"rgb(166, 28, 0)","borderpad":3,"textangle":50,"x":5,"y":1 | |||
}, | |||
{"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":5,"y":3,"ax":4,"ay":5}, | |||
{"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":3,"startarrowhead":6,"arrowsize":1.5,"startarrowsize":3,"standoff": 7,"startstandoff": 15,"arrowside":"start+end","axref":"x","ayref":"y","x":2,"y":4,"ax":3,"ay":4}, | |||
{"text":"","showarrow":true,"borderwidth":1.2,"startarrowhead":2,"startstandoff": 5,"arrowside":"start","axref":"x","ayref":"y","x":1,"y":5,"ax":2,"ay":4}, | |||
{"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":6,"y":2,"ax":3,"ay":3}, |
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.
Nice job here! Maybe we could add an arrowside: 'none'
item?
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.
Thanks. Good idea, I will add it.
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.
Since it depends on your answer here #2164 (comment), I will add it when you confirm expected behaviour.
I'll let @alexcjohnson review your I just noticed that the new headSize = 3 * Math.max(ann.arrowsize * ann.arrowwidth, ann.startarrowsize * ann.startarrowwidth) || 0; |
@@ -51,6 +58,13 @@ module.exports = function drawArrowHead(el3, ends, options) { | |||
|
|||
startRot = Math.atan2(dy, dx); | |||
endRot = startRot + Math.PI; | |||
if(backOff && startBackOff) { | |||
if(backOff * backOff + startBackOff * startBackOff > dx * dx + dy * dy) { |
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.
I don't think this condition is quite right... should be the square of the sum, not the sum of squares. Probably we should just compare lengths, not lengths squared, that was an optimization on my part but it's obviously not very intuitive or extensible!
But also I think we can simplify and only do this whole check once, instead of once for both, then again for backOff
, and a third time for startBackOff
. Looks like no matter what both values will be non-negative numbers, so we should be able to do something like:
if(backOff || startBackOff) {
if(backOff + startBackOff > Math.sqrt(dx * dx + dy * dy) {
hideline();
return;
}
}
@@ -79,41 +103,35 @@ module.exports = function drawArrowHead(el3, ends, options) { | |||
// combine the two | |||
dashArray = ''; | |||
|
|||
if(pathlen < backOff) { | |||
if(pathlen < backOff || pathlen < startBackOff || pathlen < backOff + startBackOff) { |
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.
similarly, seems like only the last condition matters here?
var doStart = ends.indexOf('start') >= 0; | ||
var doEnd = ends.indexOf('end') >= 0; | ||
var doNone = ends.indexOf('none') >= 0; |
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.
'none'
is an extras
value, so it can only be given alone - ie ends === 'none'
That said, I'm a little confused by the if(do[Start|End] || doNone)
logic below - haven't we settled on being able to apply either standoff regardless of whether there's an arrowhead shown at that end (and if not, which mechanism was used to hide it)? ie that if
statement would disappear and we always execute its block?
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.
Removed these if
statements, now we always execute end|start
blocks
} | ||
|
||
function hideLine() { el3.style('stroke-dasharray', '0px,100px'); } | ||
|
||
function drawhead(p, rot) { | ||
function drawhead(headStyle, p, rot, scale) { |
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.
This will work, but it's a bit confusing to reuse the names from the outer scope - perhaps rename to something like thisHeadStyle
and thisScale
?
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.
Changed names to arrowHeadStyle
and arrowScale
Good catch @etpinard - this one is a little tricky, we need to cover two cases:
ppadplus: Math.max(ann._xpadplus, startHeadPlus),
ppadminus: Math.max(ann._xpadminus, startHeadMinus) where
These will need a little bit of testing with the strange edge cases where the start arrowhead actually does poke out farther than anything else, like: |
@alexcjohnson @etpinard made changes based on your review. |
"arrowside":"start+end","startarrowhead":6,"startarrowsize":1,"xanchor": "right","yanchor": "bottom", | ||
"bordercolor": "#444", "borderwidth": 3, "height": 30,"textangle":0, | ||
"yshift": 10 | ||
}, |
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.
😍
@apalchys nice job, I really like the image tests. Is the test failure we're seeing right now an intermittent issue, CI-only, or do you see it locally too? Otherwise I think my only (small) blocker is #2164 (comment) |
Same test failure twice - do you see this locally @apalchys?
|
@alexcjohnson everything was fine for me locally but I made a small adjustment and they pass now. |
💃 |
Thanks for your support! 👍 |
This PR adds possibility to draw annotations arrow head on both sides of a line.
Plotly.js has the following comment in
draw-arrow-head.js
plotly.js/src/components/annotations/draw_arrow_head.js
Line 23 in 8b35a8e
but only
end
is used inannotations/draw.js
plotly.js/src/components/annotations/draw.js
Line 523 in 8b35a8e
So I added a new annotations property
arrowanchor
with the following possible options:start
end
(default)start+end
Screenshot:

Demo:
https://codepen.io/plotly-demo/pen/aVBZBq