diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 9b5426c8d69..bb6cbc6b3b4 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -173,7 +173,25 @@ module.exports = { dflt: 1, role: 'style', editType: 'arraydraw', - description: 'Sets the annotation arrow head style.' + description: 'Sets the end annotation arrow head style.' + }, + startarrowhead: { + valType: 'integer', + min: 0, + max: ARROWPATHS.length, + dflt: 1, + role: 'style', + editType: 'arraydraw', + description: 'Sets the start annotation arrow head style.' + }, + arrowside: { + valType: 'flaglist', + flags: ['end', 'start'], + extras: ['none'], + dflt: 'end', + role: 'style', + editType: 'arraydraw', + description: 'Sets the annotation arrow head position.' }, arrowsize: { valType: 'number', @@ -182,7 +200,18 @@ module.exports = { role: 'style', editType: 'calcIfAutorange', description: [ - 'Sets the size of the annotation arrow head, relative to `arrowwidth`.', + 'Sets the size of the end annotation arrow head, relative to `arrowwidth`.', + 'A value of 1 (default) gives a head about 3x as wide as the line.' + ].join(' ') + }, + startarrowsize: { + valType: 'number', + min: 0.3, + dflt: 1, + role: 'style', + editType: 'calcIfAutorange', + description: [ + 'Sets the size of the start annotation arrow head, relative to `arrowwidth`.', 'A value of 1 (default) gives a head about 3x as wide as the line.' ].join(' ') }, @@ -200,7 +229,21 @@ module.exports = { role: 'style', editType: 'calcIfAutorange', description: [ - 'Sets a distance, in pixels, to move the arrowhead away from the', + 'Sets a distance, in pixels, to move the end arrowhead away from the', + 'position it is pointing at, for example to point at the edge of', + 'a marker independent of zoom. Note that this shortens the arrow', + 'from the `ax` / `ay` vector, in contrast to `xshift` / `yshift`', + 'which moves everything by this amount.' + ].join(' ') + }, + startstandoff: { + valType: 'number', + min: 0, + dflt: 0, + role: 'style', + editType: 'calcIfAutorange', + description: [ + 'Sets a distance, in pixels, to move the start arrowhead away from the', 'position it is pointing at, for example to point at the edge of', 'a marker independent of zoom. Note that this shortens the arrow', 'from the `ax` / `ay` vector, in contrast to `xshift` / `yshift`', diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js index 37646b3993a..8da79d83448 100644 --- a/src/components/annotations/calc_autorange.js +++ b/src/components/annotations/calc_autorange.js @@ -48,13 +48,16 @@ function annAutorange(gd) { Lib.filterVisible(fullLayout.annotations).forEach(function(ann) { var xa = Axes.getFromId(gd, ann.xref), ya = Axes.getFromId(gd, ann.yref), - headSize = 3 * ann.arrowsize * ann.arrowwidth || 0; + headSize = 3 * ann.arrowsize * ann.arrowwidth || 0, + startHeadSize = 3 * ann.startarrowsize * ann.arrowwidth || 0; - var headPlus, headMinus; + var headPlus, headMinus, startHeadPlus, startHeadMinus; if(xa && xa.autorange) { headPlus = headSize + ann.xshift; headMinus = headSize - ann.xshift; + startHeadPlus = startHeadSize + ann.xshift; + startHeadMinus = startHeadSize - ann.xshift; if(ann.axref === ann.xref) { // expand for the arrowhead (padded by arrowhead) @@ -64,14 +67,16 @@ function annAutorange(gd) { }); // again for the textbox (padded by textbox) Axes.expand(xa, [xa.r2c(ann.ax)], { - ppadplus: ann._xpadplus, - ppadminus: ann._xpadminus + ppadplus: Math.max(ann._xpadplus, startHeadPlus), + ppadminus: Math.max(ann._xpadminus, startHeadMinus) }); } else { + startHeadPlus = ann.ax ? startHeadPlus + ann.ax : startHeadPlus; + startHeadMinus = ann.ax ? startHeadMinus - ann.ax : startHeadMinus; Axes.expand(xa, [xa.r2c(ann.x)], { - ppadplus: Math.max(ann._xpadplus, headPlus), - ppadminus: Math.max(ann._xpadminus, headMinus) + ppadplus: Math.max(ann._xpadplus, headPlus, startHeadPlus), + ppadminus: Math.max(ann._xpadminus, headMinus, startHeadMinus) }); } } @@ -79,6 +84,8 @@ function annAutorange(gd) { if(ya && ya.autorange) { headPlus = headSize - ann.yshift; headMinus = headSize + ann.yshift; + startHeadPlus = startHeadSize - ann.yshift; + startHeadMinus = startHeadSize + ann.yshift; if(ann.ayref === ann.yref) { Axes.expand(ya, [ya.r2c(ann.y)], { @@ -86,14 +93,16 @@ function annAutorange(gd) { ppadminus: headMinus }); Axes.expand(ya, [ya.r2c(ann.ay)], { - ppadplus: ann._ypadplus, - ppadminus: ann._ypadminus + ppadplus: Math.max(ann._ypadplus, startHeadPlus), + ppadminus: Math.max(ann._ypadminus, startHeadMinus) }); } else { + startHeadPlus = ann.ay ? startHeadPlus + ann.ay : startHeadPlus; + startHeadMinus = ann.ay ? startHeadMinus - ann.ay : startHeadMinus; Axes.expand(ya, [ya.r2c(ann.y)], { - ppadplus: Math.max(ann._ypadplus, headPlus), - ppadminus: Math.max(ann._ypadminus, headMinus) + ppadplus: Math.max(ann._ypadplus, headPlus, startHeadPlus), + ppadminus: Math.max(ann._ypadminus, headMinus, startHeadMinus) }); } } diff --git a/src/components/annotations/common_defaults.js b/src/components/annotations/common_defaults.js index ece51afec5f..104f6c9df0f 100644 --- a/src/components/annotations/common_defaults.js +++ b/src/components/annotations/common_defaults.js @@ -35,11 +35,23 @@ module.exports = function handleAnnotationCommonDefaults(annIn, annOut, fullLayo if(h) coerce('valign'); if(showArrow) { + var arrowside = coerce('arrowside'); + var arrowhead; + var arrowsize; + + if(arrowside.indexOf('end') !== -1) { + arrowhead = coerce('arrowhead'); + arrowsize = coerce('arrowsize'); + } + + if(arrowside.indexOf('start') !== -1) { + coerce('startarrowhead', arrowhead); + coerce('startarrowsize', arrowsize); + } coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine); - coerce('arrowhead'); - coerce('arrowsize'); coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2); coerce('standoff'); + coerce('startstandoff'); } diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 92f5e595c00..7b56523e8e6 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -509,7 +509,8 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { }); var strokewidth = options.arrowwidth, - arrowColor = options.arrowcolor; + arrowColor = options.arrowcolor, + arrowSide = options.arrowside; var arrowGroup = annGroup.append('g') .style({opacity: Color.opacity(arrowColor)}) @@ -520,7 +521,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { .style('stroke-width', strokewidth + 'px') .call(Color.stroke, Color.rgb(arrowColor)); - drawArrowHead(arrow, 'end', options); + drawArrowHead(arrow, arrowSide, options); // the arrow dragger is a small square right at the head, then a line to the tail, // all expanded by a stroke width of 6px plus the arrow line width diff --git a/src/components/annotations/draw_arrow_head.js b/src/components/annotations/draw_arrow_head.js index af6dde2c51f..38010f9c688 100644 --- a/src/components/annotations/draw_arrow_head.js +++ b/src/components/annotations/draw_arrow_head.js @@ -20,12 +20,15 @@ var ARROWPATHS = require('./arrow_paths'); * * @param {d3.selection} el3: a d3-selected line or path element * - * @param {string} ends: 'start', 'end', or 'start+end' for which ends get arrowheads + * @param {string} ends: 'none', 'start', 'end', or 'start+end' for which ends get arrowheads * * @param {object} options: style information. Must have all the following: - * @param {number} options.arrowhead: head style - see ./arrow_paths - * @param {number} options.arrowsize: relative size of the head vs line width - * @param {number} options.standoff: distance in px to move the arrow point from its target + * @param {number} options.arrowhead: end head style - see ./arrow_paths + * @param {number} options.startarrowhead: start head style - see ./arrow_paths + * @param {number} options.arrowsize: relative size of the end head vs line width + * @param {number} options.startarrowsize: relative size of the start head vs line width + * @param {number} options.standoff: distance in px to move the end arrow point from its target + * @param {number} options.startstandoff: distance in px to move the start arrow point from its target * @param {number} options.arrowwidth: width of the arrow line * @param {string} options.arrowcolor: color of the arrow line, for the head to match * Note that the opacity of this color is ignored, as it's assumed the container @@ -35,10 +38,13 @@ var ARROWPATHS = require('./arrow_paths'); module.exports = function drawArrowHead(el3, ends, options) { var el = el3.node(); var headStyle = ARROWPATHS[options.arrowhead || 0]; - var scale = (options.arrowwidth || 1) * options.arrowsize; + var startHeadStyle = ARROWPATHS[options.startarrowhead || 0]; + var scale = (options.arrowwidth || 1) * (options.arrowsize || 1); + var startScale = (options.arrowwidth || 1) * (options.startarrowsize || 1); var doStart = ends.indexOf('start') >= 0; var doEnd = ends.indexOf('end') >= 0; var backOff = headStyle.backoff * scale + options.standoff; + var startBackOff = startHeadStyle.backoff * startScale + options.startstandoff; var start, end, startRot, endRot; @@ -51,6 +57,13 @@ module.exports = function drawArrowHead(el3, ends, options) { startRot = Math.atan2(dy, dx); endRot = startRot + Math.PI; + if(backOff && startBackOff) { + if(backOff + startBackOff > Math.sqrt(dx * dx + dy * dy)) { + hideLine(); + return; + } + } + if(backOff) { if(backOff * backOff > dx * dx + dy * dy) { hideLine(); @@ -59,16 +72,24 @@ module.exports = function drawArrowHead(el3, ends, options) { var backOffX = backOff * Math.cos(startRot), backOffY = backOff * Math.sin(startRot); - if(doStart) { - start.x -= backOffX; - start.y -= backOffY; - el3.attr({x1: start.x, y1: start.y}); - } - if(doEnd) { - end.x += backOffX; - end.y += backOffY; - el3.attr({x2: end.x, y2: end.y}); + end.x += backOffX; + end.y += backOffY; + el3.attr({x2: end.x, y2: end.y}); + + } + + if(startBackOff) { + if(startBackOff * startBackOff > dx * dx + dy * dy) { + hideLine(); + return; } + var startBackOffX = startBackOff * Math.cos(startRot), + startbackOffY = startBackOff * Math.sin(startRot); + + start.x -= startBackOffX; + start.y -= startbackOffY; + el3.attr({x1: start.x, y1: start.y}); + } } else if(el.nodeName === 'path') { @@ -79,52 +100,46 @@ module.exports = function drawArrowHead(el3, ends, options) { // combine the two dashArray = ''; - if(pathlen < backOff) { + if(pathlen < backOff + startBackOff) { hideLine(); return; } - if(doStart) { - var start0 = el.getPointAtLength(0); - var dstart = el.getPointAtLength(0.1); - startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x); - start = el.getPointAtLength(Math.min(backOff, pathlen)); + var start0 = el.getPointAtLength(0); + var dstart = el.getPointAtLength(0.1); - if(backOff) dashArray = '0px,' + backOff + 'px,'; - } + startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x); + start = el.getPointAtLength(Math.min(startBackOff, pathlen)); - if(doEnd) { - var end0 = el.getPointAtLength(pathlen); - var dend = el.getPointAtLength(pathlen - 0.1); + dashArray = '0px,' + startBackOff + 'px,'; - endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x); - end = el.getPointAtLength(Math.max(0, pathlen - backOff)); + var end0 = el.getPointAtLength(pathlen); + var dend = el.getPointAtLength(pathlen - 0.1); - if(backOff) { - var shortening = dashArray ? 2 * backOff : backOff; - dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px'; - } - } - else if(dashArray) dashArray += pathlen + 'px'; + endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x); + end = el.getPointAtLength(Math.max(0, pathlen - backOff)); + + var shortening = dashArray ? startBackOff + backOff : backOff; + dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px'; - if(dashArray) el3.style('stroke-dasharray', dashArray); + el3.style('stroke-dasharray', dashArray); } function hideLine() { el3.style('stroke-dasharray', '0px,100px'); } - function drawhead(p, rot) { - if(!headStyle.path) return; - if(headStyle.noRotate) rot = 0; + function drawhead(arrowHeadStyle, p, rot, arrowScale) { + if(!arrowHeadStyle.path) return; + if(arrowHeadStyle.noRotate) rot = 0; d3.select(el.parentNode).append('path') .attr({ 'class': el3.attr('class'), - d: headStyle.path, + d: arrowHeadStyle.path, transform: 'translate(' + p.x + ',' + p.y + ')' + (rot ? 'rotate(' + (rot * 180 / Math.PI) + ')' : '') + - 'scale(' + scale + ')' + 'scale(' + arrowScale + ')' }) .style({ fill: Color.rgb(options.arrowcolor), @@ -132,6 +147,6 @@ module.exports = function drawArrowHead(el3, ends, options) { }); } - if(doStart) drawhead(start, startRot); - if(doEnd) drawhead(end, endRot); + if(doStart) drawhead(startHeadStyle, start, startRot, startScale); + if(doEnd) drawhead(headStyle, end, endRot, scale); }; diff --git a/src/components/annotations3d/attributes.js b/src/components/annotations3d/attributes.js index 12ec181862a..12bcf5b0c1d 100644 --- a/src/components/annotations3d/attributes.js +++ b/src/components/annotations3d/attributes.js @@ -72,9 +72,13 @@ module.exports = overrideAll({ showarrow: annAtts.showarrow, arrowcolor: annAtts.arrowcolor, arrowhead: annAtts.arrowhead, + startarrowhead: annAtts.startarrowhead, + arrowside: annAtts.arrowside, arrowsize: annAtts.arrowsize, + startarrowsize: annAtts.startarrowsize, arrowwidth: annAtts.arrowwidth, standoff: annAtts.standoff, + startstandoff: annAtts.startstandoff, hovertext: annAtts.hovertext, hoverlabel: annAtts.hoverlabel, captureevents: annAtts.captureevents, diff --git a/test/image/baselines/annotations-autorange.png b/test/image/baselines/annotations-autorange.png index 4ae3404b064..d1fb548a0fe 100644 Binary files a/test/image/baselines/annotations-autorange.png and b/test/image/baselines/annotations-autorange.png differ diff --git a/test/image/baselines/annotations.png b/test/image/baselines/annotations.png index f4e3dad3d4e..4a349f6df12 100644 Binary files a/test/image/baselines/annotations.png and b/test/image/baselines/annotations.png differ diff --git a/test/image/baselines/gl2d_annotations.png b/test/image/baselines/gl2d_annotations.png index ab0306aa89d..f1d41279b72 100644 Binary files a/test/image/baselines/gl2d_annotations.png and b/test/image/baselines/gl2d_annotations.png differ diff --git a/test/image/baselines/gl3d_annotations.png b/test/image/baselines/gl3d_annotations.png index d59ee820da2..691ee233841 100644 Binary files a/test/image/baselines/gl3d_annotations.png and b/test/image/baselines/gl3d_annotations.png differ diff --git a/test/image/mocks/annotations-autorange.json b/test/image/mocks/annotations-autorange.json index ded30e807e6..8ac8d815ad6 100644 --- a/test/image/mocks/annotations-autorange.json +++ b/test/image/mocks/annotations-autorange.json @@ -10,13 +10,13 @@ "layout":{ "autosize":false, "xaxis":{ - "domain":[0,0.3], + "domain":[0,0.21], "mirror":true, "zeroline":false, "showline":true }, "xaxis2":{ - "domain":[0.35,0.65], + "domain":[0.26,0.47], "anchor":"y2", "mirror":true, "zeroline":false, @@ -24,12 +24,19 @@ "type": "date" }, "xaxis3":{ - "domain":[0.7,1], + "domain":[0.52,0.73], "anchor":"y3", "mirror":true, "zeroline":false, "showline":true }, + "xaxis4":{ + "domain":[0.78,1], + "anchor":"y4", + "mirror":true, + "zeroline":false, + "showline":true + }, "yaxis":{ "mirror":true, "zeroline":false, @@ -48,8 +55,14 @@ "zeroline":false, "showline":true }, + "yaxis4":{ + "anchor":"x4", + "mirror":true, + "zeroline":false, + "showline":true + }, "height":360, - "width":800, + "width":1000, "margin":{"r":40,"b":100,"l":40,"t":40}, "annotations":[ {"ay":0,"ax":50,"x":1,"y":1.5,"text":"Left", "xshift": -10, "yshift": -10}, @@ -94,19 +107,41 @@ "xref":"x3","yref":"y3","text":"Top
no
arrow","y":2,"x":1.5,"showarrow":false, "yshift": -10 }, + { + "xref":"x4","yref":"y4","text":"From left","y":2,"ax":-27,"ay":0,"x":0, + "textangle": -90, "bordercolor": "#444", "xanchor": "left","yanchor": "top", + "arrowside":"start+end", "startarrowsize":2,"startarrowhead":6, + "xshift": 10 + }, + { + "xref":"x4","yref":"y4","y":2,"x":6,"ay":0,"ax":40, + "arrowside":"start+end", "startarrowsize":3,"startarrowhead":6, + "xshift": -10 + }, + { + "xref":"x4","yref":"y4","y":4,"x":4,"ay":-40,"ax":0, + "arrowside":"start+end", "startarrowsize":2,"startarrowhead":6,"xanchor": "right","yanchor": "top", + "yshift": -10 + }, + { + "xref":"x4","yref":"y4","text":"From Bottom","y":1,"ax":0,"ay":60,"x":4, + "arrowside":"start+end","startarrowhead":6,"startarrowsize":1,"xanchor": "right","yanchor": "bottom", + "bordercolor": "#444", "borderwidth": 3, "height": 30,"textangle":0, + "yshift": 10 + }, { "xref": "paper", "yref": "paper", "text": "On the
bottom of the plot", "x": 0.3, "y": -0.1, "showarrow": false, "xanchor": "right", "yanchor": "top", "width": 200, "height": 60, "bgcolor": "#eee", "bordercolor": "#444", - "xshift": -5, "yshift": 5 + "xshift": -55, "yshift": 5 }, { "xref": "paper", "yref": "paper", "text": "blah blah blah blah
blah
blah
blah
blah
blah", "x": 0.3, "y": -0.25, "ax": 100, "ay": 0, "textangle": 40, "borderpad": 4, "xanchor": "left", "yanchor": "bottom", "align": "left", "valign": "top", "width": 60, "height": 40, "bgcolor": "#eee", "bordercolor": "#444", - "xshift": -5, "yshift": 5 + "xshift": -55, "yshift": 5 } ] } diff --git a/test/image/mocks/annotations.json b/test/image/mocks/annotations.json index 98d39081075..aff13013a3e 100644 --- a/test/image/mocks/annotations.json +++ b/test/image/mocks/annotations.json @@ -45,6 +45,9 @@ "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,"startstandoff":5,"standoff":7,"arrowside":"none","axref":"x","ayref":"y","x":2,"y":3,"ax":2,"ay":4}, {"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":6,"y":2,"ax":3,"ay":3}, { "text": "arrow ML
+standoff", "x": 2, "y": 2, "ax": 20, "ay": 20, diff --git a/test/image/mocks/gl2d_annotations.json b/test/image/mocks/gl2d_annotations.json index 3e8a1c4e0d2..662959a4507 100644 --- a/test/image/mocks/gl2d_annotations.json +++ b/test/image/mocks/gl2d_annotations.json @@ -46,7 +46,10 @@ "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":2,"axref":"x","ayref":"y","x":6,"y":2,"ax":3,"ay":3} + {"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":6,"y":2,"ax":3,"ay":3}, + {"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,"startstandoff":5,"standoff":7,"arrowside":"none","axref":"x","ayref":"y","x":2,"y":3,"ax":2,"ay":4} ] } } diff --git a/test/image/mocks/gl3d_annotations.json b/test/image/mocks/gl3d_annotations.json index 231b1517431..1541d6d2ac6 100644 --- a/test/image/mocks/gl3d_annotations.json +++ b/test/image/mocks/gl3d_annotations.json @@ -1,9 +1,9 @@ { "data": [{ "type": "scatter3d", - "x": ["2017-01-01", "2017-02-10", "2017-03-20"], - "y": ["A", "B", "C"], - "z": [1, 1e3, 1e5] + "x": ["2017-01-01", "2017-02-10", "2017-03-20", "2017-04-29"], + "y": ["A", "B", "C", "B"], + "z": [1, 1e3, 1e5, 100] }], "layout": { "scene": { @@ -72,7 +72,38 @@ "z": 6, "text": "autorange bump!", "ax": -50 - }] + }, { + "x": "2017-04-29", + "y": "B", + "z": 2, + "ax": 50, + "ay": 50, + "arrowside": "start+end", + "arrowhead": 2, + "startarrowhead": 3, + "standoff": 10, + "startstandoff": 20 + }, { + "x": "2017-04-29", + "y": "B", + "z": 2, + "ax": -100, + "ay": 0, + "arrowside": "start", + "startarrowhead": 6, + "standoff": 10, + "startstandoff": 20 + }, { + "x": "2017-04-29", + "y": "B", + "z": 2, + "ax": 80, + "ay": -50, + "arrowside": "none", + "standoff": 10, + "startstandoff": 20 + } + ] } } } diff --git a/test/jasmine/tests/annotations_test.js b/test/jasmine/tests/annotations_test.js index 2f96fb22954..14d4aa975f2 100644 --- a/test/jasmine/tests/annotations_test.js +++ b/test/jasmine/tests/annotations_test.js @@ -140,6 +140,16 @@ describe('Test annotations', function() { expect(layoutOut.annotations[2]._xclick).toBe(2, 'log'); expect(layoutOut.annotations[2]._yclick).toBe('A', 'category'); }); + + it('should default to end for arrowside', function() { + var layoutIn = { + annotations: [{ showarrow: true, arrowhead: 2 }] + }; + + var out = _supply(layoutIn); + + expect(out[0].arrowside).toEqual('end'); + }); }); }); @@ -567,7 +577,7 @@ describe('annotations autorange', function() { afterEach(destroyGraphDiv); - function assertRanges(x, y, x2, y2, x3, y3) { + function assertRanges(x, y, x2, y2, x3, y3, x4, y4) { var fullLayout = gd._fullLayout; var PREC = 1; @@ -590,6 +600,8 @@ describe('annotations autorange', function() { expect(fullLayout.yaxis2.range).toBeCloseToArray(y2, PRECY2, 'yaxis2'); expect(fullLayout.xaxis3.range).toBeCloseToArray(x3, PRECX3, 'xaxis3'); expect(fullLayout.yaxis3.range).toBeCloseToArray(y3, PREC, 'yaxis3'); + expect(fullLayout.xaxis4.range).toBeCloseToArray(x4, PRECX2, 'xaxis4'); + expect(fullLayout.yaxis4.range).toBeCloseToArray(y4, PRECY2, 'yaxis4'); } function assertVisible(indices) { @@ -613,37 +625,42 @@ describe('annotations autorange', function() { assertRanges( [0.91, 2.09], [0.91, 2.09], ['2000-11-13', '2001-04-21'], [-0.069, 3.917], - [0.88, 2.05], [0.92, 2.08] + [0.88, 2.05], [0.92, 2.08], + [-1.38, 8.29], [-0.85, 5.14] ); - assertVisible([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); + assertVisible([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]); return Plotly.relayout(gd, { 'annotations[0].visible': false, 'annotations[4].visible': false, - 'annotations[8].visible': false + 'annotations[8].visible': false, + 'annotations[12].visible': false }); }) .then(function() { assertRanges( [1.44, 2.02], [0.91, 2.09], ['2001-01-18', '2001-03-27'], [-0.069, 3.917], - [1.44, 2.1], [0.92, 2.08] + [1.44, 2.1], [0.92, 2.08], + [1.41, 7.42], [-0.85, 5.14] ); - assertVisible([1, 2, 3, 5, 6, 7, 9, 10, 11, 12, 13]); + assertVisible([1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15, 16, 17]); return Plotly.relayout(gd, { 'annotations[2].visible': false, 'annotations[5].visible': false, - 'annotations[9].visible': false + 'annotations[9].visible': false, + 'annotations[13].visible': false, }); }) .then(function() { assertRanges( [1.44, 2.02], [0.99, 1.52], ['2001-01-31 23:59:59.999', '2001-02-01 00:00:00.001'], [-0.069, 3.917], - [0.5, 2.5], [0.92, 2.08] + [0.5, 2.5], [0.92, 2.08], + [3, 5], [-0.85, 5.14] ); - assertVisible([1, 3, 6, 7, 10, 11, 12, 13]); + assertVisible([1, 3, 6, 7, 10, 11, 14, 15, 16, 17]); return Plotly.relayout(gd, { 'annotations[0].visible': true, @@ -651,16 +668,19 @@ describe('annotations autorange', function() { 'annotations[4].visible': true, 'annotations[5].visible': true, 'annotations[8].visible': true, - 'annotations[9].visible': true + 'annotations[9].visible': true, + 'annotations[12].visible': true, + 'annotations[13].visible': true }); }) .then(function() { assertRanges( [0.91, 2.09], [0.91, 2.09], ['2000-11-13', '2001-04-21'], [-0.069, 3.917], - [0.88, 2.05], [0.92, 2.08] + [0.88, 2.05], [0.92, 2.08], + [-1.38, 8.29], [-0.85, 5.14] ); - assertVisible([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); + assertVisible([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]); // check that off-plot annotations are hidden - zoom in to // only one of the four on each subplot @@ -670,17 +690,20 @@ describe('annotations autorange', function() { 'xaxis2.range': ['2001-01-15', '2001-02-15'], 'yaxis2.range': [0.9, 1.1], 'xaxis3.range': [1.9, 2.1], - 'yaxis3.range': [1.4, 1.6] + 'yaxis3.range': [1.4, 1.6], + 'xaxis4.range': [3.9, 4.1], + 'yaxis4.range': [0.9, 1.1] }); }) .then(function() { assertRanges([1.4, 1.6], [0.9, 1.1], ['2001-01-15', '2001-02-15'], [0.9, 1.1], - [1.9, 2.1], [1.4, 1.6] + [1.9, 2.1], [1.4, 1.6], + [3.9, 4.1], [0.9, 1.1] ); // only one annotation on each subplot, plus the two paper-referenced // are visible after zooming in - assertVisible([3, 7, 9, 12, 13]); + assertVisible([3, 7, 9, 15, 16, 17]); }) .catch(failTest) .then(done); @@ -703,7 +726,8 @@ describe('annotations autorange', function() { [-1.09, 2.25], [0.84, 3.06], // the other axes shouldn't change ['2000-11-13', '2001-04-21'], [-0.069, 3.917], - [0.88, 2.05], [0.92, 2.08] + [0.88, 2.05], [0.92, 2.08], + [-1.38, 8.29], [-0.85, 5.14] ); }) .catch(failTest) diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 8d2cf6a11c3..668e31d379a 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -1462,27 +1462,27 @@ describe('Test gl3d annotations', function() { mock.layout.scene.annotations.forEach(function(ann, i) { ann.text = String(i); }); Plotly.plot(gd, mock).then(function() { - assertAnnotationText(['0', '1', '2', '3'], 'base'); + assertAnnotationText(['0', '1', '2', '3', '4', '5', '6'], 'base'); return Plotly.relayout(gd, 'scene.yaxis.range', [0.5, 1.5]); }) .then(function() { - assertAnnotationText(['1'], 'after yaxis range relayout'); + assertAnnotationText(['1', '4', '5', '6'], 'after yaxis range relayout'); return Plotly.relayout(gd, 'scene.yaxis.range', null); }) .then(function() { - assertAnnotationText(['0', '1', '2', '3'], 'back to base after yaxis range relayout'); + assertAnnotationText(['0', '1', '2', '3', '4', '5', '6'], 'back to base after yaxis range relayout'); return Plotly.relayout(gd, 'scene.zaxis.range', [0, 3]); }) .then(function() { - assertAnnotationText(['0'], 'after zaxis range relayout'); + assertAnnotationText(['0', '4', '5', '6'], 'after zaxis range relayout'); return Plotly.relayout(gd, 'scene.zaxis.range', null); }) .then(function() { - assertAnnotationText(['0', '1', '2', '3'], 'back to base after zaxis range relayout'); + assertAnnotationText(['0', '1', '2', '3', '4', '5', '6'], 'back to base after zaxis range relayout'); }) .catch(fail) .then(done); @@ -1502,27 +1502,27 @@ describe('Test gl3d annotations', function() { }; Plotly.plot(gd, mock).then(function() { - assertAnnotationText(['0', '1', '2', '3'], 'base'); + assertAnnotationText(['0', '1', '2', '3', '4', '5', '6'], 'base'); return Plotly.relayout(gd, 'scene.annotations[1].visible', false); }) .then(function() { - assertAnnotationText(['0', '2', '3'], 'after [1].visible:false'); + assertAnnotationText(['0', '2', '3', '4', '5', '6'], 'after [1].visible:false'); return Plotly.relayout(gd, 'scene.annotations[1].visible', true); }) .then(function() { - assertAnnotationText(['0', '1', '2', '3'], 'back to base (1)'); + assertAnnotationText(['0', '1', '2', '3', '4', '5', '6'], 'back to base (1)'); return Plotly.relayout(gd, 'scene.annotations[0]', null); }) .then(function() { - assertAnnotationText(['1', '2', '3'], 'after [0] null'); + assertAnnotationText(['1', '2', '3', '4', '5', '6'], 'after [0] null'); return Plotly.relayout(gd, 'scene.annotations[0]', annNew); }) .then(function() { - assertAnnotationText(['new!', '1', '2', '3'], 'after add new (1)'); + assertAnnotationText(['new!', '1', '2', '3', '4', '5', '6'], 'after add new (1)'); return Plotly.relayout(gd, 'scene.annotations', null); })