From 67d5f8e4bc7d325fdfee80758c65ce689f8f5dd8 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 2 Jul 2020 12:38:07 -0400 Subject: [PATCH 001/112] First iteration of adding " domain" to x and y ref specifiers This allows shapes and eventually annotations to refer to the length of an axis, so if this length is changed, the shape changes accordingly. For example for a shape, setting xref: "x2 domain", x0: 0, x1: 1 makes a shape that spans exactly the width of xaxis2. When this axis changes length (via the setting of its 'domain' attribute), then the shape will span the new axis. --- .gitignore | 1 + src/components/shapes/defaults.js | 23 ++++++++++++++++++----- src/components/shapes/draw.js | 24 +++++++++++++++++------- src/plot_api/helpers.js | 2 +- src/plots/cartesian/axes.js | 9 ++++++++- 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 0f7e76037ad..2316129015e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build/* npm-debug.log* *.sublime* +*~ .* !.circleci diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 85b18ad3463..086e53549c4 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -61,12 +61,25 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var ax; var pos2r; var r2pos; + var coerceRefExtras=['paper']; + // extract axis if domain specified + var xAx = function (ar) { + var mtch = ar.match(/^([xyz][0-9]*) domain/); + if (mtch) { return mtch[1]; } + return ar; + } + var axNumMatch = shapeIn[axLetter+'ref'].match(/[xyz]([0-9]*)/); + if (axNumMatch) { + let axNum = axNumMatch[1]; + coerceRefExtras = coerceRefExtras.concat( + axNum ? [axLetter + axNum + ' domain'] : []); + } // xref, yref - var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', 'paper'); + var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', coerceRefExtras); if(axRef !== 'paper') { - ax = Axes.getFromId(gdMock, axRef); + ax = Axes.getFromId(gdMock, xAx(axRef)); ax._shapeIndices.push(shapeOut._index); r2pos = helpers.rangeToShapePosition(ax); pos2r = helpers.shapePositionToRange(ax); @@ -94,8 +107,8 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { coerce(attr0, 0); coerce(attr1, 10); } else { - Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0); - Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1); + Axes.coercePosition(shapeOut, gdMock, coerce, xAx(axRef), attr0, dflt0); + Axes.coercePosition(shapeOut, gdMock, coerce, xAx(axRef), attr1, dflt1); } // hack part 2 @@ -111,7 +124,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var inAnchor = shapeIn[attrAnchor]; shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true); - Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attrAnchor, 0.25); + Axes.coercePosition(shapeOut, gdMock, coerce, xAx(axRef), attrAnchor, 0.25); // Hack part 2 shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]); diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index b519a654a8b..bc4fa052e98 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -183,7 +183,7 @@ function setClipPath(shapePath, gd, shapeOptions) { // note that for layer="below" the clipAxes can be different from the // subplot we're drawing this in. This could cause problems if the shape // spans two subplots. See https://github.com/plotly/plotly.js/issues/1452 - var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, ''); + var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, '').replace(/ *domain/g,''); Drawing.setClipUrl( shapePath, @@ -584,22 +584,32 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe function getPathString(gd, options) { var type = options.type; - var xa = Axes.getFromId(gd, options.xref); - var ya = Axes.getFromId(gd, options.yref); + var xref_opt = options.xref.split(' '); + var yref_opt = options.yref.split(' '); + var xa = Axes.getFromId(gd, xref_opt[0]); + var ya = Axes.getFromId(gd, yref_opt[0]); var gs = gd._fullLayout._size; var x2r, x2p, y2r, y2p; var x0, x1, y0, y1; if(xa) { - x2r = helpers.shapePositionToRange(xa); - x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); }; + if(xref_opt[1] === "domain") { + x2p = function (v) { return xa._offset + xa._length * v; }; + } else { + x2r = helpers.shapePositionToRange(xa); + x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); }; + } } else { x2p = function(v) { return gs.l + gs.w * v; }; } if(ya) { - y2r = helpers.shapePositionToRange(ya); - y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); }; + if(yref_opt[1] === "domain") { + y2p = function (v) { return ya._offset + ya._length * v; }; + } else { + y2r = helpers.shapePositionToRange(ya); + y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); }; + } } else { y2p = function(v) { return gs.t + gs.h * (1 - v); }; } diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 230b313a8b7..239ea16510f 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -217,7 +217,7 @@ exports.cleanLayout = function(layout) { function cleanAxRef(container, attr) { var valIn = container[attr]; var axLetter = attr.charAt(0); - if(valIn && valIn !== 'paper') { + if(valIn && valIn !== 'paper' && !valIn.endsWith("domain")) { container[attr] = cleanId(valIn, axLetter); } } diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 8e1aa45eb51..ce70dd1645a 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -87,9 +87,16 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption // data-ref annotations are not supported in gl2d yet + var extraOptionAsList = extraOption; + if (extraOption) { + if (typeof(extraOption)=='string') { + extraOptionAsList = [extraOption]; + } + } + attrDef[refAttr] = { valType: 'enumerated', - values: axlist.concat(extraOption ? [extraOption] : []), + values: axlist.concat(extraOptionAsList ? extraOptionAsList : []), dflt: dflt }; From 83a74523caf710e83e5933b88fdd3f6f96a91c03 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 2 Jul 2020 15:41:00 -0400 Subject: [PATCH 002/112] axis ref for axes without numbers now works --- src/components/shapes/defaults.js | 2 +- src/components/shapes/draw.js | 4 ++-- src/plots/cartesian/axis_ids.js | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 086e53549c4..bbb18737d2a 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -72,7 +72,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { if (axNumMatch) { let axNum = axNumMatch[1]; coerceRefExtras = coerceRefExtras.concat( - axNum ? [axLetter + axNum + ' domain'] : []); + (axNum != undefined) ? [axLetter + axNum + ' domain'] : []); } // xref, yref diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index bc4fa052e98..ffc3e9d6c4f 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -586,8 +586,8 @@ function getPathString(gd, options) { var type = options.type; var xref_opt = options.xref.split(' '); var yref_opt = options.yref.split(' '); - var xa = Axes.getFromId(gd, xref_opt[0]); - var ya = Axes.getFromId(gd, yref_opt[0]); + var xa = Axes.getFromId(gd, options.xref); + var ya = Axes.getFromId(gd, options.yref); var gs = gd._fullLayout._size; var x2r, x2p, y2r, y2p; var x0, x1, y0, y1; diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 5306b32452d..36e622c9b7b 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -82,6 +82,8 @@ exports.listIds = function(gd, axLetter) { // optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it exports.getFromId = function(gd, id, type) { var fullLayout = gd._fullLayout; + // remove "domain" suffix + id = id.replace(/ *domain/,''); if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); From f9e05bc334cd06606afa3322f267f98e4467e7ec Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 2 Jul 2020 18:15:05 -0400 Subject: [PATCH 003/112] handle case where axis id is undefined however there's a funny case when running ./test/image/mocks/sunburst_with-without_values.json where xAx (local function) gets passed an array in ./src/components/shapes/defaults.js (it expects a string or undefined). So we still need to solve this. --- .gitignore | 1 + src/components/shapes/defaults.js | 8 ++++++-- src/plots/cartesian/axis_ids.js | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2316129015e..f4c03139eec 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build/* npm-debug.log* *.sublime* *~ +tags .* !.circleci diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index bbb18737d2a..331d41889df 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -64,11 +64,15 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var coerceRefExtras=['paper']; // extract axis if domain specified var xAx = function (ar) { - var mtch = ar.match(/^([xyz][0-9]*) domain/); + var mtch = ar ? ar.match(/^([xyz][0-9]*) domain/) : undefined; if (mtch) { return mtch[1]; } return ar; } - var axNumMatch = shapeIn[axLetter+'ref'].match(/[xyz]([0-9]*)/); + var axNumMatch = ( + shapeIn[axLetter+'ref'] ? + shapeIn[axLetter+'ref'].match(/[xyz]([0-9]*)/) : + undefined + ); if (axNumMatch) { let axNum = axNumMatch[1]; coerceRefExtras = coerceRefExtras.concat( diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 36e622c9b7b..8b65fce5f20 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -83,7 +83,7 @@ exports.listIds = function(gd, axLetter) { exports.getFromId = function(gd, id, type) { var fullLayout = gd._fullLayout; // remove "domain" suffix - id = id.replace(/ *domain/,''); + id = (id == undefined) ? id : id.replace(/ *domain/,''); if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); From f4c3a604e4875f9d7d4cc3384ee3267fb60aad0e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 6 Jul 2020 10:56:24 -0400 Subject: [PATCH 004/112] replaced let with var --- src/components/shapes/defaults.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 331d41889df..048d04a948b 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -74,7 +74,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { undefined ); if (axNumMatch) { - let axNum = axNumMatch[1]; + var axNum = axNumMatch[1]; coerceRefExtras = coerceRefExtras.concat( (axNum != undefined) ? [axLetter + axNum + ' domain'] : []); } From 36667f5815dcacedc9e61d278736596f6d54939e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 6 Jul 2020 11:50:42 -0400 Subject: [PATCH 005/112] dflt cannot be a list in axes.coerceRef --- src/plots/cartesian/axes.js | 10 ++++++++-- src/plots/cartesian/axis_ids.js | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index ce70dd1645a..2c12e264611 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -82,14 +82,20 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption var refAttr = attr + 'ref'; var attrDef = {}; - if(!dflt) dflt = axlist[0] || extraOption; + if(!dflt) dflt = axlist[0] || ( + ((extraOption === undefined) || (typeof(extraOption) === "string")) ? + extraOption : + extraOption[0] + ); + // so in the end, if dflt, axlist[], and extraOption were undefined, they + // are still undefined if(!extraOption) extraOption = dflt; // data-ref annotations are not supported in gl2d yet var extraOptionAsList = extraOption; if (extraOption) { - if (typeof(extraOption)=='string') { + if (typeof(extraOption)==='string') { extraOptionAsList = [extraOption]; } } diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 8b65fce5f20..d90a02d9aa4 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -83,7 +83,7 @@ exports.listIds = function(gd, axLetter) { exports.getFromId = function(gd, id, type) { var fullLayout = gd._fullLayout; // remove "domain" suffix - id = (id == undefined) ? id : id.replace(/ *domain/,''); + id = (id === undefined) ? id : id.replace(/ *domain/,''); if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); From ab7b97334f3be516f0bca90c3dfd11128de45f2f Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 6 Jul 2020 12:21:22 -0400 Subject: [PATCH 006/112] Code linting --- src/components/shapes/defaults.js | 16 ++++++++-------- src/components/shapes/draw.js | 14 +++++++------- src/plot_api/helpers.js | 2 +- src/plots/cartesian/axes.js | 12 +++++++----- src/plots/cartesian/axis_ids.js | 2 +- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 048d04a948b..a615470dc89 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -61,22 +61,22 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var ax; var pos2r; var r2pos; - var coerceRefExtras=['paper']; + var coerceRefExtras = ['paper']; // extract axis if domain specified - var xAx = function (ar) { + var xAx = function(ar) { var mtch = ar ? ar.match(/^([xyz][0-9]*) domain/) : undefined; - if (mtch) { return mtch[1]; } + if(mtch) { return mtch[1]; } return ar; - } + }; var axNumMatch = ( - shapeIn[axLetter+'ref'] ? - shapeIn[axLetter+'ref'].match(/[xyz]([0-9]*)/) : + shapeIn[axLetter + 'ref'] ? + shapeIn[axLetter + 'ref'].match(/[xyz]([0-9]*)/) : undefined ); - if (axNumMatch) { + if(axNumMatch) { var axNum = axNumMatch[1]; coerceRefExtras = coerceRefExtras.concat( - (axNum != undefined) ? [axLetter + axNum + ' domain'] : []); + (axNum !== undefined) ? [axLetter + axNum + ' domain'] : []); } // xref, yref diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index ffc3e9d6c4f..590db1d3d32 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -183,7 +183,7 @@ function setClipPath(shapePath, gd, shapeOptions) { // note that for layer="below" the clipAxes can be different from the // subplot we're drawing this in. This could cause problems if the shape // spans two subplots. See https://github.com/plotly/plotly.js/issues/1452 - var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, '').replace(/ *domain/g,''); + var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, '').replace(/ *domain/g, ''); Drawing.setClipUrl( shapePath, @@ -584,8 +584,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe function getPathString(gd, options) { var type = options.type; - var xref_opt = options.xref.split(' '); - var yref_opt = options.yref.split(' '); + var xrefOpt = options.xref.split(' '); + var yrefOpt = options.yref.split(' '); var xa = Axes.getFromId(gd, options.xref); var ya = Axes.getFromId(gd, options.yref); var gs = gd._fullLayout._size; @@ -593,8 +593,8 @@ function getPathString(gd, options) { var x0, x1, y0, y1; if(xa) { - if(xref_opt[1] === "domain") { - x2p = function (v) { return xa._offset + xa._length * v; }; + if(xrefOpt[1] === 'domain') { + x2p = function(v) { return xa._offset + xa._length * v; }; } else { x2r = helpers.shapePositionToRange(xa); x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); }; @@ -604,8 +604,8 @@ function getPathString(gd, options) { } if(ya) { - if(yref_opt[1] === "domain") { - y2p = function (v) { return ya._offset + ya._length * v; }; + if(yrefOpt[1] === 'domain') { + y2p = function(v) { return ya._offset + ya._length * v; }; } else { y2r = helpers.shapePositionToRange(ya); y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); }; diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 239ea16510f..8ad571e466a 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -217,7 +217,7 @@ exports.cleanLayout = function(layout) { function cleanAxRef(container, attr) { var valIn = container[attr]; var axLetter = attr.charAt(0); - if(valIn && valIn !== 'paper' && !valIn.endsWith("domain")) { + if(valIn && valIn !== 'paper' && !valIn.endsWith('domain')) { container[attr] = cleanId(valIn, axLetter); } } diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 2c12e264611..7f1fd33b510 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -82,11 +82,13 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption var refAttr = attr + 'ref'; var attrDef = {}; - if(!dflt) dflt = axlist[0] || ( - ((extraOption === undefined) || (typeof(extraOption) === "string")) ? - extraOption : + if(!dflt) { + dflt = axlist[0] || ( + ((extraOption === undefined) || (typeof(extraOption) === 'string')) ? + extraOption : extraOption[0] ); + } // so in the end, if dflt, axlist[], and extraOption were undefined, they // are still undefined if(!extraOption) extraOption = dflt; @@ -94,8 +96,8 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption // data-ref annotations are not supported in gl2d yet var extraOptionAsList = extraOption; - if (extraOption) { - if (typeof(extraOption)==='string') { + if(extraOption) { + if(typeof(extraOption) === 'string') { extraOptionAsList = [extraOption]; } } diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index d90a02d9aa4..a6a4a94ab62 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -83,7 +83,7 @@ exports.listIds = function(gd, axLetter) { exports.getFromId = function(gd, id, type) { var fullLayout = gd._fullLayout; // remove "domain" suffix - id = (id === undefined) ? id : id.replace(/ *domain/,''); + id = (id === undefined) ? id : id.replace(/ *domain/, ''); if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); From c6005d9dd04dc5cbad6d917f855c34389ce6c186 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 6 Jul 2020 15:48:30 -0400 Subject: [PATCH 007/112] id might not be string in axis_ids.js:getId --- src/plots/cartesian/axis_ids.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index a6a4a94ab62..0cfcf3863d9 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -83,7 +83,7 @@ exports.listIds = function(gd, axLetter) { exports.getFromId = function(gd, id, type) { var fullLayout = gd._fullLayout; // remove "domain" suffix - id = (id === undefined) ? id : id.replace(/ *domain/, ''); + id = ((id === undefined) || (typeof(id) !== "string")) ? id : id.replace(/ *domain/, ''); if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); From 6235f3a78574b6ea5211df3b0c39b132d006e15e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 6 Jul 2020 16:26:13 -0400 Subject: [PATCH 008/112] code linting --- src/plots/cartesian/axis_ids.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 0cfcf3863d9..3d4caa17356 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -83,7 +83,7 @@ exports.listIds = function(gd, axLetter) { exports.getFromId = function(gd, id, type) { var fullLayout = gd._fullLayout; // remove "domain" suffix - id = ((id === undefined) || (typeof(id) !== "string")) ? id : id.replace(/ *domain/, ''); + id = ((id === undefined) || (typeof(id) !== 'string')) ? id : id.replace(/ *domain/, ''); if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); From 10cbd7858ab9cfd45acaa7b93e464f20f844da08 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 6 Jul 2020 18:41:37 -0400 Subject: [PATCH 009/112] Summarized methods compatible with " domain" This is so that shapes, annotations and images can use the same functions. Still need to add the functionality for annotations and images. --- src/components/annotations/defaults.js | 11 +++++--- src/components/annotations/draw.js | 1 + src/components/shapes/defaults.js | 26 +++++------------- src/plots/cartesian/axes.js | 37 ++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index aca202ffaa2..48bef7b148a 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -45,17 +45,20 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { for(var i = 0; i < 2; i++) { var axLetter = axLetters[i]; + var coerceRefExtras = ['paper']; + coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(annIn,axLetter,coerceRefExtras); // xref, yref - var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper'); + var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', coerceRefExtras); + var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); if(axRef !== 'paper') { - var ax = Axes.getFromId(gdMock, axRef); + var ax = Axes.getFromId(gdMock, axRefAxOnly); ax._annIndices.push(annOut._index); } // x, y - Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5); + Axes.coercePosition(annOut, gdMock, coerce, axRefAxOnly, axLetter, 0.5); if(showArrow) { var arrowPosAttr = 'a' + axLetter; @@ -65,7 +68,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis // but that would require updates to drawing & autorange code and maybe more - if(aaxRef !== 'pixel' && aaxRef !== axRef) { + if(aaxRef !== 'pixel' && aaxRef !== axRefAxOnly) { aaxRef = annOut[arrowPosAttr] = 'pixel'; } diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index f810100a420..11cf25fc00b 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -91,6 +91,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var className, containerStr; + debugger; if(subplotId) { className = 'annotation-' + subplotId; containerStr = subplotId + '.annotations'; diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index a615470dc89..42a5663f0af 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -62,28 +62,14 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var pos2r; var r2pos; var coerceRefExtras = ['paper']; - // extract axis if domain specified - var xAx = function(ar) { - var mtch = ar ? ar.match(/^([xyz][0-9]*) domain/) : undefined; - if(mtch) { return mtch[1]; } - return ar; - }; - var axNumMatch = ( - shapeIn[axLetter + 'ref'] ? - shapeIn[axLetter + 'ref'].match(/[xyz]([0-9]*)/) : - undefined - ); - if(axNumMatch) { - var axNum = axNumMatch[1]; - coerceRefExtras = coerceRefExtras.concat( - (axNum !== undefined) ? [axLetter + axNum + ' domain'] : []); - } + coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(shapeIn,axLetter,coerceRefExtras); // xref, yref var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', coerceRefExtras); + var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); if(axRef !== 'paper') { - ax = Axes.getFromId(gdMock, xAx(axRef)); + ax = Axes.getFromId(gdMock, axRefAxOnly); ax._shapeIndices.push(shapeOut._index); r2pos = helpers.rangeToShapePosition(ax); pos2r = helpers.shapePositionToRange(ax); @@ -111,8 +97,8 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { coerce(attr0, 0); coerce(attr1, 10); } else { - Axes.coercePosition(shapeOut, gdMock, coerce, xAx(axRef), attr0, dflt0); - Axes.coercePosition(shapeOut, gdMock, coerce, xAx(axRef), attr1, dflt1); + Axes.coercePosition(shapeOut, gdMock, coerce, axRefAxOnly, attr0, dflt0); + Axes.coercePosition(shapeOut, gdMock, coerce, axRefAxOnly, attr1, dflt1); } // hack part 2 @@ -128,7 +114,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var inAnchor = shapeIn[attrAnchor]; shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true); - Axes.coercePosition(shapeOut, gdMock, coerce, xAx(axRef), attrAnchor, 0.25); + Axes.coercePosition(shapeOut, gdMock, coerce, axRefAxOnly, attrAnchor, 0.25); // Hack part 2 shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 7f1fd33b510..af9becd5402 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -112,6 +112,43 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption return Lib.coerce(containerIn, containerOut, attrDef, refAttr); }; +/* + * An axis reference (e.g., the contents at the 'xref' key of an object) might + * have extra information appended. Extract the axis reference only. + * + * ar: the axis reference string + * + */ +axes.extractAxisFromAxisRef = function (ar) { + var mtch = ar ? ar.match(/^([xyz][0-9]*) domain/) : undefined; + if(mtch) { return mtch[1]; } + return ar; +}; + +/* + * Add the specified axis letter and number + " domain" to the extras for + * coerceRef. + * + * container: the object holding the [xyz]ref keys, e.g., a shape. + * axLetter: the letter of the axis of interest. + * coerceRefExtras: the current list of extras for coerceRef that will be + * appended to. + * + */ +axes.addAxRefDomainCoerceRefExtra = function (container, axLetter, coerceRefExtras) { + var axNumMatch = ( + container[axLetter + 'ref'] ? + container[axLetter + 'ref'].match(/[xyz]([0-9]*)/) : + undefined + ); + if(axNumMatch) { + var axNum = axNumMatch[1]; + coerceRefExtras = coerceRefExtras.concat( + (axNum !== undefined) ? [axLetter + axNum + ' domain'] : []); + } + return coerceRefExtras; +}; + /* * coerce position attributes (range-type) that can be either on axes or absolute * (paper or pixel) referenced. The biggest complication here is that we don't know From b54665856c37d76f7eab753acd5e2d00b2f5c36e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 6 Jul 2020 19:06:06 -0400 Subject: [PATCH 010/112] Looks like annotations can reference axis domains now, too Need to test more thoroughly though. --- src/components/annotations/draw.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 11cf25fc00b..0a70353ecd8 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -91,7 +91,6 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var className, containerStr; - debugger; if(subplotId) { className = 'annotation-' + subplotId; containerStr = subplotId + '.annotations'; @@ -297,6 +296,8 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var alignPosition; var autoAlignFraction; var textShift; + var axrefOpt = (typeof(axRef)=="string") ? axRef.split(' ') : undefined; + var yrefOpt = options.yref.split(' '); /* * calculate the *primary* pixel position @@ -316,8 +317,12 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { annotationIsOffscreen = true; } } - basePx = ax._offset + ax.r2p(options[axLetter]); - autoAlignFraction = 0.5; + if((axrefOpt != undefined) && (axrefOpt[1] === 'domain')) { + basePx = ax._offset + ax._length * options[axLetter]; + } else { + basePx = ax._offset + ax.r2p(options[axLetter]); + autoAlignFraction = 0.5; + } } else { if(axLetter === 'x') { alignPosition = options[axLetter]; From 40a639c7dc286b7478babda277d6c0f49a462003 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 7 Jul 2020 10:20:39 -0400 Subject: [PATCH 011/112] code linting --- src/components/annotations/defaults.js | 2 +- src/components/annotations/draw.js | 5 ++--- src/components/shapes/defaults.js | 2 +- src/plots/cartesian/axes.js | 18 +++++++++--------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 48bef7b148a..c2795171230 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -46,7 +46,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { for(var i = 0; i < 2; i++) { var axLetter = axLetters[i]; var coerceRefExtras = ['paper']; - coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(annIn,axLetter,coerceRefExtras); + coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(annIn, axLetter, coerceRefExtras); // xref, yref var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', coerceRefExtras); diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 0a70353ecd8..beffd356144 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -296,8 +296,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var alignPosition; var autoAlignFraction; var textShift; - var axrefOpt = (typeof(axRef)=="string") ? axRef.split(' ') : undefined; - var yrefOpt = options.yref.split(' '); + var axrefOpt = (typeof(axRef) === 'string') ? axRef.split(' ') : undefined; /* * calculate the *primary* pixel position @@ -317,7 +316,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { annotationIsOffscreen = true; } } - if((axrefOpt != undefined) && (axrefOpt[1] === 'domain')) { + if((axrefOpt !== undefined) && (axrefOpt[1] === 'domain')) { basePx = ax._offset + ax._length * options[axLetter]; } else { basePx = ax._offset + ax.r2p(options[axLetter]); diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 42a5663f0af..efdd2a4d312 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -62,7 +62,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var pos2r; var r2pos; var coerceRefExtras = ['paper']; - coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(shapeIn,axLetter,coerceRefExtras); + coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(shapeIn, axLetter, coerceRefExtras); // xref, yref var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', coerceRefExtras); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index af9becd5402..03d36123e1b 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -112,14 +112,14 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption return Lib.coerce(containerIn, containerOut, attrDef, refAttr); }; -/* +/* * An axis reference (e.g., the contents at the 'xref' key of an object) might * have extra information appended. Extract the axis reference only. * * ar: the axis reference string * */ -axes.extractAxisFromAxisRef = function (ar) { +axes.extractAxisFromAxisRef = function(ar) { var mtch = ar ? ar.match(/^([xyz][0-9]*) domain/) : undefined; if(mtch) { return mtch[1]; } return ar; @@ -135,18 +135,18 @@ axes.extractAxisFromAxisRef = function (ar) { * appended to. * */ -axes.addAxRefDomainCoerceRefExtra = function (container, axLetter, coerceRefExtras) { - var axNumMatch = ( +axes.addAxRefDomainCoerceRefExtra = function(container, axLetter, coerceRefExtras) { + var axNumMatch = ( container[axLetter + 'ref'] ? container[axLetter + 'ref'].match(/[xyz]([0-9]*)/) : undefined ); - if(axNumMatch) { - var axNum = axNumMatch[1]; - coerceRefExtras = coerceRefExtras.concat( + if(axNumMatch) { + var axNum = axNumMatch[1]; + coerceRefExtras = coerceRefExtras.concat( (axNum !== undefined) ? [axLetter + axNum + ' domain'] : []); - } - return coerceRefExtras; + } + return coerceRefExtras; }; /* From 7373e1ce96b05125ee17a1ce050d4d1f556d370d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 7 Jul 2020 13:22:15 -0400 Subject: [PATCH 012/112] Images can now be placed relative to axis domains However, when setting [xy]ref to "paper" and a sizex or sizey of 1, it doesn't seem to fill the plotting area, is this a regression? --- src/components/images/defaults.js | 10 +++++-- src/components/images/draw.js | 46 ++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 0c08874c441..c8bdb0e6712 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -49,14 +49,18 @@ function imageDefaults(imageIn, imageOut, fullLayout) { for(var i = 0; i < 2; i++) { // 'paper' is the fallback axref var axLetter = axLetters[i]; - var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper'); + var coerceRefExtras = ['paper']; + coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(imageIn, axLetter, + coerceRefExtras); + var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, coerceRefExtras); + var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); if(axRef !== 'paper') { - var ax = Axes.getFromId(gdMock, axRef); + var ax = Axes.getFromId(gdMock, axRefAxOnly); ax._imgIndices.push(imageOut._index); } - Axes.coercePosition(imageOut, gdMock, coerce, axRef, axLetter, 0); + Axes.coercePosition(imageOut, gdMock, coerce, axRefAxOnly, axLetter, 0); } return imageOut; diff --git a/src/components/images/draw.js b/src/components/images/draw.js index c1d93e3f3c5..1d016af4a5a 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -132,8 +132,25 @@ module.exports = function draw(gd) { var ya = Axes.getFromId(gd, d.yref); var size = fullLayout._size; - var width = xa ? Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) : d.sizex * size.w; - var height = ya ? Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) : d.sizey * size.h; + var width, height; + if (xa !== undefined) { + if ((typeof(d.xref) === 'string') && d.xref.endsWith(" domain")) { + width = xa._length * d.sizex; + } else { + width = Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) + } + } else { + width = d.sizex * size.w; + } + if (ya !== undefined) { + if ((typeof(d.yref) === 'string') && d.yref.endsWith(" domain")) { + height = ya._length * d.sizey; + } else { + height = Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) + } + } else { + height = d.sizey * size.h; + } // Offsets for anchor positioning var xOffset = width * anchors.x[d.xanchor].offset; @@ -142,8 +159,29 @@ module.exports = function draw(gd) { var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing; // Final positions - var xPos = (xa ? xa.r2p(d.x) + xa._offset : d.x * size.w + size.l) + xOffset; - var yPos = (ya ? ya.r2p(d.y) + ya._offset : size.h - d.y * size.h + size.t) + yOffset; + var xPos, yPos; + if (xa !== undefined) { + if ((typeof(d.xref) === 'string') && d.xref.endsWith(" domain")) { + xPos = xa._length * d.x + xa._offset; + } else { + xPos = xa.r2p(d.x) + xa._offset; + } + } else { + xPos = d.x * size.w + size.l; + } + xPos += xOffset; + if (ya !== undefined) { + if ((typeof(d.yref) === 'string') && d.yref.endsWith(" domain")) { + // consistent with "paper" yref value, where positive values + // move up the page + yPos = ya._length * (1 - d.y) + ya._offset; + } else { + yPos = ya.r2p(d.y) + ya._offset; + } + } else { + yPos = size.h - d.y * size.h + size.t; + } + yPos += yOffset; // Construct the proper aspectRatio attribute switch(d.sizing) { From 13f7203ceb90ed703f5ffc5836c41688d681a744 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 7 Jul 2020 14:27:03 -0400 Subject: [PATCH 013/112] code linting --- src/components/images/draw.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 1d016af4a5a..29289daa116 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -133,20 +133,20 @@ module.exports = function draw(gd) { var size = fullLayout._size; var width, height; - if (xa !== undefined) { - if ((typeof(d.xref) === 'string') && d.xref.endsWith(" domain")) { + if(xa !== undefined) { + if((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) { width = xa._length * d.sizex; } else { - width = Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) + width = Math.abs(xa.l2p(d.sizex) - xa.l2p(0)); } } else { width = d.sizex * size.w; } - if (ya !== undefined) { - if ((typeof(d.yref) === 'string') && d.yref.endsWith(" domain")) { + if(ya !== undefined) { + if((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) { height = ya._length * d.sizey; } else { - height = Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) + height = Math.abs(ya.l2p(d.sizey) - ya.l2p(0)); } } else { height = d.sizey * size.h; @@ -160,8 +160,8 @@ module.exports = function draw(gd) { // Final positions var xPos, yPos; - if (xa !== undefined) { - if ((typeof(d.xref) === 'string') && d.xref.endsWith(" domain")) { + if(xa !== undefined) { + if((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) { xPos = xa._length * d.x + xa._offset; } else { xPos = xa.r2p(d.x) + xa._offset; @@ -170,8 +170,8 @@ module.exports = function draw(gd) { xPos = d.x * size.w + size.l; } xPos += xOffset; - if (ya !== undefined) { - if ((typeof(d.yref) === 'string') && d.yref.endsWith(" domain")) { + if(ya !== undefined) { + if((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) { // consistent with "paper" yref value, where positive values // move up the page yPos = ya._length * (1 - d.y) + ya._offset; From b3258f6d836e8a17e274821196cabdb9cfe1879b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 7 Jul 2020 15:17:01 -0400 Subject: [PATCH 014/112] Somehow Axes.coerceRef was missing an argument --- src/components/images/defaults.js | 2 +- src/plots/cartesian/axes.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index c8bdb0e6712..8d22dd2fc0d 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -52,7 +52,7 @@ function imageDefaults(imageIn, imageOut, fullLayout) { var coerceRefExtras = ['paper']; coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(imageIn, axLetter, coerceRefExtras); - var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, coerceRefExtras); + var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, '', coerceRefExtras); var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); if(axRef !== 'paper') { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 03d36123e1b..e286c2cc6e0 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -120,7 +120,9 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption * */ axes.extractAxisFromAxisRef = function(ar) { - var mtch = ar ? ar.match(/^([xyz][0-9]*) domain/) : undefined; + var mtch = (((ar !== undefined) && (typeof(ar) === 'string')) ? + ar.match(/^([xyz][0-9]*) domain/) : + undefined); if(mtch) { return mtch[1]; } return ar; }; From e5139b06718775ef14597edd4ee4c6b594bd789d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 7 Jul 2020 16:33:37 -0400 Subject: [PATCH 015/112] Unlike shapes and attributes, requires default "paper" argument --- src/components/images/defaults.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 8d22dd2fc0d..6feb8937904 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -52,7 +52,8 @@ function imageDefaults(imageIn, imageOut, fullLayout) { var coerceRefExtras = ['paper']; coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(imageIn, axLetter, coerceRefExtras); - var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, '', coerceRefExtras); + var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper', + coerceRefExtras); var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); if(axRef !== 'paper') { From 547deaf9d027980782dda185cab58fc2ec47ca48 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 7 Jul 2020 18:07:08 -0400 Subject: [PATCH 016/112] Shapes referring to axis domains now draggable --- src/components/shapes/draw.js | 10 ++++++---- src/components/shapes/helpers.js | 32 +++++++++++++++++++++----------- src/plots/cartesian/axes.js | 17 +++++++++++++++++ 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 590db1d3d32..3509bcb55f3 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -209,11 +209,13 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe // setup conversion functions var xa = Axes.getFromId(gd, shapeOptions.xref); + var xrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.xref) var ya = Axes.getFromId(gd, shapeOptions.yref); - var x2p = helpers.getDataToPixel(gd, xa); - var y2p = helpers.getDataToPixel(gd, ya, true); - var p2x = helpers.getPixelToData(gd, xa); - var p2y = helpers.getPixelToData(gd, ya, true); + var yrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.yref) + var x2p = helpers.getDataToPixel(gd, xa, false, xrefOpt); + var y2p = helpers.getDataToPixel(gd, ya, true, yrefOpt); + var p2x = helpers.getPixelToData(gd, xa, false, xrefOpt); + var p2y = helpers.getPixelToData(gd, ya, true, yrefOpt); var sensoryElement = obtainSensoryElement(); var dragOptions = { diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index fb5416a70ef..cc39386fc3b 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -58,18 +58,24 @@ exports.extractPathCoords = function(path, paramsToUse) { return extractedCoordinates; }; -exports.getDataToPixel = function(gd, axis, isVertical) { +exports.getDataToPixel = function(gd, axis, isVertical, opt) { var gs = gd._fullLayout._size; var dataToPixel; if(axis) { - var d2r = exports.shapePositionToRange(axis); - - dataToPixel = function(v) { - return axis._offset + axis.r2p(d2r(v, true)); - }; - - if(axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel); + if (opt === 'domain') { + dataToPixel = function (v) { + return axis._length * v + axis._offset; + } + } else { + var d2r = exports.shapePositionToRange(axis); + + dataToPixel = function(v) { + return axis._offset + axis.r2p(d2r(v, true)); + }; + + if(axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel); + } } else if(isVertical) { dataToPixel = function(v) { return gs.t + gs.h * (1 - v); }; } else { @@ -79,13 +85,17 @@ exports.getDataToPixel = function(gd, axis, isVertical) { return dataToPixel; }; -exports.getPixelToData = function(gd, axis, isVertical) { +exports.getPixelToData = function(gd, axis, isVertical, opt) { var gs = gd._fullLayout._size; var pixelToData; if(axis) { - var r2d = exports.rangeToShapePosition(axis); - pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); }; + if (opt === 'domain') { + pixelToData = function(p) { return (p - axis._offset) / axis._length; } + } else { + var r2d = exports.rangeToShapePosition(axis); + pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); }; + } } else if(isVertical) { pixelToData = function(p) { return 1 - (p - gs.t) / gs.h; }; } else { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index e286c2cc6e0..41fae8ea0a8 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -120,6 +120,8 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption * */ axes.extractAxisFromAxisRef = function(ar) { + // TODO extractAxisFromAxisRef is more generic than this, this could be made + // more generic too var mtch = (((ar !== undefined) && (typeof(ar) === 'string')) ? ar.match(/^([xyz][0-9]*) domain/) : undefined); @@ -127,6 +129,21 @@ axes.extractAxisFromAxisRef = function(ar) { return ar; }; +/* +/* + * An axis reference (e.g., the contents at the 'xref' key of an object) might + * have extra information appended. Extract the extra information. + * + * ar: the axis reference string + * + */ +axes.extractInfoFromAxisRef = function(ar) { + if ((ar !== undefined) && (typeof(ar) === 'string')) { + return ar.split(' ').slice(1).join(' '); + } + return ar; +} + /* * Add the specified axis letter and number + " domain" to the extras for * coerceRef. From 31b22be9763df1bb04c34420efad6d4c50cdcdc1 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 8 Jul 2020 12:34:39 -0400 Subject: [PATCH 017/112] Trying to make it possible to drag annotations referencing axis domain It is not working yet... --- src/components/annotations/draw.js | 61 ++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index beffd356144..ca2b55363ef 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -73,6 +73,23 @@ function drawOne(gd, index) { drawRaw(gd, options, index, false, xa, ya); } +// A set of instructions that is written everywhere, so for ease of maintenance +// it is collected here. It seems to convert data coordinates to drawing +// coordinates. +// axDomainRef: if true and axa defined, draws relative to axis domain, +// otherwise draws relative to data (if axa defined) or paper (if not). +function p2rR2p(axa,optAx,dAx,gsDim,vertical,axDomainRef) { + if (optAx) { + if (axDomainRef) { + return axa._length * (vertical ? (1 - optAx) : optAx) + axa._offset + dAx; + } else { + return axa.p2r(axa.r2p(optAx) + dAx); + } + } else { + return optAx + (vertical ? -(dAx / gsDim) : dAx / gsDim); + } +} + /** * drawRaw: draw a single annotation, potentially with modifications * @@ -562,24 +579,30 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } }, moveFn: function(dx, dy) { + // TODO: Needs to be axis domain ref compatible var annxy0 = applyTransform(annx0, anny0); var xcenter = annxy0[0] + dx; var ycenter = annxy0[1] + dy; annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); - modifyItem('x', xa ? - xa.p2r(xa.r2p(options.x) + dx) : - (options.x + (dx / gs.w))); - modifyItem('y', ya ? - ya.p2r(ya.r2p(options.y) + dy) : - (options.y - (dy / gs.h))); + var xAxOpt = Axes.extractInfoFromAxisRef(options.xref), + yAxOpt = Axes.extractInfoFromAxisRef(options.yref); + modifyItem('x', + p2rR2p(xa,options.x,dx,gs.w,false,xAxOpt==='domain')); + modifyItem('y', + p2rR2p(ya,options.y,dy,gs.h,true,yAxOpt==='domain')); + // for these 2 calls to p2rR2p, it is assumed xa, ya are + // defined, so gsDim will not be used, but we put it in + // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', xa.p2r(xa.r2p(options.ax) + dx)); + modifyItem('ax', p2rR2p(xa,options.ax,dx,gs.h,false, + xAxOpt==='domain')); } if(options.ayref === options.yref) { - modifyItem('ay', ya.p2r(ya.r2p(options.ay) + dy)); + modifyItem('ay', p2rR2p(ya,options.ay,dy,gs.w,true, + yAxOpt==='domain')); } arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); @@ -612,16 +635,24 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { baseTextTransform = annTextGroup.attr('transform'); }, moveFn: function(dx, dy) { + // TODO: Needs to be axis domain ref compatible var csr = 'pointer'; + var xAxOpt = Axes.extractInfoFromAxisRef(options.xref), + yAxOpt = Axes.extractInfoFromAxisRef(options.yref); if(options.showarrow) { + // for these 2 calls to p2rR2p, it is assumed xa, ya are + // defined, so gsDim will not be used, but we put it in + // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', xa.p2r(xa.r2p(options.ax) + dx)); + modifyItem('ax', p2rR2p(xa,options.ax,dx,gs.h,false, + xAxOpt==='domain')); } else { modifyItem('ax', options.ax + dx); } if(options.ayref === options.yref) { - modifyItem('ay', ya.p2r(ya.r2p(options.ay) + dy)); + modifyItem('ay', p2rR2p(ya,options.ay,dy,gs.w,true, + yAxOpt==='domain')); } else { modifyItem('ay', options.ay + dy); } @@ -630,7 +661,10 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } else if(!subplotId) { var xUpdate, yUpdate; if(xa) { - xUpdate = xa.p2r(xa.r2p(options.x) + dx); + // p2rR2p will not execute code where xa was + // undefined, so we use to calculate xUpdate too + xUpdate = p2rR2p(xa,options.x,dx,gs.h,false, + xAxOpt==='domain'); } else { var widthFraction = options._xsize / gs.w; var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2; @@ -640,7 +674,10 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } if(ya) { - yUpdate = ya.p2r(ya.r2p(options.y) + dy); + // p2rR2p will not execute code where ya was + // undefined, so we use to calculate yUpdate too + yUpdate = p2rR2p(ya,options.y,dy,gs.w,true, + yAxOpt==='domain'); } else { var heightFraction = options._ysize / gs.h; var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2; From c1ca50aa3fcc54696d76138dcc57b083af16a58e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 8 Jul 2020 13:26:19 -0400 Subject: [PATCH 018/112] Can drag axis referenced annotations now --- src/components/annotations/draw.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index ca2b55363ef..723d7131aab 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -73,15 +73,18 @@ function drawOne(gd, index) { drawRaw(gd, options, index, false, xa, ya); } -// A set of instructions that is written everywhere, so for ease of maintenance -// it is collected here. It seems to convert data coordinates to drawing -// coordinates. +// Convert pixels to the coordinates relevant for the axis referred to. For +// example, for paper it would convert to a value normalized by the dimension of +// the plot. // axDomainRef: if true and axa defined, draws relative to axis domain, // otherwise draws relative to data (if axa defined) or paper (if not). function p2rR2p(axa,optAx,dAx,gsDim,vertical,axDomainRef) { - if (optAx) { + if (axa) { if (axDomainRef) { - return axa._length * (vertical ? (1 - optAx) : optAx) + axa._offset + dAx; + // here optAx normalized to length of axis (e.g., normally in range + // 0 to 1). But dAx is in pixels. So we normalize dAx to length of + // axis before doing the math. + return optAx + dAx / axa._length; } else { return axa.p2r(axa.r2p(optAx) + dAx); } @@ -579,7 +582,6 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } }, moveFn: function(dx, dy) { - // TODO: Needs to be axis domain ref compatible var annxy0 = applyTransform(annx0, anny0); var xcenter = annxy0[0] + dx; var ycenter = annxy0[1] + dy; @@ -635,7 +637,6 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { baseTextTransform = annTextGroup.attr('transform'); }, moveFn: function(dx, dy) { - // TODO: Needs to be axis domain ref compatible var csr = 'pointer'; var xAxOpt = Axes.extractInfoFromAxisRef(options.xref), yAxOpt = Axes.extractInfoFromAxisRef(options.yref); From e139ab07c1081ce4b2b69b01b3ca9185438edf18 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 8 Jul 2020 16:49:44 -0400 Subject: [PATCH 019/112] code linting --- src/components/annotations/draw.js | 42 +++++++++++++++--------------- src/components/shapes/draw.js | 4 +-- src/components/shapes/helpers.js | 10 +++---- src/plots/cartesian/axes.js | 4 +-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 723d7131aab..c9b2905718d 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -78,9 +78,9 @@ function drawOne(gd, index) { // the plot. // axDomainRef: if true and axa defined, draws relative to axis domain, // otherwise draws relative to data (if axa defined) or paper (if not). -function p2rR2p(axa,optAx,dAx,gsDim,vertical,axDomainRef) { - if (axa) { - if (axDomainRef) { +function p2rR2p(axa, optAx, dAx, gsDim, vertical, axDomainRef) { + if(axa) { + if(axDomainRef) { // here optAx normalized to length of axis (e.g., normally in range // 0 to 1). But dAx is in pixels. So we normalize dAx to length of // axis before doing the math. @@ -587,24 +587,24 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var ycenter = annxy0[1] + dy; annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); - var xAxOpt = Axes.extractInfoFromAxisRef(options.xref), - yAxOpt = Axes.extractInfoFromAxisRef(options.yref); + var xAxOpt = Axes.extractInfoFromAxisRef(options.xref); + var yAxOpt = Axes.extractInfoFromAxisRef(options.yref); modifyItem('x', - p2rR2p(xa,options.x,dx,gs.w,false,xAxOpt==='domain')); + p2rR2p(xa, options.x, dx, gs.w, false, xAxOpt === 'domain')); modifyItem('y', - p2rR2p(ya,options.y,dy,gs.h,true,yAxOpt==='domain')); + p2rR2p(ya, options.y, dy, gs.h, true, yAxOpt === 'domain')); // for these 2 calls to p2rR2p, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', p2rR2p(xa,options.ax,dx,gs.h,false, - xAxOpt==='domain')); + modifyItem('ax', p2rR2p(xa, options.ax, dx, gs.h, false, + xAxOpt === 'domain')); } if(options.ayref === options.yref) { - modifyItem('ay', p2rR2p(ya,options.ay,dy,gs.w,true, - yAxOpt==='domain')); + modifyItem('ay', p2rR2p(ya, options.ay, dy, gs.w, true, + yAxOpt === 'domain')); } arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); @@ -638,22 +638,22 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { }, moveFn: function(dx, dy) { var csr = 'pointer'; - var xAxOpt = Axes.extractInfoFromAxisRef(options.xref), - yAxOpt = Axes.extractInfoFromAxisRef(options.yref); + var xAxOpt = Axes.extractInfoFromAxisRef(options.xref); + var yAxOpt = Axes.extractInfoFromAxisRef(options.yref); if(options.showarrow) { // for these 2 calls to p2rR2p, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', p2rR2p(xa,options.ax,dx,gs.h,false, - xAxOpt==='domain')); + modifyItem('ax', p2rR2p(xa, options.ax, dx, gs.h, false, + xAxOpt === 'domain')); } else { modifyItem('ax', options.ax + dx); } if(options.ayref === options.yref) { - modifyItem('ay', p2rR2p(ya,options.ay,dy,gs.w,true, - yAxOpt==='domain')); + modifyItem('ay', p2rR2p(ya, options.ay, dy, gs.w, true, + yAxOpt === 'domain')); } else { modifyItem('ay', options.ay + dy); } @@ -664,8 +664,8 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { if(xa) { // p2rR2p will not execute code where xa was // undefined, so we use to calculate xUpdate too - xUpdate = p2rR2p(xa,options.x,dx,gs.h,false, - xAxOpt==='domain'); + xUpdate = p2rR2p(xa, options.x, dx, gs.h, false, + xAxOpt === 'domain'); } else { var widthFraction = options._xsize / gs.w; var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2; @@ -677,8 +677,8 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { if(ya) { // p2rR2p will not execute code where ya was // undefined, so we use to calculate yUpdate too - yUpdate = p2rR2p(ya,options.y,dy,gs.w,true, - yAxOpt==='domain'); + yUpdate = p2rR2p(ya, options.y, dy, gs.w, true, + yAxOpt === 'domain'); } else { var heightFraction = options._ysize / gs.h; var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2; diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 3509bcb55f3..9fa3fc84ba8 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -209,9 +209,9 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe // setup conversion functions var xa = Axes.getFromId(gd, shapeOptions.xref); - var xrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.xref) + var xrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.xref); var ya = Axes.getFromId(gd, shapeOptions.yref); - var yrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.yref) + var yrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.yref); var x2p = helpers.getDataToPixel(gd, xa, false, xrefOpt); var y2p = helpers.getDataToPixel(gd, ya, true, yrefOpt); var p2x = helpers.getPixelToData(gd, xa, false, xrefOpt); diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index cc39386fc3b..f80c4c4c106 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -63,10 +63,10 @@ exports.getDataToPixel = function(gd, axis, isVertical, opt) { var dataToPixel; if(axis) { - if (opt === 'domain') { - dataToPixel = function (v) { + if(opt === 'domain') { + dataToPixel = function(v) { return axis._length * v + axis._offset; - } + }; } else { var d2r = exports.shapePositionToRange(axis); @@ -90,8 +90,8 @@ exports.getPixelToData = function(gd, axis, isVertical, opt) { var pixelToData; if(axis) { - if (opt === 'domain') { - pixelToData = function(p) { return (p - axis._offset) / axis._length; } + if(opt === 'domain') { + pixelToData = function(p) { return (p - axis._offset) / axis._length; }; } else { var r2d = exports.rangeToShapePosition(axis); pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); }; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 41fae8ea0a8..8378bd31d41 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -138,11 +138,11 @@ axes.extractAxisFromAxisRef = function(ar) { * */ axes.extractInfoFromAxisRef = function(ar) { - if ((ar !== undefined) && (typeof(ar) === 'string')) { + if((ar !== undefined) && (typeof(ar) === 'string')) { return ar.split(' ').slice(1).join(' '); } return ar; -} +}; /* * Add the specified axis letter and number + " domain" to the extras for From f4b37fda757d0e6d65bf36db39fdb735e9a239fe Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 10 Jul 2020 18:53:41 -0400 Subject: [PATCH 020/112] axis domain referenced shapes don't affect autorange This is consistent with what 'paper' axis references does, which I think is expected behaviour. --- src/components/annotations/calc_autorange.js | 6 ++++-- src/components/shapes/calc_autorange.js | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js index aa47ec253e8..5b2ad91330e 100644 --- a/src/components/annotations/calc_autorange.js +++ b/src/components/annotations/calc_autorange.js @@ -34,10 +34,12 @@ function annAutorange(gd) { Lib.filterVisible(fullLayout.annotations).forEach(function(ann) { var xa = Axes.getFromId(gd, ann.xref); var ya = Axes.getFromId(gd, ann.yref); + var xrefInfo = Axes.extractInfoFromAxisRef(ann.xref); + var yrefInfo = Axes.extractInfoFromAxisRef(ann.yref); ann._extremes = {}; - if(xa) calcAxisExpansion(ann, xa); - if(ya) calcAxisExpansion(ann, ya); + if(xa && (xrefInfo !== 'domain')) calcAxisExpansion(ann, xa); + if(ya && (yrefInfo !== 'domain')) calcAxisExpansion(ann, ya); }); } diff --git a/src/components/shapes/calc_autorange.js b/src/components/shapes/calc_autorange.js index 6aae1d05fe1..27207369e87 100644 --- a/src/components/shapes/calc_autorange.js +++ b/src/components/shapes/calc_autorange.js @@ -25,9 +25,12 @@ module.exports = function calcAutorange(gd) { var shape = shapeList[i]; shape._extremes = {}; - var ax, bounds; + var ax, bounds, + xrefInfo = Axes.extractInfoFromAxisRef(shape.xref), + yrefInfo = Axes.extractInfoFromAxisRef(shape.yref); - if(shape.xref !== 'paper') { + // paper and axis domain referenced shapes don't affect autorange + if(shape.xref !== 'paper' && xrefInfo !== 'domain') { var vx0 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x0; var vx1 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x1; ax = Axes.getFromId(gd, shape.xref); @@ -38,7 +41,7 @@ module.exports = function calcAutorange(gd) { } } - if(shape.yref !== 'paper') { + if(shape.yref !== 'paper' && yrefInfo !== 'domain') { var vy0 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y0; var vy1 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y1; ax = Axes.getFromId(gd, shape.yref); From 6bb7b822456c3d66005a53a07e5e7c6942832b51 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 16 Jul 2020 16:07:34 -0400 Subject: [PATCH 021/112] Domain referenced shapes and annotations can be dragged anywhere Still funny problem with log axes and shapes though. --- src/components/annotations/draw.js | 24 ++++++++++++++---------- src/components/shapes/draw.js | 5 ++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index c9b2905718d..b0592e8ae64 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -316,14 +316,14 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var alignPosition; var autoAlignFraction; var textShift; - var axrefOpt = (typeof(axRef) === 'string') ? axRef.split(' ') : undefined; + var axrefOpt = Axes.extractInfoFromAxisRef(axRef); /* * calculate the *primary* pixel position * which is the arrowhead if there is one, * otherwise the text anchor point */ - if(ax) { + if(ax && (axrefOpt !== 'domain')) { // check if annotation is off screen, to bypass DOM manipulations var posFraction = ax.r2fraction(options[axLetter]); if(posFraction < 0 || posFraction > 1) { @@ -336,19 +336,23 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { annotationIsOffscreen = true; } } - if((axrefOpt !== undefined) && (axrefOpt[1] === 'domain')) { - basePx = ax._offset + ax._length * options[axLetter]; - } else { - basePx = ax._offset + ax.r2p(options[axLetter]); - autoAlignFraction = 0.5; - } + basePx = ax._offset + ax.r2p(options[axLetter]); + autoAlignFraction = 0.5; } else { if(axLetter === 'x') { alignPosition = options[axLetter]; - basePx = gs.l + gs.w * alignPosition; + if (axrefOpt === 'domain') { + basePx = ax._offset + ax._length * options[axLetter]; + } else { + basePx = gs.l + gs.w * alignPosition; + } } else { alignPosition = 1 - options[axLetter]; - basePx = gs.t + gs.h * alignPosition; + if (axrefOpt === 'domain') { + basePx = ax._offset + ax._length * options[axLetter]; + } else { + basePx = gs.t + gs.h * alignPosition; + } } autoAlignFraction = options.showarrow ? 0.5 : alignPosition; } diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 9fa3fc84ba8..17cf4f645ea 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -183,7 +183,10 @@ function setClipPath(shapePath, gd, shapeOptions) { // note that for layer="below" the clipAxes can be different from the // subplot we're drawing this in. This could cause problems if the shape // spans two subplots. See https://github.com/plotly/plotly.js/issues/1452 - var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, '').replace(/ *domain/g, ''); + // + // if axis is 'paper' or an axis with " domain" appended, then there is no + // clip axis + var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, '').replace(/[xyz][1-9]* *domain/g, ''); Drawing.setClipUrl( shapePath, From d92c4da0bf76841aada204c152f3beab83cc023e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 16 Jul 2020 16:25:11 -0400 Subject: [PATCH 022/112] Syntax fix --- src/components/shapes/calc_autorange.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/shapes/calc_autorange.js b/src/components/shapes/calc_autorange.js index 27207369e87..2f2e866d1d7 100644 --- a/src/components/shapes/calc_autorange.js +++ b/src/components/shapes/calc_autorange.js @@ -25,9 +25,9 @@ module.exports = function calcAutorange(gd) { var shape = shapeList[i]; shape._extremes = {}; - var ax, bounds, - xrefInfo = Axes.extractInfoFromAxisRef(shape.xref), - yrefInfo = Axes.extractInfoFromAxisRef(shape.yref); + var ax; var bounds; + var xrefInfo = Axes.extractInfoFromAxisRef(shape.xref); + var yrefInfo = Axes.extractInfoFromAxisRef(shape.yref); // paper and axis domain referenced shapes don't affect autorange if(shape.xref !== 'paper' && xrefInfo !== 'domain') { From f5c5a172247c36f5f5e64dadc6254635284ead4a Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 16 Jul 2020 16:47:50 -0400 Subject: [PATCH 023/112] More syntax repair --- src/components/annotations/draw.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index b0592e8ae64..f724255383d 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -341,14 +341,14 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } else { if(axLetter === 'x') { alignPosition = options[axLetter]; - if (axrefOpt === 'domain') { + if(axrefOpt === 'domain') { basePx = ax._offset + ax._length * options[axLetter]; } else { basePx = gs.l + gs.w * alignPosition; } } else { alignPosition = 1 - options[axLetter]; - if (axrefOpt === 'domain') { + if(axrefOpt === 'domain') { basePx = ax._offset + ax._length * options[axLetter]; } else { basePx = gs.t + gs.h * alignPosition; From 2225eb584b7311a31e22d07c2dddcf192f82fc50 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 17 Jul 2020 11:13:43 -0400 Subject: [PATCH 024/112] Domain referenced shapes work for log plots now before, set_convert was clipping coordinates to 0 if they were negative, so we changed it so domain referenced shapes behave like paper referenced shapes. --- src/components/shapes/defaults.js | 3 ++- src/components/shapes/draw.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index efdd2a4d312..09173ad779c 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -67,8 +67,9 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { // xref, yref var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', coerceRefExtras); var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); + var axRefInfo = Axes.extractInfoFromAxisRef(axRef); - if(axRef !== 'paper') { + if(axRef !== 'paper' && axRefInfo !== 'domain') { ax = Axes.getFromId(gdMock, axRefAxOnly); ax._shapeIndices.push(shapeOut._index); r2pos = helpers.rangeToShapePosition(ax); diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 17cf4f645ea..425c31a9f47 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -589,8 +589,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe function getPathString(gd, options) { var type = options.type; - var xrefOpt = options.xref.split(' '); - var yrefOpt = options.yref.split(' '); + var xrefOpt = Axes.extractInfoFromAxisRef(options.xref); + var yrefOpt = Axes.extractInfoFromAxisRef(options.yref); var xa = Axes.getFromId(gd, options.xref); var ya = Axes.getFromId(gd, options.yref); var gs = gd._fullLayout._size; @@ -598,7 +598,7 @@ function getPathString(gd, options) { var x0, x1, y0, y1; if(xa) { - if(xrefOpt[1] === 'domain') { + if(xrefOpt === 'domain') { x2p = function(v) { return xa._offset + xa._length * v; }; } else { x2r = helpers.shapePositionToRange(xa); @@ -609,7 +609,7 @@ function getPathString(gd, options) { } if(ya) { - if(yrefOpt[1] === 'domain') { + if(yrefOpt === 'domain') { y2p = function(v) { return ya._offset + ya._length * v; }; } else { y2r = helpers.shapePositionToRange(ya); From 23d8888ef5c7a07f5fbb5b4021cf75561911f0b4 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 17 Jul 2020 13:01:21 -0400 Subject: [PATCH 025/112] fixed y direction for domain referenced shapes and annotations The curent paper axis reference has y = 0 at the bottom and y = 1 at the top, so we also make this the case for domain axis references. --- src/components/annotations/draw.js | 8 ++++---- src/components/shapes/draw.js | 2 +- src/components/shapes/helpers.js | 8 ++++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index f724255383d..9840b4a11a0 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -84,12 +84,12 @@ function p2rR2p(axa, optAx, dAx, gsDim, vertical, axDomainRef) { // here optAx normalized to length of axis (e.g., normally in range // 0 to 1). But dAx is in pixels. So we normalize dAx to length of // axis before doing the math. - return optAx + dAx / axa._length; + return optAx + (vertical ? -dAx : dAx) / axa._length; } else { return axa.p2r(axa.r2p(optAx) + dAx); } } else { - return optAx + (vertical ? -(dAx / gsDim) : dAx / gsDim); + return optAx + (vertical ? -dAx : dAx) / gsDim; } } @@ -342,14 +342,14 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { if(axLetter === 'x') { alignPosition = options[axLetter]; if(axrefOpt === 'domain') { - basePx = ax._offset + ax._length * options[axLetter]; + basePx = ax._offset + ax._length * alignPosition; } else { basePx = gs.l + gs.w * alignPosition; } } else { alignPosition = 1 - options[axLetter]; if(axrefOpt === 'domain') { - basePx = ax._offset + ax._length * options[axLetter]; + basePx = ax._offset + ax._length * alignPosition; } else { basePx = gs.t + gs.h * alignPosition; } diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 425c31a9f47..cda709767c6 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -610,7 +610,7 @@ function getPathString(gd, options) { if(ya) { if(yrefOpt === 'domain') { - y2p = function(v) { return ya._offset + ya._length * v; }; + y2p = function(v) { return ya._offset + ya._length * (1 - v); }; } else { y2r = helpers.shapePositionToRange(ya); y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); }; diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index f80c4c4c106..cfcf08c117b 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -65,7 +65,7 @@ exports.getDataToPixel = function(gd, axis, isVertical, opt) { if(axis) { if(opt === 'domain') { dataToPixel = function(v) { - return axis._length * v + axis._offset; + return axis._length * (isVertical ? (1 - v) : v) + axis._offset; }; } else { var d2r = exports.shapePositionToRange(axis); @@ -91,7 +91,11 @@ exports.getPixelToData = function(gd, axis, isVertical, opt) { if(axis) { if(opt === 'domain') { - pixelToData = function(p) { return (p - axis._offset) / axis._length; }; + pixelToData = function(p) { + return ((isVertical ? + (1 - (p - axis._offset) / axis._length) : + (p - axis._offset) / axis._length)); + }; } else { var r2d = exports.rangeToShapePosition(axis); pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); }; From aa316e841664e54563767e888423f53076f62e7c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 22 Jul 2020 16:46:41 -0400 Subject: [PATCH 026/112] Started adding tests for domain referenced shapes They are getting written in test/domain_ref_tests.html for now so they can be debugged quickly. We can rebase this file out once moved to the jasmine tests. --- src/components/shapes/attributes.js | 17 +- test/domain_ref_tests.html | 505 ++++++++++++++++++++++++++++ 2 files changed, 519 insertions(+), 3 deletions(-) create mode 100644 test/domain_ref_tests.html diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index 1b8b0ff2665..6c2d5e7aa8f 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -68,6 +68,10 @@ module.exports = templatedArray('shape', { 'If set to *paper*, the `x` position refers to the distance from', 'the left side of the plotting area in normalized coordinates', 'where *0* (*1*) corresponds to the left (right) side.', + 'If set to an x axis id followed by *domain* (separated by a space),', + 'the position behaves like for *paper*, but refers to the distance', + 'from the left of the domain of that axis: e.g., *x2 domain* refers', + 'to the domain of the second x axis.', 'If the axis `type` is *log*, then you must take the', 'log of your desired range.', 'If the axis `type` is *date*, then you must convert', @@ -126,11 +130,18 @@ module.exports = templatedArray('shape', { yref: extendFlat({}, annAttrs.yref, { description: [ 'Sets the annotation\'s y coordinate axis.', - 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to an y coordinate', + 'If set to a y axis id (e.g. *y* or *y2*), the `y` position', + 'refers to a y coordinate', 'If set to *paper*, the `y` position refers to the distance from', 'the bottom of the plotting area in normalized coordinates', - 'where *0* (*1*) corresponds to the bottom (top).' + 'where *0* (*1*) corresponds to the bottom (top).', + 'If set to a y axis id followed by *domain* (separated by a space),', + 'the position behaves like for *paper*, but refers to the distance', + 'in fractions of the domain length', + 'from the bottom of the domain of that axis: e.g., *y2 domain*', + 'refers to the domain of the second y axis and a y position of', + '0.5 refers to the point between the bottom and the top of the', + 'domain of the second y axis.', ].join(' ') }), ysizemode: { diff --git a/test/domain_ref_tests.html b/test/domain_ref_tests.html new file mode 100644 index 00000000000..c11396d9a15 --- /dev/null +++ b/test/domain_ref_tests.html @@ -0,0 +1,505 @@ + + + + + + + + +
+ + + + + From 0289e6fb665f520daa79a3e674dcbaeafbfdd2e5 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 23 Jul 2020 16:55:41 -0400 Subject: [PATCH 027/112] Added domain to xref and yref attribute values --- src/components/annotations/attributes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 1e27069b618..7fabc951a11 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -316,7 +316,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.idRegex.x.toString() + cartesianConstants.idRegex.x.toString()+"( domain)?" ], role: 'info', editType: 'calc', @@ -379,7 +379,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.idRegex.y.toString() + cartesianConstants.idRegex.y.toString()+"( domain)?" ], role: 'info', editType: 'calc', From 360d322f2f6485cee8a13fa457325a3bc7deb43c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 23 Jul 2020 17:35:23 -0400 Subject: [PATCH 028/112] Corrected domain reference regex Should now correctly extract ( domain). --- src/components/annotations/attributes.js | 4 ++-- src/components/images/attributes.js | 4 ++-- src/plots/cartesian/constants.js | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 7fabc951a11..c918019a6c3 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -316,7 +316,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.idRegex.x.toString()+"( domain)?" + cartesianConstants.AX_ID_EXTRAS_PATTERN ], role: 'info', editType: 'calc', @@ -379,7 +379,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.idRegex.y.toString()+"( domain)?" + cartesianConstants.AX_ID_EXTRAS_PATTERN ], role: 'info', editType: 'calc', diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index 1018355839e..3bebadb49f8 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -143,7 +143,7 @@ module.exports = templatedArray('image', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.idRegex.x.toString() + cartesianConstants.AX_ID_EXTRAS_PATTERN ], dflt: 'paper', role: 'info', @@ -162,7 +162,7 @@ module.exports = templatedArray('image', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.idRegex.y.toString() + cartesianConstants.AX_ID_EXTRAS_PATTERN ], dflt: 'paper', role: 'info', diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index 98ef0bc9f18..80534f8ccb3 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -26,6 +26,7 @@ module.exports = { // note that this is more permissive than counterRegex, as // id2name, name2id, and cleanId accept "x1" etc AX_ID_PATTERN: /^[xyz][0-9]*$/, + AX_ID_EXTRAS_PATTERN: /^[xyz]([2-9]|[1-9][0-9]+)?( domain)?$/, AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/, // and for 2D subplots From 474f7d920963c93ae4eccb2eec169e22278c2827 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 23 Jul 2020 18:07:07 -0400 Subject: [PATCH 029/112] Missing "" around the regex --- src/components/annotations/attributes.js | 4 ++-- src/components/images/attributes.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index c918019a6c3..3cd4d14c19b 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -316,7 +316,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN + cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() ], role: 'info', editType: 'calc', @@ -379,7 +379,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN + cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() ], role: 'info', editType: 'calc', diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index 3bebadb49f8..a13a734dad0 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -143,7 +143,7 @@ module.exports = templatedArray('image', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN + cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() ], dflt: 'paper', role: 'info', @@ -162,7 +162,7 @@ module.exports = templatedArray('image', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN + cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() ], dflt: 'paper', role: 'info', From 0891c0f9b1aab1acbf37c02095b390113e490f4b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 27 Jul 2020 11:48:03 -0400 Subject: [PATCH 030/112] Generalization of description for axis placeable objects Now the description string for the documentation of shapes, annotations and images is generated by a function call to keep the text consistent. --- src/components/annotations/attributes.js | 13 +++------- src/components/images/attributes.js | 13 +++------- src/components/shapes/attributes.js | 24 +++---------------- src/constants/axis_placeable_objects.js | 30 ++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 src/constants/axis_placeable_objects.js diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 3cd4d14c19b..0bbd8a18e2b 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -12,6 +12,7 @@ var ARROWPATHS = require('./arrow_paths'); var fontAttrs = require('../../plots/font_attributes'); var cartesianConstants = require('../../plots/cartesian/constants'); var templatedArray = require('../../plot_api/plot_template').templatedArray; +var axisPlaceableObjs = require('../../constants/axis_placeable_objects.js'); module.exports = templatedArray('annotation', { @@ -322,11 +323,7 @@ module.exports = templatedArray('annotation', { editType: 'calc', description: [ 'Sets the annotation\'s x coordinate axis.', - 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x coordinate', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left side of the plotting area in normalized coordinates', - 'where 0 (1) corresponds to the left (right) side.' + axisPlaceableObjs.axisRefDescription('x','left','right'), ].join(' ') }, x: { @@ -385,11 +382,7 @@ module.exports = templatedArray('annotation', { editType: 'calc', description: [ 'Sets the annotation\'s y coordinate axis.', - 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to an y coordinate', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plotting area in normalized coordinates', - 'where 0 (1) corresponds to the bottom (top).' + axisPlaceableObjs.axisRefDescription('y','bottom','top'), ].join(' ') }, y: { diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index a13a734dad0..d037679e24d 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -10,6 +10,7 @@ var cartesianConstants = require('../../plots/cartesian/constants'); var templatedArray = require('../../plot_api/plot_template').templatedArray; +var axisPlaceableObjs = require('../../constants/axis_placeable_objects.js'); module.exports = templatedArray('image', { @@ -150,11 +151,7 @@ module.exports = templatedArray('image', { editType: 'arraydraw', description: [ 'Sets the images\'s x coordinate axis.', - 'If set to a x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x data coordinate', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left of plot in normalized coordinates', - 'where *0* (*1*) corresponds to the left (right).' + axisPlaceableObjs.axisRefDescription('x','left','right'), ].join(' ') }, @@ -169,11 +166,7 @@ module.exports = templatedArray('image', { editType: 'arraydraw', description: [ 'Sets the images\'s y coordinate axis.', - 'If set to a y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to a y data coordinate.', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plot in normalized coordinates', - 'where *0* (*1*) corresponds to the bottom (top).' + axisPlaceableObjs.axisRefDescription('y','bottom','top'), ].join(' ') }, editType: 'arraydraw' diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index 6c2d5e7aa8f..cd3222c80e6 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -13,6 +13,7 @@ var scatterLineAttrs = require('../../traces/scatter/attributes').line; var dash = require('../drawing/attributes').dash; var extendFlat = require('../../lib/extend').extendFlat; var templatedArray = require('../../plot_api/plot_template').templatedArray; +var axisPlaceableObjs = require('../../constants/axis_placeable_objects.js'); module.exports = templatedArray('shape', { visible: { @@ -63,15 +64,7 @@ module.exports = templatedArray('shape', { xref: extendFlat({}, annAttrs.xref, { description: [ 'Sets the shape\'s x coordinate axis.', - 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x coordinate.', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left side of the plotting area in normalized coordinates', - 'where *0* (*1*) corresponds to the left (right) side.', - 'If set to an x axis id followed by *domain* (separated by a space),', - 'the position behaves like for *paper*, but refers to the distance', - 'from the left of the domain of that axis: e.g., *x2 domain* refers', - 'to the domain of the second x axis.', + axisPlaceableObjs.axisRefDescription('x','left','right'), 'If the axis `type` is *log*, then you must take the', 'log of your desired range.', 'If the axis `type` is *date*, then you must convert', @@ -130,18 +123,7 @@ module.exports = templatedArray('shape', { yref: extendFlat({}, annAttrs.yref, { description: [ 'Sets the annotation\'s y coordinate axis.', - 'If set to a y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to a y coordinate', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plotting area in normalized coordinates', - 'where *0* (*1*) corresponds to the bottom (top).', - 'If set to a y axis id followed by *domain* (separated by a space),', - 'the position behaves like for *paper*, but refers to the distance', - 'in fractions of the domain length', - 'from the bottom of the domain of that axis: e.g., *y2 domain*', - 'refers to the domain of the second y axis and a y position of', - '0.5 refers to the point between the bottom and the top of the', - 'domain of the second y axis.', + axisPlaceableObjs.axisRefDescription('y','bottom','top'), ].join(' ') }), ysizemode: { diff --git a/src/constants/axis_placeable_objects.js b/src/constants/axis_placeable_objects.js new file mode 100644 index 00000000000..2e452bed7ab --- /dev/null +++ b/src/constants/axis_placeable_objects.js @@ -0,0 +1,30 @@ +/** +* Copyright 2012-2020, 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'; + +module.exports = { + axisRefDescription: function (axisname,lower,upper) { + return [ + `If set to a ${axisname} axis id (e.g. *${axisname}* or`, + `*${axisname}2*), the \`${axisname}\` position refers to a`, + `${axisname} coordinate. If set to *paper*, the \`${axisname}\``, + `position refers to the distance from the ${lower} of the plotting`, + `area in normalized coordinates where *0* (*1*) corresponds to the`, + `${lower} (${upper}). If set to a ${axisname} axis ID followed by`, + `*domain* (separated by a space), the position behaves like for`, + `*paper*, but refers to the distance in fractions of the domain`, + `length from the ${lower} of the domain of that axis: e.g.,`, + `*${axisname}2 domain* refers to the domain of the second`, + `${axisname} axis and a ${axisname} position of 0.5 refers to the`, + `point between the ${lower} and the ${upper} of the domain of the`, + `second ${axisname} axis.`, + ].join(' '); + } +} From 0bb16dbcb77f80c219ebf86b84ce0844c851ade4 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 27 Jul 2020 16:29:04 -0400 Subject: [PATCH 031/112] Test correct position of domain referenced annotation --- test/domain_ref_tests.html | 141 +++++++++++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 23 deletions(-) diff --git a/test/domain_ref_tests.html b/test/domain_ref_tests.html index c11396d9a15..3ba0f86d939 100644 --- a/test/domain_ref_tests.html +++ b/test/domain_ref_tests.html @@ -17,8 +17,12 @@ return d3.selectAll('.shapelayer path'); } -function getFirstGridNode(axis) { - return d3.selectAll('.gridlayer .'+axis+' .'+axis+'grid.crisp').node(); +function getFirstGridNode(axis,subplot) { + return d3.selectAll(`.subplot.${subplot} .gridlayer .${axis} .${axis}grid.crisp`).node(); +} + +function getAnnotationNodes() { + return d3.selectAll('.annotation-arrow-g path')[0]; } var nm="([0-9]+[.]?[0-9]*)"; @@ -129,16 +133,35 @@ x: [1, 5], y: [1, 10], type: 'scatter' +},{ + x: [10, 100], + y: [1, 10], + type: 'scatter', + xaxis: 'x2', + yaxis: 'y2' }]; layout = { title: 'Axis domain referenced shape', + grid: {rows: 1, columns: 2, pattern: 'independant'}, width: 0.3, height: 0.2, + xaxis: { - domain: [0.2,0.9] + domain: [0.2,0.4] }, yaxis: { - domain: [0.1,0.8] + domain: [0.1,0.5] + }, + xaxis2: { + domain: [0.6,0.8], + type: 'log', + autorange: true + }, + yaxis2: { + domain: [0.1,0.5], + anchor: 'x2', + type: 'log', + autorange: true } }; @@ -265,6 +288,19 @@ xref: 'x domain', yref: 'y domain', line: { color: 'rgb(0,0,0)' } +}, +// rect on log axis +{ + type: 'rect', + xref: 'x2 domain', + yref: 'y2 domain', + xsizemode: 'scaled', + ysizemode: 'scaled', + x0: 0.1, + x1: 1.1, + y0: 0.1, + y1: 1.1, + line: { color: 'rgb(0,0,0)' } } ]; @@ -323,10 +359,27 @@ y0: 0, y1: 1, line: { color: 'rgb(255, 127, 80)' } -} -]; +}]; layout.shapes=domainRefShapes.concat(hlineVlineShapes).concat(hrectVrectShapes); +layout.annotations=[{ + text: 'A', + x: 0.75, + y: 10, + xref: 'x domain', + yref: 'y', + showarrow: true, + arrowhead: 0 + },{ + text: 'B', + x: 1, + y: 0.75, + xref: 'x', + yref: 'y domain', + showarrow: true, + arrowhead: 0 + } +]; Plotly.plot(TESTER, data, layout); @@ -383,16 +436,21 @@ //// extracting this information from the grid lines. function checkDomainRefShapes() { // full span horizontal (vertical) line should have same length as y (x) grid line - var ygridlinec = svgShapeToCoords(getFirstGridNode('y').getAttribute('d')); - var xgridlinec = svgShapeToCoords(getFirstGridNode('x').getAttribute('d')); - var plotdims = plotDimsFromGridLines(xgridlinec,ygridlinec); console.log("checking domain referenced shapes"); zip(getShapeNodes()[0].filter( sh=>sh.getAttribute('style').match( /stroke: rgb\(0, 0, 0\)/)),domainRefShapes).forEach(function (args) { var shapec_ = args[0], shapes = args[1]; - var shapec =svgShapeToCoords(shapec_.getAttribute('d')); - console.log('Correct line',assertCorrectLineOrRect( + var xref = shapes.xref.match(/([xyz][2-9]?).*$/)[1]; + var yref = shapes.yref.match(/([xyz][2-9]?).*$/)[1]; + var shapec = svgShapeToCoords(shapec_.getAttribute('d')); + var ygridlinec = svgShapeToCoords( + getFirstGridNode(yref,xref+yref).getAttribute('d')); + var xgridlinec = svgShapeToCoords( + getFirstGridNode(xref,xref+yref).getAttribute('d')); + var plotdims = plotDimsFromGridLines(xgridlinec,ygridlinec); + + console.log('Correct shape',assertCorrectLineOrRect( shapec,shapes,plotdims)); }); } @@ -476,24 +534,61 @@ } checkHrectVrectShapes(); +// Check annotations that are aligned to data coordinate on one axis and axis +// domain on the other axis (A realistic application e.g., for hline annotations). +// This compares the position of the point of the arrow. +function checkAnnotations () { + console.log("Checking annotations"); + // extract the annotations from the plot + var svgAnnos=getAnnotationNodes(); + var dataLine=d3.selectAll('.js-line').node(); + var transCoords=getPlotTransformCoords(); + // first compare ann1's y with y coordinate of point on plot + var ann1Coords=svgLineCoords(svgAnnos[0].getAttribute('d')); + var dataLineCoords=svgLineCoords(dataLine.getAttribute('d')); + console.log('y matches for ann1', + ann1Coords[1][1]==(dataLineCoords[1][1]+transCoords[1])); + // then compare ann2's x with x coordinate of point on plot + var ann2Coords=svgLineCoords(svgAnnos[1].getAttribute('d')); + console.log('x matches for ann2', + ann2Coords[1][0]==(dataLineCoords[0][0]+transCoords[0])); +} +checkAnnotations(); + //// TEST positions are still correct after relayout of axis -Plotly.relayout(TESTER,{ - xaxis: { - domain: [0.1,0.8] - }, - yaxis: { - domain: [0.2,0.9] - } -}); +window.setTimeout(function () { + Plotly.relayout(TESTER,{ + xaxis: { + domain: [0.1,0.4] + }, + yaxis: { + domain: [0.2,0.9] + }, + xaxis2: { + domain: [0.6,0.9], + type: 'log', + autorange: true + }, + yaxis2: { + domain: [0.3,0.5], + anchor: 'x2', + type: 'log', + autorange: true + } + }); -checkDomainRefShapes(); -checkHlineVlineShapes(); -checkHrectVrectShapes(); + checkDomainRefShapes(); + checkHlineVlineShapes(); + checkHrectVrectShapes(); + checkAnnotations(); +},2000); //// TEST You can drag an axis referenced shape to any place on the paper and it will -//// be visible. +//// be visible. (Not needed: drags are tested at a higher level) //// TODO + + //// TEST The shape stays the correct size when one or more of its points are outside //// of a log-axis domain. //// TODO From 696f8dfe76b27010a5ecfeafd2c5c613497027e7 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 27 Jul 2020 17:39:05 -0400 Subject: [PATCH 032/112] Test domain referenced annotation on log plot That it is placed in the correct position. --- test/domain_ref_tests.html | 49 +++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/test/domain_ref_tests.html b/test/domain_ref_tests.html index 3ba0f86d939..f6d2a8ad3b4 100644 --- a/test/domain_ref_tests.html +++ b/test/domain_ref_tests.html @@ -300,7 +300,7 @@ x1: 1.1, y0: 0.1, y1: 1.1, - line: { color: 'rgb(0,0,0)' } + line: { color: 'rgb(1,1,1)' } } ]; @@ -369,7 +369,8 @@ xref: 'x domain', yref: 'y', showarrow: true, - arrowhead: 0 + arrowhead: 0, + arrowcolor: 'rgb(0,0,0)' },{ text: 'B', x: 1, @@ -377,7 +378,17 @@ xref: 'x', yref: 'y domain', showarrow: true, - arrowhead: 0 + arrowhead: 0, + arrowcolor: 'rgb(0,0,0)' + },{ + text: 'C', + x: 1.1, + y: 1.1, + xref: 'x2 domain', + yref: 'y2 domain', + showarrow: true, + arrowhead: 0, + arrowcolor: 'rgb(127,255,0)' } ]; @@ -439,7 +450,7 @@ console.log("checking domain referenced shapes"); zip(getShapeNodes()[0].filter( sh=>sh.getAttribute('style').match( - /stroke: rgb\(0, 0, 0\)/)),domainRefShapes).forEach(function (args) { + /stroke: rgb\([01], [01], [01]\)/)),domainRefShapes).forEach(function (args) { var shapec_ = args[0], shapes = args[1]; var xref = shapes.xref.match(/([xyz][2-9]?).*$/)[1]; var yref = shapes.yref.match(/([xyz][2-9]?).*$/)[1]; @@ -540,7 +551,9 @@ function checkAnnotations () { console.log("Checking annotations"); // extract the annotations from the plot - var svgAnnos=getAnnotationNodes(); + var svgAnnos=getAnnotationNodes().filter( + sh=>sh.getAttribute('style').match( + /stroke: rgb\(0, 0, 0\)/)); var dataLine=d3.selectAll('.js-line').node(); var transCoords=getPlotTransformCoords(); // first compare ann1's y with y coordinate of point on plot @@ -555,6 +568,28 @@ } checkAnnotations(); +// Check annotation remains visibile when domain referenced and outside of the domain on a log axis +function checkLogDomainRefAnnotation() { + console.log("Checking domain referenced annotation on log axes"); + // extract the annotations from the plot + var svgAnnos=getAnnotationNodes().filter( + sh=>sh.getAttribute('style').match( + /stroke: rgb\(127, 255, 0\)/)); + var shapes = getShapeNodes()[0].filter( + sh=>sh.getAttribute('style').match( + /stroke: rgb\(1, 1, 1\)/)); + zip(shapes,svgAnnos).forEach(function (args) { + var shapec_ = args[0], anno = args[1]; + var xref = 'x2'; + var yref = 'x2'; + var shapec = svgShapeToCoords(shapec_.getAttribute('d')); + var annCoords=svgLineCoords(anno.getAttribute('d')); + console.log('x,y matches for anno', + coordsEq([annCoords[1]],[shapec[1]])); + }); +} +checkLogDomainRefAnnotation(); + //// TEST positions are still correct after relayout of axis window.setTimeout(function () { @@ -582,6 +617,7 @@ checkHlineVlineShapes(); checkHrectVrectShapes(); checkAnnotations(); + checkLogDomainRefAnnotation(); },2000); //// TEST You can drag an axis referenced shape to any place on the paper and it will @@ -589,9 +625,6 @@ //// TODO -//// TEST The shape stays the correct size when one or more of its points are outside -//// of a log-axis domain. -//// TODO From 2e4ed197d2ae7df68a439b773c4b40ca2bbb42cb Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 13 Aug 2020 14:39:33 -0400 Subject: [PATCH 033/112] Added tail to counterRegex Now will change the code to use this instead of AX_ID_EXTRAS_PATTERN. --- src/plots/cartesian/constants.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index 80534f8ccb3..b7eb9e10c60 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -12,8 +12,8 @@ var counterRegex = require('../../lib/regex').counter; module.exports = { idRegex: { - x: counterRegex('x'), - y: counterRegex('y') + x: counterRegex('x','( domain)?'), + y: counterRegex('y','( domain)?') }, attrRegex: counterRegex('[xy]axis'), From cf6e040aba5aa74cc61047a5225005d40efe4afb Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 13 Aug 2020 17:24:50 -0400 Subject: [PATCH 034/112] Use tail argument of counterRegex We use this to add the '( domain)?' part to the regex. This requires changing less code. --- src/components/annotations/attributes.js | 4 ++-- src/components/images/attributes.js | 4 ++-- src/plots/cartesian/constants.js | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 0bbd8a18e2b..380964d8b6e 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -317,7 +317,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() + cartesianConstants.idRegex.x.toString() ], role: 'info', editType: 'calc', @@ -376,7 +376,7 @@ module.exports = templatedArray('annotation', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() + cartesianConstants.idRegex.y.toString() ], role: 'info', editType: 'calc', diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index d037679e24d..a9cc3341019 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -144,7 +144,7 @@ module.exports = templatedArray('image', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() + cartesianConstants.idRegex.x.toString() ], dflt: 'paper', role: 'info', @@ -159,7 +159,7 @@ module.exports = templatedArray('image', { valType: 'enumerated', values: [ 'paper', - cartesianConstants.AX_ID_EXTRAS_PATTERN.toString() + cartesianConstants.idRegex.y.toString() ], dflt: 'paper', role: 'info', diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index b7eb9e10c60..d87aa5a44aa 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -26,7 +26,6 @@ module.exports = { // note that this is more permissive than counterRegex, as // id2name, name2id, and cleanId accept "x1" etc AX_ID_PATTERN: /^[xyz][0-9]*$/, - AX_ID_EXTRAS_PATTERN: /^[xyz]([2-9]|[1-9][0-9]+)?( domain)?$/, AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/, // and for 2D subplots From 6577c57bf86b414b5135ea57d0bd1adc1b52f058 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 14 Aug 2020 17:45:23 -0400 Subject: [PATCH 035/112] Reworked axis checking Use counterRegex with extra '( domain)?' tail argument. An extra argument to axes.CoerceRef is used to indicate we could have "domain" appended to axis references, and augments the list of axis ids with ones with "domain" appended. This mostly works but one of my test cases fails. --- src/components/annotations/defaults.js | 4 +--- src/components/images/defaults.js | 7 ++----- src/components/shapes/defaults.js | 5 ++--- src/plots/cartesian/axes.js | 22 ++++------------------ src/plots/cartesian/axis_ids.js | 3 ++- src/plots/cartesian/constants.js | 2 +- src/plots/cartesian/include_components.js | 11 +++++++++-- 7 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index c2795171230..fa0f73caecc 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -45,11 +45,9 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { for(var i = 0; i < 2; i++) { var axLetter = axLetters[i]; - var coerceRefExtras = ['paper']; - coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(annIn, axLetter, coerceRefExtras); // xref, yref - var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', coerceRefExtras); + var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, undefined, 'paper', true); var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); if(axRef !== 'paper') { diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 6feb8937904..2f7805908c7 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -49,11 +49,8 @@ function imageDefaults(imageIn, imageOut, fullLayout) { for(var i = 0; i < 2; i++) { // 'paper' is the fallback axref var axLetter = axLetters[i]; - var coerceRefExtras = ['paper']; - coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(imageIn, axLetter, - coerceRefExtras); - var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper', - coerceRefExtras); + var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, undefined, 'paper', + true); var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); if(axRef !== 'paper') { diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 09173ad779c..65016caaf78 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -61,11 +61,10 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var ax; var pos2r; var r2pos; - var coerceRefExtras = ['paper']; - coerceRefExtras = Axes.addAxRefDomainCoerceRefExtra(shapeIn, axLetter, coerceRefExtras); // xref, yref - var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', coerceRefExtras); + var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, + 'paper', true); var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); var axRefInfo = Axes.extractInfoFromAxisRef(axRef); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 8378bd31d41..1dadd2ab6ef 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -76,35 +76,21 @@ function expandRange(range) { * extraOption: aside from existing axes with this letter, what non-axis value is allowed? * Only required if it's different from `dflt` */ -axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) { +axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption, domainRef) { var axLetter = attr.charAt(attr.length - 1); var axlist = gd._fullLayout._subplots[axLetter + 'axis']; var refAttr = attr + 'ref'; var attrDef = {}; - if(!dflt) { - dflt = axlist[0] || ( - ((extraOption === undefined) || (typeof(extraOption) === 'string')) ? - extraOption : - extraOption[0] - ); - } - // so in the end, if dflt, axlist[], and extraOption were undefined, they - // are still undefined + if(!dflt) dflt = axlist[0] || extraOption; if(!extraOption) extraOption = dflt; + if(domainRef) axlist = axlist.concat([axlist[0]+' domain']); // data-ref annotations are not supported in gl2d yet - var extraOptionAsList = extraOption; - if(extraOption) { - if(typeof(extraOption) === 'string') { - extraOptionAsList = [extraOption]; - } - } - attrDef[refAttr] = { valType: 'enumerated', - values: axlist.concat(extraOptionAsList ? extraOptionAsList : []), + values: axlist.concat(extraOption ? [extraOption] : []), dflt: dflt }; diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 3d4caa17356..77b7756b8dd 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -19,7 +19,8 @@ var constants = require('./constants'); exports.id2name = function id2name(id) { if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; var axNum = id.substr(1); - if(axNum === '1') axNum = ''; + // axNum could be ' ' if domain specified and axis omitted number + if(axNum === '1' || axNum === ' ') axNum = ''; return id.charAt(0) + 'axis' + axNum; }; diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index d87aa5a44aa..e43389f6bf3 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -25,7 +25,7 @@ module.exports = { // pattern matching axis ids and names // note that this is more permissive than counterRegex, as // id2name, name2id, and cleanId accept "x1" etc - AX_ID_PATTERN: /^[xyz][0-9]*$/, + AX_ID_PATTERN: /^[xyz][0-9]*( domain)?$/, AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/, // and for 2D subplots diff --git a/src/plots/cartesian/include_components.js b/src/plots/cartesian/include_components.js index cd817b55464..bd6024a0ee8 100644 --- a/src/plots/cartesian/include_components.js +++ b/src/plots/cartesian/include_components.js @@ -35,6 +35,13 @@ module.exports = function makeIncludeComponents(containerArrayName) { var yaList = subplots.yaxis; var cartesianList = subplots.cartesian; var hasCartesianOrGL2D = layoutOut._has('cartesian') || layoutOut._has('gl2d'); + // Test if the axRef looks like "x", "x2", etc. and has nothing + // appended, e.g., this will return false if axRef "x2 domain". + var hasOnlyAxRef = function (axLetter,axRef) { + var axRefMatch = axRef.match(idRegex[axLetter]); + if (axRefMatch) { return axRefMatch[0].split(' ') === axRefMatch[0]; } + return false; + } for(var i = 0; i < array.length; i++) { var itemi = array[i]; @@ -43,8 +50,8 @@ module.exports = function makeIncludeComponents(containerArrayName) { var xref = itemi.xref; var yref = itemi.yref; - var hasXref = idRegex.x.test(xref); - var hasYref = idRegex.y.test(yref); + var hasXref = hasOnlyAxRef('x',xref); + var hasYref = hasOnlyAxRef('y',yref); if(hasXref || hasYref) { if(!hasCartesianOrGL2D) Lib.pushUnique(layoutOut._basePlotModules, Cartesian); From 4402dc7189eede65f0906038248ad93a3cdb130c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 17 Aug 2020 12:24:48 -0400 Subject: [PATCH 036/112] ' domain' appended to all items in axlist This means that a valid axis could be `axname + ' domain'` for all axes in the original axlist. --- src/plots/cartesian/axes.js | 4 +++- test/domain_ref_tests.html | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 1dadd2ab6ef..f69b63d31dc 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -75,6 +75,8 @@ function expandRange(range) { * extraOption if there is no axis) * extraOption: aside from existing axes with this letter, what non-axis value is allowed? * Only required if it's different from `dflt` + * domainRef: true if ' domain' should be appended to the axis items in the list + * of possible values for this axis reference. */ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption, domainRef) { var axLetter = attr.charAt(attr.length - 1); @@ -84,7 +86,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption if(!dflt) dflt = axlist[0] || extraOption; if(!extraOption) extraOption = dflt; - if(domainRef) axlist = axlist.concat([axlist[0]+' domain']); + if(domainRef) axlist = axlist.concat(axlist.map(function (x) { return x + ' domain'; })); // data-ref annotations are not supported in gl2d yet diff --git a/test/domain_ref_tests.html b/test/domain_ref_tests.html index f6d2a8ad3b4..f226942d715 100644 --- a/test/domain_ref_tests.html +++ b/test/domain_ref_tests.html @@ -584,6 +584,8 @@ var yref = 'x2'; var shapec = svgShapeToCoords(shapec_.getAttribute('d')); var annCoords=svgLineCoords(anno.getAttribute('d')); + // NOTE: This test depends on the position of shapes being correct, + // which is the case if the test for drawing shapes passes. console.log('x,y matches for anno', coordsEq([annCoords[1]],[shapec[1]])); }); From 896fbde88ffc965b122ee472d94b7163a5e08034 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 17 Aug 2020 12:54:28 -0400 Subject: [PATCH 037/112] Changed extractInfoFromAxisRef to getRefType getRefType returns one of 'range', 'paper', or 'domain' depending on the pattern of the axis reference. --- src/components/annotations/calc_autorange.js | 4 ++-- src/components/annotations/draw.js | 10 +++++----- src/components/shapes/calc_autorange.js | 4 ++-- src/components/shapes/defaults.js | 2 +- src/components/shapes/draw.js | 8 ++++---- src/plots/cartesian/axes.js | 16 ++++++++-------- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js index 5b2ad91330e..592a088da3f 100644 --- a/src/components/annotations/calc_autorange.js +++ b/src/components/annotations/calc_autorange.js @@ -34,8 +34,8 @@ function annAutorange(gd) { Lib.filterVisible(fullLayout.annotations).forEach(function(ann) { var xa = Axes.getFromId(gd, ann.xref); var ya = Axes.getFromId(gd, ann.yref); - var xrefInfo = Axes.extractInfoFromAxisRef(ann.xref); - var yrefInfo = Axes.extractInfoFromAxisRef(ann.yref); + var xrefInfo = Axes.getRefType(ann.xref); + var yrefInfo = Axes.getRefType(ann.yref); ann._extremes = {}; if(xa && (xrefInfo !== 'domain')) calcAxisExpansion(ann, xa); diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 9840b4a11a0..cf4df000115 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -316,7 +316,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var alignPosition; var autoAlignFraction; var textShift; - var axrefOpt = Axes.extractInfoFromAxisRef(axRef); + var axrefOpt = Axes.getRefType(axRef); /* * calculate the *primary* pixel position @@ -591,8 +591,8 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var ycenter = annxy0[1] + dy; annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); - var xAxOpt = Axes.extractInfoFromAxisRef(options.xref); - var yAxOpt = Axes.extractInfoFromAxisRef(options.yref); + var xAxOpt = Axes.getRefType(options.xref); + var yAxOpt = Axes.getRefType(options.yref); modifyItem('x', p2rR2p(xa, options.x, dx, gs.w, false, xAxOpt === 'domain')); modifyItem('y', @@ -642,8 +642,8 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { }, moveFn: function(dx, dy) { var csr = 'pointer'; - var xAxOpt = Axes.extractInfoFromAxisRef(options.xref); - var yAxOpt = Axes.extractInfoFromAxisRef(options.yref); + var xAxOpt = Axes.getRefType(options.xref); + var yAxOpt = Axes.getRefType(options.yref); if(options.showarrow) { // for these 2 calls to p2rR2p, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in diff --git a/src/components/shapes/calc_autorange.js b/src/components/shapes/calc_autorange.js index 2f2e866d1d7..a72714f020b 100644 --- a/src/components/shapes/calc_autorange.js +++ b/src/components/shapes/calc_autorange.js @@ -26,8 +26,8 @@ module.exports = function calcAutorange(gd) { shape._extremes = {}; var ax; var bounds; - var xrefInfo = Axes.extractInfoFromAxisRef(shape.xref); - var yrefInfo = Axes.extractInfoFromAxisRef(shape.yref); + var xrefInfo = Axes.getRefType(shape.xref); + var yrefInfo = Axes.getRefType(shape.yref); // paper and axis domain referenced shapes don't affect autorange if(shape.xref !== 'paper' && xrefInfo !== 'domain') { diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 65016caaf78..7c1695c8824 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -66,7 +66,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, 'paper', true); var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); - var axRefInfo = Axes.extractInfoFromAxisRef(axRef); + var axRefInfo = Axes.getRefType(axRef); if(axRef !== 'paper' && axRefInfo !== 'domain') { ax = Axes.getFromId(gdMock, axRefAxOnly); diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index cda709767c6..25c6b1119bc 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -212,9 +212,9 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe // setup conversion functions var xa = Axes.getFromId(gd, shapeOptions.xref); - var xrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.xref); + var xrefOpt = Axes.getRefType(shapeOptions.xref); var ya = Axes.getFromId(gd, shapeOptions.yref); - var yrefOpt = Axes.extractInfoFromAxisRef(shapeOptions.yref); + var yrefOpt = Axes.getRefType(shapeOptions.yref); var x2p = helpers.getDataToPixel(gd, xa, false, xrefOpt); var y2p = helpers.getDataToPixel(gd, ya, true, yrefOpt); var p2x = helpers.getPixelToData(gd, xa, false, xrefOpt); @@ -589,8 +589,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe function getPathString(gd, options) { var type = options.type; - var xrefOpt = Axes.extractInfoFromAxisRef(options.xref); - var yrefOpt = Axes.extractInfoFromAxisRef(options.yref); + var xrefOpt = Axes.getRefType(options.xref); + var yrefOpt = Axes.getRefType(options.yref); var xa = Axes.getFromId(gd, options.xref); var ya = Axes.getFromId(gd, options.yref); var gs = gd._fullLayout._size; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index f69b63d31dc..6525d34b83a 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -118,18 +118,18 @@ axes.extractAxisFromAxisRef = function(ar) { }; /* -/* - * An axis reference (e.g., the contents at the 'xref' key of an object) might - * have extra information appended. Extract the extra information. + * Get the type of an axis reference. This can be 'range', 'domain', or 'paper'. + * This assumes ar is a valid axis reference and returns 'range' if it doesn't + * match the patterns for 'paper' or 'domain'. * * ar: the axis reference string * */ -axes.extractInfoFromAxisRef = function(ar) { - if((ar !== undefined) && (typeof(ar) === 'string')) { - return ar.split(' ').slice(1).join(' '); - } - return ar; +axes.getRefType = function(ar) { + if (ar === undefined) { return ar; } + if (ar === 'paper') { return 'paper'; } + if (/( domain)$/.test(ar)) { return 'domain'; } + else { return 'range'; } }; /* From 647eef21c5759d282f72e4bf4e814ea434124d69 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 17 Aug 2020 13:31:16 -0400 Subject: [PATCH 038/112] Replaced Axes.extractAxisFromAxisRef with AxisIds.ref2id This makes more sense because the function manipulates axis IDs and not the axes themselves. Also assumptions are made that make the extraction of the axis ID simpler. --- src/components/annotations/defaults.js | 3 ++- src/components/images/defaults.js | 3 ++- src/components/shapes/defaults.js | 3 ++- src/plots/cartesian/axes.js | 17 ----------------- src/plots/cartesian/axis_ids.js | 17 +++++++++++++++++ 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index fa0f73caecc..0bd0bd6224f 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); +var AxisIds = require('../../plots/cartesian/axis_ids'); var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var handleAnnotationCommonDefaults = require('./common_defaults'); @@ -48,7 +49,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { // xref, yref var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, undefined, 'paper', true); - var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); + var axRefAxOnly = AxisIds.ref2id(axRef); if(axRef !== 'paper') { var ax = Axes.getFromId(gdMock, axRefAxOnly); diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 2f7805908c7..6c806ca0ff6 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -10,6 +10,7 @@ var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); +var AxisIds = require('../../plots/cartesian/axis_ids'); var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var attributes = require('./attributes'); @@ -51,7 +52,7 @@ function imageDefaults(imageIn, imageOut, fullLayout) { var axLetter = axLetters[i]; var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, undefined, 'paper', true); - var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); + var axRefAxOnly = AxisIds.ref2id(axRef); if(axRef !== 'paper') { var ax = Axes.getFromId(gdMock, axRefAxOnly); diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 7c1695c8824..6a37d8e39ef 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); +var AxisIds = require('../../plots/cartesian/axis_ids'); var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var attributes = require('./attributes'); @@ -65,7 +66,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { // xref, yref var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, 'paper', true); - var axRefAxOnly = Axes.extractAxisFromAxisRef(axRef); + var axRefAxOnly = AxisIds.ref2id(axRef); var axRefInfo = Axes.getRefType(axRef); if(axRef !== 'paper' && axRefInfo !== 'domain') { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 6525d34b83a..05737bdbd82 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -100,23 +100,6 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption return Lib.coerce(containerIn, containerOut, attrDef, refAttr); }; -/* - * An axis reference (e.g., the contents at the 'xref' key of an object) might - * have extra information appended. Extract the axis reference only. - * - * ar: the axis reference string - * - */ -axes.extractAxisFromAxisRef = function(ar) { - // TODO extractAxisFromAxisRef is more generic than this, this could be made - // more generic too - var mtch = (((ar !== undefined) && (typeof(ar) === 'string')) ? - ar.match(/^([xyz][0-9]*) domain/) : - undefined); - if(mtch) { return mtch[1]; } - return ar; -}; - /* * Get the type of an axis reference. This can be 'range', 'domain', or 'paper'. * This assumes ar is a valid axis reference and returns 'range' if it doesn't diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 77b7756b8dd..312aa70a86d 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -126,3 +126,20 @@ exports.getAxisGroup = function getAxisGroup(fullLayout, axId) { } return axId; }; + +/* + * An axis reference (e.g., the contents at the 'xref' key of an object) might + * have extra information appended. Extract the axis ID only. + * + * ar: the axis reference string + * + */ +exports.ref2id = function(ar) { + // This assumes ar has been coerced via coerceRef, and uses the shortcut of + // checking if the first letter matches [xyz] to determine if it should + // return the axis ID. Otherwise it returns false. + if(/^[xyz]/.test(ar)) { + return ar.split(' ')[0]; + } + return false; +}; From 2f625a702086e700107154f5b2f9cc7b3a715f65 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 18 Aug 2020 12:19:55 -0400 Subject: [PATCH 039/112] Added image test to domain_ref_tests.html --- test/domain_ref_tests.html | 97 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/test/domain_ref_tests.html b/test/domain_ref_tests.html index f226942d715..ab45fca3523 100644 --- a/test/domain_ref_tests.html +++ b/test/domain_ref_tests.html @@ -361,7 +361,33 @@ line: { color: 'rgb(255, 127, 80)' } }]; -layout.shapes=domainRefShapes.concat(hlineVlineShapes).concat(hrectVrectShapes); +var imageCheckingShapes = [ + // box around first image + { + type: 'rect', + xref: 'x2 domain', + yref: 'y2 domain', + x0: 0.25, + y0: 0.1, + x1: 0.45, + y1: 0.3, + line: { color: 'rgb(255, 255, 2)' } + }, + // box around second image + { + type: 'rect', + xref: 'x domain', + yref: 'y domain', + x0: 0.1, + y0: 0.25, + x1: 0.6, + y1: 0.75, + line: { color: 'rgb(255, 255, 3)' } + }, +]; + + +layout.shapes=domainRefShapes.concat(hlineVlineShapes).concat(hrectVrectShapes).concat(imageCheckingShapes); layout.annotations=[{ text: 'A', x: 0.75, @@ -376,7 +402,8 @@ x: 1, y: 0.75, xref: 'x', - yref: 'y domain', + // cleanId should correct this + yref: 'y1 domain', showarrow: true, arrowhead: 0, arrowcolor: 'rgb(0,0,0)' @@ -392,6 +419,30 @@ } ]; +layout.images=[{ + x: 0.25, + y: 0.1, + sizex: 0.2, + sizey: 0.2, + source: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Mysovskiy_Sergey_surfing_Nazare.jpg/1024px-Mysovskiy_Sergey_surfing_Nazare.jpg", + xanchor: "left", + xref: "x2 domain", + yanchor: "bottom", + yref: "y2 domain", + sizing: "stretch" +},{ + x: 0.1, + y: 0.25, + sizex: 0.5, + sizey: 0.5, + source: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Mysovskiy_Sergey_surfing_Nazare.jpg/1024px-Mysovskiy_Sergey_surfing_Nazare.jpg", + xanchor: "left", + xref: "x domain", + yanchor: "bottom", + yref: "y domain", + sizing: "stretch" +}] + Plotly.plot(TESTER, data, layout); function xyPairToBoxCoords(x,y,plotdims) { @@ -441,7 +492,6 @@ return r; } - //// TEST: Check the drawing of a bunch of shapes //// Draws shapes and checks their positioning against the axes' dimensions by //// extracting this information from the grid lines. @@ -498,6 +548,19 @@ return ret; } +function imageCoords(imageNode) { + var x = Number(imageNode.getAttribute('x')); + var y = Number(imageNode.getAttribute('y')); + var width = Number(imageNode.getAttribute('width')); + var height = Number(imageNode.getAttribute('height')); + // Change the corners of the rectangle to make it compatible with Plotly's + // rectangles so they can be compared. + var ret = [[x,y+height], + [x+width,y]]; + return ret; +} + + //// TEST: Check the drawing of lines that reference axis domains in 1 dimension //// and data in the other //// Strategy is to compare the coordinates that should equal data coordinates @@ -592,6 +655,33 @@ } checkLogDomainRefAnnotation(); +function checkImagePosition(imageIndex, shapeColor) { + // Check the position of an image by comparing its bounding box with the + // coordinates of a rectangle + // imageIndex: the index of the image when selecting using d3 + // shapeColor: the color of the shape that is the bounding box of the shape + + // escape () in shapeColor so it can be used in regexp + shapeColor = shapeColor.replaceAll(/[()]/g, "\\$&"); + var imageNode = d3.selectAll('.layer-above .imagelayer image')[0][imageIndex]; + var shapeNode = getShapeNodes()[0].filter( + sh=>sh.getAttribute('style').match( + RegExp('stroke: ' + shapeColor)))[0]; + var imageCrds = imageCoords(imageNode); + var shapeCrds = svgRectCoords(shapeNode.getAttribute('d')); + return coordsEq(imageCrds,shapeCrds); +} + +function imagePositionTest() { + ['rgb(255, 255, 2)', 'rgb(255, 255, 3)'].forEach( + function (c,i) { + console.log("Checking image position: ", + checkImagePosition(i,c)); + } + ); +} +imagePositionTest(); + //// TEST positions are still correct after relayout of axis window.setTimeout(function () { @@ -620,6 +710,7 @@ checkHrectVrectShapes(); checkAnnotations(); checkLogDomainRefAnnotation(); + imagePositionTest(); },2000); //// TEST You can drag an axis referenced shape to any place on the paper and it will From 9c68c6744d55b6118614b4e03c15edd1c404f3da Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 18 Aug 2020 12:21:19 -0400 Subject: [PATCH 040/112] cleanId accepts domain referencing IDs But the acceptance of domain referencing IDs is only enabled for certain elements, namely images, annotations, and shapes. So cleanAxRef has been adapted to pass the domain argument to cleanId and accept such elements. cleanAxRef is what cleans the IDs on these kinds of elements. Furthermore, cleanAxRef is called on image elements now in src/plot_api/helpers.js. --- src/plot_api/helpers.js | 14 ++++++++++++-- src/plots/cartesian/axis_ids.js | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 8ad571e466a..dc688ce8600 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -174,6 +174,16 @@ exports.cleanLayout = function(layout) { cleanAxRef(shape, 'yref'); } + var imagesLen = Array.isArray(layout.images) ? layout.images.length : 0; + for(i = 0; i < imagesLen; i++) { + var image = layout.images[i]; + + if(!Lib.isPlainObject(image)) continue; + + cleanAxRef(image, 'xref'); + cleanAxRef(image, 'yref'); + } + var legend = layout.legend; if(legend) { // check for old-style legend positioning (x or y is +/- 100) @@ -217,8 +227,8 @@ exports.cleanLayout = function(layout) { function cleanAxRef(container, attr) { var valIn = container[attr]; var axLetter = attr.charAt(0); - if(valIn && valIn !== 'paper' && !valIn.endsWith('domain')) { - container[attr] = cleanId(valIn, axLetter); + if(valIn && valIn !== 'paper') { + container[attr] = cleanId(valIn, axLetter, true); } } diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 312aa70a86d..a5758287134 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -31,12 +31,21 @@ exports.name2id = function name2id(name) { return name.charAt(0) + axNum; }; -exports.cleanId = function cleanId(id, axLetter) { +/* + * Cleans up the number of an axis, e.g., 'x002'->'x2', 'x0'->'x', 'x1' -> 'x', + * etc. + * If domainId is true, then id could be a domain reference and if it is, the + * ' domain' part is kept at the end of the axis ID string. + */ +exports.cleanId = function cleanId(id, axLetter, domainId) { if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; if(axLetter && id.charAt(0) !== axLetter) return; - - var axNum = id.substr(1).replace(/^0+/, ''); + if(/( domain)$/.test(id) && (!domainId)) return; + var axNum = id.split(' ')[0].substr(1).replace(/^0+/, ''); if(axNum === '1') axNum = ''; + if (/( domain)$/.test(id) && domainId) { + return id.charAt(0) + axNum + ' domain'; + } return id.charAt(0) + axNum; }; From 57c25bdddccadc11a9b0659a7d3a2da132720c58 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 18 Aug 2020 12:41:36 -0400 Subject: [PATCH 041/112] Doc string made ES5 compatible --- src/constants/axis_placeable_objects.js | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/constants/axis_placeable_objects.js b/src/constants/axis_placeable_objects.js index 2e452bed7ab..543868dcb20 100644 --- a/src/constants/axis_placeable_objects.js +++ b/src/constants/axis_placeable_objects.js @@ -12,19 +12,19 @@ module.exports = { axisRefDescription: function (axisname,lower,upper) { return [ - `If set to a ${axisname} axis id (e.g. *${axisname}* or`, - `*${axisname}2*), the \`${axisname}\` position refers to a`, - `${axisname} coordinate. If set to *paper*, the \`${axisname}\``, - `position refers to the distance from the ${lower} of the plotting`, - `area in normalized coordinates where *0* (*1*) corresponds to the`, - `${lower} (${upper}). If set to a ${axisname} axis ID followed by`, - `*domain* (separated by a space), the position behaves like for`, - `*paper*, but refers to the distance in fractions of the domain`, - `length from the ${lower} of the domain of that axis: e.g.,`, - `*${axisname}2 domain* refers to the domain of the second`, - `${axisname} axis and a ${axisname} position of 0.5 refers to the`, - `point between the ${lower} and the ${upper} of the domain of the`, - `second ${axisname} axis.`, + 'If set to a ', axisname, ' axis id (e.g. *', axisname, '* or', + '*', axisname, '2*), the `', axisname, '` position refers to a', + '', axisname, ' coordinate. If set to *paper*, the `', axisname, '`', + 'position refers to the distance from the ', lower, ' of the plotting', + 'area in normalized coordinates where *0* (*1*) corresponds to the', + '', lower, ' (', upper, '). If set to a ', axisname, ' axis ID followed by', + '*domain* (separated by a space), the position behaves like for', + '*paper*, but refers to the distance in fractions of the domain', + 'length from the ', lower, ' of the domain of that axis: e.g.,', + '*', axisname, '2 domain* refers to the domain of the second', + '', axisname, ' axis and a ', axisname, ' position of 0.5 refers to the', + 'point between the ', lower, ' and the ', upper, ' of the domain of the', + 'second ', axisname, ' axis.', ].join(' '); } } From dda98a7a5b9393e14fc2d7b84d9de603af3cde73 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 18 Aug 2020 15:23:54 -0400 Subject: [PATCH 042/112] Annotation arrows can be placed like in data coordinates This works now with 'paper' and 'domain' axis reference types. However, the annotations are not yet properly moveable in with 'paper' axis reference types. Note that the placement is absolute, not relative as when 'axref' or 'ayref' are set to 'pixel'. This is consistent with how these already work when the arrow's axis reference is set to data coordinates. For this to work, the arrow's xref and yref (i.e., axref, ayref) must be set to exactly the same thing as the annotation's xref and yref (xref, yref), otherwise arrow's corresponding axis reference will fall back to 'pixel' and be specified relative to the annotation's position. This is constent with how this already works with data coordinates. --- src/components/annotations/defaults.js | 10 ++--- src/components/annotations/draw.js | 59 +++++++++++++++++--------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 0bd0bd6224f..118ec06b9c6 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -49,25 +49,25 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { // xref, yref var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, undefined, 'paper', true); - var axRefAxOnly = AxisIds.ref2id(axRef); if(axRef !== 'paper') { - var ax = Axes.getFromId(gdMock, axRefAxOnly); + var ax = Axes.getFromId(gdMock, axRef); ax._annIndices.push(annOut._index); } // x, y - Axes.coercePosition(annOut, gdMock, coerce, axRefAxOnly, axLetter, 0.5); + Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5); if(showArrow) { var arrowPosAttr = 'a' + axLetter; // axref, ayref - var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel'); + var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel', + 'paper', true); // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis // but that would require updates to drawing & autorange code and maybe more - if(aaxRef !== 'pixel' && aaxRef !== axRefAxOnly) { + if(aaxRef !== 'pixel' && aaxRef !== axRef) { aaxRef = annOut[arrowPosAttr] = 'pixel'; } diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index cf4df000115..d0086eebd23 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -78,7 +78,7 @@ function drawOne(gd, index) { // the plot. // axDomainRef: if true and axa defined, draws relative to axis domain, // otherwise draws relative to data (if axa defined) or paper (if not). -function p2rR2p(axa, optAx, dAx, gsDim, vertical, axDomainRef) { +function shiftPosition(axa, optAx, dAx, gsDim, vertical, axDomainRef) { if(axa) { if(axDomainRef) { // here optAx normalized to length of axis (e.g., normally in range @@ -316,14 +316,14 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var alignPosition; var autoAlignFraction; var textShift; - var axrefOpt = Axes.getRefType(axRef); + var axRefType = Axes.getRefType(axRef); /* * calculate the *primary* pixel position * which is the arrowhead if there is one, * otherwise the text anchor point */ - if(ax && (axrefOpt !== 'domain')) { + if(ax && (axRefType !== 'domain')) { // check if annotation is off screen, to bypass DOM manipulations var posFraction = ax.r2fraction(options[axLetter]); if(posFraction < 0 || posFraction > 1) { @@ -341,14 +341,14 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } else { if(axLetter === 'x') { alignPosition = options[axLetter]; - if(axrefOpt === 'domain') { + if(axRefType === 'domain') { basePx = ax._offset + ax._length * alignPosition; } else { basePx = gs.l + gs.w * alignPosition; } } else { alignPosition = 1 - options[axLetter]; - if(axrefOpt === 'domain') { + if(axRefType === 'domain') { basePx = ax._offset + ax._length * alignPosition; } else { basePx = gs.t + gs.h * alignPosition; @@ -369,8 +369,29 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { annSizeFromHeight * shiftFraction(0.5, options.yanchor); if(tailRef === axRef) { - posPx.tail = ax._offset + ax.r2p(arrowLength); - // tail is data-referenced: autorange pads the text in px from the tail + // In the case tailRefType is 'domain' or 'paper', the arrow's + // position is set absolutely, which is consistent with how + // it behaves when its position is set in data ('range') + // coordinates. + var tailRefType = Axes.getRefType(tailRef); + if (tailRefType === 'domain') { + if (axLetter === 'y') { + arrowLength = 1 - arrowLength; + } + posPx.tail = ax._offset + ax._length * arrowLength; + } else if (tailRefType === 'paper') { + if (axLetter == 'y') { + arrowLength = 1 - arrowLength; + posPx.tail = gs.t + gs.h * arrowLength; + } else { + posPx.tail = gs.l + gs.w * arrowLength; + } + } else { + // assumed tailRef is range or paper referenced + posPx.tail = ax._offset + ax.r2p(arrowLength); + } + // tail is range- or domain-referenced: autorange pads the + // text in px from the tail textPadShift = textShift; } else { posPx.tail = basePx + arrowLength; @@ -594,20 +615,20 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var xAxOpt = Axes.getRefType(options.xref); var yAxOpt = Axes.getRefType(options.yref); modifyItem('x', - p2rR2p(xa, options.x, dx, gs.w, false, xAxOpt === 'domain')); + shiftPosition(xa, options.x, dx, gs.w, false, xAxOpt === 'domain')); modifyItem('y', - p2rR2p(ya, options.y, dy, gs.h, true, yAxOpt === 'domain')); + shiftPosition(ya, options.y, dy, gs.h, true, yAxOpt === 'domain')); - // for these 2 calls to p2rR2p, it is assumed xa, ya are + // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', p2rR2p(xa, options.ax, dx, gs.h, false, + modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.h, false, xAxOpt === 'domain')); } if(options.ayref === options.yref) { - modifyItem('ay', p2rR2p(ya, options.ay, dy, gs.w, true, + modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.w, true, yAxOpt === 'domain')); } @@ -645,18 +666,18 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var xAxOpt = Axes.getRefType(options.xref); var yAxOpt = Axes.getRefType(options.yref); if(options.showarrow) { - // for these 2 calls to p2rR2p, it is assumed xa, ya are + // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', p2rR2p(xa, options.ax, dx, gs.h, false, + modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.h, false, xAxOpt === 'domain')); } else { modifyItem('ax', options.ax + dx); } if(options.ayref === options.yref) { - modifyItem('ay', p2rR2p(ya, options.ay, dy, gs.w, true, + modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.w, true, yAxOpt === 'domain')); } else { modifyItem('ay', options.ay + dy); @@ -666,9 +687,9 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } else if(!subplotId) { var xUpdate, yUpdate; if(xa) { - // p2rR2p will not execute code where xa was + // shiftPosition will not execute code where xa was // undefined, so we use to calculate xUpdate too - xUpdate = p2rR2p(xa, options.x, dx, gs.h, false, + xUpdate = shiftPosition(xa, options.x, dx, gs.h, false, xAxOpt === 'domain'); } else { var widthFraction = options._xsize / gs.w; @@ -679,9 +700,9 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } if(ya) { - // p2rR2p will not execute code where ya was + // shiftPosition will not execute code where ya was // undefined, so we use to calculate yUpdate too - yUpdate = p2rR2p(ya, options.y, dy, gs.w, true, + yUpdate = shiftPosition(ya, options.y, dy, gs.w, true, yAxOpt === 'domain'); } else { var heightFraction = options._ysize / gs.h; From 4972a2bac19fecc1434969e1508ae997ffe84121 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 18 Aug 2020 15:55:07 -0400 Subject: [PATCH 043/112] Width and height were switch when moving in paper coordinates! That means we definitely need tests for the editable case. --- src/components/annotations/draw.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index d0086eebd23..901bf38c044 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -623,12 +623,12 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.h, false, + modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.w, false, xAxOpt === 'domain')); } if(options.ayref === options.yref) { - modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.w, true, + modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.h, true, yAxOpt === 'domain')); } From e2b440f46560b9a8ad2bcde7ad871f07c81daaec Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 18 Aug 2020 17:29:16 -0400 Subject: [PATCH 044/112] More explicit selection based on axis type in calc_autorange --- src/components/annotations/calc_autorange.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js index 592a088da3f..96d2964255f 100644 --- a/src/components/annotations/calc_autorange.js +++ b/src/components/annotations/calc_autorange.js @@ -38,8 +38,8 @@ function annAutorange(gd) { var yrefInfo = Axes.getRefType(ann.yref); ann._extremes = {}; - if(xa && (xrefInfo !== 'domain')) calcAxisExpansion(ann, xa); - if(ya && (yrefInfo !== 'domain')) calcAxisExpansion(ann, ya); + if(xRefType === 'range') calcAxisExpansion(ann, xa); + if(yRefType === 'range') calcAxisExpansion(ann, ya); }); } From 167da289c15e4db89e09a348a2cdc426bb5aeeb9 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 18 Aug 2020 18:00:28 -0400 Subject: [PATCH 045/112] Use RefType variable naming convention now In all the places that call Axes.getRefType --- src/components/annotations/calc_autorange.js | 4 ++-- src/components/annotations/draw.js | 24 ++++++++++---------- src/components/shapes/calc_autorange.js | 8 +++---- src/components/shapes/defaults.js | 4 ++-- src/components/shapes/draw.js | 20 ++++++++-------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js index 96d2964255f..2c598b7ba48 100644 --- a/src/components/annotations/calc_autorange.js +++ b/src/components/annotations/calc_autorange.js @@ -34,8 +34,8 @@ function annAutorange(gd) { Lib.filterVisible(fullLayout.annotations).forEach(function(ann) { var xa = Axes.getFromId(gd, ann.xref); var ya = Axes.getFromId(gd, ann.yref); - var xrefInfo = Axes.getRefType(ann.xref); - var yrefInfo = Axes.getRefType(ann.yref); + var xRefType = Axes.getRefType(ann.xref); + var yRefType = Axes.getRefType(ann.yref); ann._extremes = {}; if(xRefType === 'range') calcAxisExpansion(ann, xa); diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 901bf38c044..3905780101c 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -612,24 +612,24 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var ycenter = annxy0[1] + dy; annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); - var xAxOpt = Axes.getRefType(options.xref); - var yAxOpt = Axes.getRefType(options.yref); + var xRefType = Axes.getRefType(options.xref); + var yRefType = Axes.getRefType(options.yref); modifyItem('x', - shiftPosition(xa, options.x, dx, gs.w, false, xAxOpt === 'domain')); + shiftPosition(xa, options.x, dx, gs.w, false, xRefType === 'domain')); modifyItem('y', - shiftPosition(ya, options.y, dy, gs.h, true, yAxOpt === 'domain')); + shiftPosition(ya, options.y, dy, gs.h, true, yRefType === 'domain')); // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.w, false, - xAxOpt === 'domain')); + xRefType === 'domain')); } if(options.ayref === options.yref) { modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.h, true, - yAxOpt === 'domain')); + yRefType === 'domain')); } arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); @@ -663,22 +663,22 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { }, moveFn: function(dx, dy) { var csr = 'pointer'; - var xAxOpt = Axes.getRefType(options.xref); - var yAxOpt = Axes.getRefType(options.yref); + var xRefType = Axes.getRefType(options.xref); + var yRefType = Axes.getRefType(options.yref); if(options.showarrow) { // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.h, false, - xAxOpt === 'domain')); + xRefType === 'domain')); } else { modifyItem('ax', options.ax + dx); } if(options.ayref === options.yref) { modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.w, true, - yAxOpt === 'domain')); + yRefType === 'domain')); } else { modifyItem('ay', options.ay + dy); } @@ -690,7 +690,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // shiftPosition will not execute code where xa was // undefined, so we use to calculate xUpdate too xUpdate = shiftPosition(xa, options.x, dx, gs.h, false, - xAxOpt === 'domain'); + xRefType === 'domain'); } else { var widthFraction = options._xsize / gs.w; var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2; @@ -703,7 +703,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // shiftPosition will not execute code where ya was // undefined, so we use to calculate yUpdate too yUpdate = shiftPosition(ya, options.y, dy, gs.w, true, - yAxOpt === 'domain'); + yRefType === 'domain'); } else { var heightFraction = options._ysize / gs.h; var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2; diff --git a/src/components/shapes/calc_autorange.js b/src/components/shapes/calc_autorange.js index a72714f020b..b9e022b3b84 100644 --- a/src/components/shapes/calc_autorange.js +++ b/src/components/shapes/calc_autorange.js @@ -26,11 +26,11 @@ module.exports = function calcAutorange(gd) { shape._extremes = {}; var ax; var bounds; - var xrefInfo = Axes.getRefType(shape.xref); - var yrefInfo = Axes.getRefType(shape.yref); + var xRefType = Axes.getRefType(shape.xref); + var yRefType = Axes.getRefType(shape.yref); // paper and axis domain referenced shapes don't affect autorange - if(shape.xref !== 'paper' && xrefInfo !== 'domain') { + if(shape.xref !== 'paper' && xRefType !== 'domain') { var vx0 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x0; var vx1 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x1; ax = Axes.getFromId(gd, shape.xref); @@ -41,7 +41,7 @@ module.exports = function calcAutorange(gd) { } } - if(shape.yref !== 'paper' && yrefInfo !== 'domain') { + if(shape.yref !== 'paper' && yRefType !== 'domain') { var vy0 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y0; var vy1 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y1; ax = Axes.getFromId(gd, shape.yref); diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 6a37d8e39ef..38999e919a6 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -67,9 +67,9 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, 'paper', true); var axRefAxOnly = AxisIds.ref2id(axRef); - var axRefInfo = Axes.getRefType(axRef); + var axRefType = Axes.getRefType(axRef); - if(axRef !== 'paper' && axRefInfo !== 'domain') { + if(axRef !== 'paper' && axRefType !== 'domain') { ax = Axes.getFromId(gdMock, axRefAxOnly); ax._shapeIndices.push(shapeOut._index); r2pos = helpers.rangeToShapePosition(ax); diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 25c6b1119bc..14f3faef6f7 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -212,13 +212,13 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe // setup conversion functions var xa = Axes.getFromId(gd, shapeOptions.xref); - var xrefOpt = Axes.getRefType(shapeOptions.xref); + var xRefType = Axes.getRefType(shapeOptions.xref); var ya = Axes.getFromId(gd, shapeOptions.yref); - var yrefOpt = Axes.getRefType(shapeOptions.yref); - var x2p = helpers.getDataToPixel(gd, xa, false, xrefOpt); - var y2p = helpers.getDataToPixel(gd, ya, true, yrefOpt); - var p2x = helpers.getPixelToData(gd, xa, false, xrefOpt); - var p2y = helpers.getPixelToData(gd, ya, true, yrefOpt); + var yRefType = Axes.getRefType(shapeOptions.yref); + var x2p = helpers.getDataToPixel(gd, xa, false, xRefType); + var y2p = helpers.getDataToPixel(gd, ya, true, yRefType); + var p2x = helpers.getPixelToData(gd, xa, false, xRefType); + var p2y = helpers.getPixelToData(gd, ya, true, yRefType); var sensoryElement = obtainSensoryElement(); var dragOptions = { @@ -589,8 +589,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe function getPathString(gd, options) { var type = options.type; - var xrefOpt = Axes.getRefType(options.xref); - var yrefOpt = Axes.getRefType(options.yref); + var xRefType = Axes.getRefType(options.xref); + var yRefType = Axes.getRefType(options.yref); var xa = Axes.getFromId(gd, options.xref); var ya = Axes.getFromId(gd, options.yref); var gs = gd._fullLayout._size; @@ -598,7 +598,7 @@ function getPathString(gd, options) { var x0, x1, y0, y1; if(xa) { - if(xrefOpt === 'domain') { + if(xRefType === 'domain') { x2p = function(v) { return xa._offset + xa._length * v; }; } else { x2r = helpers.shapePositionToRange(xa); @@ -609,7 +609,7 @@ function getPathString(gd, options) { } if(ya) { - if(yrefOpt === 'domain') { + if(yRefType === 'domain') { y2p = function(v) { return ya._offset + ya._length * (1 - v); }; } else { y2r = helpers.shapePositionToRange(ya); From 2b29d194ce778cd34531d7afcbb9bc6e3acedb92 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 21 Aug 2020 10:03:18 -0400 Subject: [PATCH 046/112] Code linting --- src/components/annotations/attributes.js | 4 ++-- src/components/annotations/draw.js | 8 ++++---- src/components/images/attributes.js | 4 ++-- src/components/shapes/attributes.js | 4 ++-- src/constants/axis_placeable_objects.js | 4 ++-- src/plots/cartesian/axes.js | 9 ++++----- src/plots/cartesian/axis_ids.js | 2 +- src/plots/cartesian/constants.js | 4 ++-- src/plots/cartesian/include_components.js | 10 +++++----- 9 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 380964d8b6e..8ea67e4ab2e 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -323,7 +323,7 @@ module.exports = templatedArray('annotation', { editType: 'calc', description: [ 'Sets the annotation\'s x coordinate axis.', - axisPlaceableObjs.axisRefDescription('x','left','right'), + axisPlaceableObjs.axisRefDescription('x', 'left', 'right'), ].join(' ') }, x: { @@ -382,7 +382,7 @@ module.exports = templatedArray('annotation', { editType: 'calc', description: [ 'Sets the annotation\'s y coordinate axis.', - axisPlaceableObjs.axisRefDescription('y','bottom','top'), + axisPlaceableObjs.axisRefDescription('y', 'bottom', 'top'), ].join(' ') }, y: { diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 3905780101c..7bda259b614 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -374,13 +374,13 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // it behaves when its position is set in data ('range') // coordinates. var tailRefType = Axes.getRefType(tailRef); - if (tailRefType === 'domain') { - if (axLetter === 'y') { + if(tailRefType === 'domain') { + if(axLetter === 'y') { arrowLength = 1 - arrowLength; } posPx.tail = ax._offset + ax._length * arrowLength; - } else if (tailRefType === 'paper') { - if (axLetter == 'y') { + } else if(tailRefType === 'paper') { + if(axLetter == 'y') { arrowLength = 1 - arrowLength; posPx.tail = gs.t + gs.h * arrowLength; } else { diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index a9cc3341019..0e916396345 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -151,7 +151,7 @@ module.exports = templatedArray('image', { editType: 'arraydraw', description: [ 'Sets the images\'s x coordinate axis.', - axisPlaceableObjs.axisRefDescription('x','left','right'), + axisPlaceableObjs.axisRefDescription('x', 'left', 'right'), ].join(' ') }, @@ -166,7 +166,7 @@ module.exports = templatedArray('image', { editType: 'arraydraw', description: [ 'Sets the images\'s y coordinate axis.', - axisPlaceableObjs.axisRefDescription('y','bottom','top'), + axisPlaceableObjs.axisRefDescription('y', 'bottom', 'top'), ].join(' ') }, editType: 'arraydraw' diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index cd3222c80e6..1bf9fef20b4 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -64,7 +64,7 @@ module.exports = templatedArray('shape', { xref: extendFlat({}, annAttrs.xref, { description: [ 'Sets the shape\'s x coordinate axis.', - axisPlaceableObjs.axisRefDescription('x','left','right'), + axisPlaceableObjs.axisRefDescription('x', 'left', 'right'), 'If the axis `type` is *log*, then you must take the', 'log of your desired range.', 'If the axis `type` is *date*, then you must convert', @@ -123,7 +123,7 @@ module.exports = templatedArray('shape', { yref: extendFlat({}, annAttrs.yref, { description: [ 'Sets the annotation\'s y coordinate axis.', - axisPlaceableObjs.axisRefDescription('y','bottom','top'), + axisPlaceableObjs.axisRefDescription('y', 'bottom', 'top'), ].join(' ') }), ysizemode: { diff --git a/src/constants/axis_placeable_objects.js b/src/constants/axis_placeable_objects.js index 543868dcb20..32f2dd50dec 100644 --- a/src/constants/axis_placeable_objects.js +++ b/src/constants/axis_placeable_objects.js @@ -10,7 +10,7 @@ 'use strict'; module.exports = { - axisRefDescription: function (axisname,lower,upper) { + axisRefDescription: function(axisname, lower, upper) { return [ 'If set to a ', axisname, ' axis id (e.g. *', axisname, '* or', '*', axisname, '2*), the `', axisname, '` position refers to a', @@ -27,4 +27,4 @@ module.exports = { 'second ', axisname, ' axis.', ].join(' '); } -} +}; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 05737bdbd82..733d388b274 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -86,7 +86,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption if(!dflt) dflt = axlist[0] || extraOption; if(!extraOption) extraOption = dflt; - if(domainRef) axlist = axlist.concat(axlist.map(function (x) { return x + ' domain'; })); + if(domainRef) axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); // data-ref annotations are not supported in gl2d yet @@ -109,10 +109,9 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption * */ axes.getRefType = function(ar) { - if (ar === undefined) { return ar; } - if (ar === 'paper') { return 'paper'; } - if (/( domain)$/.test(ar)) { return 'domain'; } - else { return 'range'; } + if(ar === undefined) { return ar; } + if(ar === 'paper') { return 'paper'; } + if(/( domain)$/.test(ar)) { return 'domain'; } else { return 'range'; } }; /* diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index a5758287134..781b6aad1cd 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -43,7 +43,7 @@ exports.cleanId = function cleanId(id, axLetter, domainId) { if(/( domain)$/.test(id) && (!domainId)) return; var axNum = id.split(' ')[0].substr(1).replace(/^0+/, ''); if(axNum === '1') axNum = ''; - if (/( domain)$/.test(id) && domainId) { + if(/( domain)$/.test(id) && domainId) { return id.charAt(0) + axNum + ' domain'; } return id.charAt(0) + axNum; diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index e43389f6bf3..c6a523fe2df 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -12,8 +12,8 @@ var counterRegex = require('../../lib/regex').counter; module.exports = { idRegex: { - x: counterRegex('x','( domain)?'), - y: counterRegex('y','( domain)?') + x: counterRegex('x', '( domain)?'), + y: counterRegex('y', '( domain)?') }, attrRegex: counterRegex('[xy]axis'), diff --git a/src/plots/cartesian/include_components.js b/src/plots/cartesian/include_components.js index bd6024a0ee8..b1f08ca9253 100644 --- a/src/plots/cartesian/include_components.js +++ b/src/plots/cartesian/include_components.js @@ -37,11 +37,11 @@ module.exports = function makeIncludeComponents(containerArrayName) { var hasCartesianOrGL2D = layoutOut._has('cartesian') || layoutOut._has('gl2d'); // Test if the axRef looks like "x", "x2", etc. and has nothing // appended, e.g., this will return false if axRef "x2 domain". - var hasOnlyAxRef = function (axLetter,axRef) { + var hasOnlyAxRef = function(axLetter, axRef) { var axRefMatch = axRef.match(idRegex[axLetter]); - if (axRefMatch) { return axRefMatch[0].split(' ') === axRefMatch[0]; } + if(axRefMatch) { return axRefMatch[0].split(' ') === axRefMatch[0]; } return false; - } + }; for(var i = 0; i < array.length; i++) { var itemi = array[i]; @@ -50,8 +50,8 @@ module.exports = function makeIncludeComponents(containerArrayName) { var xref = itemi.xref; var yref = itemi.yref; - var hasXref = hasOnlyAxRef('x',xref); - var hasYref = hasOnlyAxRef('y',yref); + var hasXref = hasOnlyAxRef('x', xref); + var hasYref = hasOnlyAxRef('y', yref); if(hasXref || hasYref) { if(!hasCartesianOrGL2D) Lib.pushUnique(layoutOut._basePlotModules, Cartesian); From 416cc3b0699c57dbc825ac46cec7cc3a946c9b19 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 21 Aug 2020 10:16:42 -0400 Subject: [PATCH 047/112] More linting --- src/components/annotations/defaults.js | 1 - src/components/annotations/draw.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 118ec06b9c6..abc3c004659 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -11,7 +11,6 @@ var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); -var AxisIds = require('../../plots/cartesian/axis_ids'); var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var handleAnnotationCommonDefaults = require('./common_defaults'); diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 7bda259b614..0cc3fdebea9 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -380,7 +380,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } posPx.tail = ax._offset + ax._length * arrowLength; } else if(tailRefType === 'paper') { - if(axLetter == 'y') { + if(axLetter === 'y') { arrowLength = 1 - arrowLength; posPx.tail = gs.t + gs.h * arrowLength; } else { From 1d06c4f24300de17e4d3195edbf0113f77283182 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 21 Aug 2020 11:19:38 -0400 Subject: [PATCH 048/112] No need to use axRefAxOnly anymore --- src/components/shapes/defaults.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 38999e919a6..46519e006b8 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -11,7 +11,6 @@ var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); -var AxisIds = require('../../plots/cartesian/axis_ids'); var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var attributes = require('./attributes'); @@ -66,11 +65,10 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { // xref, yref var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, 'paper', true); - var axRefAxOnly = AxisIds.ref2id(axRef); var axRefType = Axes.getRefType(axRef); - if(axRef !== 'paper' && axRefType !== 'domain') { - ax = Axes.getFromId(gdMock, axRefAxOnly); + if(axRefType === 'range') { + ax = Axes.getFromId(gdMock, axRef); ax._shapeIndices.push(shapeOut._index); r2pos = helpers.rangeToShapePosition(ax); pos2r = helpers.shapePositionToRange(ax); @@ -98,8 +96,8 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { coerce(attr0, 0); coerce(attr1, 10); } else { - Axes.coercePosition(shapeOut, gdMock, coerce, axRefAxOnly, attr0, dflt0); - Axes.coercePosition(shapeOut, gdMock, coerce, axRefAxOnly, attr1, dflt1); + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0); + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1); } // hack part 2 @@ -115,7 +113,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var inAnchor = shapeIn[attrAnchor]; shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true); - Axes.coercePosition(shapeOut, gdMock, coerce, axRefAxOnly, attrAnchor, 0.25); + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attrAnchor, 0.25); // Hack part 2 shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]); From a588f1eb229a622064efe581a5a9e96bf740b41f Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 21 Aug 2020 12:00:17 -0400 Subject: [PATCH 049/112] hasOnlyAxRef returns false if axRef is falsy --- src/plots/cartesian/include_components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/cartesian/include_components.js b/src/plots/cartesian/include_components.js index b1f08ca9253..20c67e9a6df 100644 --- a/src/plots/cartesian/include_components.js +++ b/src/plots/cartesian/include_components.js @@ -38,7 +38,7 @@ module.exports = function makeIncludeComponents(containerArrayName) { // Test if the axRef looks like "x", "x2", etc. and has nothing // appended, e.g., this will return false if axRef "x2 domain". var hasOnlyAxRef = function(axLetter, axRef) { - var axRefMatch = axRef.match(idRegex[axLetter]); + var axRefMatch = axRef ? axRef.match(idRegex[axLetter]) : false; if(axRefMatch) { return axRefMatch[0].split(' ') === axRefMatch[0]; } return false; }; From e405e5bbdfbff3d64e89f859df6ded4c3f37806c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 21 Aug 2020 13:33:33 -0400 Subject: [PATCH 050/112] image's coercePosition can just use axRef directly --- src/components/images/defaults.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 6c806ca0ff6..05ed486e447 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -52,14 +52,13 @@ function imageDefaults(imageIn, imageOut, fullLayout) { var axLetter = axLetters[i]; var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, undefined, 'paper', true); - var axRefAxOnly = AxisIds.ref2id(axRef); if(axRef !== 'paper') { - var ax = Axes.getFromId(gdMock, axRefAxOnly); + var ax = Axes.getFromId(gdMock, axRef); ax._imgIndices.push(imageOut._index); } - Axes.coercePosition(imageOut, gdMock, coerce, axRefAxOnly, axLetter, 0); + Axes.coercePosition(imageOut, gdMock, coerce, axRef, axLetter, 0); } return imageOut; From 63cbc298526d0212ceee563c551790505f8ae0a2 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 21 Aug 2020 16:31:48 -0400 Subject: [PATCH 051/112] Maintain 'paper' as image default axis reference --- src/components/images/defaults.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 05ed486e447..5f3ea0d8503 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -10,7 +10,6 @@ var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); -var AxisIds = require('../../plots/cartesian/axis_ids'); var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var attributes = require('./attributes'); @@ -50,7 +49,7 @@ function imageDefaults(imageIn, imageOut, fullLayout) { for(var i = 0; i < 2; i++) { // 'paper' is the fallback axref var axLetter = axLetters[i]; - var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, undefined, 'paper', + var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper', undefined, true); if(axRef !== 'paper') { From 4bb72c10d47a8c8d4e29f9a0fccb5a5fa78fbb9f Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 24 Aug 2020 20:20:16 -0400 Subject: [PATCH 052/112] Passes test_annotations now We'll probably have to change src/plots/cartesian/include_components.js so that it only adds the axis ID without ' domain' appended to the subplot list. --- src/components/annotations/defaults.js | 4 ++-- src/plots/cartesian/axes.js | 1 + src/plots/cartesian/include_components.js | 11 ++--------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index abc3c004659..3a331be495b 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -47,7 +47,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { var axLetter = axLetters[i]; // xref, yref - var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, undefined, 'paper', true); + var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper', true); if(axRef !== 'paper') { var ax = Axes.getFromId(gdMock, axRef); @@ -61,7 +61,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { var arrowPosAttr = 'a' + axLetter; // axref, ayref var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel', - 'paper', true); + undefined, true); // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 6a4b0e4e588..e185d18d200 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -171,6 +171,7 @@ axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) { cleanPos = Lib.ensureNumber; pos = coerce(attr, dflt); } else { + // TODO: This doesn't seem to work for dates var ax = axes.getFromId(gd, axRef); dflt = ax.fraction2r(dflt); pos = coerce(attr, dflt); diff --git a/src/plots/cartesian/include_components.js b/src/plots/cartesian/include_components.js index 20c67e9a6df..cd817b55464 100644 --- a/src/plots/cartesian/include_components.js +++ b/src/plots/cartesian/include_components.js @@ -35,13 +35,6 @@ module.exports = function makeIncludeComponents(containerArrayName) { var yaList = subplots.yaxis; var cartesianList = subplots.cartesian; var hasCartesianOrGL2D = layoutOut._has('cartesian') || layoutOut._has('gl2d'); - // Test if the axRef looks like "x", "x2", etc. and has nothing - // appended, e.g., this will return false if axRef "x2 domain". - var hasOnlyAxRef = function(axLetter, axRef) { - var axRefMatch = axRef ? axRef.match(idRegex[axLetter]) : false; - if(axRefMatch) { return axRefMatch[0].split(' ') === axRefMatch[0]; } - return false; - }; for(var i = 0; i < array.length; i++) { var itemi = array[i]; @@ -50,8 +43,8 @@ module.exports = function makeIncludeComponents(containerArrayName) { var xref = itemi.xref; var yref = itemi.yref; - var hasXref = hasOnlyAxRef('x', xref); - var hasYref = hasOnlyAxRef('y', yref); + var hasXref = idRegex.x.test(xref); + var hasYref = idRegex.y.test(yref); if(hasXref || hasYref) { if(!hasCartesianOrGL2D) Lib.pushUnique(layoutOut._basePlotModules, Cartesian); From 55300b792269b5a0fe51acfab114e229511fc254 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 25 Aug 2020 10:29:43 -0400 Subject: [PATCH 053/112] Updated valid axis IDs to include ' domain' --- test/jasmine/bundle_tests/plotschema_test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jasmine/bundle_tests/plotschema_test.js b/test/jasmine/bundle_tests/plotschema_test.js index 36149adb965..7011aa61a8e 100644 --- a/test/jasmine/bundle_tests/plotschema_test.js +++ b/test/jasmine/bundle_tests/plotschema_test.js @@ -14,6 +14,7 @@ var gl3dAttrs = require('@src/plots/gl3d').layoutAttributes; var polarLayoutAttrs = require('@src/plots/polar/legacy/axis_attributes'); var annotationAttrs = require('@src/components/annotations').layoutAttributes; var updatemenuAttrs = require('@src/components/updatemenus').layoutAttributes; +var cartesianIdRegex = require('@src/plots/cartesian/constants').idRegex; describe('plot schema', function() { 'use strict'; @@ -363,9 +364,9 @@ describe('plot schema', function() { var splomAttrs = plotSchema.traces.splom.attributes; expect(typeof splomAttrs.xaxes.items.regex).toBe('string'); - expect(splomAttrs.xaxes.items.regex).toBe('/^x([2-9]|[1-9][0-9]+)?$/'); + expect(splomAttrs.xaxes.items.regex).toBe(cartesianIdRegex.x.toString()); expect(typeof splomAttrs.yaxes.items.regex).toBe('string'); - expect(splomAttrs.yaxes.items.regex).toBe('/^y([2-9]|[1-9][0-9]+)?$/'); + expect(splomAttrs.yaxes.items.regex).toBe(cartesianIdRegex.y.toString()); }); it('should prune unsupported global-level trace attributes', function() { From adbc6a57ad3e85d030493c5afd7c982736249de1 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 25 Aug 2020 11:44:58 -0400 Subject: [PATCH 054/112] Remove ' domain' from xref and yref --- src/plots/cartesian/include_components.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/include_components.js b/src/plots/cartesian/include_components.js index cd817b55464..02d4841ba89 100644 --- a/src/plots/cartesian/include_components.js +++ b/src/plots/cartesian/include_components.js @@ -11,6 +11,7 @@ var Registry = require('../../registry'); var Lib = require('../../lib'); +var axisIds = require('./axis_ids'); /** * Factory function for checking component arrays for subplot references. @@ -40,8 +41,10 @@ module.exports = function makeIncludeComponents(containerArrayName) { var itemi = array[i]; if(!Lib.isPlainObject(itemi)) continue; - var xref = itemi.xref; - var yref = itemi.yref; + // call cleanId because if xref has something appended (e.g., ' + // domain') this will get removed. + var xref = axisIds.cleanId(itemi.xref, 'x', false); + var yref = axisIds.cleanId(itemi.yref, 'y', false); var hasXref = idRegex.x.test(xref); var hasYref = idRegex.y.test(yref); From 5d4cf7478e0687a4cdd414cc4f368c3ae9f5fc3b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 25 Aug 2020 17:49:56 -0400 Subject: [PATCH 055/112] Starting to write the jasmine tests for domain referenced shapes --- package-lock.json | 5 + package.json | 1 + src/plots/cartesian/include_components.js | 4 +- test/domain_ref_test.html | 9 ++ test/domain_ref_test.js | 14 ++ test/image/mocks/domain_ref_base.json | 38 +++++ test/image/mocks/domain_refs_editable.json | 40 +++++ .../assets/get_svg_elem_screen_bbox.js | 29 ++++ test/jasmine/assets/pixel_calc.js | 52 +++++++ test/jasmine/assets/svg_tools.js | 15 ++ test/jasmine/tests/shapes_test.js | 142 ++++++++++++++++++ 11 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 test/domain_ref_test.html create mode 100644 test/domain_ref_test.js create mode 100644 test/image/mocks/domain_ref_base.json create mode 100644 test/image/mocks/domain_refs_editable.json create mode 100644 test/jasmine/assets/get_svg_elem_screen_bbox.js create mode 100644 test/jasmine/assets/pixel_calc.js create mode 100644 test/jasmine/assets/svg_tools.js diff --git a/package-lock.json b/package-lock.json index 11b12e26841..883caeb2122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4097,6 +4097,11 @@ } } }, + "extra-iterable": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/extra-iterable/-/extra-iterable-2.5.12.tgz", + "integrity": "sha512-bNfNKrxHvPPtfL5R8khTLSZuH737J4eDDU8Rtse81oRV8sGhk+gJJ7T+2upbqOU/a369rO/ioWwHKyfXOrTsIw==" + }, "extract-frustum-planes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/extract-frustum-planes/-/extract-frustum-planes-1.0.0.tgz", diff --git a/package.json b/package.json index 5a27e1e314a..2c1ff3b19d4 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "d3-time-format": "^2.2.3", "delaunay-triangulate": "^1.1.6", "es6-promise": "^4.2.8", + "extra-iterable": "^2.5.12", "fast-isnumeric": "^1.1.4", "gl-cone3d": "^1.5.2", "gl-contour2d": "^1.1.7", diff --git a/src/plots/cartesian/include_components.js b/src/plots/cartesian/include_components.js index 02d4841ba89..e8738b42c1d 100644 --- a/src/plots/cartesian/include_components.js +++ b/src/plots/cartesian/include_components.js @@ -41,8 +41,8 @@ module.exports = function makeIncludeComponents(containerArrayName) { var itemi = array[i]; if(!Lib.isPlainObject(itemi)) continue; - // call cleanId because if xref has something appended (e.g., ' - // domain') this will get removed. + // call cleanId because if xref, or yref has something appended + // (e.g., ' domain') this will get removed. var xref = axisIds.cleanId(itemi.xref, 'x', false); var yref = axisIds.cleanId(itemi.yref, 'y', false); diff --git a/test/domain_ref_test.html b/test/domain_ref_test.html new file mode 100644 index 00000000000..e16949a5b38 --- /dev/null +++ b/test/domain_ref_test.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/domain_ref_test.js b/test/domain_ref_test.js new file mode 100644 index 00000000000..bc9d23e713e --- /dev/null +++ b/test/domain_ref_test.js @@ -0,0 +1,14 @@ +var Plotly = require('../lib/index'); +var d3 = require('d3'); +var createGraphDiv = require('../test/jasmine/assets/create_graph_div'); +var pixelCalc = require('../test/jasmine/assets/pixel_calc'); +var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_bbox'); +var Lib = require('../src/lib'); + +var dothething = function() { + var gd = createGraphDiv(); + var mock = Lib.extendDeep({}, require('../test/image/mocks/domain_ref_base.json')); + Plotly.newPlot(gd,mock); +} + +dothething(); diff --git a/test/image/mocks/domain_ref_base.json b/test/image/mocks/domain_ref_base.json new file mode 100644 index 00000000000..627b3952c06 --- /dev/null +++ b/test/image/mocks/domain_ref_base.json @@ -0,0 +1,38 @@ +{ + "data": [{ + "x": [1, 2], + "y": [3, 1], + "type": "scatter", + "xaxis": "x", + "yaxis": "y", + "line": { "color": "rgb(127,127,127)" } + },{ + "x": [3, 1], + "y": [1, 2], + "type": "scatter", + "xaxis": "x2", + "yaxis": "y2", + "line": { "color": "rgb(127,127,127)" } + }], + "layout": { + "xaxis": { + "domain": [0,0.75], + "range": [0, 3] + }, + "yaxis": { + "domain": [0,.4], + "range": [0, 4] + }, + "xaxis2": { + "domain": [0.75,1], + "range": [0, 4] + }, + "yaxis2": { + "domain": [.4,1], + "range": [0, 3] + }, + "margin": { "l": 100, "r": 100, "t": 100, "b": 100, "autoexpand": false }, + "width": 400, + "height": 400 + } +} diff --git a/test/image/mocks/domain_refs_editable.json b/test/image/mocks/domain_refs_editable.json new file mode 100644 index 00000000000..ad56f4b0394 --- /dev/null +++ b/test/image/mocks/domain_refs_editable.json @@ -0,0 +1,40 @@ +{ + "data": [{ + "x": [1, 5], + "y": [1, 10], + "type": "scatter" + }, { + "x": [1, 5], + "y": [0.1, 1.0], + "type": "scatter", + "yaxis": "y2" + }], + "layout": { + "title": "Axis domain referenced shape", + "width": 0.3, + "height": 0.2, + "xaxis": { + "domain": [0.2, 0.9] + }, + "yaxis": { + "domain": [0.1, 0.8] + }, + "yaxis2": { + "overlaying": "y", + "side": "right" + }, + "shapes": [{ + "type": "rect", + "xref": "x domain", + "yref": "y domain", + "layer": "above", + "x0": 0.1, + "y0": 0.2, + "x1": 0.8, + "y1": 0.5 + }] + }, + "config": { + "editable": true + } +} diff --git a/test/jasmine/assets/get_svg_elem_screen_bbox.js b/test/jasmine/assets/get_svg_elem_screen_bbox.js new file mode 100644 index 00000000000..a6d95d0001e --- /dev/null +++ b/test/jasmine/assets/get_svg_elem_screen_bbox.js @@ -0,0 +1,29 @@ +'use strict'; + +var SVGTools = require('./svg_tools'); + +/** + * Get the bounding box in screen coordinates of an SVG element. + * + * @param {elem} SVG element's node. + */ +module.exports = getSVGElemScreenBBox; + +// Get the screen coordinates of an SVG Element's bounding box +// Based off of this: +// https://stackoverflow.com/questions/26049488/how-to-get-absolute-coordinates-of-object-inside-a-g-group +function getSVGElemScreenBBox(elem) { + var svg = SVGTools.findParentSVG(elem); + var rect = svg.createSVGRect(); + var pt = svg.createSVGPoint(); + var ctm = elem.getScreenCTM(); + var bbox = elem.getBBox(); + pt.x = bbox.x; + pt.y = bbox.y; + rect.width = bbox.width; + rect.height = bbox.height; + pt = pt.matrixTransform(ctm); + rect.x = pt.x; + rect.y = pt.y; + return rect; +} diff --git a/test/jasmine/assets/pixel_calc.js b/test/jasmine/assets/pixel_calc.js new file mode 100644 index 00000000000..2926bcc0f37 --- /dev/null +++ b/test/jasmine/assets/pixel_calc.js @@ -0,0 +1,52 @@ +// Calculate the pixel values from various objects + +// {layout} is where the margins are obtained from +// {axis} can be x or y and is used to extract the values for making the +// calculation. If axis is y then d is converted to 1 - d to be consistent with +// how the y axis in Plotly.js works. +// {domain} depends on the application: if we're converting from the paper domain +// to pixels then domain can just be [0,1]. If we're converting from an axis +// domain to pixels, then domain[0] is the start of its domain and domain[1] the +// end (as obtained from layout.xaxis.domain for example). +// {d} Is normalized to domain length, so 0 is the beginning of the domain and 1 +// the end, but it can have values beyond this (e.g., -2 is twice the domain +// length in the opposite direction). For the case where you want to convert +// from range to pixels, convert the range to a normalized using the range for +// that axis (e.g., layout.xaxis.range) +function mapToPixelHelper(layout, axis, domain, d) { + var dim; + var lower; + var upper; + if (axis === 'x') { + dim = 'width'; + lower = 'l'; + upper = 'r'; + } else if (axis === 'y') { + dim = 'height'; + lower = 'b'; + upper = 't'; + d = 1 - d; + } else { + throw "Bad axis letter: " + axis; + } + var plotwidth = layout[dim] - layout.margin[lower] - layout.margin[upper]; + var domwidth = (domain[1] - domain[0]) * plotwidth; + return layout.margin[lower] + domain[0] * plotwidth + domwidth * d; +} + +// axis must be single letter, e.g., x or y +function mapPaperToPixel(layout, axis, d) { + return mapToPixelHelper(layout,axis,[0,1],d); +} + +// Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc. +function mapDomainToPixel(layout, axis, d) { + return mapToPixelHelper(layout,axis[0],layout[axis].domain,d); +} + +// Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc. +function mapRangeToPixel(layout, axis, r) { + var d = (r - layout[axis].range[0]) * (layout[axis].range[1] - layout[axis].range[0]); + return mapDomainToPixel(layout, axis, d); +} + diff --git a/test/jasmine/assets/svg_tools.js b/test/jasmine/assets/svg_tools.js new file mode 100644 index 00000000000..e5bb7a235f3 --- /dev/null +++ b/test/jasmine/assets/svg_tools.js @@ -0,0 +1,15 @@ +'use strict'; + +module.exports = { + findParentSVG: findParentSVG +}; + +function findParentSVG(node) { + var parentNode = node.parentNode; + + if(parentNode.tagName === 'svg') { + return parentNode; + } else { + return findParentSVG(parentNode); + } +} diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index 1a0950088c7..4f754824473 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -12,6 +12,9 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var failTest = require('../assets/fail_test'); var drag = require('../assets/drag'); +var mouseEvent = require('../assets/mouse_event'); +var delay = require('../assets/delay'); +var pixelCalc = require('../assets/pixel_calc.js'); var customAssertions = require('../assets/custom_assertions'); var assertElemRightTo = customAssertions.assertElemRightTo; @@ -1650,3 +1653,142 @@ describe('Test shapes', function() { return coordinates; } }); + +// Domain referenced shapes tests +// Check shapes are drawn indeed where they should be +// Possibilities: for xref and yref are 'range', 'paper', and 'domain', and each +// can or cannot be a log axis, so there are 36 possibilities x the number of +// shapes. +// The test should be simple: for paper and domain, a shape is drawn in pixels +// based on the chosen plot or axis size and checked against the one produced by +// Plotly. For 'range', data should be produced and the shape checked against +// this data. +// Check that shapes are still drawn where they should be after the domain size +// changes (just change the domain sizes and run the same test). +// Also for each of the above cases, check that we can move the shape properly. +jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; + +// delay time in milliseconds +DELAYTIME = 2500; + +// acts on an Object representing a shape which could be a line or a rect +function shapeFromShapePos(shape,axletter,axnum,shapepos) { + shape[axletter+'0'] = shapepos.value[0]; + shape[axletter+'1'] = shapepos.value[1]; + if (shapepos.ref === 'range') { + shape[axletter+'ref'] = axletter + axnum; + } else if (shapepos.ref === 'domain') { + shape[axletter+'ref'] = axletter + axnum + ' domain'; + } else if (shapepos.ref === 'paper') { + shape[axletter+'ref'] = 'paper'; + } +} + +// +function checkShapePosition(gd) {} + +fdescribe('Test correct shape positions', function() { + beforeEach(function () { + gd = createGraphDiv(); + mock = Lib.extendDeep({}, require('@mocks/domain_ref_base.json')); + }); + var iterable = require('extra-iterable'); + // some made-up values for testing + var shapePositions = [ + { + // shapes referring to data + ref: 'range', + // two values for rects + value: [2,3] + }, + { + // shapes referring to domains + ref: 'domain', + value: [0.2,0.75], + }, + { + // shapes referring to paper + ref: 'paper', + value: [0.25, 0.8] + } + ]; + var axisTypes = [ 'linear', 'log' ]; + // Test on 'x', 'y', 'x2', 'y2' axes + // TODO the 'paper' position references are tested twice when once would + // suffice. + var axNum = ['','2']; + // only test line and rect for now + var shapeType = ['line','rect']; + // for both x and y axes + var testCombos = iterable.cartesianProduct([ + axNum,axisTypes,shapePositions,axNum,axisTypes,shapePositions,shapeType + ]); + // map all the combinations to a shape definition and check this shape is + // placed properly + testCombos.forEach(function(combo) { + var xAxNum = combo[0]; + var xaxisType = combo[1]; + var xshapePos = combo[2]; + var yAxNum = combo[3]; + var yaxisType = combo[4]; + var yshapePos = combo[5]; + var shapeType = combo[6]; + it('should draw a ' + shapeType + + ' for x' + xAxNum + ' of type ' + + xaxisType + + ' with a value referencing ' + + xshapePos.ref + + ' and for y' + yAxNum + ' of type ' + + yaxisType + + ' with a value referencing ' + + yshapePos.ref, + function (done) { + var gd = createGraphDiv(); + var mock = Lib.extendDeep({}, require('@mocks/domain_ref_base.json')); + Plotly.newPlot(gd, mock) + .then(function() { + var shape = { + type: shapeType, + // this color chosen so it can easily be found with d3 + line: { color: 'rgb(100, 200, 300)' } + }; + shapeFromShapePos(shape,'x',xAxNum,xshapePos); + shapeFromShapePos(shape,'y',yAxNum,yshapePos); + return shape; + }) + .then(function (shape) { + return Plotly.relayout(gd,{shapes: [shape]}); + }).then(function () { + checkShapePosition(gd, + .catch(failTest) + .then(done); + } + }); + ); +}); + +describe('Test shape move', function() { + beforeEach(function() { + gd = createGraphDiv(); + mock = Lib.extendDeep({}, require('@mocks/domain_refs_editable.json')); + }); + + // afterAll(function (done) { setTimeout(done,1000); }); + + it('should work, no?', function(done) { + Plotly.newPlot(gd, mock) + .then(function() {mouseEvent('mousemove', 350, 280);}) + .then(function() {mouseEvent('mousedown', 350, 280);}) + .then(delay(DELAYTIME)) + .then(function() {mouseEvent('mousemove', 350, 200);}) + .then(delay(DELAYTIME)) + .then(function() {mouseEvent('mousemove', 450, 200);}) + .then(function() { + mouseEvent('mouseup', 450, 200); + expect(1).toEqual(1); + }) + .then(delay(DELAYTIME)) + .catch(failTest) + .then(done); + }); +}); From 0f204ad95e80d0852a6043336e13e4aed2bb4d3d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 25 Aug 2020 18:45:34 -0400 Subject: [PATCH 056/112] Testing pixel_calc The y values seem to be wrong --- test/domain_ref_test.js | 32 ++++++++++++++++++++++++- test/domain_ref_tests.html | 39 +++++++++++++++++++++++++++++++ test/jasmine/assets/pixel_calc.js | 11 ++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/test/domain_ref_test.js b/test/domain_ref_test.js index bc9d23e713e..2a484374bdb 100644 --- a/test/domain_ref_test.js +++ b/test/domain_ref_test.js @@ -6,9 +6,39 @@ var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_b var Lib = require('../src/lib'); var dothething = function() { + var shapeColor = 'rgb(50, 150, 250)'; var gd = createGraphDiv(); var mock = Lib.extendDeep({}, require('../test/image/mocks/domain_ref_base.json')); - Plotly.newPlot(gd,mock); + var shape = { + type: 'rect', + xref: 'x2', + yref: 'y2', + x0: 2, + x1: 3, + y0: 1, + y1: 2, + line: { color: shapeColor } + }; + Plotly.newPlot(gd,mock) + .then(function () { + var layout = { + shapes: [shape] + } + return layout; + }) + .then(function(layout) { + return Plotly.relayout(gd,layout); + }) + .then(function () { + var shapePath = d3.selectAll('path').filter(function () { + return this.style.stroke === shapeColor; + }).node(); + console.log(getSVGElemScreenBBox(shapePath)); + console.log('x0',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0)); + console.log('x1',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x1)); + console.log('y0',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y0)); + console.log('y1',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y1)); + }); } dothething(); diff --git a/test/domain_ref_tests.html b/test/domain_ref_tests.html index ab45fca3523..3e8cc6b7223 100644 --- a/test/domain_ref_tests.html +++ b/test/domain_ref_tests.html @@ -416,6 +416,45 @@ showarrow: true, arrowhead: 0, arrowcolor: 'rgb(127,255,0)' + },{ + text: 'D', + x: 2, + y: 3, + xref: 'x', + yref: 'y', + showarrow: true, + arrowhead: 0, + arrowcolor: 'rgb(127,255,1)', + ax: 4, + ay: 5, + axref: 'x', + ayref: 'y' + },{ + text: 'E', + x: 0.2, + y: 0.3, + xref: 'paper', + yref: 'paper', + showarrow: true, + arrowhead: 0, + arrowcolor: 'rgb(127,255,2)', + ax: 0.8, + ay: 0.9, + axref: 'paper', + ayref: 'paper' + },{ + text: 'F', + x: 0.2, + y: 0.3, + xref: 'x2 domain', + yref: 'y2 domain', + showarrow: true, + arrowhead: 0, + arrowcolor: 'rgb(127,255,3)', + ax: 0.8, + ay: 0.9, + axref: 'x2 domain', + ayref: 'y2 domain' } ]; diff --git a/test/jasmine/assets/pixel_calc.js b/test/jasmine/assets/pixel_calc.js index 2926bcc0f37..8b85524d349 100644 --- a/test/jasmine/assets/pixel_calc.js +++ b/test/jasmine/assets/pixel_calc.js @@ -1,3 +1,7 @@ +'use strict' + +// TODO: Something is wrong with the y values? + // Calculate the pixel values from various objects // {layout} is where the margins are obtained from @@ -46,7 +50,12 @@ function mapDomainToPixel(layout, axis, d) { // Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc. function mapRangeToPixel(layout, axis, r) { - var d = (r - layout[axis].range[0]) * (layout[axis].range[1] - layout[axis].range[0]); + var d = (r - layout[axis].range[0]) / (layout[axis].range[1] - layout[axis].range[0]); return mapDomainToPixel(layout, axis, d); } +module.exports = { +mapPaperToPixel: mapPaperToPixel, +mapDomainToPixel: mapDomainToPixel, +mapRangeToPixel: mapRangeToPixel +}; From c328243da8737f4bf52f948a95145fa3a9d3949c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 26 Aug 2020 16:45:19 -0400 Subject: [PATCH 057/112] Made a test for domain referencing shapes It can easily be wrapped in jasmine directives. Before I do this I want to abstract it a bit to make the same code work for annotations and images. --- src/plots/cartesian/axis_ids.js | 5 +- test/domain_ref_shapes_test.html | 9 ++ test/domain_ref_shapes_test.js | 210 +++++++++++++++++++++++++ test/domain_ref_test.html | 1 + test/domain_ref_test.js | 18 ++- test/image/mocks/domain_ref_base.json | 11 +- test/jasmine/assets/get_rect_center.js | 14 +- test/jasmine/assets/pixel_calc.js | 10 +- 8 files changed, 255 insertions(+), 23 deletions(-) create mode 100644 test/domain_ref_shapes_test.html create mode 100644 test/domain_ref_shapes_test.js diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 781b6aad1cd..57deb8b073a 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -18,9 +18,8 @@ var constants = require('./constants'); // completely in favor of just 'x' if it weren't ingrained in the API etc. exports.id2name = function id2name(id) { if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; - var axNum = id.substr(1); - // axNum could be ' ' if domain specified and axis omitted number - if(axNum === '1' || axNum === ' ') axNum = ''; + var axNum = id.split(' ')[0].substr(1); + if(axNum === '1') axNum = ''; return id.charAt(0) + 'axis' + axNum; }; diff --git a/test/domain_ref_shapes_test.html b/test/domain_ref_shapes_test.html new file mode 100644 index 00000000000..635ea367bc2 --- /dev/null +++ b/test/domain_ref_shapes_test.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js new file mode 100644 index 00000000000..392f6599291 --- /dev/null +++ b/test/domain_ref_shapes_test.js @@ -0,0 +1,210 @@ +var Plotly = require('../lib/index'); +var d3 = require('d3'); +var createGraphDiv = require('../test/jasmine/assets/create_graph_div'); +var destroyGraphDiv = require('../test/jasmine/assets/destroy_graph_div'); +var pixelCalc = require('../test/jasmine/assets/pixel_calc'); +var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_bbox'); +var Lib = require('../src/lib'); +var Axes = require('../src/plots/cartesian/axes'); +var axisIds = require('../src/plots/cartesian/axis_ids'); + +// NOTE: this tolerance is in pixels +var EQUALITY_TOLERANCE = 1e-2; + +var DEBUG = true; + +var it = function(s,f) { + console.log('testing ' + s); + f(function() { console.log(s + ' is done.'); }); +} + +// acts on an Object representing a shape which could be a line or a rect +function shapeFromShapePos(shape,axletter,axnum,shapepos) { + shape[axletter+'0'] = shapepos.value[0]; + shape[axletter+'1'] = shapepos.value[1]; + if (shapepos.ref === 'range') { + shape[axletter+'ref'] = axletter + axnum; + } else if (shapepos.ref === 'domain') { + shape[axletter+'ref'] = axletter + axnum + ' domain'; + } else if (shapepos.ref === 'paper') { + shape[axletter+'ref'] = 'paper'; + } +} + +// axid is e.g., 'x', 'y2' etc. +function logAxisIfAxType(layoutIn,layoutOut,axid,axtype) { + if (axtype === 'log') { + var axname = axisIds.id2name(axid); + var axis = {...layoutIn[axname]}; + axis.type = 'log'; + axis.range = axis.range.map(Math.log10); + layoutOut[axname] = axis; + } +} + + +// axref can be xref or yref +// c can be x0, x1, y0, y1 +function mapShapeCoordToPixel(layout,axref,shape,c) { + var reftype = Axes.getRefType(shape[axref]); + var axletter = axref[0]; + var ret; + if (reftype === 'range') { + var axis = axisIds.id2name(shape[axref]); + ret = pixelCalc.mapRangeToPixel(layout, axis, shape[c]); + } else if (reftype === 'domain') { + var axis = axisIds.id2name(shape[axref]); + ret = pixelCalc.mapDomainToPixel(layout, axis, shape[c]); + } else if (reftype === 'paper') { + var axis = axref[0]; + ret = pixelCalc.mapPaperToPixel(layout, axis, shape[c]); + } + return ret; +} + +// compute the bounding box of the shape so that it can be compared with the SVG +// bounding box +function shapeToBBox(layout,shape) { + var bbox = {}; + var x1; + var y1; + // map x coordinates + bbox.x = mapShapeCoordToPixel(layout,'xref',shape,'x0'); + x1 = mapShapeCoordToPixel(layout,'xref',shape,'x1'); + // SVG bounding boxes have x,y referring to top left corner, but here we are + // specifying shapes where y0 refers to the bottom left corner like + // Plotly.js, so we swap y0 and y1 + bbox.y = mapShapeCoordToPixel(layout,'yref',shape,'y1'); + y1 = mapShapeCoordToPixel(layout,'yref',shape,'y0'); + bbox.width = x1 - bbox.x; + bbox.height = y1 - bbox.y; + return bbox; +} + +function coordsEq(a,b) { + return Math.abs(a - b) < EQUALITY_TOLERANCE; +} + +function compareBBoxes(a,b) { + return ['x','y','width','height'].map( + (k,)=>coordsEq(a[k],b[k])).reduce( + (l,r)=>l&&r, + true); +} + +// gets the SVG bounding box of the shape and checks it against what mapToPixel +// gives +function checkShapePosition(gd,shape) { + var shapePath = d3.selectAll('path').filter(function () { + return this.style.stroke === shape.line.color; + }).node(); + var shapePathBBox = getSVGElemScreenBBox(shapePath); + var shapeBBox = shapeToBBox(gd.layout,shape); + var ret = compareBBoxes(shapeBBox,shapePathBBox); + if (DEBUG) { + console.log('SVG BBox',shapePathBBox); + console.log('shape BBox',shapeBBox); + } + return ret; +} + +// some made-up values for testing +var shapePositionsX = [ + { + // shapes referring to data + ref: 'range', + // two values for rects + value: [2,3] + }, + { + // shapes referring to domains + ref: 'domain', + value: [0.2,0.75], + }, + { + // shapes referring to paper + ref: 'paper', + value: [0.25, 0.8] + } +]; +var shapePositionsY = [ + { + // shapes referring to data + ref: 'range', + // two values for rects + value: [1,2] + }, + { + // shapes referring to domains + ref: 'domain', + value: [0.25,0.7], + }, + { + // shapes referring to paper + ref: 'paper', + value: [0.2, 0.85] + } +]; +var axisTypes = [ 'linear', 'log' ]; +// Test on 'x', 'y', 'x2', 'y2' axes +// TODO the 'paper' position references are tested twice when once would +// suffice. +var axNum = ['','2']; +// only test line and rect for now +var shapeType = ['line','rect']; +// this color chosen so it can easily be found with d3 +var shapeColor = 'rgb(50, 100, 150)'; +var testDomRefShapeCombo = function(combo) { + var xAxNum = combo[0]; + var xaxisType = combo[1]; + var xshapePos = combo[2]; + var yAxNum = combo[3]; + var yaxisType = combo[4]; + var yshapePos = combo[5]; + var shapeType = combo[6]; + it('should draw a ' + shapeType + + ' for x' + xAxNum + ' of type ' + + xaxisType + + ' with a value referencing ' + + xshapePos.ref + + ' and for y' + yAxNum + ' of type ' + + yaxisType + + ' with a value referencing ' + + yshapePos.ref, + function (done) { + var gd = createGraphDiv(); + var mock = Lib.extendDeep({}, + require('../test/image/mocks/domain_ref_base.json')); + if (DEBUG) { + console.log(combo); + } + Plotly.newPlot(gd, mock) + var shape = { + type: shapeType, + line: { color: shapeColor } + }; + shapeFromShapePos(shape,'x',xAxNum,xshapePos); + shapeFromShapePos(shape,'y',yAxNum,yshapePos); + var layout = {shapes: [shape]}; + // change to log axes if need be + logAxisIfAxType(gd.layout,layout,'x'+xAxNum,xaxisType); + logAxisIfAxType(gd.layout,layout,'y'+yAxNum,yaxisType); + Plotly.relayout(gd,layout); + console.log(checkShapePosition(gd,shape)); + destroyGraphDiv(); + }); +} + +// Test correct shape positions +function test_correct_shape_positions () { + var iterable = require('extra-iterable'); + // for both x and y axes + var testCombos = [...iterable.cartesianProduct([ + axNum,axisTypes,shapePositionsX,axNum,axisTypes,shapePositionsY,shapeType + ])]; + // map all the combinations to a shape definition and check this shape is + // placed properly + testCombos.forEach(testDomRefShapeCombo); +} + +test_correct_shape_positions(); diff --git a/test/domain_ref_test.html b/test/domain_ref_test.html index e16949a5b38..95c0f9bca5e 100644 --- a/test/domain_ref_test.html +++ b/test/domain_ref_test.html @@ -2,6 +2,7 @@ + diff --git a/test/domain_ref_test.js b/test/domain_ref_test.js index 2a484374bdb..d3b9dd26a25 100644 --- a/test/domain_ref_test.js +++ b/test/domain_ref_test.js @@ -21,8 +21,16 @@ var dothething = function() { }; Plotly.newPlot(gd,mock) .then(function () { + var xaxis2 = {...gd.layout.xaxis2}; + var yaxis2 = {...gd.layout.yaxis2}; + xaxis2.type = 'log'; + xaxis2.range = xaxis2.range.map(Math.log10); + yaxis2.type = 'log'; + yaxis2.range = yaxis2.range.map(Math.log10); var layout = { - shapes: [shape] + shapes: [shape], + xaxis2: xaxis2, + yaxis2: yaxis2 } return layout; }) @@ -33,11 +41,17 @@ var dothething = function() { var shapePath = d3.selectAll('path').filter(function () { return this.style.stroke === shapeColor; }).node(); - console.log(getSVGElemScreenBBox(shapePath)); + var bbox = getSVGElemScreenBBox(shapePath) + console.log(bbox); + console.log('property names',Object.keys(bbox)); console.log('x0',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0)); console.log('x1',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x1)); console.log('y0',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y0)); console.log('y1',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y1)); + console.log('bbox.x0 - shape.x0', + bbox.x - + pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0) + ); }); } diff --git a/test/image/mocks/domain_ref_base.json b/test/image/mocks/domain_ref_base.json index 627b3952c06..a5d07f46e7b 100644 --- a/test/image/mocks/domain_ref_base.json +++ b/test/image/mocks/domain_ref_base.json @@ -7,13 +7,14 @@ "yaxis": "y", "line": { "color": "rgb(127,127,127)" } },{ - "x": [3, 1], + "x": [1, 3], "y": [1, 2], "type": "scatter", "xaxis": "x2", "yaxis": "y2", "line": { "color": "rgb(127,127,127)" } - }], + } + ], "layout": { "xaxis": { "domain": [0,0.75], @@ -25,11 +26,13 @@ }, "xaxis2": { "domain": [0.75,1], - "range": [0, 4] + "range": [0, 4], + "anchor": "y2" }, "yaxis2": { "domain": [.4,1], - "range": [0, 3] + "range": [0, 3], + "anchor": "x2" }, "margin": { "l": 100, "r": 100, "t": 100, "b": 100, "autoexpand": false }, "width": 400, diff --git a/test/jasmine/assets/get_rect_center.js b/test/jasmine/assets/get_rect_center.js index 1117383354b..fec124acfe7 100644 --- a/test/jasmine/assets/get_rect_center.js +++ b/test/jasmine/assets/get_rect_center.js @@ -1,5 +1,7 @@ 'use strict'; +var SVGTools = require('./svg_tools'); + /** * Get the screen coordinates of the center of @@ -18,7 +20,7 @@ module.exports = function getRectCenter(rect) { // Taken from: http://stackoverflow.com/a/5835212/4068492 function getRectScreenCoords(rect) { - var svg = findParentSVG(rect); + var svg = SVGTools.findParentSVG(rect); var pt = svg.createSVGPoint(); var corners = {}; var matrix = rect.getScreenCTM(); @@ -35,13 +37,3 @@ function getRectScreenCoords(rect) { return corners; } - -function findParentSVG(node) { - var parentNode = node.parentNode; - - if(parentNode.tagName === 'svg') { - return parentNode; - } else { - return findParentSVG(parentNode); - } -} diff --git a/test/jasmine/assets/pixel_calc.js b/test/jasmine/assets/pixel_calc.js index 8b85524d349..df30214b23d 100644 --- a/test/jasmine/assets/pixel_calc.js +++ b/test/jasmine/assets/pixel_calc.js @@ -1,7 +1,5 @@ 'use strict' -// TODO: Something is wrong with the y values? - // Calculate the pixel values from various objects // {layout} is where the margins are obtained from @@ -29,12 +27,15 @@ function mapToPixelHelper(layout, axis, domain, d) { dim = 'height'; lower = 'b'; upper = 't'; - d = 1 - d; } else { throw "Bad axis letter: " + axis; } var plotwidth = layout[dim] - layout.margin[lower] - layout.margin[upper]; var domwidth = (domain[1] - domain[0]) * plotwidth; + if (dim === 'height') { + // y-axes relative to bottom of plot in plotly.js + return layout[dim] - (layout.margin[lower] + domain[0] * plotwidth + domwidth * d); + } return layout.margin[lower] + domain[0] * plotwidth + domwidth * d; } @@ -50,6 +51,9 @@ function mapDomainToPixel(layout, axis, d) { // Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc. function mapRangeToPixel(layout, axis, r) { + if (layout[axis].type === 'log') { + r = Math.log10(r); + } var d = (r - layout[axis].range[0]) / (layout[axis].range[1] - layout[axis].range[0]); return mapDomainToPixel(layout, axis, d); } From e12dcb40dde919019b6f1e9051e132719e2f22b3 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 27 Aug 2020 17:36:57 -0400 Subject: [PATCH 058/112] Starting to generalize the domain reference tests So we can use the same code for shapes, annotations and images. Found a way to test the correct annotation size without having to know how the arrow is shortened to deal with the box around the text. --- test/domain_ref_shapes_test.js | 215 +++++++++++++++++++++++---------- test/domain_ref_test.html | 8 ++ test/domain_ref_test.js | 159 +++++++++++++++++------- 3 files changed, 273 insertions(+), 109 deletions(-) diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js index 392f6599291..fac7825f400 100644 --- a/test/domain_ref_shapes_test.js +++ b/test/domain_ref_shapes_test.js @@ -1,3 +1,5 @@ +// Test the placement of Axis Referencing Objects (AROs) + var Plotly = require('../lib/index'); var d3 = require('d3'); var createGraphDiv = require('../test/jasmine/assets/create_graph_div'); @@ -7,6 +9,7 @@ var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_b var Lib = require('../src/lib'); var Axes = require('../src/plots/cartesian/axes'); var axisIds = require('../src/plots/cartesian/axis_ids'); +var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; // NOTE: this tolerance is in pixels var EQUALITY_TOLERANCE = 1e-2; @@ -18,17 +21,76 @@ var it = function(s,f) { f(function() { console.log(s + ' is done.'); }); } -// acts on an Object representing a shape which could be a line or a rect -function shapeFromShapePos(shape,axletter,axnum,shapepos) { - shape[axletter+'0'] = shapepos.value[0]; - shape[axletter+'1'] = shapepos.value[1]; - if (shapepos.ref === 'range') { - shape[axletter+'ref'] = axletter + axnum; - } else if (shapepos.ref === 'domain') { - shape[axletter+'ref'] = axletter + axnum + ' domain'; - } else if (shapepos.ref === 'paper') { - shape[axletter+'ref'] = 'paper'; +// acts on an Object representing a aro which could be a line or a rect +// DEPRECATED +function aroFromAROPos(aro,axletter,axnum,aropos) { + aro[axletter+'0'] = aropos.value[0]; + aro[axletter+'1'] = aropos.value[1]; + if (aropos.ref === 'range') { + aro[axletter+'ref'] = axletter + axnum; + } else if (aropos.ref === 'domain') { + aro[axletter+'ref'] = axletter + axnum + ' domain'; + } else if (aropos.ref === 'paper') { + aro[axletter+'ref'] = 'paper'; + } +} + +// set common parameters of an ARO +// {aro} is the object whose parameters to set +// {axletter} is the axis, x or y +// {axnum} is the axis number +// {value} is the value of the first coordinate (e.g., x0 if axletter is x) +// {ref} is ['range'|'domain'|'paper'] +function aroSetCommonParams(aro,axletter,axnum,value,ref) { + aro[axletter+'0'] = value; + if (ref === 'range') { + aro[axletter+'ref'] = axletter + axnum; + } else if (aropos.ref === 'domain') { + aro[axletter+'ref'] = axletter + axnum + ' domain'; + } else if (aropos.ref === 'paper') { + aro[axletter+'ref'] = 'paper'; + } +} + +// shape, annotation and image all take x0, y0, xref, yref, color parameters +// arotype can be 'shape', 'annotation', or 'image' +// shapes take type=[line|rect], x1, y1 +// annotations take ax, ay, axref, ayref, (text is just set to "A" and xanchor +// and yanchor are always set to left because these are text attributes which we +// don't test) +// images take xsize, ysize, xanchor, yanchor (sizing is set to stretch for simplicity +// in computing the bounding box and source is something predetermined) +function aroFromParams(arotype,x0,y0,xreftype,yreftype,xaxnum,yaxnum,color,opts) { + var aro = {}; + // fill with common values + aroSetCommonParams(aro,'x',xaxnum,x0,xreftype); + aroSetCommonParams(aro,'y',yaxnum,y0,yreftype); + switch (arotype) { + case 'shape': + aro.x1 = opts.x1; + aro.y1 = opts.y1; + aro.type = opts.type; + aro.line = {color: color}; + case 'annotation': + aro.text = "A"; + aro.ax = opts.ax; + aro.ay = opts.ay; + aro.axref = opts.axref; + aro.ayref = opts.ayref; + aro.showarrow = true; + aro.arrowhead = 0; + aro.arrowcolor = color; + case 'image': + aro.sizex = opts.sizex; + aro.sizey = opts.sizey; + aro.xanchor = opts.xanchor; + aro.yanchor = opts.yanchor; + aro.sizing = "stretch"; + aro.source = testImage; + default: + throw "Bad arotype: " + arotype; } + return aro; } // axid is e.g., 'x', 'y2' etc. @@ -45,37 +107,37 @@ function logAxisIfAxType(layoutIn,layoutOut,axid,axtype) { // axref can be xref or yref // c can be x0, x1, y0, y1 -function mapShapeCoordToPixel(layout,axref,shape,c) { - var reftype = Axes.getRefType(shape[axref]); +function mapAROCoordToPixel(layout,axref,aro,c) { + var reftype = Axes.getRefType(aro[axref]); var axletter = axref[0]; var ret; if (reftype === 'range') { - var axis = axisIds.id2name(shape[axref]); - ret = pixelCalc.mapRangeToPixel(layout, axis, shape[c]); + var axis = axisIds.id2name(aro[axref]); + ret = pixelCalc.mapRangeToPixel(layout, axis, aro[c]); } else if (reftype === 'domain') { - var axis = axisIds.id2name(shape[axref]); - ret = pixelCalc.mapDomainToPixel(layout, axis, shape[c]); + var axis = axisIds.id2name(aro[axref]); + ret = pixelCalc.mapDomainToPixel(layout, axis, aro[c]); } else if (reftype === 'paper') { var axis = axref[0]; - ret = pixelCalc.mapPaperToPixel(layout, axis, shape[c]); + ret = pixelCalc.mapPaperToPixel(layout, axis, aro[c]); } return ret; } -// compute the bounding box of the shape so that it can be compared with the SVG +// compute the bounding box of the aro so that it can be compared with the SVG // bounding box -function shapeToBBox(layout,shape) { +function aroToBBox(layout,aro) { var bbox = {}; var x1; var y1; // map x coordinates - bbox.x = mapShapeCoordToPixel(layout,'xref',shape,'x0'); - x1 = mapShapeCoordToPixel(layout,'xref',shape,'x1'); + bbox.x = mapAROCoordToPixel(layout,'xref',aro,'x0'); + x1 = mapAROCoordToPixel(layout,'xref',aro,'x1'); // SVG bounding boxes have x,y referring to top left corner, but here we are - // specifying shapes where y0 refers to the bottom left corner like + // specifying aros where y0 refers to the bottom left corner like // Plotly.js, so we swap y0 and y1 - bbox.y = mapShapeCoordToPixel(layout,'yref',shape,'y1'); - y1 = mapShapeCoordToPixel(layout,'yref',shape,'y0'); + bbox.y = mapAROCoordToPixel(layout,'yref',aro,'y1'); + y1 = mapAROCoordToPixel(layout,'yref',aro,'y0'); bbox.width = x1 - bbox.x; bbox.height = y1 - bbox.y; return bbox; @@ -92,85 +154,104 @@ function compareBBoxes(a,b) { true); } -// gets the SVG bounding box of the shape and checks it against what mapToPixel +// gets the SVG bounding box of the aro and checks it against what mapToPixel // gives -function checkShapePosition(gd,shape) { - var shapePath = d3.selectAll('path').filter(function () { - return this.style.stroke === shape.line.color; +function checkAROPosition(gd,aro) { + var aroPath = d3.selectAll('path').filter(function () { + return this.style.stroke === aro.line.color; }).node(); - var shapePathBBox = getSVGElemScreenBBox(shapePath); - var shapeBBox = shapeToBBox(gd.layout,shape); - var ret = compareBBoxes(shapeBBox,shapePathBBox); + var aroPathBBox = getSVGElemScreenBBox(aroPath); + var aroBBox = aroToBBox(gd.layout,aro); + var ret = compareBBoxes(aroBBox,aroPathBBox); if (DEBUG) { - console.log('SVG BBox',shapePathBBox); - console.log('shape BBox',shapeBBox); + console.log('SVG BBox',aroPathBBox); + console.log('aro BBox',aroBBox); } return ret; } // some made-up values for testing -var shapePositionsX = [ +var aroPositionsX = [ { - // shapes referring to data + // aros referring to data ref: 'range', - // two values for rects - value: [2,3] + value: [2,3], + // for objects that need a size (i.e., images) + size: 1.5 }, { - // shapes referring to domains + // aros referring to domains ref: 'domain', value: [0.2,0.75], + size: 0.3 }, { - // shapes referring to paper + // aros referring to paper ref: 'paper', - value: [0.25, 0.8] - } + value: [0.25, 0.8], + size: 0.35 + }, ]; -var shapePositionsY = [ +var aroRelPixelSizeX [ + // this means specify the offset with the second value in aroPositions{X,Y} + { ref: 'nopixel' }, + // with this you can specify the arrow in pixels + { ref: 'pixel', value: 100 } +]; +var aroPositionsY = [ { - // shapes referring to data + // aros referring to data ref: 'range', // two values for rects value: [1,2] }, { - // shapes referring to domains + // aros referring to domains ref: 'domain', value: [0.25,0.7], }, { - // shapes referring to paper + // aros referring to paper ref: 'paper', value: [0.2, 0.85] } ]; +var aroRelPixelSizeY [ + // this means specify the offset with the second value in aroPositions{X,Y} + { ref: 'nopixel' }, + // with this you can specify the arrow in pixels + { ref: 'pixel', value: 200 } +]; + +var aroTypes = ['shape', 'annotation', 'image']; var axisTypes = [ 'linear', 'log' ]; // Test on 'x', 'y', 'x2', 'y2' axes // TODO the 'paper' position references are tested twice when once would // suffice. var axNum = ['','2']; // only test line and rect for now -var shapeType = ['line','rect']; +var aroType = ['line','rect']; // this color chosen so it can easily be found with d3 -var shapeColor = 'rgb(50, 100, 150)'; -var testDomRefShapeCombo = function(combo) { +// NOTE: for images color cannot be set but it will be the only image in the +// plot so you can use d3.select('g image').node() +var aroColor = 'rgb(50, 100, 150)'; +var testDomRefAROCombo = function(combo) { var xAxNum = combo[0]; var xaxisType = combo[1]; - var xshapePos = combo[2]; + var xaroPos = combo[2]; var yAxNum = combo[3]; var yaxisType = combo[4]; - var yshapePos = combo[5]; - var shapeType = combo[6]; - it('should draw a ' + shapeType + var yaroPos = combo[5]; + var aroType = combo[6]; + it('should draw a ' + aroType + ' for x' + xAxNum + ' of type ' + xaxisType + ' with a value referencing ' - + xshapePos.ref + + xaroPos.ref + ' and for y' + yAxNum + ' of type ' + yaxisType + ' with a value referencing ' - + yshapePos.ref, + + yaroPos.ref, function (done) { var gd = createGraphDiv(); var mock = Lib.extendDeep({}, @@ -179,32 +260,32 @@ var testDomRefShapeCombo = function(combo) { console.log(combo); } Plotly.newPlot(gd, mock) - var shape = { - type: shapeType, - line: { color: shapeColor } + var aro = { + type: aroType, + line: { color: aroColor } }; - shapeFromShapePos(shape,'x',xAxNum,xshapePos); - shapeFromShapePos(shape,'y',yAxNum,yshapePos); - var layout = {shapes: [shape]}; + aroFromAROPos(aro,'x',xAxNum,xaroPos); + aroFromAROPos(aro,'y',yAxNum,yaroPos); + var layout = {shapes: [aro]}; // change to log axes if need be logAxisIfAxType(gd.layout,layout,'x'+xAxNum,xaxisType); logAxisIfAxType(gd.layout,layout,'y'+yAxNum,yaxisType); Plotly.relayout(gd,layout); - console.log(checkShapePosition(gd,shape)); + console.log(checkAROPosition(gd,aro)); destroyGraphDiv(); }); } -// Test correct shape positions -function test_correct_shape_positions () { +// Test correct aro positions +function test_correct_aro_positions () { var iterable = require('extra-iterable'); // for both x and y axes var testCombos = [...iterable.cartesianProduct([ - axNum,axisTypes,shapePositionsX,axNum,axisTypes,shapePositionsY,shapeType + axNum,axisTypes,aroPositionsX,axNum,axisTypes,aroPositionsY,aroType ])]; - // map all the combinations to a shape definition and check this shape is + // map all the combinations to a aro definition and check this aro is // placed properly - testCombos.forEach(testDomRefShapeCombo); + testCombos.forEach(testDomRefAROCombo); } -test_correct_shape_positions(); +test_correct_aro_positions(); diff --git a/test/domain_ref_test.html b/test/domain_ref_test.html index 95c0f9bca5e..365767cbdb8 100644 --- a/test/domain_ref_test.html +++ b/test/domain_ref_test.html @@ -6,5 +6,13 @@ + diff --git a/test/domain_ref_test.js b/test/domain_ref_test.js index d3b9dd26a25..9bfe19485ac 100644 --- a/test/domain_ref_test.js +++ b/test/domain_ref_test.js @@ -4,55 +4,130 @@ var createGraphDiv = require('../test/jasmine/assets/create_graph_div'); var pixelCalc = require('../test/jasmine/assets/pixel_calc'); var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_bbox'); var Lib = require('../src/lib'); +var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; + +function findPathWithColor(c) { + var aroPath = d3.selectAll('path').filter(function() { + return this.style.stroke === c; + }).node(); + return aroPath; +} var dothething = function() { + var arrowColor0 = 'rgb(10, 20, 30)'; + var arrowColor1 = 'rgb(10, 20, 31)'; var shapeColor = 'rgb(50, 150, 250)'; + var arrowX0 = .75; + var arrowY0 = 3; + var arrowX1 = 1.; + var arrowY1 = 4; var gd = createGraphDiv(); var mock = Lib.extendDeep({}, require('../test/image/mocks/domain_ref_base.json')); var shape = { - type: 'rect', - xref: 'x2', - yref: 'y2', - x0: 2, - x1: 3, - y0: 1, - y1: 2, - line: { color: shapeColor } - }; - Plotly.newPlot(gd,mock) - .then(function () { - var xaxis2 = {...gd.layout.xaxis2}; - var yaxis2 = {...gd.layout.yaxis2}; - xaxis2.type = 'log'; - xaxis2.range = xaxis2.range.map(Math.log10); - yaxis2.type = 'log'; - yaxis2.range = yaxis2.range.map(Math.log10); - var layout = { - shapes: [shape], - xaxis2: xaxis2, - yaxis2: yaxis2 + type: 'rect', + xref: 'x2', + yref: 'y2', + x0: 2, + x1: 3, + y0: 1, + y1: 2, + line: { + color: shapeColor } - return layout; - }) - .then(function(layout) { - return Plotly.relayout(gd,layout); - }) - .then(function () { - var shapePath = d3.selectAll('path').filter(function () { - return this.style.stroke === shapeColor; - }).node(); - var bbox = getSVGElemScreenBBox(shapePath) - console.log(bbox); - console.log('property names',Object.keys(bbox)); - console.log('x0',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0)); - console.log('x1',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x1)); - console.log('y0',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y0)); - console.log('y1',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y1)); - console.log('bbox.x0 - shape.x0', - bbox.x - - pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0) - ); - }); + }; + var anno0 = { + width: 10, + height: 10, + x: arrowX0, + y: arrowY0, + xref: 'x domain', + yref: 'y', + ax: arrowX1, + ay: arrowY1, + axref: 'x domain', + ayref: 'y', + showarrow: true, + arrowhead: 0, + arrowcolor: arrowColor0, + bgcolor: 'rgb(10,100,30)', + }; + var anno1 = {...anno0}; + anno1.ax += arrowX1 - arrowX0; + anno1.ay += arrowY1 - arrowY0; + anno1.arrowcolor = arrowColor1; + Plotly.newPlot(gd, mock) + .then(function() { + var xaxis2 = { + ...gd.layout.xaxis2 + }; + var yaxis2 = { + ...gd.layout.yaxis2 + }; + xaxis2.type = 'log'; + xaxis2.range = xaxis2.range.map(Math.log10); + yaxis2.type = 'log'; + yaxis2.range = yaxis2.range.map(Math.log10); + var layout = { + shapes: [shape], + xaxis2: xaxis2, + yaxis2: yaxis2, + // adding image for test + images: [{ + x: 0.25, + y: 0.1, + sizex: 0.7, + sizey: 0.7, + source: testImage, + xanchor: "left", + xref: "x domain", + yanchor: "bottom", + yref: "y domain", + sizing: "stretch" + }], + // adding annotation for test + annotations: [anno0,anno1] + } + return layout; + }) + .then(function(layout) { + return Plotly.relayout(gd, layout); + }) + .then(function() { + var shapePath = d3.selectAll('path').filter(function() { + return this.style.stroke === shapeColor; + }).node(); + var bbox = getSVGElemScreenBBox(shapePath) + console.log(bbox); + console.log('property names', Object.keys(bbox)); + console.log('x0', pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0)); + console.log('x1', pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x1)); + console.log('y0', pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y0)); + console.log('y1', pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y1)); + console.log('bbox.x0 - shape.x0', + bbox.x - + pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0) + ); + // here we check to see the annotation arrow length is as expected + // by comparing 2 annotations, one of which has arrow dimensions + // twice the other one. + var annoPath0 = d3.selectAll('path').filter(function() { + return this.style.stroke === arrowColor0; + }).node(); + var annoPath1 = d3.selectAll('path').filter(function() { + return this.style.stroke === arrowColor1; + }).node(); + var anbbox0 = getSVGElemScreenBBox(annoPath0); + var anbbox1 = getSVGElemScreenBBox(annoPath1); + console.log(anbbox0); + console.log(anbbox1); + var arrowXest = ((anbbox1.x+anbbox1.width) - (anbbox0.x+anbbox0.width)); + console.log("Annotation 0 ax " + arrowXest + " correct: ", + pixelCalc.mapDomainToPixel(gd.layout, 'xaxis', arrowX1)-pixelCalc.mapDomainToPixel(gd.layout, 'xaxis', arrowX0) == arrowXest); + // SVG's y is the top of the box + var arrowYest = (anbbox1.y - anbbox0.y); + console.log("Annotation 1 ay " + arrowYest + " correct: ", + pixelCalc.mapRangeToPixel(gd.layout, 'yaxis', arrowY1)-pixelCalc.mapRangeToPixel(gd.layout, 'yaxis', arrowY0) == arrowYest); + }); } dothething(); From 117b3ed1e1cf7224758caefa34f32b60befe8c49 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 28 Aug 2020 17:59:59 -0400 Subject: [PATCH 059/112] Tests written for shape, annotation and image But they aren't yet running :( --- test/domain_ref_shapes_test.js | 334 +++++++++++++++++++++++++---- test/jasmine/karma.conf.js | 8 +- test/jasmine/tests/parcats_test.js | 2 +- 3 files changed, 296 insertions(+), 48 deletions(-) diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js index fac7825f400..99944de892a 100644 --- a/test/domain_ref_shapes_test.js +++ b/test/domain_ref_shapes_test.js @@ -10,6 +10,7 @@ var Lib = require('../src/lib'); var Axes = require('../src/plots/cartesian/axes'); var axisIds = require('../src/plots/cartesian/axis_ids'); var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; +var iterable = require('extra-iterable'); // NOTE: this tolerance is in pixels var EQUALITY_TOLERANCE = 1e-2; @@ -35,24 +36,38 @@ function aroFromAROPos(aro,axletter,axnum,aropos) { } } -// set common parameters of an ARO -// {aro} is the object whose parameters to set -// {axletter} is the axis, x or y -// {axnum} is the axis number -// {value} is the value of the first coordinate (e.g., x0 if axletter is x) + + +// {axid} is the axis id, e.g., x2, y, etc. // {ref} is ['range'|'domain'|'paper'] -function aroSetCommonParams(aro,axletter,axnum,value,ref) { - aro[axletter+'0'] = value; +function makeAxRef(axid,ref) { + var axref; if (ref === 'range') { - aro[axletter+'ref'] = axletter + axnum; + axref = axid; } else if (aropos.ref === 'domain') { - aro[axletter+'ref'] = axletter + axnum + ' domain'; + axref = axid + ' domain'; } else if (aropos.ref === 'paper') { - aro[axletter+'ref'] = 'paper'; + axref = 'paper'; + } else { + throw 'Bad axis type (ref): ' + ref; } + return axref; +} + +// set common parameters of an ARO +// {aro} is the object whose parameters to set +// {coordletter} the letter of the coordinate to assign +// {axref} is the axis the coordinate refers to +// {value} is the value of the first coordinate (e.g., x0 if axletter is x) +function aroSetCommonParams(aro,coordletter,axref,value) { + aro[coordletter+'0'] = value; + aro.axref = axref; } // shape, annotation and image all take x0, y0, xref, yref, color parameters +// x0, y0 are numerical values, xref, yref are strings that could be passed to +// the xref field of an ANO (e.g., 'x2 domain' or 'paper'), color should be +// specified using the 'rgb(r, g, b)' syntax // arotype can be 'shape', 'annotation', or 'image' // shapes take type=[line|rect], x1, y1 // annotations take ax, ay, axref, ayref, (text is just set to "A" and xanchor @@ -60,11 +75,11 @@ function aroSetCommonParams(aro,axletter,axnum,value,ref) { // don't test) // images take xsize, ysize, xanchor, yanchor (sizing is set to stretch for simplicity // in computing the bounding box and source is something predetermined) -function aroFromParams(arotype,x0,y0,xreftype,yreftype,xaxnum,yaxnum,color,opts) { +function aroFromParams(arotype,x0,y0,xref,yref,color,opts) { var aro = {}; // fill with common values - aroSetCommonParams(aro,'x',xaxnum,x0,xreftype); - aroSetCommonParams(aro,'y',yaxnum,y0,yreftype); + aroSetCommonParams(aro,'x',xref,x0); + aroSetCommonParams(aro,'y',yref,y0); switch (arotype) { case 'shape': aro.x1 = opts.x1; @@ -93,6 +108,103 @@ function aroFromParams(arotype,x0,y0,xreftype,yreftype,xaxnum,yaxnum,color,opts) return aro; } +function setAxType(layout,axref,axtype) { + axname = axisIds.id2name(axref); + layout[axname].type = axtype; +} + +// Calculate the ax value of an annotation given a particular desired scaling K +// This also works with log axes by taking logs of each part of the sum, so that +// the length in pixels is multiplied by the scalar +function annaxscale(ac,axistype,c0,K) { + var ret; + if (axistype === 'log') { + ret = Math.pow(10, Math.log10(x0) + 2 * (Math.log10(ax) - Math.log10(x0))); + } else { + ret = x0 + 2 * (ax - x0); + } + return ret; +} + +// This tests to see that an annotation was drawn correctly. +// Determinining the length of the arrow seems complicated due to the +// rectangle containing the text, so we draw 2 annotations, one K times the +// length of the other, and solve for the desired arrow length from the +// length measured on the screen. This works because multiplying the length +// of the arrow doesn't change where the arrow meets the text box. +// xaxistype can be linear|log, only used if xref has type 'range' or 'domain', +// same for yaxistype and yref +function annotationTest(gd,layout,x0,y0,ax,ay,xref,yref,axref,ayref,xaxistype,yaxistype) { + // if xref != axref or axref === 'pixel' then ax is a value relative to + // x0 but in pixels. Same for yref + var xreftype = Axes.getRefType(xref); + var yreftype = Axes.getRefType(yref); + var axpixels = false; + var xaxname; + var yaxname; + if (xreftype != 'paper') { + setAxType(layout,xref,xaxistype); + } + if (yreftype != 'paper') { + setAxType(layout,yref,yaxistype); + } + var xpixels; + var ypixels; + var opts0 = { + ax: ax, + ay: ay, + axref: axref, + ayref: ayref, + }; + var opts1 = { + ax: axpixels ? 2 * ax : annaxscale(ax,xaxistype,x0,2), + ay: aypixels ? 2 * ay : annaxscale(ay,yaxistype,y0,2), + axref: axref, + ayref: ayref, + }; + // 2 colors so we can extract each annotation individually + var color0 = 'rgb(10, 20, 30)'; + var color1 = 'rgb(10, 20, 31)'; + var anno0 = aroFromParams('annotation',x0,y0,xref,yref,color0,opts0); + var anno1 = aroFromParams('annotation',x0,y0,xref,yref,color1,opts1); + layout.annotations = [anno0,anno1]; + Plotly.relayout(gd,layout); + // the choice of anno1 or anno0 is arbitrary + var xabspixels = mapAROCoordToPixel(gd.layout,xref,anno1,'x0'); + var yabspixels = mapAROCoordToPixel(gd.layout,yref,anno1,'y0'); + if((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { + axpixels = true; + // no need to map the specified values to pixels (because that's what + // they are already) + xpixels = ax; + } else { + axpixels = false; + xpixels = mapAROCoordToPixel(gd.layout,xref,anno0,'ax') - + - xabspixels; + } + if((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { + aypixels = true; + // no need to map the specified values to pixels (because that's what + // they are already) + ypixels = ay; + } else { + aypixels = false; + ypixels = mapAROCoordToPixel(gd.layout,yref,anno0,'ay') - + - yabspixels; + } + var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0)); + var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1)); + // solve for the arrow length's x coordinate + var arrowLenX = ((annobbox1.x+annobbox1.width) - (annobbox0.x+annobbox0.width)); + // SVG's y is the top of the box, so no need to offset by height + var arrowLenY = annobbox1.y - annobbox0.y; + var ret = coordsEq(arrowLenX,xpixels) + && coordsEq(arrowLenY,ypixels) + && coordsEq(xabspixels,annobbox0.x) + && coordsEq(yabspixels,annobbox0.y+annobbox0.height); + return ret; +} + // axid is e.g., 'x', 'y2' etc. function logAxisIfAxType(layoutIn,layoutOut,axid,axtype) { if (axtype === 'log') { @@ -104,29 +216,32 @@ function logAxisIfAxType(layoutIn,layoutOut,axid,axtype) { } } - // axref can be xref or yref // c can be x0, x1, y0, y1 -function mapAROCoordToPixel(layout,axref,aro,c) { +// offset allows adding something to the coordinate before converting, say if +// you want to map the point on the other side of a square +function mapAROCoordToPixel(layout,axref,aro,c,offset) { var reftype = Axes.getRefType(aro[axref]); var axletter = axref[0]; var ret; + offset = (offset === undefined) ? 0 : offset; + var val = aro[c] + offset; if (reftype === 'range') { var axis = axisIds.id2name(aro[axref]); - ret = pixelCalc.mapRangeToPixel(layout, axis, aro[c]); + ret = pixelCalc.mapRangeToPixel(layout, axis, val); } else if (reftype === 'domain') { var axis = axisIds.id2name(aro[axref]); - ret = pixelCalc.mapDomainToPixel(layout, axis, aro[c]); + ret = pixelCalc.mapDomainToPixel(layout, axis, val); } else if (reftype === 'paper') { var axis = axref[0]; - ret = pixelCalc.mapPaperToPixel(layout, axis, aro[c]); + ret = pixelCalc.mapPaperToPixel(layout, axis, val); } return ret; } -// compute the bounding box of the aro so that it can be compared with the SVG +// compute the bounding box of the shape so that it can be compared with the SVG // bounding box -function aroToBBox(layout,aro) { +function shapeToBBox(layout,aro) { var bbox = {}; var x1; var y1; @@ -143,6 +258,51 @@ function aroToBBox(layout,aro) { return bbox; } +function imageToBBox(layout,img) { + var bbox = {}; + // these will be pixels from the bottom of the plot and will be manipulated + // below to be compatible with the SVG bounding box + var x0; + var x1; + var y0; + var y1; + switch (img.xanchor) { + case 'left': + x0 = mapAROCoordToPixel(layout,'xref',img,'x'); + x1 = mapAROCoordToPixel(layout,'xref',img,'x',img.sizex); + case 'right': + x0 = mapAROCoordToPixel(layout,'xref',img,'x',-img.sizex); + x1 = mapAROCoordToPixel(layout,'xref',img,'x'); + case 'center': + x0 = mapAROCoordToPixel(layout,'xref',img,'x',-img.sizex*0.5); + x1 = mapAROCoordToPixel(layout,'xref',img,'x',img.sizex*0.5); + default: + throw 'Bad xanchor: ' + img.xanchor; + } + switch (img.yanchor) { + case 'bottom': + y0 = mapAROCoordToPixel(layout,'yref',img,'y'); + y1 = mapAROCoordToPixel(layout,'yref',img,'y',img.sizey); + case 'top': + y0 = mapAROCoordToPixel(layout,'yref',img,'y',-img.sizey); + y1 = mapAROCoordToPixel(layout,'yref',img,'y'); + case 'middle': + y0 = mapAROCoordToPixel(layout,'yref',img,'y',-img.sizey*0.5); + y1 = mapAROCoordToPixel(layout,'yref',img,'y',img.sizey*0.5); + default: + throw 'Bad yanchor: ' + img.yanchor; + } + bbox.x = x0; + bbox.width = x1 - x0; + // done this way because the pixel value of y1 will be smaller than the + // pixel value x0 if y1 > y0 (because of how SVG draws relative to the top + // of the screen) + bbox.y = y1; + bbox.height = y0 - y1; + return bbox; +} + + function coordsEq(a,b) { return Math.abs(a - b) < EQUALITY_TOLERANCE; } @@ -154,14 +314,57 @@ function compareBBoxes(a,b) { true); } +function findAROByColor(color) { + var ret = d3.selectAll('path').filter(function () { + return this.style.stroke === color; + }).node(); + return ret; +} + +function findImage() { + var ret = d3.select('g image').node(); + return ret; +} + +function checkImage(layout,imageObj,imageBBox) { +} + +function imageTest(gd,layout,xaxtype,yaxtype,x,y,sizex,sizey,xanchor,yanchor,xref,yref) { + var image = { + x: x, + y: y, + sizex: sizex, + sizey: sizey, + source: testImage, + xanchor: xanchor, + yanchor: yanchor, + xref: xref, + yref: yref, + sizing: "stretch" + }; + var xreftype = Axes.getRefType(xref); + var yreftype = Axes.getRefType(yref); + if (xreftype != 'paper') { + setAxType(layout,xref,xaxistype); + } + if (yreftype != 'paper') { + setAxType(layout,yref,yaxistype); + } + layout.images=[image]; + Plotly.relayout(gd,layout); + var imageElem = findImage(); + var svgImageBBox = getSVGElemScreenBBox(imageElem); + var imageBBox = imageToBBox(gd.layout,image); + var ret = compareBBoxes(svgImageBBox,imageBBox); + return ret; +} + // gets the SVG bounding box of the aro and checks it against what mapToPixel // gives function checkAROPosition(gd,aro) { - var aroPath = d3.selectAll('path').filter(function () { - return this.style.stroke === aro.line.color; - }).node(); + var aroPath = findAROByColor(aro.line.color); var aroPathBBox = getSVGElemScreenBBox(aroPath); - var aroBBox = aroToBBox(gd.layout,aro); + var aroBBox = shapeToBBox(gd.layout,aro); var ret = compareBBoxes(aroBBox,aroPathBBox); if (DEBUG) { console.log('SVG BBox',aroPathBBox); @@ -177,60 +380,65 @@ var aroPositionsX = [ ref: 'range', value: [2,3], // for objects that need a size (i.e., images) - size: 1.5 + size: 1.5, + // for the case when annotations specifies arrow in pixels, this value + // is read instead of value[1] + pixel: 50 }, { // aros referring to domains ref: 'domain', value: [0.2,0.75], - size: 0.3 + size: 0.3, + pixel: 60 }, { // aros referring to paper ref: 'paper', value: [0.25, 0.8], - size: 0.35 + size: 0.35, + pixel: 70 }, ]; -var aroRelPixelSizeX [ - // this means specify the offset with the second value in aroPositions{X,Y} - { ref: 'nopixel' }, - // with this you can specify the arrow in pixels - { ref: 'pixel', value: 100 } -]; var aroPositionsY = [ { // aros referring to data ref: 'range', // two values for rects - value: [1,2] + value: [1,2], + pixel: 30, + size: 1.2 }, { // aros referring to domains ref: 'domain', value: [0.25,0.7], + pixel: 40, + size: .2 }, { // aros referring to paper ref: 'paper', - value: [0.2, 0.85] + value: [0.2, 0.85], + pixel: 80, + size: .3 } ]; -var aroRelPixelSizeY [ - // this means specify the offset with the second value in aroPositions{X,Y} - { ref: 'nopixel' }, - // with this you can specify the arrow in pixels - { ref: 'pixel', value: 200 } -]; var aroTypes = ['shape', 'annotation', 'image']; var axisTypes = [ 'linear', 'log' ]; // Test on 'x', 'y', 'x2', 'y2' axes // TODO the 'paper' position references are tested twice when once would // suffice. -var axNum = ['','2']; -// only test line and rect for now -var aroType = ['line','rect']; +var axisPairs = [['x','y'],['x2','y'],['x','y2'],['x2','y2']]; +// For annotations: if arrow coordinate is in the same coordinate system 's', if +// pixel then 'p' +var arrowAxis = [['s','s'],['p','s'],['s','p'],['p','p']]; +// only test the shapes line and rect for now +var shapeType = ['line','rect']; +// anchor positions for images +var xAnchors = ['left', 'center', 'right']; +var yAnchors = ['top', 'middle', 'bottom']; // this color chosen so it can easily be found with d3 // NOTE: for images color cannot be set but it will be the only image in the // plot so you can use d3.select('g image').node() @@ -278,7 +486,6 @@ var testDomRefAROCombo = function(combo) { // Test correct aro positions function test_correct_aro_positions () { - var iterable = require('extra-iterable'); // for both x and y axes var testCombos = [...iterable.cartesianProduct([ axNum,axisTypes,aroPositionsX,axNum,axisTypes,aroPositionsY,aroType @@ -288,4 +495,39 @@ function test_correct_aro_positions () { testCombos.forEach(testDomRefAROCombo); } -test_correct_aro_positions(); +function runImageTests () { + var testCombos = [...iterable.cartesianProduct([ + axisTypes, axisTypes, axisPairs, + // axis reference types are contained in here + aroPositionsX, aroPositionsY, + xAnchors, yAnchors + ])]; + testCombos.forEach(function(combo) { + var axistypex = combo[0]; + var axistypey = combo[1]; + var axispair = combo[2]; + var aroposx = combo[3]; + var aroposy = combo[4]; + var xanchor = combo[5]; + var yanchor = combo[6]; + var xid = axispair[0]; + var yid = axispair[1]; + var xref = makeAxRef(xid,aroposx.ref); + var yref = makeAxRef(yid,aroposy.ref); + console.log([ + "Testing layout image with parameters:", + "x-axis type:", axistypex, "\n", + "y-axis type:", axistypey, "\n", + "xanchor:", xanchor, "\n", + "yanchor:", yanchor, "\n", + "xref:", xref, "\n", + "yref:", yref, "\n", + ].join(' '),imageTest(gd,layout,axistypex,axistypey, + aroposx.value[0],aroposy.value[0],aroposx.size,aroposy.size, + xanchor,yanchor,xref,yref) + ); + }); +} + +runImageTests(); + diff --git a/test/jasmine/karma.conf.js b/test/jasmine/karma.conf.js index cde3011b4eb..113f3185ade 100644 --- a/test/jasmine/karma.conf.js +++ b/test/jasmine/karma.conf.js @@ -246,7 +246,13 @@ func.defaultConfig = { }, _Firefox: { base: 'Firefox', - flags: ['--width=' + argv.width, '--height=' + argv.height] + flags: ['--width=' + argv.width, '--height=' + argv.height], + prefs: { + 'devtools.toolbox.zoomValue': '1.5', + 'devtools.toolbox.host': 'window', + 'devtools.toolbox.previousHost': 'bottom', + 'devtools.command-button-rulers.enabled': true + } } }, diff --git a/test/jasmine/tests/parcats_test.js b/test/jasmine/tests/parcats_test.js index 9255740ba6e..eb5828ef87b 100644 --- a/test/jasmine/tests/parcats_test.js +++ b/test/jasmine/tests/parcats_test.js @@ -12,7 +12,7 @@ var delay = require('../assets/delay'); var customAssertions = require('../assets/custom_assertions'); var assertHoverLabelContent = customAssertions.assertHoverLabelContent; -var CALLBACK_DELAY = 500; +var CALLBACK_DELAY = 3000; // Testing constants // ================= From 68c807bd5ccb06820c409069ed337290b5ae50f0 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 31 Aug 2020 17:01:26 -0400 Subject: [PATCH 060/112] Trying to get the image tests to pass Running them using a bunch of promises chained with .then makes the browser too slow? And tests fail now that before didn't? --- test/all_layout_images_test.js | 3 + test/domain_ref_shapes_test.js | 429 +++++++++++++---------- test/image/mocks/domain_ref_base.json | 8 +- test/jasmine/assets/create_graph_div.js | 5 +- test/jasmine/assets/destroy_graph_div.js | 5 +- test/jasmine/assets/pixel_calc.js | 8 +- test/run_js_script.sh | 39 +++ 7 files changed, 306 insertions(+), 191 deletions(-) create mode 100644 test/all_layout_images_test.js create mode 100644 test/run_js_script.sh diff --git a/test/all_layout_images_test.js b/test/all_layout_images_test.js new file mode 100644 index 00000000000..dbc0e906bff --- /dev/null +++ b/test/all_layout_images_test.js @@ -0,0 +1,3 @@ +var domainRefTests = require('./domain_ref_shapes_test'); +domainRefTests.runImageTests(); + diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js index 99944de892a..8977fc0d0ac 100644 --- a/test/domain_ref_shapes_test.js +++ b/test/domain_ref_shapes_test.js @@ -1,11 +1,13 @@ // Test the placement of Axis Referencing Objects (AROs) +'use strict' var Plotly = require('../lib/index'); var d3 = require('d3'); var createGraphDiv = require('../test/jasmine/assets/create_graph_div'); var destroyGraphDiv = require('../test/jasmine/assets/destroy_graph_div'); var pixelCalc = require('../test/jasmine/assets/pixel_calc'); -var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_bbox'); +var getSVGElemScreenBBox = require( + '../test/jasmine/assets/get_svg_elem_screen_bbox'); var Lib = require('../src/lib'); var Axes = require('../src/plots/cartesian/axes'); var axisIds = require('../src/plots/cartesian/axis_ids'); @@ -17,22 +19,24 @@ var EQUALITY_TOLERANCE = 1e-2; var DEBUG = true; -var it = function(s,f) { +var it = function(s, f) { console.log('testing ' + s); - f(function() { console.log(s + ' is done.'); }); + f(function() { + console.log(s + ' is done.'); + }); } // acts on an Object representing a aro which could be a line or a rect // DEPRECATED -function aroFromAROPos(aro,axletter,axnum,aropos) { - aro[axletter+'0'] = aropos.value[0]; - aro[axletter+'1'] = aropos.value[1]; +function aroFromAROPos(aro, axletter, axnum, aropos) { + aro[axletter + '0'] = aropos.value[0]; + aro[axletter + '1'] = aropos.value[1]; if (aropos.ref === 'range') { - aro[axletter+'ref'] = axletter + axnum; + aro[axletter + 'ref'] = axletter + axnum; } else if (aropos.ref === 'domain') { - aro[axletter+'ref'] = axletter + axnum + ' domain'; + aro[axletter + 'ref'] = axletter + axnum + ' domain'; } else if (aropos.ref === 'paper') { - aro[axletter+'ref'] = 'paper'; + aro[axletter + 'ref'] = 'paper'; } } @@ -40,16 +44,20 @@ function aroFromAROPos(aro,axletter,axnum,aropos) { // {axid} is the axis id, e.g., x2, y, etc. // {ref} is ['range'|'domain'|'paper'] -function makeAxRef(axid,ref) { +function makeAxRef(axid, ref) { var axref; - if (ref === 'range') { - axref = axid; - } else if (aropos.ref === 'domain') { - axref = axid + ' domain'; - } else if (aropos.ref === 'paper') { - axref = 'paper'; - } else { - throw 'Bad axis type (ref): ' + ref; + switch (ref) { + case 'range': + axref = axid; + break; + case 'domain': + axref = axid + ' domain'; + break; + case 'paper': + axref = 'paper'; + break; + default: + throw 'Bad axis type (ref): ' + ref; } return axref; } @@ -59,8 +67,8 @@ function makeAxRef(axid,ref) { // {coordletter} the letter of the coordinate to assign // {axref} is the axis the coordinate refers to // {value} is the value of the first coordinate (e.g., x0 if axletter is x) -function aroSetCommonParams(aro,coordletter,axref,value) { - aro[coordletter+'0'] = value; +function aroSetCommonParams(aro, coordletter, axref, value) { + aro[coordletter + '0'] = value; aro.axref = axref; } @@ -75,17 +83,20 @@ function aroSetCommonParams(aro,coordletter,axref,value) { // don't test) // images take xsize, ysize, xanchor, yanchor (sizing is set to stretch for simplicity // in computing the bounding box and source is something predetermined) -function aroFromParams(arotype,x0,y0,xref,yref,color,opts) { +function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { var aro = {}; // fill with common values - aroSetCommonParams(aro,'x',xref,x0); - aroSetCommonParams(aro,'y',yref,y0); + aroSetCommonParams(aro, 'x', xref, x0); + aroSetCommonParams(aro, 'y', yref, y0); switch (arotype) { case 'shape': aro.x1 = opts.x1; aro.y1 = opts.y1; aro.type = opts.type; - aro.line = {color: color}; + aro.line = { + color: color + }; + break; case 'annotation': aro.text = "A"; aro.ax = opts.ax; @@ -95,6 +106,7 @@ function aroFromParams(arotype,x0,y0,xref,yref,color,opts) { aro.showarrow = true; aro.arrowhead = 0; aro.arrowcolor = color; + break; case 'image': aro.sizex = opts.sizex; aro.sizey = opts.sizey; @@ -102,13 +114,14 @@ function aroFromParams(arotype,x0,y0,xref,yref,color,opts) { aro.yanchor = opts.yanchor; aro.sizing = "stretch"; aro.source = testImage; + break; default: throw "Bad arotype: " + arotype; } return aro; } -function setAxType(layout,axref,axtype) { +function setAxType(layout, axref, axtype) { axname = axisIds.id2name(axref); layout[axname].type = axtype; } @@ -116,10 +129,11 @@ function setAxType(layout,axref,axtype) { // Calculate the ax value of an annotation given a particular desired scaling K // This also works with log axes by taking logs of each part of the sum, so that // the length in pixels is multiplied by the scalar -function annaxscale(ac,axistype,c0,K) { +function annaxscale(ac, axistype, c0, K) { var ret; if (axistype === 'log') { - ret = Math.pow(10, Math.log10(x0) + 2 * (Math.log10(ax) - Math.log10(x0))); + ret = Math.pow(10, Math.log10(x0) + 2 * (Math.log10(ax) - Math.log10( + x0))); } else { ret = x0 + 2 * (ax - x0); } @@ -134,7 +148,8 @@ function annaxscale(ac,axistype,c0,K) { // of the arrow doesn't change where the arrow meets the text box. // xaxistype can be linear|log, only used if xref has type 'range' or 'domain', // same for yaxistype and yref -function annotationTest(gd,layout,x0,y0,ax,ay,xref,yref,axref,ayref,xaxistype,yaxistype) { +function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, + xaxistype, yaxistype) { // if xref != axref or axref === 'pixel' then ax is a value relative to // x0 but in pixels. Same for yref var xreftype = Axes.getRefType(xref); @@ -143,10 +158,10 @@ function annotationTest(gd,layout,x0,y0,ax,ay,xref,yref,axref,ayref,xaxistype,ya var xaxname; var yaxname; if (xreftype != 'paper') { - setAxType(layout,xref,xaxistype); + setAxType(layout, xref, xaxistype); } if (yreftype != 'paper') { - setAxType(layout,yref,yaxistype); + setAxType(layout, yref, yaxistype); } var xpixels; var ypixels; @@ -157,59 +172,62 @@ function annotationTest(gd,layout,x0,y0,ax,ay,xref,yref,axref,ayref,xaxistype,ya ayref: ayref, }; var opts1 = { - ax: axpixels ? 2 * ax : annaxscale(ax,xaxistype,x0,2), - ay: aypixels ? 2 * ay : annaxscale(ay,yaxistype,y0,2), + ax: axpixels ? 2 * ax : annaxscale(ax, xaxistype, x0, 2), + ay: aypixels ? 2 * ay : annaxscale(ay, yaxistype, y0, 2), axref: axref, ayref: ayref, }; // 2 colors so we can extract each annotation individually var color0 = 'rgb(10, 20, 30)'; var color1 = 'rgb(10, 20, 31)'; - var anno0 = aroFromParams('annotation',x0,y0,xref,yref,color0,opts0); - var anno1 = aroFromParams('annotation',x0,y0,xref,yref,color1,opts1); - layout.annotations = [anno0,anno1]; - Plotly.relayout(gd,layout); + var anno0 = aroFromParams('annotation', x0, y0, xref, yref, color0, opts0); + var anno1 = aroFromParams('annotation', x0, y0, xref, yref, color1, opts1); + layout.annotations = [anno0, anno1]; + Plotly.relayout(gd, layout); // the choice of anno1 or anno0 is arbitrary - var xabspixels = mapAROCoordToPixel(gd.layout,xref,anno1,'x0'); - var yabspixels = mapAROCoordToPixel(gd.layout,yref,anno1,'y0'); - if((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { + var xabspixels = mapAROCoordToPixel(gd.layout, xref, anno1, 'x0'); + var yabspixels = mapAROCoordToPixel(gd.layout, yref, anno1, 'y0'); + if ((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { axpixels = true; // no need to map the specified values to pixels (because that's what // they are already) xpixels = ax; } else { axpixels = false; - xpixels = mapAROCoordToPixel(gd.layout,xref,anno0,'ax') - - - xabspixels; + xpixels = mapAROCoordToPixel(gd.layout, xref, anno0, 'ax') - + -xabspixels; } - if((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { + if ((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { aypixels = true; // no need to map the specified values to pixels (because that's what // they are already) ypixels = ay; } else { aypixels = false; - ypixels = mapAROCoordToPixel(gd.layout,yref,anno0,'ay') - - - yabspixels; + ypixels = mapAROCoordToPixel(gd.layout, yref, anno0, 'ay') - + -yabspixels; } - var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0)); - var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1)); + var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0,"#"+gd.id)); + var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1,"#"+gd.id)); // solve for the arrow length's x coordinate - var arrowLenX = ((annobbox1.x+annobbox1.width) - (annobbox0.x+annobbox0.width)); + var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 + .width)); // SVG's y is the top of the box, so no need to offset by height var arrowLenY = annobbox1.y - annobbox0.y; - var ret = coordsEq(arrowLenX,xpixels) - && coordsEq(arrowLenY,ypixels) - && coordsEq(xabspixels,annobbox0.x) - && coordsEq(yabspixels,annobbox0.y+annobbox0.height); + var ret = coordsEq(arrowLenX, xpixels) && + coordsEq(arrowLenY, ypixels) && + coordsEq(xabspixels, annobbox0.x) && + coordsEq(yabspixels, annobbox0.y + annobbox0.height); return ret; } // axid is e.g., 'x', 'y2' etc. -function logAxisIfAxType(layoutIn,layoutOut,axid,axtype) { - if (axtype === 'log') { - var axname = axisIds.id2name(axid); - var axis = {...layoutIn[axname]}; +function logAxisIfAxType(layoutIn, layoutOut, axid, axtype) { + var axname = axisIds.id2name(axid); + if ((axtype === 'log') && (axid !== undefined)) { + var axis = { + ...layoutIn[axname] + }; axis.type = 'log'; axis.range = axis.range.map(Math.log10); layoutOut[axname] = axis; @@ -220,7 +238,7 @@ function logAxisIfAxType(layoutIn,layoutOut,axid,axtype) { // c can be x0, x1, y0, y1 // offset allows adding something to the coordinate before converting, say if // you want to map the point on the other side of a square -function mapAROCoordToPixel(layout,axref,aro,c,offset) { +function mapAROCoordToPixel(layout, axref, aro, c, offset, nolog) { var reftype = Axes.getRefType(aro[axref]); var axletter = axref[0]; var ret; @@ -228,7 +246,7 @@ function mapAROCoordToPixel(layout,axref,aro,c,offset) { var val = aro[c] + offset; if (reftype === 'range') { var axis = axisIds.id2name(aro[axref]); - ret = pixelCalc.mapRangeToPixel(layout, axis, val); + ret = pixelCalc.mapRangeToPixel(layout, axis, val, nolog); } else if (reftype === 'domain') { var axis = axisIds.id2name(aro[axref]); ret = pixelCalc.mapDomainToPixel(layout, axis, val); @@ -241,24 +259,24 @@ function mapAROCoordToPixel(layout,axref,aro,c,offset) { // compute the bounding box of the shape so that it can be compared with the SVG // bounding box -function shapeToBBox(layout,aro) { +function shapeToBBox(layout, aro) { var bbox = {}; var x1; var y1; // map x coordinates - bbox.x = mapAROCoordToPixel(layout,'xref',aro,'x0'); - x1 = mapAROCoordToPixel(layout,'xref',aro,'x1'); + bbox.x = mapAROCoordToPixel(layout, 'xref', aro, 'x0'); + x1 = mapAROCoordToPixel(layout, 'xref', aro, 'x1'); // SVG bounding boxes have x,y referring to top left corner, but here we are // specifying aros where y0 refers to the bottom left corner like // Plotly.js, so we swap y0 and y1 - bbox.y = mapAROCoordToPixel(layout,'yref',aro,'y1'); - y1 = mapAROCoordToPixel(layout,'yref',aro,'y0'); + bbox.y = mapAROCoordToPixel(layout, 'yref', aro, 'y1'); + y1 = mapAROCoordToPixel(layout, 'yref', aro, 'y0'); bbox.width = x1 - bbox.x; bbox.height = y1 - bbox.y; return bbox; } -function imageToBBox(layout,img) { +function imageToBBox(layout, img) { var bbox = {}; // these will be pixels from the bottom of the plot and will be manipulated // below to be compatible with the SVG bounding box @@ -268,27 +286,33 @@ function imageToBBox(layout,img) { var y1; switch (img.xanchor) { case 'left': - x0 = mapAROCoordToPixel(layout,'xref',img,'x'); - x1 = mapAROCoordToPixel(layout,'xref',img,'x',img.sizex); + x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', undefined, true); + x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', img.sizex, true); + break; case 'right': - x0 = mapAROCoordToPixel(layout,'xref',img,'x',-img.sizex); - x1 = mapAROCoordToPixel(layout,'xref',img,'x'); + x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', -img.sizex, true); + x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', undefined, true); + break; case 'center': - x0 = mapAROCoordToPixel(layout,'xref',img,'x',-img.sizex*0.5); - x1 = mapAROCoordToPixel(layout,'xref',img,'x',img.sizex*0.5); + x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', -img.sizex * 0.5, true); + x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', img.sizex * 0.5, true); + break; default: throw 'Bad xanchor: ' + img.xanchor; } switch (img.yanchor) { case 'bottom': - y0 = mapAROCoordToPixel(layout,'yref',img,'y'); - y1 = mapAROCoordToPixel(layout,'yref',img,'y',img.sizey); + y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', undefined, true); + y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', img.sizey, true); + break; case 'top': - y0 = mapAROCoordToPixel(layout,'yref',img,'y',-img.sizey); - y1 = mapAROCoordToPixel(layout,'yref',img,'y'); + y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', -img.sizey, true); + y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', undefined, true); + break; case 'middle': - y0 = mapAROCoordToPixel(layout,'yref',img,'y',-img.sizey*0.5); - y1 = mapAROCoordToPixel(layout,'yref',img,'y',img.sizey*0.5); + y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', -img.sizey * 0.5, true); + y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', img.sizey * 0.5, true); + break; default: throw 'Bad yanchor: ' + img.yanchor; } @@ -303,33 +327,37 @@ function imageToBBox(layout,img) { } -function coordsEq(a,b) { +function coordsEq(a, b) { return Math.abs(a - b) < EQUALITY_TOLERANCE; } -function compareBBoxes(a,b) { - return ['x','y','width','height'].map( - (k,)=>coordsEq(a[k],b[k])).reduce( - (l,r)=>l&&r, - true); +function compareBBoxes(a, b) { + return ['x', 'y', 'width', 'height'].map( + (k, ) => coordsEq(a[k], b[k])).reduce( + (l, r) => l && r, + true); } -function findAROByColor(color) { - var ret = d3.selectAll('path').filter(function () { +function findAROByColor(color,id) { + id = (id === undefined) ? '' : id + ' '; + var selector = id + 'path'; + var ret = d3.selectAll(id).filter(function() { return this.style.stroke === color; }).node(); return ret; } -function findImage() { +function findImage(id) { + id = (id === undefined) ? '' : id + ' '; + var selector = id + 'g image'; var ret = d3.select('g image').node(); return ret; } -function checkImage(layout,imageObj,imageBBox) { -} +function checkImage(layout, imageObj, imageBBox) {} -function imageTest(gd,layout,xaxtype,yaxtype,x,y,sizex,sizey,xanchor,yanchor,xref,yref) { +function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, + yanchor, xref, yref, xid, yid) { var image = { x: x, y: y, @@ -344,41 +372,44 @@ function imageTest(gd,layout,xaxtype,yaxtype,x,y,sizex,sizey,xanchor,yanchor,xre }; var xreftype = Axes.getRefType(xref); var yreftype = Axes.getRefType(yref); - if (xreftype != 'paper') { - setAxType(layout,xref,xaxistype); - } - if (yreftype != 'paper') { - setAxType(layout,yref,yaxistype); - } - layout.images=[image]; - Plotly.relayout(gd,layout); - var imageElem = findImage(); - var svgImageBBox = getSVGElemScreenBBox(imageElem); - var imageBBox = imageToBBox(gd.layout,image); - var ret = compareBBoxes(svgImageBBox,imageBBox); - return ret; + var ret; + // we pass xid, yid because we possibly want to change some axes to log, + // even if we refer to paper in the end + logAxisIfAxType(gd.layout, layout, xid, xaxtype); + logAxisIfAxType(gd.layout, layout, yid, yaxtype); + layout.images = [image]; + return Plotly.relayout(gd, layout) + .then(function (gd) { + var imageElem = findImage("#"+gd.id); + var svgImageBBox = getSVGElemScreenBBox(imageElem); + var imageBBox = imageToBBox(gd.layout, image); + ret = compareBBoxes(svgImageBBox, imageBBox); + //if (!ret) { + // throw 'test failed'; + //} + return ret; + }); } // gets the SVG bounding box of the aro and checks it against what mapToPixel // gives -function checkAROPosition(gd,aro) { - var aroPath = findAROByColor(aro.line.color); +function checkAROPosition(gd, aro) { + var aroPath = findAROByColor(aro.line.color,"#"+gd.id); var aroPathBBox = getSVGElemScreenBBox(aroPath); - var aroBBox = shapeToBBox(gd.layout,aro); - var ret = compareBBoxes(aroBBox,aroPathBBox); + var aroBBox = shapeToBBox(gd.layout, aro); + var ret = compareBBoxes(aroBBox, aroPathBBox); if (DEBUG) { - console.log('SVG BBox',aroPathBBox); - console.log('aro BBox',aroBBox); + console.log('SVG BBox', aroPathBBox); + console.log('aro BBox', aroBBox); } return ret; } // some made-up values for testing -var aroPositionsX = [ - { +var aroPositionsX = [{ // aros referring to data ref: 'range', - value: [2,3], + value: [2, 3], // for objects that need a size (i.e., images) size: 1.5, // for the case when annotations specifies arrow in pixels, this value @@ -388,7 +419,7 @@ var aroPositionsX = [ { // aros referring to domains ref: 'domain', - value: [0.2,0.75], + value: [0.2, 0.75], size: 0.3, pixel: 60 }, @@ -400,19 +431,18 @@ var aroPositionsX = [ pixel: 70 }, ]; -var aroPositionsY = [ - { +var aroPositionsY = [{ // aros referring to data ref: 'range', // two values for rects - value: [1,2], + value: [1, 2], pixel: 30, size: 1.2 }, { // aros referring to domains ref: 'domain', - value: [0.25,0.7], + value: [0.25, 0.7], pixel: 40, size: .2 }, @@ -426,16 +456,26 @@ var aroPositionsY = [ ]; var aroTypes = ['shape', 'annotation', 'image']; -var axisTypes = [ 'linear', 'log' ]; +var axisTypes = ['linear', 'log']; // Test on 'x', 'y', 'x2', 'y2' axes // TODO the 'paper' position references are tested twice when once would // suffice. -var axisPairs = [['x','y'],['x2','y'],['x','y2'],['x2','y2']]; +var axisPairs = [ + ['x', 'y'], + ['x2', 'y'], + ['x', 'y2'], + ['x2', 'y2'] +]; // For annotations: if arrow coordinate is in the same coordinate system 's', if // pixel then 'p' -var arrowAxis = [['s','s'],['p','s'],['s','p'],['p','p']]; +var arrowAxis = [ + ['s', 's'], + ['p', 's'], + ['s', 'p'], + ['p', 'p'] +]; // only test the shapes line and rect for now -var shapeType = ['line','rect']; +var shapeType = ['line', 'rect']; // anchor positions for images var xAnchors = ['left', 'center', 'right']; var yAnchors = ['top', 'middle', 'bottom']; @@ -444,65 +484,63 @@ var yAnchors = ['top', 'middle', 'bottom']; // plot so you can use d3.select('g image').node() var aroColor = 'rgb(50, 100, 150)'; var testDomRefAROCombo = function(combo) { - var xAxNum = combo[0]; - var xaxisType = combo[1]; - var xaroPos = combo[2]; - var yAxNum = combo[3]; - var yaxisType = combo[4]; - var yaroPos = combo[5]; - var aroType = combo[6]; - it('should draw a ' + aroType - + ' for x' + xAxNum + ' of type ' - + xaxisType - + ' with a value referencing ' - + xaroPos.ref - + ' and for y' + yAxNum + ' of type ' - + yaxisType - + ' with a value referencing ' - + yaroPos.ref, - function (done) { - var gd = createGraphDiv(); - var mock = Lib.extendDeep({}, - require('../test/image/mocks/domain_ref_base.json')); - if (DEBUG) { - console.log(combo); + var xAxNum = combo[0]; + var xaxisType = combo[1]; + var xaroPos = combo[2]; + var yAxNum = combo[3]; + var yaxisType = combo[4]; + var yaroPos = combo[5]; + var aroType = combo[6]; + it('should draw a ' + aroType + + ' for x' + xAxNum + ' of type ' + + xaxisType + + ' with a value referencing ' + + xaroPos.ref + + ' and for y' + yAxNum + ' of type ' + + yaxisType + + ' with a value referencing ' + + yaroPos.ref, + function(done) { + var gd = createGraphDiv(); + var mock = Lib.extendDeep({}, + require('../test/image/mocks/domain_ref_base.json')); + if (DEBUG) { + console.log(combo); + } + Plotly.newPlot(gd, mock) + var aro = { + type: aroType, + line: { + color: aroColor } - Plotly.newPlot(gd, mock) - var aro = { - type: aroType, - line: { color: aroColor } - }; - aroFromAROPos(aro,'x',xAxNum,xaroPos); - aroFromAROPos(aro,'y',yAxNum,yaroPos); - var layout = {shapes: [aro]}; - // change to log axes if need be - logAxisIfAxType(gd.layout,layout,'x'+xAxNum,xaxisType); - logAxisIfAxType(gd.layout,layout,'y'+yAxNum,yaxisType); - Plotly.relayout(gd,layout); - console.log(checkAROPosition(gd,aro)); - destroyGraphDiv(); - }); + }; + aroFromAROPos(aro, 'x', xAxNum, xaroPos); + aroFromAROPos(aro, 'y', yAxNum, yaroPos); + var layout = { + shapes: [aro] + }; + // change to log axes if need be + logAxisIfAxType(gd.layout, layout, 'x' + xAxNum, xaxisType); + logAxisIfAxType(gd.layout, layout, 'y' + yAxNum, yaxisType); + Plotly.relayout(gd, layout); + console.log(checkAROPosition(gd, aro)); + destroyGraphDiv(); + }); } // Test correct aro positions -function test_correct_aro_positions () { +function test_correct_aro_positions() { // for both x and y axes var testCombos = [...iterable.cartesianProduct([ - axNum,axisTypes,aroPositionsX,axNum,axisTypes,aroPositionsY,aroType + axNum, axisTypes, aroPositionsX, axNum, axisTypes, + aroPositionsY, aroType ])]; // map all the combinations to a aro definition and check this aro is // placed properly testCombos.forEach(testDomRefAROCombo); } -function runImageTests () { - var testCombos = [...iterable.cartesianProduct([ - axisTypes, axisTypes, axisPairs, - // axis reference types are contained in here - aroPositionsX, aroPositionsY, - xAnchors, yAnchors - ])]; - testCombos.forEach(function(combo) { +function testImageCombo(combo,keep_graph_div) { var axistypex = combo[0]; var axistypey = combo[1]; var axispair = combo[2]; @@ -510,24 +548,53 @@ function runImageTests () { var aroposy = combo[4]; var xanchor = combo[5]; var yanchor = combo[6]; + var gd_id = combo[7]; var xid = axispair[0]; var yid = axispair[1]; - var xref = makeAxRef(xid,aroposx.ref); - var yref = makeAxRef(yid,aroposy.ref); - console.log([ - "Testing layout image with parameters:", - "x-axis type:", axistypex, "\n", - "y-axis type:", axistypey, "\n", - "xanchor:", xanchor, "\n", - "yanchor:", yanchor, "\n", - "xref:", xref, "\n", - "yref:", yref, "\n", - ].join(' '),imageTest(gd,layout,axistypex,axistypey, - aroposx.value[0],aroposy.value[0],aroposx.size,aroposy.size, - xanchor,yanchor,xref,yref) - ); - }); + var xref = makeAxRef(xid, aroposx.ref); + var yref = makeAxRef(yid, aroposy.ref); + var gd = createGraphDiv(gd_id); + var mock = Lib.extendDeep({}, + require('../test/image/mocks/domain_ref_base.json')); + if (DEBUG) { + console.log(combo); + } + return Plotly.newPlot(gd, mock).then(function (gd) { + return imageTest(gd, {}, axistypex, axistypey, + aroposx.value[0], aroposy.value[0], aroposx.size, + aroposy.size, + xanchor, yanchor, xref, yref, xid, yid); + }).then( function (test_ret) { + console.log([ + "Testing layout image with parameters:", + "x-axis type:", axistypex, "\n", + "y-axis type:", axistypey, "\n", + "xanchor:", xanchor, "\n", + "yanchor:", yanchor, "\n", + "xref:", xref, "\n", + "yref:", yref, "\n", + ].join(' '), test_ret); + }).then( function () { + if (!keep_graph_div) { + destroyGraphDiv(gd_id); + } + }); } -runImageTests(); +function runImageTests() { + var testCombos = [...iterable.cartesianProduct([ + axisTypes, axisTypes, axisPairs, + // axis reference types are contained in here + aroPositionsX, aroPositionsY, + xAnchors, yAnchors + ])]; + // add a unique id to each combination so we can instantiate a unique graph + // each time + testCombos=testCombos.map((c,i)=>c.concat(['graph-'+i])).slice(0,20); + testCombos.map(testImageCombo).reduce((a,v)=>a.then(v)); +} +module.exports = { + runImageTests: runImageTests, + testImageCombo: testImageCombo +}; diff --git a/test/image/mocks/domain_ref_base.json b/test/image/mocks/domain_ref_base.json index a5d07f46e7b..62ecb8b08fd 100644 --- a/test/image/mocks/domain_ref_base.json +++ b/test/image/mocks/domain_ref_base.json @@ -18,20 +18,20 @@ "layout": { "xaxis": { "domain": [0,0.75], - "range": [0, 3] + "range": [1, 3] }, "yaxis": { "domain": [0,.4], - "range": [0, 4] + "range": [1, 4] }, "xaxis2": { "domain": [0.75,1], - "range": [0, 4], + "range": [1, 4], "anchor": "y2" }, "yaxis2": { "domain": [.4,1], - "range": [0, 3], + "range": [1, 3], "anchor": "x2" }, "margin": { "l": 100, "r": 100, "t": 100, "b": 100, "autoexpand": false }, diff --git a/test/jasmine/assets/create_graph_div.js b/test/jasmine/assets/create_graph_div.js index 9791d46018c..3db6073f61a 100644 --- a/test/jasmine/assets/create_graph_div.js +++ b/test/jasmine/assets/create_graph_div.js @@ -1,8 +1,9 @@ 'use strict'; -module.exports = function createGraphDiv() { +module.exports = function createGraphDiv(id) { + id = (id === undefined) ? 'graph' : id; var gd = document.createElement('div'); - gd.id = 'graph'; + gd.id = id; document.body.appendChild(gd); // force the graph to be at position 0,0 no matter what diff --git a/test/jasmine/assets/destroy_graph_div.js b/test/jasmine/assets/destroy_graph_div.js index a1bd18b9741..eb891513020 100644 --- a/test/jasmine/assets/destroy_graph_div.js +++ b/test/jasmine/assets/destroy_graph_div.js @@ -1,7 +1,8 @@ 'use strict'; -module.exports = function destroyGraphDiv() { - var gd = document.getElementById('graph'); +module.exports = function destroyGraphDiv(id) { + id = (id === undefined) ? 'graph' : id; + var gd = document.getElementById(id); if(gd) document.body.removeChild(gd); }; diff --git a/test/jasmine/assets/pixel_calc.js b/test/jasmine/assets/pixel_calc.js index df30214b23d..7a6ec5ca14c 100644 --- a/test/jasmine/assets/pixel_calc.js +++ b/test/jasmine/assets/pixel_calc.js @@ -50,8 +50,12 @@ function mapDomainToPixel(layout, axis, d) { } // Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc. -function mapRangeToPixel(layout, axis, r) { - if (layout[axis].type === 'log') { +// nolog is provided to avoid taking the log of the value even if the axis is a +// log axis. This is used in the case of layout images, whose corner coordinates +// and dimensions are specified in powers of 10, e.g., if the corner's x +// coordinate is at data 10, then the x value passed is 1 +function mapRangeToPixel(layout, axis, r, nolog) { + if ((!nolog)&&(layout[axis].type === 'log')) { r = Math.log10(r); } var d = (r - layout[axis].range[0]) / (layout[axis].range[1] - layout[axis].range[0]); diff --git a/test/run_js_script.sh b/test/run_js_script.sh new file mode 100644 index 00000000000..072f7a3d8d6 --- /dev/null +++ b/test/run_js_script.sh @@ -0,0 +1,39 @@ +# wraps a JS in a simple webpage and runs it in a browser + +[ -z "$1" ] && (echo "Provide path to script as argument" && exit -1) +[ -z "$BROWSER_BIN" ] && BROWSER_BIN=firefox +# delay time in seconds before launching browser +[ -z "$D" ] && D=10 + +stripped_script="$(basename ${1%.*})" + +html_out="/tmp/$stripped_script.html" + +cat > "$html_out" < + + + + + + + + +HEREDOC + +node_modules/.bin/watchify "$1" -o "/tmp/$stripped_script-min.js" -v -d& +watchify_proc=$! + +_kill_bg_and_exit () { + kill -9 "$watchify_proc" && exit 0 +} + +sleep "$D" + +echo "Opening browser at URL:" +echo "$html_out" +$BROWSER_BIN "$html_out" + +trap _kill_bg_and_exit SIGINT + +while [ 1 ]; do sleep 1; done From 60e053dc0d998fada0c05efbcabe559ef0530c55 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 1 Sep 2020 14:57:25 -0400 Subject: [PATCH 061/112] It seems the image tests are running correctly The only way to actually know is to run them wrapped in Jasmine stuff, which we will do soon. --- test/all_layout_images_test.js | 7 ++++-- test/domain_ref_shapes_test.js | 40 ++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/test/all_layout_images_test.js b/test/all_layout_images_test.js index dbc0e906bff..08eeb1a3712 100644 --- a/test/all_layout_images_test.js +++ b/test/all_layout_images_test.js @@ -1,3 +1,6 @@ var domainRefTests = require('./domain_ref_shapes_test'); -domainRefTests.runImageTests(); - +domainRefTests.runImageTests( +{start:0,stop:10}, +function (combo) { + return combo[1] === 'log'; +}); diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js index 8977fc0d0ac..c40a81a177b 100644 --- a/test/domain_ref_shapes_test.js +++ b/test/domain_ref_shapes_test.js @@ -358,6 +358,7 @@ function checkImage(layout, imageObj, imageBBox) {} function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, yanchor, xref, yref, xid, yid) { + console.log('running imageTest on ',gd); var image = { x: x, y: y, @@ -540,6 +541,9 @@ function test_correct_aro_positions() { testCombos.forEach(testDomRefAROCombo); } +var testImageComboMock = Lib.extendDeep({}, + require('../test/image/mocks/domain_ref_base.json')); + function testImageCombo(combo,keep_graph_div) { var axistypex = combo[0]; var axistypey = combo[1]; @@ -553,13 +557,14 @@ function testImageCombo(combo,keep_graph_div) { var yid = axispair[1]; var xref = makeAxRef(xid, aroposx.ref); var yref = makeAxRef(yid, aroposy.ref); - var gd = createGraphDiv(gd_id); - var mock = Lib.extendDeep({}, - require('../test/image/mocks/domain_ref_base.json')); if (DEBUG) { console.log(combo); } - return Plotly.newPlot(gd, mock).then(function (gd) { + return new Promise(function(resolve){ + var gd = createGraphDiv(gd_id); + resolve(gd); + }).then(function (gd) { return Plotly.newPlot(gd, testImageComboMock); }) + .then(function (gd) { return imageTest(gd, {}, axistypex, axistypey, aroposx.value[0], aroposy.value[0], aroposx.size, aroposy.size, @@ -575,13 +580,16 @@ function testImageCombo(combo,keep_graph_div) { "yref:", yref, "\n", ].join(' '), test_ret); }).then( function () { + console.log("Hello?", keep_graph_div); if (!keep_graph_div) { + console.log('destroying graph div ', gd_id); + Plotly.purge(gd_id); destroyGraphDiv(gd_id); } }); } -function runImageTests() { +function runImageTests(start_stop,filter) { var testCombos = [...iterable.cartesianProduct([ axisTypes, axisTypes, axisPairs, // axis reference types are contained in here @@ -590,8 +598,26 @@ function runImageTests() { ])]; // add a unique id to each combination so we can instantiate a unique graph // each time - testCombos=testCombos.map((c,i)=>c.concat(['graph-'+i])).slice(0,20); - testCombos.map(testImageCombo).reduce((a,v)=>a.then(v)); + testCombos=testCombos.map((c,i)=>c.concat(['graph-'+i])); + if(filter) { + testCombos=testCombos.filter(filter); + } + if(start_stop) { + testCombos=testCombos.slice(start_stop.start,start_stop.stop); + } + console.log("Executing " + testCombos.length + " tests"); + var tc = testCombos.map(c=>testImageCombo(c,false)).reduce((a,v)=>a.then(v)); +} + +function testAnnotationCombo(combo) { + var axistypex = combo[0]; + var axistypey = combo[1]; + var axispair = combo[2]; + var aroposx = combo[3]; + var aroposy = combo[4]; + var xanchor = combo[5]; + var yanchor = combo[6]; + var gd_id = combo[7]; } module.exports = { From f9d9c35ecb134bdeeb472ed5cd1315f1948caa9b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 1 Sep 2020 18:00:07 -0400 Subject: [PATCH 062/112] Working on the annotation tests It seems that "paper" doesn't work as expected when axref or ayref are set as the same as xref and or yref (who are also set as "paper"). Indeed, the "range" and "domain" reference types do work the new way, where the arrow head can be placed absolutely as long as axref or ayref is exactly the same as the corresponding xref or yref. So "paper" should take up this functionality, or the spec should make it clear that it doesn't work and the test updated accordingly. --- test/all_layout_annotations_test.js | 5 + test/domain_ref_shapes_test.js | 203 +++++++++++++++++--------- test/image/mocks/domain_ref_base.json | 4 +- 3 files changed, 140 insertions(+), 72 deletions(-) create mode 100644 test/all_layout_annotations_test.js diff --git a/test/all_layout_annotations_test.js b/test/all_layout_annotations_test.js new file mode 100644 index 00000000000..a152e25b52f --- /dev/null +++ b/test/all_layout_annotations_test.js @@ -0,0 +1,5 @@ +var domainRefTests = require('./domain_ref_shapes_test'); +domainRefTests.runAnnotationTests( +{start:8,stop:9}, +); + diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js index c40a81a177b..63e18fba18e 100644 --- a/test/domain_ref_shapes_test.js +++ b/test/domain_ref_shapes_test.js @@ -84,12 +84,15 @@ function aroSetCommonParams(aro, coordletter, axref, value) { // images take xsize, ysize, xanchor, yanchor (sizing is set to stretch for simplicity // in computing the bounding box and source is something predetermined) function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { - var aro = {}; // fill with common values - aroSetCommonParams(aro, 'x', xref, x0); - aroSetCommonParams(aro, 'y', yref, y0); + var aro = { + xref: xref, + yref: yref + }; switch (arotype) { case 'shape': + aro.x0 = x0; + aro.y0 = y0; aro.x1 = opts.x1; aro.y1 = opts.y1; aro.type = opts.type; @@ -98,6 +101,8 @@ function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { }; break; case 'annotation': + aro.x = x0; + aro.y = y0; aro.text = "A"; aro.ax = opts.ax; aro.ay = opts.ay; @@ -108,6 +113,8 @@ function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { aro.arrowcolor = color; break; case 'image': + aro.x = x0; + aro.y = y0; aro.sizex = opts.sizex; aro.sizey = opts.sizey; aro.xanchor = opts.xanchor; @@ -132,10 +139,10 @@ function setAxType(layout, axref, axtype) { function annaxscale(ac, axistype, c0, K) { var ret; if (axistype === 'log') { - ret = Math.pow(10, Math.log10(x0) + 2 * (Math.log10(ax) - Math.log10( - x0))); + ret = Math.pow(10, Math.log10(c0) + 2 * (Math.log10(ac) - Math.log10( + c0))); } else { - ret = x0 + 2 * (ax - x0); + ret = c0 + 2 * (ac - c0); } return ret; } @@ -149,20 +156,23 @@ function annaxscale(ac, axistype, c0, K) { // xaxistype can be linear|log, only used if xref has type 'range' or 'domain', // same for yaxistype and yref function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, - xaxistype, yaxistype) { + xaxistype, yaxistype, xid, yid) { // if xref != axref or axref === 'pixel' then ax is a value relative to // x0 but in pixels. Same for yref var xreftype = Axes.getRefType(xref); var yreftype = Axes.getRefType(yref); var axpixels = false; - var xaxname; - var yaxname; - if (xreftype != 'paper') { - setAxType(layout, xref, xaxistype); + if ((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { + axpixels = true; } - if (yreftype != 'paper') { - setAxType(layout, yref, yaxistype); + var aypixels = false; + if ((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { + aypixels = true; } + var xaxname; + var yaxname; + logAxisIfAxType(gd.layout, layout, xid, xaxistype); + logAxisIfAxType(gd.layout, layout, yid, yaxistype); var xpixels; var ypixels; var opts0 = { @@ -183,42 +193,50 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, var anno0 = aroFromParams('annotation', x0, y0, xref, yref, color0, opts0); var anno1 = aroFromParams('annotation', x0, y0, xref, yref, color1, opts1); layout.annotations = [anno0, anno1]; - Plotly.relayout(gd, layout); - // the choice of anno1 or anno0 is arbitrary - var xabspixels = mapAROCoordToPixel(gd.layout, xref, anno1, 'x0'); - var yabspixels = mapAROCoordToPixel(gd.layout, yref, anno1, 'y0'); - if ((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { - axpixels = true; - // no need to map the specified values to pixels (because that's what - // they are already) - xpixels = ax; - } else { - axpixels = false; - xpixels = mapAROCoordToPixel(gd.layout, xref, anno0, 'ax') - - -xabspixels; - } - if ((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { - aypixels = true; - // no need to map the specified values to pixels (because that's what - // they are already) - ypixels = ay; - } else { - aypixels = false; - ypixels = mapAROCoordToPixel(gd.layout, yref, anno0, 'ay') - - -yabspixels; - } - var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0,"#"+gd.id)); - var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1,"#"+gd.id)); - // solve for the arrow length's x coordinate - var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 - .width)); - // SVG's y is the top of the box, so no need to offset by height - var arrowLenY = annobbox1.y - annobbox0.y; - var ret = coordsEq(arrowLenX, xpixels) && - coordsEq(arrowLenY, ypixels) && - coordsEq(xabspixels, annobbox0.x) && - coordsEq(yabspixels, annobbox0.y + annobbox0.height); - return ret; + return Plotly.relayout(gd, layout).then(function (gd) { + // the choice of anno1 or anno0 is arbitrary + var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x'); + var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y'); + if (axpixels) { + // no need to map the specified values to pixels (because that's what + // they are already) + xpixels = ax; + } else { + xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax') - + xabspixels; + } + if (aypixels) { + // no need to map the specified values to pixels (because that's what + // they are already) + ypixels = ay; + } else { + ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay') - + yabspixels; + } + var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0,"#"+gd.id)); + var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1,"#"+gd.id)); + // solve for the arrow length's x coordinate + var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 + .width)); + var arrowLenY; + var yabspixelscmp; + if (aypixels) { + // for annotations whose arrows are specified in relative pixels, + // positive pixel values on the y axis mean moving down the page like + // SVG coordinates, so we have to add height + var arrowLenY = (annobbox1.y + annobbox1.height) + - (annobbox0.y + annobbox0.height); + yabspixelscmp = annobbox0.y; + } else { + var arrowLenY = annobbox1.y - annobbox0.y; + yabspixelscmp = annobbox0.y + annobbox0.height; + } + var ret = coordsEq(arrowLenX, xpixels) && + coordsEq(arrowLenY, ypixels) && + coordsEq(xabspixels, annobbox0.x) && + coordsEq(yabspixels, yabspixelscmp); + return ret; + }); } // axid is e.g., 'x', 'y2' etc. @@ -341,7 +359,7 @@ function compareBBoxes(a, b) { function findAROByColor(color,id) { id = (id === undefined) ? '' : id + ' '; var selector = id + 'path'; - var ret = d3.selectAll(id).filter(function() { + var ret = d3.selectAll(selector).filter(function() { return this.style.stroke === color; }).node(); return ret; @@ -350,7 +368,7 @@ function findAROByColor(color,id) { function findImage(id) { id = (id === undefined) ? '' : id + ' '; var selector = id + 'g image'; - var ret = d3.select('g image').node(); + var ret = d3.select(selector).node(); return ret; } @@ -541,6 +559,19 @@ function test_correct_aro_positions() { testCombos.forEach(testDomRefAROCombo); } +function runComboTests(productItems,testCombo,start_stop,filter) { + var testCombos = [...iterable.cartesianProduct(productItems)]; + testCombos=testCombos.map((c,i)=>c.concat(['graph-'+i])); + if(filter) { + testCombos=testCombos.filter(filter); + } + if(start_stop) { + testCombos=testCombos.slice(start_stop.start,start_stop.stop); + } + console.log("Executing " + testCombos.length + " tests"); + var tc = testCombos.map(c=>testCombo(c,false)).reduce((a,v)=>a.then(v)); +} + var testImageComboMock = Lib.extendDeep({}, require('../test/image/mocks/domain_ref_base.json')); @@ -580,7 +611,6 @@ function testImageCombo(combo,keep_graph_div) { "yref:", yref, "\n", ].join(' '), test_ret); }).then( function () { - console.log("Hello?", keep_graph_div); if (!keep_graph_div) { console.log('destroying graph div ', gd_id); Plotly.purge(gd_id); @@ -590,37 +620,70 @@ function testImageCombo(combo,keep_graph_div) { } function runImageTests(start_stop,filter) { - var testCombos = [...iterable.cartesianProduct([ + runComboTests([ axisTypes, axisTypes, axisPairs, // axis reference types are contained in here aroPositionsX, aroPositionsY, xAnchors, yAnchors - ])]; - // add a unique id to each combination so we can instantiate a unique graph - // each time - testCombos=testCombos.map((c,i)=>c.concat(['graph-'+i])); - if(filter) { - testCombos=testCombos.filter(filter); - } - if(start_stop) { - testCombos=testCombos.slice(start_stop.start,start_stop.stop); - } - console.log("Executing " + testCombos.length + " tests"); - var tc = testCombos.map(c=>testImageCombo(c,false)).reduce((a,v)=>a.then(v)); + ],testImageCombo,start_stop,filter); } -function testAnnotationCombo(combo) { +function testAnnotationCombo(combo,keep_graph_div) { var axistypex = combo[0]; var axistypey = combo[1]; var axispair = combo[2]; var aroposx = combo[3]; var aroposy = combo[4]; - var xanchor = combo[5]; - var yanchor = combo[6]; - var gd_id = combo[7]; + var arrowaxispair = combo[5]; + var gd_id = combo[6]; + var xid = axispair[0]; + var yid = axispair[1]; + var xref = makeAxRef(xid, aroposx.ref); + var yref = makeAxRef(yid, aroposy.ref); + var axref = arrowaxispair[0] === 'p' ? 'pixel' : xref; + var ayref = arrowaxispair[1] === 'p' ? 'pixel' : yref; + var x0 = aroposx.value[0]; + var y0 = aroposy.value[0]; + var ax = axref === 'pixel' ? aroposx.pixel : aroposx.value[1]; + var ay = ayref === 'pixel' ? aroposy.pixel : aroposy.value[1]; + if (DEBUG) { + console.log(combo); + } + return new Promise(function(resolve){ + var gd = createGraphDiv(gd_id); + resolve(gd); + }).then(function (gd) { return Plotly.newPlot(gd, testImageComboMock); }) + .then(function (gd) { + return annotationTest(gd, {}, x0, y0, ax, ay, xref, yref, axref, + ayref, axistypex, axistypey, xid, yid); + }).then( function (test_ret) { + console.log([ + "Testing layout annotation " + gd_id + " with parameters:", + "x-axis type:", axistypex, "\n", + "y-axis type:", axistypey, "\n", + "arrow axis pair:", arrowaxispair, "\n", + "xref:", xref, "\n", + "yref:", yref, "\n", + ].join(' '), test_ret); + }).then( function () { + if (!keep_graph_div) { + console.log('destroying graph div ', gd_id); + Plotly.purge(gd_id); + destroyGraphDiv(gd_id); + } + }); +} + +function runAnnotationTests(start_stop,filter) { + runComboTests([ + axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis + ], + testAnnotationCombo,start_stop,filter); } module.exports = { runImageTests: runImageTests, - testImageCombo: testImageCombo + testImageCombo: testImageCombo, + runAnnotationTests: runAnnotationTests, + testAnnotationCombo: testAnnotationCombo }; diff --git a/test/image/mocks/domain_ref_base.json b/test/image/mocks/domain_ref_base.json index 62ecb8b08fd..f8f205899b9 100644 --- a/test/image/mocks/domain_ref_base.json +++ b/test/image/mocks/domain_ref_base.json @@ -21,7 +21,7 @@ "range": [1, 3] }, "yaxis": { - "domain": [0,.4], + "domain": [0,0.4], "range": [1, 4] }, "xaxis2": { @@ -30,7 +30,7 @@ "anchor": "y2" }, "yaxis2": { - "domain": [.4,1], + "domain": [0.4,1], "range": [1, 3], "anchor": "x2" }, From 3896b08a761e88cb81f52edd5f0a662fb35b23a6 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 2 Sep 2020 09:36:02 -0400 Subject: [PATCH 063/112] Annotation arrows can now be placed using absolute paper coordinates --- src/components/annotations/defaults.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 3a331be495b..89bc9e0b221 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -60,8 +60,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { if(showArrow) { var arrowPosAttr = 'a' + axLetter; // axref, ayref - var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel', - undefined, true); + var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, '', 'paper', true); // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis From af7103f070ad77ae7b5fec8cacb76183af6df235 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 2 Sep 2020 18:19:28 -0400 Subject: [PATCH 064/112] Test values chosen so they are never beyond the plot boundaries This is because if some components (e.g., annotations) are outside of the plotting area, they are deemed not visible and are removed, which wrecks the tests. --- test/domain_ref_shapes_test.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js index 63e18fba18e..48b4be8b27b 100644 --- a/test/domain_ref_shapes_test.js +++ b/test/domain_ref_shapes_test.js @@ -425,6 +425,11 @@ function checkAROPosition(gd, aro) { } // some made-up values for testing +// NOTE: The pixel values are intentionally set so that 2*pixel is never greater +// than the mock's margin. This is so that annotations are not unintentionally +// clipped out because they exceed the plotting area. The reason for using twice +// the pixel value is because the annotation test requires plotting 2 +// annotations, the second having arrow components twice as long as the first. var aroPositionsX = [{ // aros referring to data ref: 'range', @@ -433,21 +438,21 @@ var aroPositionsX = [{ size: 1.5, // for the case when annotations specifies arrow in pixels, this value // is read instead of value[1] - pixel: 50 + pixel: 25 }, { // aros referring to domains ref: 'domain', value: [0.2, 0.75], size: 0.3, - pixel: 60 + pixel: 30 }, { // aros referring to paper ref: 'paper', value: [0.25, 0.8], size: 0.35, - pixel: 70 + pixel: 35 }, ]; var aroPositionsY = [{ @@ -469,7 +474,7 @@ var aroPositionsY = [{ // aros referring to paper ref: 'paper', value: [0.2, 0.85], - pixel: 80, + pixel: 45, size: .3 } ]; @@ -559,7 +564,7 @@ function test_correct_aro_positions() { testCombos.forEach(testDomRefAROCombo); } -function runComboTests(productItems,testCombo,start_stop,filter) { +function runComboTests(productItems,testCombo,start_stop,filter,keep_graph_div) { var testCombos = [...iterable.cartesianProduct(productItems)]; testCombos=testCombos.map((c,i)=>c.concat(['graph-'+i])); if(filter) { @@ -569,7 +574,7 @@ function runComboTests(productItems,testCombo,start_stop,filter) { testCombos=testCombos.slice(start_stop.start,start_stop.stop); } console.log("Executing " + testCombos.length + " tests"); - var tc = testCombos.map(c=>testCombo(c,false)).reduce((a,v)=>a.then(v)); + var tc = testCombos.map(c=>testCombo(c,keep_graph_div)).reduce((a,v)=>a.then(v)); } var testImageComboMock = Lib.extendDeep({}, @@ -674,16 +679,17 @@ function testAnnotationCombo(combo,keep_graph_div) { }); } -function runAnnotationTests(start_stop,filter) { +function runAnnotationTests(start_stop,filter,keep_graph_div) { runComboTests([ axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis ], - testAnnotationCombo,start_stop,filter); + testAnnotationCombo,start_stop,filter,keep_graph_div); } module.exports = { runImageTests: runImageTests, testImageCombo: testImageCombo, runAnnotationTests: runAnnotationTests, - testAnnotationCombo: testAnnotationCombo + testAnnotationCombo: testAnnotationCombo, + findAROByColor: findAROByColor }; From 35ff5a7d51f700cd2e25dda8f3ab22f4fba599a4 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 2 Sep 2020 18:59:44 -0400 Subject: [PATCH 065/112] Accept arrays for extra argument of Axes.coerceRef This way we can say that either 'pixel' or 'paper' is OK for axis references of annotations. --- src/components/annotations/defaults.js | 3 ++- src/plots/cartesian/axes.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 89bc9e0b221..f3dccfac328 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -60,7 +60,8 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { if(showArrow) { var arrowPosAttr = 'a' + axLetter; // axref, ayref - var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, '', 'paper', true); + var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, '', + ['paper','pixel'], true); // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index e185d18d200..0dfe7b04c2e 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -96,7 +96,9 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption attrDef[refAttr] = { valType: 'enumerated', - values: axlist.concat(extraOption ? [extraOption] : []), + values: axlist.concat(extraOption ? + (typeof extraOption === 'string' ? [extraOption] : extraOption) + : []), dflt: dflt }; From 9f0d3313c1914f48d65c3294dbe9902f7ad8391e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 2 Sep 2020 19:03:23 -0400 Subject: [PATCH 066/112] Annotation tests work properly with log axes We still have the problem that tests pass when run "individually" but not when all run. --- test/domain_ref_shapes_test.js | 42 +++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js index 48b4be8b27b..8a37bbb7ff4 100644 --- a/test/domain_ref_shapes_test.js +++ b/test/domain_ref_shapes_test.js @@ -136,14 +136,9 @@ function setAxType(layout, axref, axtype) { // Calculate the ax value of an annotation given a particular desired scaling K // This also works with log axes by taking logs of each part of the sum, so that // the length in pixels is multiplied by the scalar -function annaxscale(ac, axistype, c0, K) { +function annaxscale(ac, c0, K) { var ret; - if (axistype === 'log') { - ret = Math.pow(10, Math.log10(c0) + 2 * (Math.log10(ac) - Math.log10( - c0))); - } else { - ret = c0 + 2 * (ac - c0); - } + ret = c0 + 2 * (ac - c0); return ret; } @@ -157,6 +152,14 @@ function annaxscale(ac, axistype, c0, K) { // same for yaxistype and yref function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, xaxistype, yaxistype, xid, yid) { + // Take the log of values corresponding to log axes. This is because the + // test is designed to make predicting the pixel positions easy, and it's + // easiest when we work with the logarithm of values on log axes (doubling + // the log value doubles the pixel value, etc.). + x0 = xaxistype === 'log' ? Math.log10(x0) : x0; + y0 = yaxistype === 'log' ? Math.log10(y0) : y0; + ax = xaxistype === 'log' ? Math.log10(ax) : ax; + ay = yaxistype === 'log' ? Math.log10(ay) : ay; // if xref != axref or axref === 'pixel' then ax is a value relative to // x0 but in pixels. Same for yref var xreftype = Axes.getRefType(xref); @@ -182,8 +185,8 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, ayref: ayref, }; var opts1 = { - ax: axpixels ? 2 * ax : annaxscale(ax, xaxistype, x0, 2), - ay: aypixels ? 2 * ay : annaxscale(ay, yaxistype, y0, 2), + ax: axpixels ? 2 * ax : annaxscale(ax, x0, 2), + ay: aypixels ? 2 * ay : annaxscale(ay, y0, 2), axref: axref, ayref: ayref, }; @@ -195,14 +198,14 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, layout.annotations = [anno0, anno1]; return Plotly.relayout(gd, layout).then(function (gd) { // the choice of anno1 or anno0 is arbitrary - var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x'); - var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y'); + var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x', 0, true); + var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y', 0, true); if (axpixels) { // no need to map the specified values to pixels (because that's what // they are already) xpixels = ax; } else { - xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax') - + xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax', 0, true) - xabspixels; } if (aypixels) { @@ -210,7 +213,7 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, // they are already) ypixels = ay; } else { - ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay') - + ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay', 0, true) - yabspixels; } var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0,"#"+gd.id)); @@ -252,10 +255,17 @@ function logAxisIfAxType(layoutIn, layoutOut, axid, axtype) { } } -// axref can be xref or yref -// c can be x0, x1, y0, y1 -// offset allows adding something to the coordinate before converting, say if +// {layout} is required to map to pixels using its domain, range and size +// {axref} can be xref or yref +// {aro} is the components object where c and axref will be looked up +// {c} can be x0, x1, y0, y1 +// {offset} allows adding something to the coordinate before converting, say if // you want to map the point on the other side of a square +// {nolog} if set to true, the log of a range value will not be taken before +// computing its pixel position. This is useful for components whose positions +// are specified in log coordinates (i.e., images and annotations). +// You can tell I first wrote this function for shapes only and then learned +// later this was the case for images and annotations :'). function mapAROCoordToPixel(layout, axref, aro, c, offset, nolog) { var reftype = Axes.getRefType(aro[axref]); var axletter = axref[0]; From 11a8d3aa80deae9e9ae8061802aa47de923d8236 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 3 Sep 2020 15:19:41 -0400 Subject: [PATCH 067/112] Tests are working in browser but not Jasmine? Gonna check if there's something funky with the teardown that deletes the graph divs. --- test/jasmine/assets/domain_ref_components.js | 776 +++++++++++++++++++ test/jasmine/tests/domain_ref_test.js | 27 + 2 files changed, 803 insertions(+) create mode 100644 test/jasmine/assets/domain_ref_components.js create mode 100644 test/jasmine/tests/domain_ref_test.js diff --git a/test/jasmine/assets/domain_ref_components.js b/test/jasmine/assets/domain_ref_components.js new file mode 100644 index 00000000000..75bfb6c1953 --- /dev/null +++ b/test/jasmine/assets/domain_ref_components.js @@ -0,0 +1,776 @@ +// Test the placement of Axis Referencing Objects (AROs) +// Tools that can be wrapped in Jasmine tests. +// +// TODO: To make it work with Jasmine, we need to return a list of promises, +// one for each combination in the combo. When we're debugging / exploring, we +// want to be able to call the promise from the browser. When in a jasmine +// test, we need a description of the test and the promise doing the test +// itself. In this case, it needs to tell jasmine if it passed or failed, so we +// pass in an assert function that the promise can call. Then in jasmine, the +// promise is followed by .catch(failTest).then(done) +'use strict' + +var Plotly = require('../../../lib/index'); +var d3 = require('d3'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var pixelCalc = require('../assets/pixel_calc'); +var getSVGElemScreenBBox = require( + '../assets/get_svg_elem_screen_bbox'); +var Lib = require('../../../src/lib'); +var Axes = require('../../../src/plots/cartesian/axes'); +var axisIds = require('../../../src/plots/cartesian/axis_ids'); +var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; +var iterable = require('extra-iterable'); + +var testMock = Lib.extendDeep({}, + require('../../image/mocks/domain_ref_base.json')); + +// NOTE: this tolerance is in pixels +var EQUALITY_TOLERANCE = 1e-2; + +var DEBUG = true; + +var it = function(s, f) { + console.log('testing ' + s); + f(function() { + console.log(s + ' is done.'); + }); +} + +// acts on an Object representing a aro which could be a line or a rect +// DEPRECATED +function aroFromAROPos(aro, axletter, axnum, aropos) { + aro[axletter + '0'] = aropos.value[0]; + aro[axletter + '1'] = aropos.value[1]; + if (aropos.ref === 'range') { + aro[axletter + 'ref'] = axletter + axnum; + } else if (aropos.ref === 'domain') { + aro[axletter + 'ref'] = axletter + axnum + ' domain'; + } else if (aropos.ref === 'paper') { + aro[axletter + 'ref'] = 'paper'; + } +} + + + +// {axid} is the axis id, e.g., x2, y, etc. +// {ref} is ['range'|'domain'|'paper'] +function makeAxRef(axid, ref) { + var axref; + switch (ref) { + case 'range': + axref = axid; + break; + case 'domain': + axref = axid + ' domain'; + break; + case 'paper': + axref = 'paper'; + break; + default: + throw 'Bad axis type (ref): ' + ref; + } + return axref; +} + +// set common parameters of an ARO +// {aro} is the object whose parameters to set +// {coordletter} the letter of the coordinate to assign +// {axref} is the axis the coordinate refers to +// {value} is the value of the first coordinate (e.g., x0 if axletter is x) +function aroSetCommonParams(aro, coordletter, axref, value) { + aro[coordletter + '0'] = value; + aro.axref = axref; +} + +// shape, annotation and image all take x0, y0, xref, yref, color parameters +// x0, y0 are numerical values, xref, yref are strings that could be passed to +// the xref field of an ANO (e.g., 'x2 domain' or 'paper'), color should be +// specified using the 'rgb(r, g, b)' syntax +// arotype can be 'shape', 'annotation', or 'image' +// shapes take type=[line|rect], x1, y1 +// annotations take ax, ay, axref, ayref, (text is just set to "A" and xanchor +// and yanchor are always set to left because these are text attributes which we +// don't test) +// images take xsize, ysize, xanchor, yanchor (sizing is set to stretch for simplicity +// in computing the bounding box and source is something predetermined) +function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { + // fill with common values + var aro = { + xref: xref, + yref: yref + }; + switch (arotype) { + case 'shape': + aro.x0 = x0; + aro.y0 = y0; + aro.x1 = opts.x1; + aro.y1 = opts.y1; + aro.type = opts.type; + aro.line = { + color: color + }; + break; + case 'annotation': + aro.x = x0; + aro.y = y0; + aro.text = "A"; + aro.ax = opts.ax; + aro.ay = opts.ay; + aro.axref = opts.axref; + aro.ayref = opts.ayref; + aro.showarrow = true; + aro.arrowhead = 0; + aro.arrowcolor = color; + break; + case 'image': + aro.x = x0; + aro.y = y0; + aro.sizex = opts.sizex; + aro.sizey = opts.sizey; + aro.xanchor = opts.xanchor; + aro.yanchor = opts.yanchor; + aro.sizing = "stretch"; + aro.source = testImage; + break; + default: + throw "Bad arotype: " + arotype; + } + return aro; +} + +function setAxType(layout, axref, axtype) { + axname = axisIds.id2name(axref); + layout[axname].type = axtype; +} + +// Calculate the ax value of an annotation given a particular desired scaling K +// This also works with log axes by taking logs of each part of the sum, so that +// the length in pixels is multiplied by the scalar +function annaxscale(ac, c0, K) { + var ret; + ret = c0 + 2 * (ac - c0); + return ret; +} + +// This tests to see that an annotation was drawn correctly. +// Determinining the length of the arrow seems complicated due to the +// rectangle containing the text, so we draw 2 annotations, one K times the +// length of the other, and solve for the desired arrow length from the +// length measured on the screen. This works because multiplying the length +// of the arrow doesn't change where the arrow meets the text box. +// xaxistype can be linear|log, only used if xref has type 'range' or 'domain', +// same for yaxistype and yref +function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, + xaxistype, yaxistype, xid, yid) { + // Take the log of values corresponding to log axes. This is because the + // test is designed to make predicting the pixel positions easy, and it's + // easiest when we work with the logarithm of values on log axes (doubling + // the log value doubles the pixel value, etc.). + var xreftype = Axes.getRefType(xref); + var yreftype = Axes.getRefType(yref); + if (xreftype === 'range') { + x0 = xaxistype === 'log' ? Math.log10(x0) : x0; + ax = xaxistype === 'log' ? Math.log10(ax) : ax; + } + if (yreftype === 'range') { + y0 = yaxistype === 'log' ? Math.log10(y0) : y0; + ay = yaxistype === 'log' ? Math.log10(ay) : ay; + } + // if xref != axref or axref === 'pixel' then ax is a value relative to + // x0 but in pixels. Same for yref + var axpixels = false; + if ((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { + axpixels = true; + } + var aypixels = false; + if ((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { + aypixels = true; + } + var xaxname; + var yaxname; + logAxisIfAxType(gd.layout, layout, xid, xaxistype); + logAxisIfAxType(gd.layout, layout, yid, yaxistype); + var xpixels; + var ypixels; + var opts0 = { + ax: ax, + ay: ay, + axref: axref, + ayref: ayref, + }; + var opts1 = { + ax: axpixels ? 2 * ax : annaxscale(ax, x0, 2), + ay: aypixels ? 2 * ay : annaxscale(ay, y0, 2), + axref: axref, + ayref: ayref, + }; + // 2 colors so we can extract each annotation individually + var color0 = 'rgb(10, 20, 30)'; + var color1 = 'rgb(10, 20, 31)'; + var anno0 = aroFromParams('annotation', x0, y0, xref, yref, color0, opts0); + var anno1 = aroFromParams('annotation', x0, y0, xref, yref, color1, opts1); + layout.annotations = [anno0, anno1]; + return Plotly.relayout(gd, layout).then(function(gd) { + // the choice of anno1 or anno0 is arbitrary + var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x', 0, true); + var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y', 0, true); + if (axpixels) { + // no need to map the specified values to pixels (because that's what + // they are already) + xpixels = ax; + } else { + xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax', 0, true) - + xabspixels; + } + if (aypixels) { + // no need to map the specified values to pixels (because that's what + // they are already) + ypixels = ay; + } else { + ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay', 0, true) - + yabspixels; + } + var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0, "#" + gd.id)); + var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1, "#" + gd.id)); + // solve for the arrow length's x coordinate + var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 + .width)); + var arrowLenY; + var yabspixelscmp; + if (aypixels) { + // for annotations whose arrows are specified in relative pixels, + // positive pixel values on the y axis mean moving down the page like + // SVG coordinates, so we have to add height + var arrowLenY = (annobbox1.y + annobbox1.height) - + (annobbox0.y + annobbox0.height); + yabspixelscmp = annobbox0.y; + } else { + var arrowLenY = annobbox1.y - annobbox0.y; + yabspixelscmp = annobbox0.y + annobbox0.height; + } + var ret = coordsEq(arrowLenX, xpixels) && + coordsEq(arrowLenY, ypixels) && + coordsEq(xabspixels, annobbox0.x) && + coordsEq(yabspixels, yabspixelscmp); + return ret; + }); +} + +// axid is e.g., 'x', 'y2' etc. +function logAxisIfAxType(layoutIn, layoutOut, axid, axtype) { + var axname = axisIds.id2name(axid); + if ((axtype === 'log') && (axid !== undefined)) { + var axis = { + ...layoutIn[axname] + }; + axis.type = 'log'; + axis.range = axis.range.map(Math.log10); + layoutOut[axname] = axis; + } +} + +// {layout} is required to map to pixels using its domain, range and size +// {axref} can be xref or yref +// {aro} is the components object where c and axref will be looked up +// {c} can be x0, x1, y0, y1 +// {offset} allows adding something to the coordinate before converting, say if +// you want to map the point on the other side of a square +// {nolog} if set to true, the log of a range value will not be taken before +// computing its pixel position. This is useful for components whose positions +// are specified in log coordinates (i.e., images and annotations). +// You can tell I first wrote this function for shapes only and then learned +// later this was the case for images and annotations :'). +function mapAROCoordToPixel(layout, axref, aro, c, offset, nolog) { + var reftype = Axes.getRefType(aro[axref]); + var axletter = axref[0]; + var ret; + offset = (offset === undefined) ? 0 : offset; + var val = aro[c] + offset; + if (reftype === 'range') { + var axis = axisIds.id2name(aro[axref]); + ret = pixelCalc.mapRangeToPixel(layout, axis, val, nolog); + } else if (reftype === 'domain') { + var axis = axisIds.id2name(aro[axref]); + ret = pixelCalc.mapDomainToPixel(layout, axis, val); + } else if (reftype === 'paper') { + var axis = axref[0]; + ret = pixelCalc.mapPaperToPixel(layout, axis, val); + } + return ret; +} + +// compute the bounding box of the shape so that it can be compared with the SVG +// bounding box +function shapeToBBox(layout, aro) { + var bbox = {}; + var x1; + var y1; + // map x coordinates + bbox.x = mapAROCoordToPixel(layout, 'xref', aro, 'x0'); + x1 = mapAROCoordToPixel(layout, 'xref', aro, 'x1'); + // SVG bounding boxes have x,y referring to top left corner, but here we are + // specifying aros where y0 refers to the bottom left corner like + // Plotly.js, so we swap y0 and y1 + bbox.y = mapAROCoordToPixel(layout, 'yref', aro, 'y1'); + y1 = mapAROCoordToPixel(layout, 'yref', aro, 'y0'); + bbox.width = x1 - bbox.x; + bbox.height = y1 - bbox.y; + return bbox; +} + +function imageToBBox(layout, img) { + var bbox = {}; + // these will be pixels from the bottom of the plot and will be manipulated + // below to be compatible with the SVG bounding box + var x0; + var x1; + var y0; + var y1; + switch (img.xanchor) { + case 'left': + x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', undefined, true); + x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', img.sizex, true); + break; + case 'right': + x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', -img.sizex, true); + x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', undefined, true); + break; + case 'center': + x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', -img.sizex * 0.5, true); + x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', img.sizex * 0.5, true); + break; + default: + throw 'Bad xanchor: ' + img.xanchor; + } + switch (img.yanchor) { + case 'bottom': + y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', undefined, true); + y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', img.sizey, true); + break; + case 'top': + y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', -img.sizey, true); + y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', undefined, true); + break; + case 'middle': + y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', -img.sizey * 0.5, true); + y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', img.sizey * 0.5, true); + break; + default: + throw 'Bad yanchor: ' + img.yanchor; + } + bbox.x = x0; + bbox.width = x1 - x0; + // done this way because the pixel value of y1 will be smaller than the + // pixel value x0 if y1 > y0 (because of how SVG draws relative to the top + // of the screen) + bbox.y = y1; + bbox.height = y0 - y1; + return bbox; +} + + +function coordsEq(a, b) { + return Math.abs(a - b) < EQUALITY_TOLERANCE; +} + +function compareBBoxes(a, b) { + return ['x', 'y', 'width', 'height'].map( + (k, ) => coordsEq(a[k], b[k])).reduce( + (l, r) => l && r, + true); +} + +function findAROByColor(color, id) { + id = (id === undefined) ? '' : id + ' '; + var selector = id + 'path'; + var ret = d3.selectAll(selector).filter(function() { + return this.style.stroke === color; + }).node(); + return ret; +} + +function findImage(id) { + id = (id === undefined) ? '' : id + ' '; + var selector = id + 'g image'; + var ret = d3.select(selector).node(); + return ret; +} + +function checkImage(layout, imageObj, imageBBox) {} + +function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, + yanchor, xref, yref, xid, yid) { + console.log('running imageTest on ', gd); + var image = { + x: x, + y: y, + sizex: sizex, + sizey: sizey, + source: testImage, + xanchor: xanchor, + yanchor: yanchor, + xref: xref, + yref: yref, + sizing: "stretch" + }; + var xreftype = Axes.getRefType(xref); + var yreftype = Axes.getRefType(yref); + var ret; + // we pass xid, yid because we possibly want to change some axes to log, + // even if we refer to paper in the end + logAxisIfAxType(gd.layout, layout, xid, xaxtype); + logAxisIfAxType(gd.layout, layout, yid, yaxtype); + layout.images = [image]; + return Plotly.relayout(gd, layout) + .then(function(gd) { + var imageElem = findImage("#" + gd.id); + var svgImageBBox = getSVGElemScreenBBox(imageElem); + var imageBBox = imageToBBox(gd.layout, image); + ret = compareBBoxes(svgImageBBox, imageBBox); + //if (!ret) { + // throw 'test failed'; + //} + return ret; + }); +} + +// gets the SVG bounding box of the aro and checks it against what mapToPixel +// gives +function checkAROPosition(gd, aro) { + var aroPath = findAROByColor(aro.line.color, "#" + gd.id); + var aroPathBBox = getSVGElemScreenBBox(aroPath); + var aroBBox = shapeToBBox(gd.layout, aro); + var ret = compareBBoxes(aroBBox, aroPathBBox); + if (DEBUG) { + console.log('SVG BBox', aroPathBBox); + console.log('aro BBox', aroBBox); + } + return ret; +} + +// some made-up values for testing +// NOTE: The pixel values are intentionally set so that 2*pixel is never greater +// than the mock's margin. This is so that annotations are not unintentionally +// clipped out because they exceed the plotting area. The reason for using twice +// the pixel value is because the annotation test requires plotting 2 +// annotations, the second having arrow components twice as long as the first. +var aroPositionsX = [{ + // aros referring to data + ref: 'range', + value: [2, 3], + // for objects that need a size (i.e., images) + size: 1.5, + // for the case when annotations specifies arrow in pixels, this value + // is read instead of value[1] + pixel: 25 + }, + { + // aros referring to domains + ref: 'domain', + value: [0.2, 0.75], + size: 0.3, + pixel: 30 + }, + { + // aros referring to paper + ref: 'paper', + value: [0.25, 0.8], + size: 0.35, + pixel: 35 + }, +]; +var aroPositionsY = [{ + // aros referring to data + ref: 'range', + // two values for rects + value: [1, 2], + pixel: 30, + size: 1.2 + }, + { + // aros referring to domains + ref: 'domain', + value: [0.25, 0.7], + pixel: 40, + size: .2 + }, + { + // aros referring to paper + ref: 'paper', + value: [0.2, 0.85], + pixel: 45, + size: .3 + } +]; + +var aroTypes = ['shape', 'annotation', 'image']; +var axisTypes = ['linear', 'log']; +// Test on 'x', 'y', 'x2', 'y2' axes +// TODO the 'paper' position references are tested twice when once would +// suffice. +var axisPairs = [ + ['x', 'y'], + ['x2', 'y'], + ['x', 'y2'], + ['x2', 'y2'] +]; +// For annotations: if arrow coordinate is in the same coordinate system 's', if +// pixel then 'p' +var arrowAxis = [ + ['s', 's'], + ['p', 's'], + ['s', 'p'], + ['p', 'p'] +]; +// only test the shapes line and rect for now +var shapeType = ['line', 'rect']; +// anchor positions for images +var xAnchors = ['left', 'center', 'right']; +var yAnchors = ['top', 'middle', 'bottom']; +// this color chosen so it can easily be found with d3 +// NOTE: for images color cannot be set but it will be the only image in the +// plot so you can use d3.select('g image').node() +var aroColor = 'rgb(50, 100, 150)'; +var testDomRefAROCombo = function(combo) { + var xAxNum = combo[0]; + var xaxisType = combo[1]; + var xaroPos = combo[2]; + var yAxNum = combo[3]; + var yaxisType = combo[4]; + var yaroPos = combo[5]; + var aroType = combo[6]; + it('should draw a ' + aroType + + ' for x' + xAxNum + ' of type ' + + xaxisType + + ' with a value referencing ' + + xaroPos.ref + + ' and for y' + yAxNum + ' of type ' + + yaxisType + + ' with a value referencing ' + + yaroPos.ref, + function(done) { + var gd = createGraphDiv(); + var mock = testMock; + if (DEBUG) { + console.log(combo); + } + Plotly.newPlot(gd, mock) + var aro = { + type: aroType, + line: { + color: aroColor + } + }; + aroFromAROPos(aro, 'x', xAxNum, xaroPos); + aroFromAROPos(aro, 'y', yAxNum, yaroPos); + var layout = { + shapes: [aro] + }; + // change to log axes if need be + logAxisIfAxType(gd.layout, layout, 'x' + xAxNum, xaxisType); + logAxisIfAxType(gd.layout, layout, 'y' + yAxNum, yaxisType); + Plotly.relayout(gd, layout); + console.log(checkAROPosition(gd, aro)); + destroyGraphDiv(); + }); +} + +// Test correct aro positions +function test_correct_aro_positions() { + // for both x and y axes + var testCombos = [...iterable.cartesianProduct([ + axNum, axisTypes, aroPositionsX, axNum, axisTypes, + aroPositionsY, aroType + ])]; + // map all the combinations to a aro definition and check this aro is + // placed properly + testCombos.forEach(testDomRefAROCombo); +} + +function runComboTests(productItems, testCombo, start_stop, filter, keep_graph_div) { + var testCombos = [...iterable.cartesianProduct(productItems)]; + testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); + if (filter) { + testCombos = testCombos.filter(filter); + } + if (start_stop) { + testCombos = testCombos.slice(start_stop.start, start_stop.stop); + } + console.log("Executing " + testCombos.length + " tests"); + var tc = testCombos.map(c => testCombo(c, keep_graph_div)).reduce((a, v) => a.then(v)); +} + +function testImageCombo(combo, keep_graph_div) { + var axistypex = combo[0]; + var axistypey = combo[1]; + var axispair = combo[2]; + var aroposx = combo[3]; + var aroposy = combo[4]; + var xanchor = combo[5]; + var yanchor = combo[6]; + var gd_id = combo[7]; + var xid = axispair[0]; + var yid = axispair[1]; + var xref = makeAxRef(xid, aroposx.ref); + var yref = makeAxRef(yid, aroposy.ref); + if (DEBUG) { + console.log(combo); + } + return new Promise(function(resolve) { + var gd = createGraphDiv(gd_id); + resolve(gd); + }).then(function(gd) { + return Plotly.newPlot(gd, testMock); + }) + .then(function(gd) { + return imageTest(gd, {}, axistypex, axistypey, + aroposx.value[0], aroposy.value[0], aroposx.size, + aroposy.size, + xanchor, yanchor, xref, yref, xid, yid); + }).then(function(test_ret) { + console.log([ + "Testing layout image with parameters:", + "x-axis type:", axistypex, "\n", + "y-axis type:", axistypey, "\n", + "xanchor:", xanchor, "\n", + "yanchor:", yanchor, "\n", + "xref:", xref, "\n", + "yref:", yref, "\n", + ].join(' '), test_ret); + }).then(function() { + if (!keep_graph_div) { + console.log('destroying graph div ', gd_id); + Plotly.purge(gd_id); + destroyGraphDiv(gd_id); + } + }); +} + +function runImageTests(start_stop, filter) { + runComboTests([ + axisTypes, axisTypes, axisPairs, + // axis reference types are contained in here + aroPositionsX, aroPositionsY, + xAnchors, yAnchors + ], testImageCombo, start_stop, filter); +} + +function describeAnnotationComboTest(combo) { + var axistypex = combo[0]; + var axistypey = combo[1]; + var axispair = combo[2]; + var aroposx = combo[3]; + var aroposy = combo[4]; + var arrowaxispair = combo[5]; + var gd_id = combo[6]; + var xid = axispair[0]; + var yid = axispair[1]; + var xref = makeAxRef(xid, aroposx.ref); + var yref = makeAxRef(yid, aroposy.ref); + return [ + "should create a plot with graph ID", + gd_id, + " with parameters:", "\n", + "x-axis type:", axistypex, "\n", + "y-axis type:", axistypey, "\n", + "axis pair:", axispair, "\n", + "ARO position x:", aroposx, "\n", + "ARO position y:", aroposy, "\n", + "arrow axis pair:", arrowaxispair, "\n", + "xref:", xref, "\n", + "yref:", yref, "\n", + ].join(' '); +} + +function testAnnotationCombo(combo, keep_graph_div, assert) { + var axistypex = combo[0]; + var axistypey = combo[1]; + var axispair = combo[2]; + var aroposx = combo[3]; + var aroposy = combo[4]; + var arrowaxispair = combo[5]; + var gd_id = combo[6]; + var xid = axispair[0]; + var yid = axispair[1]; + var xref = makeAxRef(xid, aroposx.ref); + var yref = makeAxRef(yid, aroposy.ref); + var axref = arrowaxispair[0] === 'p' ? 'pixel' : xref; + var ayref = arrowaxispair[1] === 'p' ? 'pixel' : yref; + var x0 = aroposx.value[0]; + var y0 = aroposy.value[0]; + var ax = axref === 'pixel' ? aroposx.pixel : aroposx.value[1]; + var ay = ayref === 'pixel' ? aroposy.pixel : aroposy.value[1]; + return new Promise(function(resolve) { + var gd = createGraphDiv(gd_id); + resolve(gd); + }).then(function(gd) { + return Plotly.newPlot(gd, testMock); + }) + .then(function(gd) { + return annotationTest(gd, {}, x0, y0, ax, ay, xref, yref, axref, + ayref, axistypex, axistypey, xid, yid); + }).then(function(test_ret) { + assert(test_ret); + }).then(function() { + if (!keep_graph_div) { + Plotly.purge(gd_id); + destroyGraphDiv(gd_id); + } + }); +} + +// return a list of functions, each returning a promise that executes a +// particular test. This function takes the keepGraphDiv argument, which if true +// will prevent destroying the generated graph after the test is executed, and +// an assert argument, which is a function that will be passed true if the test +// passed. +// {testCombos} is a list of combinations each of which will be passed to the +// test function +// {test} is the function returning a Promise that executes this test +function comboTests(testCombos,test) { + var ret = testCombos.map(function (combo) { + return function (keepGraphDiv, assert) { + return test(combo, keepGraphDiv, assert); + } + }); + return ret; +} + +// return a list of strings, each describing a corresponding test +// describe is a function taking a combination and returning a description of +// the test +function comboTestDescriptions(testCombos,desribe) { + var ret = testCombos.map(desribe); + return ret; +} + +function annotationTestCombos() { + var testCombos = [...iterable.cartesianProduct([ + axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis + ] + )]; + testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); + return testCombos; +} + +function annotationTests() { + var testCombos = annotationTestCombos(); + return comboTests(testCombos,testAnnotationCombo); +} + +function annotationTestDescriptions() { + var testCombos = annotationTestCombos(); + return comboTestDescriptions(testCombos,describeAnnotationComboTest); +} + +module.exports = { + // tests + annotations: { + descriptions: annotationTestDescriptions, + tests: annotationTests, + }, + // utilities + findAROByColor: findAROByColor +}; diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js new file mode 100644 index 00000000000..2a2bfeaab8c --- /dev/null +++ b/test/jasmine/tests/domain_ref_test.js @@ -0,0 +1,27 @@ +var failTest = require('../assets/fail_test'); +var domainRefComponents = require('../assets/domain_ref_components'); + +function makeTests(component, filter) { + return function() { + filter = filter === undefined ? function() { + return true + } : filter; + var descriptions = component.descriptions().filter(filter); + var tests = component.tests().filter(filter); + descriptions.forEach(function(d, i) { + it(d, function(done) { + tests[i](false, function(v) { + expect(v).toBe(true); + }) + .catch(failTest) + .then(done); + }); + }); + }; +} + +describe('Test annotations', makeTests(domainRefComponents.annotations, +undefined)); +// function(f, i) { +// return i == 565; +// })); From e651d57e8ab41b04a21378c327a72f43cdd070d4 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 4 Sep 2020 12:47:53 -0400 Subject: [PATCH 068/112] For annotation test no log ax or ay is pixel Don't take the log of the given test values if ax or ay is pixel. --- src/plots/cartesian/axes.js | 1 + test/domain_ref_shapes_test.js | 705 ------------------ .../components.js} | 64 +- test/jasmine/assets/domain_ref/testnumber.js | 3 + test/jasmine/tests/domain_ref_test.js | 27 +- 5 files changed, 51 insertions(+), 749 deletions(-) delete mode 100644 test/domain_ref_shapes_test.js rename test/jasmine/assets/{domain_ref_components.js => domain_ref/components.js} (94%) create mode 100644 test/jasmine/assets/domain_ref/testnumber.js diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 0dfe7b04c2e..bbfaa404816 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -117,6 +117,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption axes.getRefType = function(ar) { if(ar === undefined) { return ar; } if(ar === 'paper') { return 'paper'; } + if(ar === 'pixel') { return 'pixel'; } if(/( domain)$/.test(ar)) { return 'domain'; } else { return 'range'; } }; diff --git a/test/domain_ref_shapes_test.js b/test/domain_ref_shapes_test.js deleted file mode 100644 index 8a37bbb7ff4..00000000000 --- a/test/domain_ref_shapes_test.js +++ /dev/null @@ -1,705 +0,0 @@ -// Test the placement of Axis Referencing Objects (AROs) -'use strict' - -var Plotly = require('../lib/index'); -var d3 = require('d3'); -var createGraphDiv = require('../test/jasmine/assets/create_graph_div'); -var destroyGraphDiv = require('../test/jasmine/assets/destroy_graph_div'); -var pixelCalc = require('../test/jasmine/assets/pixel_calc'); -var getSVGElemScreenBBox = require( - '../test/jasmine/assets/get_svg_elem_screen_bbox'); -var Lib = require('../src/lib'); -var Axes = require('../src/plots/cartesian/axes'); -var axisIds = require('../src/plots/cartesian/axis_ids'); -var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; -var iterable = require('extra-iterable'); - -// NOTE: this tolerance is in pixels -var EQUALITY_TOLERANCE = 1e-2; - -var DEBUG = true; - -var it = function(s, f) { - console.log('testing ' + s); - f(function() { - console.log(s + ' is done.'); - }); -} - -// acts on an Object representing a aro which could be a line or a rect -// DEPRECATED -function aroFromAROPos(aro, axletter, axnum, aropos) { - aro[axletter + '0'] = aropos.value[0]; - aro[axletter + '1'] = aropos.value[1]; - if (aropos.ref === 'range') { - aro[axletter + 'ref'] = axletter + axnum; - } else if (aropos.ref === 'domain') { - aro[axletter + 'ref'] = axletter + axnum + ' domain'; - } else if (aropos.ref === 'paper') { - aro[axletter + 'ref'] = 'paper'; - } -} - - - -// {axid} is the axis id, e.g., x2, y, etc. -// {ref} is ['range'|'domain'|'paper'] -function makeAxRef(axid, ref) { - var axref; - switch (ref) { - case 'range': - axref = axid; - break; - case 'domain': - axref = axid + ' domain'; - break; - case 'paper': - axref = 'paper'; - break; - default: - throw 'Bad axis type (ref): ' + ref; - } - return axref; -} - -// set common parameters of an ARO -// {aro} is the object whose parameters to set -// {coordletter} the letter of the coordinate to assign -// {axref} is the axis the coordinate refers to -// {value} is the value of the first coordinate (e.g., x0 if axletter is x) -function aroSetCommonParams(aro, coordletter, axref, value) { - aro[coordletter + '0'] = value; - aro.axref = axref; -} - -// shape, annotation and image all take x0, y0, xref, yref, color parameters -// x0, y0 are numerical values, xref, yref are strings that could be passed to -// the xref field of an ANO (e.g., 'x2 domain' or 'paper'), color should be -// specified using the 'rgb(r, g, b)' syntax -// arotype can be 'shape', 'annotation', or 'image' -// shapes take type=[line|rect], x1, y1 -// annotations take ax, ay, axref, ayref, (text is just set to "A" and xanchor -// and yanchor are always set to left because these are text attributes which we -// don't test) -// images take xsize, ysize, xanchor, yanchor (sizing is set to stretch for simplicity -// in computing the bounding box and source is something predetermined) -function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { - // fill with common values - var aro = { - xref: xref, - yref: yref - }; - switch (arotype) { - case 'shape': - aro.x0 = x0; - aro.y0 = y0; - aro.x1 = opts.x1; - aro.y1 = opts.y1; - aro.type = opts.type; - aro.line = { - color: color - }; - break; - case 'annotation': - aro.x = x0; - aro.y = y0; - aro.text = "A"; - aro.ax = opts.ax; - aro.ay = opts.ay; - aro.axref = opts.axref; - aro.ayref = opts.ayref; - aro.showarrow = true; - aro.arrowhead = 0; - aro.arrowcolor = color; - break; - case 'image': - aro.x = x0; - aro.y = y0; - aro.sizex = opts.sizex; - aro.sizey = opts.sizey; - aro.xanchor = opts.xanchor; - aro.yanchor = opts.yanchor; - aro.sizing = "stretch"; - aro.source = testImage; - break; - default: - throw "Bad arotype: " + arotype; - } - return aro; -} - -function setAxType(layout, axref, axtype) { - axname = axisIds.id2name(axref); - layout[axname].type = axtype; -} - -// Calculate the ax value of an annotation given a particular desired scaling K -// This also works with log axes by taking logs of each part of the sum, so that -// the length in pixels is multiplied by the scalar -function annaxscale(ac, c0, K) { - var ret; - ret = c0 + 2 * (ac - c0); - return ret; -} - -// This tests to see that an annotation was drawn correctly. -// Determinining the length of the arrow seems complicated due to the -// rectangle containing the text, so we draw 2 annotations, one K times the -// length of the other, and solve for the desired arrow length from the -// length measured on the screen. This works because multiplying the length -// of the arrow doesn't change where the arrow meets the text box. -// xaxistype can be linear|log, only used if xref has type 'range' or 'domain', -// same for yaxistype and yref -function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, - xaxistype, yaxistype, xid, yid) { - // Take the log of values corresponding to log axes. This is because the - // test is designed to make predicting the pixel positions easy, and it's - // easiest when we work with the logarithm of values on log axes (doubling - // the log value doubles the pixel value, etc.). - x0 = xaxistype === 'log' ? Math.log10(x0) : x0; - y0 = yaxistype === 'log' ? Math.log10(y0) : y0; - ax = xaxistype === 'log' ? Math.log10(ax) : ax; - ay = yaxistype === 'log' ? Math.log10(ay) : ay; - // if xref != axref or axref === 'pixel' then ax is a value relative to - // x0 but in pixels. Same for yref - var xreftype = Axes.getRefType(xref); - var yreftype = Axes.getRefType(yref); - var axpixels = false; - if ((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { - axpixels = true; - } - var aypixels = false; - if ((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { - aypixels = true; - } - var xaxname; - var yaxname; - logAxisIfAxType(gd.layout, layout, xid, xaxistype); - logAxisIfAxType(gd.layout, layout, yid, yaxistype); - var xpixels; - var ypixels; - var opts0 = { - ax: ax, - ay: ay, - axref: axref, - ayref: ayref, - }; - var opts1 = { - ax: axpixels ? 2 * ax : annaxscale(ax, x0, 2), - ay: aypixels ? 2 * ay : annaxscale(ay, y0, 2), - axref: axref, - ayref: ayref, - }; - // 2 colors so we can extract each annotation individually - var color0 = 'rgb(10, 20, 30)'; - var color1 = 'rgb(10, 20, 31)'; - var anno0 = aroFromParams('annotation', x0, y0, xref, yref, color0, opts0); - var anno1 = aroFromParams('annotation', x0, y0, xref, yref, color1, opts1); - layout.annotations = [anno0, anno1]; - return Plotly.relayout(gd, layout).then(function (gd) { - // the choice of anno1 or anno0 is arbitrary - var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x', 0, true); - var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y', 0, true); - if (axpixels) { - // no need to map the specified values to pixels (because that's what - // they are already) - xpixels = ax; - } else { - xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax', 0, true) - - xabspixels; - } - if (aypixels) { - // no need to map the specified values to pixels (because that's what - // they are already) - ypixels = ay; - } else { - ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay', 0, true) - - yabspixels; - } - var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0,"#"+gd.id)); - var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1,"#"+gd.id)); - // solve for the arrow length's x coordinate - var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 - .width)); - var arrowLenY; - var yabspixelscmp; - if (aypixels) { - // for annotations whose arrows are specified in relative pixels, - // positive pixel values on the y axis mean moving down the page like - // SVG coordinates, so we have to add height - var arrowLenY = (annobbox1.y + annobbox1.height) - - (annobbox0.y + annobbox0.height); - yabspixelscmp = annobbox0.y; - } else { - var arrowLenY = annobbox1.y - annobbox0.y; - yabspixelscmp = annobbox0.y + annobbox0.height; - } - var ret = coordsEq(arrowLenX, xpixels) && - coordsEq(arrowLenY, ypixels) && - coordsEq(xabspixels, annobbox0.x) && - coordsEq(yabspixels, yabspixelscmp); - return ret; - }); -} - -// axid is e.g., 'x', 'y2' etc. -function logAxisIfAxType(layoutIn, layoutOut, axid, axtype) { - var axname = axisIds.id2name(axid); - if ((axtype === 'log') && (axid !== undefined)) { - var axis = { - ...layoutIn[axname] - }; - axis.type = 'log'; - axis.range = axis.range.map(Math.log10); - layoutOut[axname] = axis; - } -} - -// {layout} is required to map to pixels using its domain, range and size -// {axref} can be xref or yref -// {aro} is the components object where c and axref will be looked up -// {c} can be x0, x1, y0, y1 -// {offset} allows adding something to the coordinate before converting, say if -// you want to map the point on the other side of a square -// {nolog} if set to true, the log of a range value will not be taken before -// computing its pixel position. This is useful for components whose positions -// are specified in log coordinates (i.e., images and annotations). -// You can tell I first wrote this function for shapes only and then learned -// later this was the case for images and annotations :'). -function mapAROCoordToPixel(layout, axref, aro, c, offset, nolog) { - var reftype = Axes.getRefType(aro[axref]); - var axletter = axref[0]; - var ret; - offset = (offset === undefined) ? 0 : offset; - var val = aro[c] + offset; - if (reftype === 'range') { - var axis = axisIds.id2name(aro[axref]); - ret = pixelCalc.mapRangeToPixel(layout, axis, val, nolog); - } else if (reftype === 'domain') { - var axis = axisIds.id2name(aro[axref]); - ret = pixelCalc.mapDomainToPixel(layout, axis, val); - } else if (reftype === 'paper') { - var axis = axref[0]; - ret = pixelCalc.mapPaperToPixel(layout, axis, val); - } - return ret; -} - -// compute the bounding box of the shape so that it can be compared with the SVG -// bounding box -function shapeToBBox(layout, aro) { - var bbox = {}; - var x1; - var y1; - // map x coordinates - bbox.x = mapAROCoordToPixel(layout, 'xref', aro, 'x0'); - x1 = mapAROCoordToPixel(layout, 'xref', aro, 'x1'); - // SVG bounding boxes have x,y referring to top left corner, but here we are - // specifying aros where y0 refers to the bottom left corner like - // Plotly.js, so we swap y0 and y1 - bbox.y = mapAROCoordToPixel(layout, 'yref', aro, 'y1'); - y1 = mapAROCoordToPixel(layout, 'yref', aro, 'y0'); - bbox.width = x1 - bbox.x; - bbox.height = y1 - bbox.y; - return bbox; -} - -function imageToBBox(layout, img) { - var bbox = {}; - // these will be pixels from the bottom of the plot and will be manipulated - // below to be compatible with the SVG bounding box - var x0; - var x1; - var y0; - var y1; - switch (img.xanchor) { - case 'left': - x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', undefined, true); - x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', img.sizex, true); - break; - case 'right': - x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', -img.sizex, true); - x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', undefined, true); - break; - case 'center': - x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', -img.sizex * 0.5, true); - x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', img.sizex * 0.5, true); - break; - default: - throw 'Bad xanchor: ' + img.xanchor; - } - switch (img.yanchor) { - case 'bottom': - y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', undefined, true); - y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', img.sizey, true); - break; - case 'top': - y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', -img.sizey, true); - y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', undefined, true); - break; - case 'middle': - y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', -img.sizey * 0.5, true); - y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', img.sizey * 0.5, true); - break; - default: - throw 'Bad yanchor: ' + img.yanchor; - } - bbox.x = x0; - bbox.width = x1 - x0; - // done this way because the pixel value of y1 will be smaller than the - // pixel value x0 if y1 > y0 (because of how SVG draws relative to the top - // of the screen) - bbox.y = y1; - bbox.height = y0 - y1; - return bbox; -} - - -function coordsEq(a, b) { - return Math.abs(a - b) < EQUALITY_TOLERANCE; -} - -function compareBBoxes(a, b) { - return ['x', 'y', 'width', 'height'].map( - (k, ) => coordsEq(a[k], b[k])).reduce( - (l, r) => l && r, - true); -} - -function findAROByColor(color,id) { - id = (id === undefined) ? '' : id + ' '; - var selector = id + 'path'; - var ret = d3.selectAll(selector).filter(function() { - return this.style.stroke === color; - }).node(); - return ret; -} - -function findImage(id) { - id = (id === undefined) ? '' : id + ' '; - var selector = id + 'g image'; - var ret = d3.select(selector).node(); - return ret; -} - -function checkImage(layout, imageObj, imageBBox) {} - -function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, - yanchor, xref, yref, xid, yid) { - console.log('running imageTest on ',gd); - var image = { - x: x, - y: y, - sizex: sizex, - sizey: sizey, - source: testImage, - xanchor: xanchor, - yanchor: yanchor, - xref: xref, - yref: yref, - sizing: "stretch" - }; - var xreftype = Axes.getRefType(xref); - var yreftype = Axes.getRefType(yref); - var ret; - // we pass xid, yid because we possibly want to change some axes to log, - // even if we refer to paper in the end - logAxisIfAxType(gd.layout, layout, xid, xaxtype); - logAxisIfAxType(gd.layout, layout, yid, yaxtype); - layout.images = [image]; - return Plotly.relayout(gd, layout) - .then(function (gd) { - var imageElem = findImage("#"+gd.id); - var svgImageBBox = getSVGElemScreenBBox(imageElem); - var imageBBox = imageToBBox(gd.layout, image); - ret = compareBBoxes(svgImageBBox, imageBBox); - //if (!ret) { - // throw 'test failed'; - //} - return ret; - }); -} - -// gets the SVG bounding box of the aro and checks it against what mapToPixel -// gives -function checkAROPosition(gd, aro) { - var aroPath = findAROByColor(aro.line.color,"#"+gd.id); - var aroPathBBox = getSVGElemScreenBBox(aroPath); - var aroBBox = shapeToBBox(gd.layout, aro); - var ret = compareBBoxes(aroBBox, aroPathBBox); - if (DEBUG) { - console.log('SVG BBox', aroPathBBox); - console.log('aro BBox', aroBBox); - } - return ret; -} - -// some made-up values for testing -// NOTE: The pixel values are intentionally set so that 2*pixel is never greater -// than the mock's margin. This is so that annotations are not unintentionally -// clipped out because they exceed the plotting area. The reason for using twice -// the pixel value is because the annotation test requires plotting 2 -// annotations, the second having arrow components twice as long as the first. -var aroPositionsX = [{ - // aros referring to data - ref: 'range', - value: [2, 3], - // for objects that need a size (i.e., images) - size: 1.5, - // for the case when annotations specifies arrow in pixels, this value - // is read instead of value[1] - pixel: 25 - }, - { - // aros referring to domains - ref: 'domain', - value: [0.2, 0.75], - size: 0.3, - pixel: 30 - }, - { - // aros referring to paper - ref: 'paper', - value: [0.25, 0.8], - size: 0.35, - pixel: 35 - }, -]; -var aroPositionsY = [{ - // aros referring to data - ref: 'range', - // two values for rects - value: [1, 2], - pixel: 30, - size: 1.2 - }, - { - // aros referring to domains - ref: 'domain', - value: [0.25, 0.7], - pixel: 40, - size: .2 - }, - { - // aros referring to paper - ref: 'paper', - value: [0.2, 0.85], - pixel: 45, - size: .3 - } -]; - -var aroTypes = ['shape', 'annotation', 'image']; -var axisTypes = ['linear', 'log']; -// Test on 'x', 'y', 'x2', 'y2' axes -// TODO the 'paper' position references are tested twice when once would -// suffice. -var axisPairs = [ - ['x', 'y'], - ['x2', 'y'], - ['x', 'y2'], - ['x2', 'y2'] -]; -// For annotations: if arrow coordinate is in the same coordinate system 's', if -// pixel then 'p' -var arrowAxis = [ - ['s', 's'], - ['p', 's'], - ['s', 'p'], - ['p', 'p'] -]; -// only test the shapes line and rect for now -var shapeType = ['line', 'rect']; -// anchor positions for images -var xAnchors = ['left', 'center', 'right']; -var yAnchors = ['top', 'middle', 'bottom']; -// this color chosen so it can easily be found with d3 -// NOTE: for images color cannot be set but it will be the only image in the -// plot so you can use d3.select('g image').node() -var aroColor = 'rgb(50, 100, 150)'; -var testDomRefAROCombo = function(combo) { - var xAxNum = combo[0]; - var xaxisType = combo[1]; - var xaroPos = combo[2]; - var yAxNum = combo[3]; - var yaxisType = combo[4]; - var yaroPos = combo[5]; - var aroType = combo[6]; - it('should draw a ' + aroType + - ' for x' + xAxNum + ' of type ' + - xaxisType + - ' with a value referencing ' + - xaroPos.ref + - ' and for y' + yAxNum + ' of type ' + - yaxisType + - ' with a value referencing ' + - yaroPos.ref, - function(done) { - var gd = createGraphDiv(); - var mock = Lib.extendDeep({}, - require('../test/image/mocks/domain_ref_base.json')); - if (DEBUG) { - console.log(combo); - } - Plotly.newPlot(gd, mock) - var aro = { - type: aroType, - line: { - color: aroColor - } - }; - aroFromAROPos(aro, 'x', xAxNum, xaroPos); - aroFromAROPos(aro, 'y', yAxNum, yaroPos); - var layout = { - shapes: [aro] - }; - // change to log axes if need be - logAxisIfAxType(gd.layout, layout, 'x' + xAxNum, xaxisType); - logAxisIfAxType(gd.layout, layout, 'y' + yAxNum, yaxisType); - Plotly.relayout(gd, layout); - console.log(checkAROPosition(gd, aro)); - destroyGraphDiv(); - }); -} - -// Test correct aro positions -function test_correct_aro_positions() { - // for both x and y axes - var testCombos = [...iterable.cartesianProduct([ - axNum, axisTypes, aroPositionsX, axNum, axisTypes, - aroPositionsY, aroType - ])]; - // map all the combinations to a aro definition and check this aro is - // placed properly - testCombos.forEach(testDomRefAROCombo); -} - -function runComboTests(productItems,testCombo,start_stop,filter,keep_graph_div) { - var testCombos = [...iterable.cartesianProduct(productItems)]; - testCombos=testCombos.map((c,i)=>c.concat(['graph-'+i])); - if(filter) { - testCombos=testCombos.filter(filter); - } - if(start_stop) { - testCombos=testCombos.slice(start_stop.start,start_stop.stop); - } - console.log("Executing " + testCombos.length + " tests"); - var tc = testCombos.map(c=>testCombo(c,keep_graph_div)).reduce((a,v)=>a.then(v)); -} - -var testImageComboMock = Lib.extendDeep({}, - require('../test/image/mocks/domain_ref_base.json')); - -function testImageCombo(combo,keep_graph_div) { - var axistypex = combo[0]; - var axistypey = combo[1]; - var axispair = combo[2]; - var aroposx = combo[3]; - var aroposy = combo[4]; - var xanchor = combo[5]; - var yanchor = combo[6]; - var gd_id = combo[7]; - var xid = axispair[0]; - var yid = axispair[1]; - var xref = makeAxRef(xid, aroposx.ref); - var yref = makeAxRef(yid, aroposy.ref); - if (DEBUG) { - console.log(combo); - } - return new Promise(function(resolve){ - var gd = createGraphDiv(gd_id); - resolve(gd); - }).then(function (gd) { return Plotly.newPlot(gd, testImageComboMock); }) - .then(function (gd) { - return imageTest(gd, {}, axistypex, axistypey, - aroposx.value[0], aroposy.value[0], aroposx.size, - aroposy.size, - xanchor, yanchor, xref, yref, xid, yid); - }).then( function (test_ret) { - console.log([ - "Testing layout image with parameters:", - "x-axis type:", axistypex, "\n", - "y-axis type:", axistypey, "\n", - "xanchor:", xanchor, "\n", - "yanchor:", yanchor, "\n", - "xref:", xref, "\n", - "yref:", yref, "\n", - ].join(' '), test_ret); - }).then( function () { - if (!keep_graph_div) { - console.log('destroying graph div ', gd_id); - Plotly.purge(gd_id); - destroyGraphDiv(gd_id); - } - }); -} - -function runImageTests(start_stop,filter) { - runComboTests([ - axisTypes, axisTypes, axisPairs, - // axis reference types are contained in here - aroPositionsX, aroPositionsY, - xAnchors, yAnchors - ],testImageCombo,start_stop,filter); -} - -function testAnnotationCombo(combo,keep_graph_div) { - var axistypex = combo[0]; - var axistypey = combo[1]; - var axispair = combo[2]; - var aroposx = combo[3]; - var aroposy = combo[4]; - var arrowaxispair = combo[5]; - var gd_id = combo[6]; - var xid = axispair[0]; - var yid = axispair[1]; - var xref = makeAxRef(xid, aroposx.ref); - var yref = makeAxRef(yid, aroposy.ref); - var axref = arrowaxispair[0] === 'p' ? 'pixel' : xref; - var ayref = arrowaxispair[1] === 'p' ? 'pixel' : yref; - var x0 = aroposx.value[0]; - var y0 = aroposy.value[0]; - var ax = axref === 'pixel' ? aroposx.pixel : aroposx.value[1]; - var ay = ayref === 'pixel' ? aroposy.pixel : aroposy.value[1]; - if (DEBUG) { - console.log(combo); - } - return new Promise(function(resolve){ - var gd = createGraphDiv(gd_id); - resolve(gd); - }).then(function (gd) { return Plotly.newPlot(gd, testImageComboMock); }) - .then(function (gd) { - return annotationTest(gd, {}, x0, y0, ax, ay, xref, yref, axref, - ayref, axistypex, axistypey, xid, yid); - }).then( function (test_ret) { - console.log([ - "Testing layout annotation " + gd_id + " with parameters:", - "x-axis type:", axistypex, "\n", - "y-axis type:", axistypey, "\n", - "arrow axis pair:", arrowaxispair, "\n", - "xref:", xref, "\n", - "yref:", yref, "\n", - ].join(' '), test_ret); - }).then( function () { - if (!keep_graph_div) { - console.log('destroying graph div ', gd_id); - Plotly.purge(gd_id); - destroyGraphDiv(gd_id); - } - }); -} - -function runAnnotationTests(start_stop,filter,keep_graph_div) { - runComboTests([ - axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis - ], - testAnnotationCombo,start_stop,filter,keep_graph_div); -} - -module.exports = { - runImageTests: runImageTests, - testImageCombo: testImageCombo, - runAnnotationTests: runAnnotationTests, - testAnnotationCombo: testAnnotationCombo, - findAROByColor: findAROByColor -}; diff --git a/test/jasmine/assets/domain_ref_components.js b/test/jasmine/assets/domain_ref/components.js similarity index 94% rename from test/jasmine/assets/domain_ref_components.js rename to test/jasmine/assets/domain_ref/components.js index 75bfb6c1953..b25f27d4fda 100644 --- a/test/jasmine/assets/domain_ref_components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -10,21 +10,22 @@ // promise is followed by .catch(failTest).then(done) 'use strict' -var Plotly = require('../../../lib/index'); +var Plotly = require('../../../../lib/index'); var d3 = require('d3'); -var createGraphDiv = require('../assets/create_graph_div'); -var destroyGraphDiv = require('../assets/destroy_graph_div'); -var pixelCalc = require('../assets/pixel_calc'); +var createGraphDiv = require('../../assets/create_graph_div'); +var destroyGraphDiv = require('../../assets/destroy_graph_div'); +var pixelCalc = require('../../assets/pixel_calc'); var getSVGElemScreenBBox = require( - '../assets/get_svg_elem_screen_bbox'); -var Lib = require('../../../src/lib'); -var Axes = require('../../../src/plots/cartesian/axes'); -var axisIds = require('../../../src/plots/cartesian/axis_ids'); + '../../assets/get_svg_elem_screen_bbox'); +var Lib = require('../../../../src/lib'); +var Axes = require('../../../../src/plots/cartesian/axes'); +var axisIds = require('../../../../src/plots/cartesian/axis_ids'); var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; var iterable = require('extra-iterable'); +var delay = require('../../assets/delay'); var testMock = Lib.extendDeep({}, - require('../../image/mocks/domain_ref_base.json')); + require('../../../image/mocks/domain_ref_base.json')); // NOTE: this tolerance is in pixels var EQUALITY_TOLERANCE = 1e-2; @@ -170,22 +171,20 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, // the log value doubles the pixel value, etc.). var xreftype = Axes.getRefType(xref); var yreftype = Axes.getRefType(yref); - if (xreftype === 'range') { - x0 = xaxistype === 'log' ? Math.log10(x0) : x0; - ax = xaxistype === 'log' ? Math.log10(ax) : ax; - } - if (yreftype === 'range') { - y0 = yaxistype === 'log' ? Math.log10(y0) : y0; - ay = yaxistype === 'log' ? Math.log10(ay) : ay; - } + var axreftype = Axes.getRefType(axref); + var ayreftype = Axes.getRefType(ayref); + x0 = xreftype === 'range' && xaxistype === 'log' ? Math.log10(x0) : x0; + ax = axreftype === 'range' && xaxistype === 'log' ? Math.log10(ax) : ax; + y0 = yreftype === 'range' && yaxistype === 'log' ? Math.log10(y0) : y0; + ay = ayreftype === 'range' && yaxistype === 'log' ? Math.log10(ay) : ay; // if xref != axref or axref === 'pixel' then ax is a value relative to // x0 but in pixels. Same for yref var axpixels = false; - if ((axref === 'pixel') || (Axes.getRefType(axref) != xreftype)) { + if ((axreftype === 'pixel') || (axreftype != xreftype)) { axpixels = true; } var aypixels = false; - if ((ayref === 'pixel') || (Axes.getRefType(ayref) != yreftype)) { + if ((ayreftype === 'pixel') || (ayreftype != yreftype)) { aypixels = true; } var xaxname; @@ -212,7 +211,8 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, var anno0 = aroFromParams('annotation', x0, y0, xref, yref, color0, opts0); var anno1 = aroFromParams('annotation', x0, y0, xref, yref, color1, opts1); layout.annotations = [anno0, anno1]; - return Plotly.relayout(gd, layout).then(function(gd) { + return Plotly.relayout(gd, layout) + .then(function(gd) { // the choice of anno1 or anno0 is arbitrary var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x', 0, true); var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y', 0, true); @@ -232,8 +232,8 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay', 0, true) - yabspixels; } - var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0, "#" + gd.id)); - var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1, "#" + gd.id)); + var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0)); + var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1)); // solve for the arrow length's x coordinate var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 .width)); @@ -684,7 +684,7 @@ function describeAnnotationComboTest(combo) { ].join(' '); } -function testAnnotationCombo(combo, keep_graph_div, assert) { +function testAnnotationCombo(combo, assert, gd) { var axistypex = combo[0]; var axistypey = combo[1]; var axispair = combo[2]; @@ -702,23 +702,13 @@ function testAnnotationCombo(combo, keep_graph_div, assert) { var y0 = aroposy.value[0]; var ax = axref === 'pixel' ? aroposx.pixel : aroposx.value[1]; var ay = ayref === 'pixel' ? aroposy.pixel : aroposy.value[1]; - return new Promise(function(resolve) { - var gd = createGraphDiv(gd_id); - resolve(gd); - }).then(function(gd) { - return Plotly.newPlot(gd, testMock); - }) + return Plotly.newPlot(gd, testMock) .then(function(gd) { return annotationTest(gd, {}, x0, y0, ax, ay, xref, yref, axref, ayref, axistypex, axistypey, xid, yid); }).then(function(test_ret) { assert(test_ret); - }).then(function() { - if (!keep_graph_div) { - Plotly.purge(gd_id); - destroyGraphDiv(gd_id); - } - }); + }) } // return a list of functions, each returning a promise that executes a @@ -731,8 +721,8 @@ function testAnnotationCombo(combo, keep_graph_div, assert) { // {test} is the function returning a Promise that executes this test function comboTests(testCombos,test) { var ret = testCombos.map(function (combo) { - return function (keepGraphDiv, assert) { - return test(combo, keepGraphDiv, assert); + return function (assert, gd) { + return test(combo, assert, gd); } }); return ret; diff --git a/test/jasmine/assets/domain_ref/testnumber.js b/test/jasmine/assets/domain_ref/testnumber.js new file mode 100644 index 00000000000..7950d000264 --- /dev/null +++ b/test/jasmine/assets/domain_ref/testnumber.js @@ -0,0 +1,3 @@ +// module.exports can be set to a specific test number to run just a single +// test, otherwise if set to undefined, runs all the tests. +module.exports=undefined; diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 2a2bfeaab8c..11ff3bf30c8 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -1,5 +1,12 @@ +'use strict' var failTest = require('../assets/fail_test'); -var domainRefComponents = require('../assets/domain_ref_components'); +var domainRefComponents = require('../assets/domain_ref/components'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var Plotly = require('../../../lib/index'); +var delay = require('../assets/delay'); +// optionally specify a test number in a file to run just a single test +var testNumber = require('../assets/domain_ref/testnumber'); function makeTests(component, filter) { return function() { @@ -8,11 +15,17 @@ function makeTests(component, filter) { } : filter; var descriptions = component.descriptions().filter(filter); var tests = component.tests().filter(filter); + var gd; + beforeEach(function () { gd = createGraphDiv(); }); + afterEach(function () { + Plotly.purge(gd); + destroyGraphDiv(gd); + }); descriptions.forEach(function(d, i) { it(d, function(done) { - tests[i](false, function(v) { + tests[i](function(v) { expect(v).toBe(true); - }) + },gd) .catch(failTest) .then(done); }); @@ -21,7 +34,7 @@ function makeTests(component, filter) { } describe('Test annotations', makeTests(domainRefComponents.annotations, -undefined)); -// function(f, i) { -// return i == 565; -// })); + function(f, i) { + if (testNumber === undefined) { return true; } + return i == testNumber; + })); From 27d8c439aba9cb6f932096446472bc4093902077 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 4 Sep 2020 15:48:29 -0400 Subject: [PATCH 069/112] Don't take log of range for images Because we would take the log of the range values but not the x, y, sizex, sizey, values, the image would plot, exist and be correct, but it would never be shown because it would usually be off the plot, which was strange. --- test/jasmine/assets/domain_ref/components.js | 112 +++++++++++-------- test/jasmine/tests/domain_ref_test.js | 6 + 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index b25f27d4fda..825d23c5979 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -259,14 +259,15 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, } // axid is e.g., 'x', 'y2' etc. -function logAxisIfAxType(layoutIn, layoutOut, axid, axtype) { +// if nologrange is true, log of range is not taken +function logAxisIfAxType(layoutIn, layoutOut, axid, axtype, nologrange) { var axname = axisIds.id2name(axid); if ((axtype === 'log') && (axid !== undefined)) { var axis = { ...layoutIn[axname] }; axis.type = 'log'; - axis.range = axis.range.map(Math.log10); + axis.range = nologrange ? axis.range : axis.range.map(Math.log10); layoutOut[axname] = axis; } } @@ -398,11 +399,8 @@ function findImage(id) { return ret; } -function checkImage(layout, imageObj, imageBBox) {} - function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, yanchor, xref, yref, xid, yid) { - console.log('running imageTest on ', gd); var image = { x: x, y: y, @@ -420,8 +418,8 @@ function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, var ret; // we pass xid, yid because we possibly want to change some axes to log, // even if we refer to paper in the end - logAxisIfAxType(gd.layout, layout, xid, xaxtype); - logAxisIfAxType(gd.layout, layout, yid, yaxtype); + logAxisIfAxType(gd.layout, layout, xid, xaxtype, true); + logAxisIfAxType(gd.layout, layout, yid, yaxtype, true); layout.images = [image]; return Plotly.relayout(gd, layout) .then(function(gd) { @@ -429,9 +427,6 @@ function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, var svgImageBBox = getSVGElemScreenBBox(imageElem); var imageBBox = imageToBBox(gd.layout, image); ret = compareBBoxes(svgImageBBox, imageBBox); - //if (!ret) { - // throw 'test failed'; - //} return ret; }); } @@ -602,7 +597,7 @@ function runComboTests(productItems, testCombo, start_stop, filter, keep_graph_d var tc = testCombos.map(c => testCombo(c, keep_graph_div)).reduce((a, v) => a.then(v)); } -function testImageCombo(combo, keep_graph_div) { +function describeImageComboTest(combo) { var axistypex = combo[0]; var axistypey = combo[1]; var axispair = combo[2]; @@ -615,48 +610,47 @@ function testImageCombo(combo, keep_graph_div) { var yid = axispair[1]; var xref = makeAxRef(xid, aroposx.ref); var yref = makeAxRef(yid, aroposy.ref); - if (DEBUG) { - console.log(combo); - } - return new Promise(function(resolve) { - var gd = createGraphDiv(gd_id); - resolve(gd); - }).then(function(gd) { - return Plotly.newPlot(gd, testMock); - }) + // TODO Add image combo test description + return [ + "layout image with graph ID", + gd_id, + "with parameters:", + "x-axis type:", axistypex, "\n", + "y-axis type:", axistypey, "\n", + "axis pair:", axispair, "\n", + "ARO position x:", aroposx, "\n", + "ARO position y:", aroposy, "\n", + "xanchor:", xanchor, "\n", + "yanchor:", yanchor, "\n", + "xref:", xref, "\n", + "yref:", yref, "\n", + ].join(' '); +} + +function testImageCombo(combo, assert, gd) { + var axistypex = combo[0]; + var axistypey = combo[1]; + var axispair = combo[2]; + var aroposx = combo[3]; + var aroposy = combo[4]; + var xanchor = combo[5]; + var yanchor = combo[6]; + var gd_id = combo[7]; + var xid = axispair[0]; + var yid = axispair[1]; + var xref = makeAxRef(xid, aroposx.ref); + var yref = makeAxRef(yid, aroposy.ref); + return Plotly.newPlot(gd, testMock) .then(function(gd) { return imageTest(gd, {}, axistypex, axistypey, aroposx.value[0], aroposy.value[0], aroposx.size, aroposy.size, xanchor, yanchor, xref, yref, xid, yid); }).then(function(test_ret) { - console.log([ - "Testing layout image with parameters:", - "x-axis type:", axistypex, "\n", - "y-axis type:", axistypey, "\n", - "xanchor:", xanchor, "\n", - "yanchor:", yanchor, "\n", - "xref:", xref, "\n", - "yref:", yref, "\n", - ].join(' '), test_ret); - }).then(function() { - if (!keep_graph_div) { - console.log('destroying graph div ', gd_id); - Plotly.purge(gd_id); - destroyGraphDiv(gd_id); - } + assert(test_ret); }); } -function runImageTests(start_stop, filter) { - runComboTests([ - axisTypes, axisTypes, axisPairs, - // axis reference types are contained in here - aroPositionsX, aroPositionsY, - xAnchors, yAnchors - ], testImageCombo, start_stop, filter); -} - function describeAnnotationComboTest(combo) { var axistypex = combo[0]; var axistypey = combo[1]; @@ -708,7 +702,7 @@ function testAnnotationCombo(combo, assert, gd) { ayref, axistypex, axistypey, xid, yid); }).then(function(test_ret) { assert(test_ret); - }) + }); } // return a list of functions, each returning a promise that executes a @@ -755,12 +749,40 @@ function annotationTestDescriptions() { return comboTestDescriptions(testCombos,describeAnnotationComboTest); } + +function imageTestCombos() { + var testCombos = [...iterable.cartesianProduct( + [ + axisTypes, axisTypes, axisPairs, + // axis reference types are contained in here + aroPositionsX, aroPositionsY, + xAnchors, yAnchors + ] + )]; + testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); + return testCombos; +} + +function imageTests() { + var testCombos = imageTestCombos(); + return comboTests(testCombos,testImageCombo); +} + +function imageTestDescriptions() { + var testCombos = imageTestCombos(); + return comboTestDescriptions(testCombos,describeImageComboTest); +} + module.exports = { // tests annotations: { descriptions: annotationTestDescriptions, tests: annotationTests, }, + images: { + descriptions: imageTestDescriptions, + tests: imageTests, + }, // utilities findAROByColor: findAROByColor }; diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 11ff3bf30c8..75ed261f474 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -38,3 +38,9 @@ describe('Test annotations', makeTests(domainRefComponents.annotations, if (testNumber === undefined) { return true; } return i == testNumber; })); + +fdescribe('Test images', makeTests(domainRefComponents.images, + function(f, i) { + if (testNumber === undefined) { return true; } + return i == testNumber; + })); From 0f1eae61267d92ed50a9d443caef8a4d684097cd Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 4 Sep 2020 17:13:38 -0400 Subject: [PATCH 070/112] Shapes tests now run as Jasmine tests Will test them individually to see what's going on. --- test/jasmine/assets/domain_ref/components.js | 216 +++++++++++++------ test/jasmine/tests/domain_ref_test.js | 8 +- 2 files changed, 152 insertions(+), 72 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index 825d23c5979..660b218a30b 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -213,49 +213,49 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, layout.annotations = [anno0, anno1]; return Plotly.relayout(gd, layout) .then(function(gd) { - // the choice of anno1 or anno0 is arbitrary - var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x', 0, true); - var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y', 0, true); - if (axpixels) { - // no need to map the specified values to pixels (because that's what - // they are already) - xpixels = ax; - } else { - xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax', 0, true) - - xabspixels; - } - if (aypixels) { - // no need to map the specified values to pixels (because that's what - // they are already) - ypixels = ay; - } else { - ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay', 0, true) - - yabspixels; - } - var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0)); - var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1)); - // solve for the arrow length's x coordinate - var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 - .width)); - var arrowLenY; - var yabspixelscmp; - if (aypixels) { - // for annotations whose arrows are specified in relative pixels, - // positive pixel values on the y axis mean moving down the page like - // SVG coordinates, so we have to add height - var arrowLenY = (annobbox1.y + annobbox1.height) - - (annobbox0.y + annobbox0.height); - yabspixelscmp = annobbox0.y; - } else { - var arrowLenY = annobbox1.y - annobbox0.y; - yabspixelscmp = annobbox0.y + annobbox0.height; - } - var ret = coordsEq(arrowLenX, xpixels) && - coordsEq(arrowLenY, ypixels) && - coordsEq(xabspixels, annobbox0.x) && - coordsEq(yabspixels, yabspixelscmp); - return ret; - }); + // the choice of anno1 or anno0 is arbitrary + var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x', 0, true); + var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y', 0, true); + if (axpixels) { + // no need to map the specified values to pixels (because that's what + // they are already) + xpixels = ax; + } else { + xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax', 0, true) - + xabspixels; + } + if (aypixels) { + // no need to map the specified values to pixels (because that's what + // they are already) + ypixels = ay; + } else { + ypixels = mapAROCoordToPixel(gd.layout, 'yref', anno0, 'ay', 0, true) - + yabspixels; + } + var annobbox0 = getSVGElemScreenBBox(findAROByColor(color0)); + var annobbox1 = getSVGElemScreenBBox(findAROByColor(color1)); + // solve for the arrow length's x coordinate + var arrowLenX = ((annobbox1.x + annobbox1.width) - (annobbox0.x + annobbox0 + .width)); + var arrowLenY; + var yabspixelscmp; + if (aypixels) { + // for annotations whose arrows are specified in relative pixels, + // positive pixel values on the y axis mean moving down the page like + // SVG coordinates, so we have to add height + var arrowLenY = (annobbox1.y + annobbox1.height) - + (annobbox0.y + annobbox0.height); + yabspixelscmp = annobbox0.y; + } else { + var arrowLenY = annobbox1.y - annobbox0.y; + yabspixelscmp = annobbox0.y + annobbox0.height; + } + var ret = coordsEq(arrowLenX, xpixels) && + coordsEq(arrowLenY, ypixels) && + coordsEq(xabspixels, annobbox0.x) && + coordsEq(yabspixels, yabspixelscmp); + return ret; + }); } // axid is e.g., 'x', 'y2' etc. @@ -438,10 +438,6 @@ function checkAROPosition(gd, aro) { var aroPathBBox = getSVGElemScreenBBox(aroPath); var aroBBox = shapeToBBox(gd.layout, aro); var ret = compareBBoxes(aroBBox, aroPathBBox); - if (DEBUG) { - console.log('SVG BBox', aroPathBBox); - console.log('aro BBox', aroBBox); - } return ret; } @@ -528,6 +524,72 @@ var yAnchors = ['top', 'middle', 'bottom']; // NOTE: for images color cannot be set but it will be the only image in the // plot so you can use d3.select('g image').node() var aroColor = 'rgb(50, 100, 150)'; + +function testShape( + gd, + xAxNum, + xaxisType, + xaroPos, + yAxNum, + yaxisType, + yaroPos, + aroType +) { + var aro = { + type: aroType, + line: { + color: aroColor + } + }; + aroFromAROPos(aro, 'x', xAxNum, xaroPos); + aroFromAROPos(aro, 'y', yAxNum, yaroPos); + var layout = { + shapes: [aro] + }; + // change to log axes if need be + logAxisIfAxType(gd.layout, layout, 'x' + xAxNum, xaxisType); + logAxisIfAxType(gd.layout, layout, 'y' + yAxNum, yaxisType); + return Plotly.relayout(gd, layout) + .then(function(gd) { + return checkAROPosition(gd, aro); + }); +} + +function describeShapeComboTest(combo) { + var xaxisType = combo[0]; + var yaxisType = combo[1]; + var axispair = combo[2]; + var xaroPos = combo[3]; + var yaroPos = combo[4]; + var gd_id = combo[5]; + return [ + "should create a plot with graph ID", + gd_id, + " with parameters:", "\n", + "x-axis type:", xaxisType, "\n", + "y-axis type:", yaxisType, "\n", + "axis pair:", axispair, "\n", + "ARO position x:", xaroPos, "\n", + "ARO position y:", yaroPos, "\n", + ].join(' '); +} + +function testShapeCombo(combo, assert, gd) { + var xaxisType = combo[0]; + var yaxisType = combo[1]; + var axispair = combo[2]; + var xaroPos = combo[3]; + var yaroPos = combo[4]; + var xAxNum = axispair[0].substr(1); + var yAxNum = axispair[1].substr(1); + return Plotly.newPlot(gd, testMock) + .then(function(gd) { + return testShape(gd,xAxNum,xaxisType,xaroPos,yAxNum,yaxisType,yaroPos,'shape'); + }).then(function(test_ret) { + assert(test_ret); + }); +} + var testDomRefAROCombo = function(combo) { var xAxNum = combo[0]; var xaxisType = combo[1]; @@ -584,19 +646,6 @@ function test_correct_aro_positions() { testCombos.forEach(testDomRefAROCombo); } -function runComboTests(productItems, testCombo, start_stop, filter, keep_graph_div) { - var testCombos = [...iterable.cartesianProduct(productItems)]; - testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); - if (filter) { - testCombos = testCombos.filter(filter); - } - if (start_stop) { - testCombos = testCombos.slice(start_stop.start, start_stop.stop); - } - console.log("Executing " + testCombos.length + " tests"); - var tc = testCombos.map(c => testCombo(c, keep_graph_div)).reduce((a, v) => a.then(v)); -} - function describeImageComboTest(combo) { var axistypex = combo[0]; var axistypey = combo[1]; @@ -713,9 +762,9 @@ function testAnnotationCombo(combo, assert, gd) { // {testCombos} is a list of combinations each of which will be passed to the // test function // {test} is the function returning a Promise that executes this test -function comboTests(testCombos,test) { - var ret = testCombos.map(function (combo) { - return function (assert, gd) { +function comboTests(testCombos, test) { + var ret = testCombos.map(function(combo) { + return function(assert, gd) { return test(combo, assert, gd); } }); @@ -725,28 +774,27 @@ function comboTests(testCombos,test) { // return a list of strings, each describing a corresponding test // describe is a function taking a combination and returning a description of // the test -function comboTestDescriptions(testCombos,desribe) { +function comboTestDescriptions(testCombos, desribe) { var ret = testCombos.map(desribe); return ret; } function annotationTestCombos() { var testCombos = [...iterable.cartesianProduct([ - axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis - ] - )]; + axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis + ])]; testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); return testCombos; } function annotationTests() { var testCombos = annotationTestCombos(); - return comboTests(testCombos,testAnnotationCombo); + return comboTests(testCombos, testAnnotationCombo); } function annotationTestDescriptions() { var testCombos = annotationTestCombos(); - return comboTestDescriptions(testCombos,describeAnnotationComboTest); + return comboTestDescriptions(testCombos, describeAnnotationComboTest); } @@ -765,12 +813,34 @@ function imageTestCombos() { function imageTests() { var testCombos = imageTestCombos(); - return comboTests(testCombos,testImageCombo); + return comboTests(testCombos, testImageCombo); } function imageTestDescriptions() { var testCombos = imageTestCombos(); - return comboTestDescriptions(testCombos,describeImageComboTest); + return comboTestDescriptions(testCombos, describeImageComboTest); +} + +function shapeTestCombos() { + var testCombos = [...iterable.cartesianProduct( + [ + axisTypes, axisTypes, axisPairs, + // axis reference types are contained in here + aroPositionsX, aroPositionsY, + ] + )]; + testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); + return testCombos; +} + +function shapeTests() { + var testCombos = shapeTestCombos(); + return comboTests(testCombos, testShapeCombo); +} + +function shapeTestDescriptions() { + var testCombos = shapeTestCombos(); + return comboTestDescriptions(testCombos, describeShapeComboTest); } module.exports = { @@ -783,6 +853,10 @@ module.exports = { descriptions: imageTestDescriptions, tests: imageTests, }, + shapes: { + descriptions: shapeTestDescriptions, + tests: shapeTests + }, // utilities findAROByColor: findAROByColor }; diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 75ed261f474..76d9d3716d3 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -39,7 +39,13 @@ describe('Test annotations', makeTests(domainRefComponents.annotations, return i == testNumber; })); -fdescribe('Test images', makeTests(domainRefComponents.images, +describe('Test images', makeTests(domainRefComponents.images, + function(f, i) { + if (testNumber === undefined) { return true; } + return i == testNumber; + })); + +fdescribe('Test shapes', makeTests(domainRefComponents.shapes, function(f, i) { if (testNumber === undefined) { return true; } return i == testNumber; From 83834f89c59da44bc0a17b085892478abdeb194c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 10 Sep 2020 16:29:28 -0400 Subject: [PATCH 071/112] Static shape, annotation and image tests are passing What seemed like the problem was that the gd.layout remained referring to a single object obtained from the test/image/mocks/domain_ref_base.json file, so if we changed the layout in one test, it remained changed in the other test. What this seems to mean is that Plotly.newPlot doesn't make a copy of the layout it's handed (?). Any how, this was fixed by calling `Lib.extendDeep({},mock)` for each test, which obtains a new object pointing to new memory and therefore will not be affected by previous tests. Tests for editable mode do not exist yet. --- test/jasmine/assets/domain_ref/components.js | 145 ++++++++----------- test/jasmine/assets/svg_tools.js | 14 +- test/jasmine/tests/domain_ref_test.js | 27 +++- 3 files changed, 94 insertions(+), 92 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index 660b218a30b..0be73dc2eff 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -17,6 +17,8 @@ var destroyGraphDiv = require('../../assets/destroy_graph_div'); var pixelCalc = require('../../assets/pixel_calc'); var getSVGElemScreenBBox = require( '../../assets/get_svg_elem_screen_bbox'); +var SVGTools = require( + '../../assets/svg_tools'); var Lib = require('../../../../src/lib'); var Axes = require('../../../../src/plots/cartesian/axes'); var axisIds = require('../../../../src/plots/cartesian/axis_ids'); @@ -24,8 +26,37 @@ var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; var iterable = require('extra-iterable'); var delay = require('../../assets/delay'); -var testMock = Lib.extendDeep({}, - require('../../../image/mocks/domain_ref_base.json')); +var testMock = require('../../../image/mocks/domain_ref_base.json'); + +var defaultLayout = { + "xaxis": { + "domain": [0, 0.75], + "range": [1, 3] + }, + "yaxis": { + "domain": [0, 0.4], + "range": [1, 4] + }, + "xaxis2": { + "domain": [0.75, 1], + "range": [1, 4], + "anchor": "y2" + }, + "yaxis2": { + "domain": [0.4, 1], + "range": [1, 3], + "anchor": "x2" + }, + "margin": { + "l": 100, + "r": 100, + "t": 100, + "b": 100, + "autoexpand": false + }, + "width": 400, + "height": 400 +}; // NOTE: this tolerance is in pixels var EQUALITY_TOLERANCE = 1e-2; @@ -373,7 +404,10 @@ function imageToBBox(layout, img) { function coordsEq(a, b) { - return Math.abs(a - b) < EQUALITY_TOLERANCE; + if (a && b) { + return Math.abs(a - b) < EQUALITY_TOLERANCE; + } + return false; } function compareBBoxes(a, b) { @@ -438,6 +472,8 @@ function checkAROPosition(gd, aro) { var aroPathBBox = getSVGElemScreenBBox(aroPath); var aroBBox = shapeToBBox(gd.layout, aro); var ret = compareBBoxes(aroBBox, aroPathBBox); + console.log("aroBBox: " + JSON.stringify(aroBBox)) + console.log("aroPathBBox: " + JSON.stringify(SVGTools.SVGRectToObj(aroPathBBox))) return ret; } @@ -535,6 +571,7 @@ function testShape( yaroPos, aroType ) { + console.log('gd.layout: ', JSON.stringify(gd.layout)); var aro = { type: aroType, line: { @@ -549,6 +586,7 @@ function testShape( // change to log axes if need be logAxisIfAxType(gd.layout, layout, 'x' + xAxNum, xaxisType); logAxisIfAxType(gd.layout, layout, 'y' + yAxNum, yaxisType); + console.log('layout: ', JSON.stringify(layout)); return Plotly.relayout(gd, layout) .then(function(gd) { return checkAROPosition(gd, aro); @@ -562,15 +600,16 @@ function describeShapeComboTest(combo) { var xaroPos = combo[3]; var yaroPos = combo[4]; var gd_id = combo[5]; + var xid = axispair[0]; + var yid = axispair[1]; return [ - "should create a plot with graph ID", - gd_id, - " with parameters:", "\n", + "#", gd_id, + "should create a plot with parameters:", "\n", "x-axis type:", xaxisType, "\n", "y-axis type:", yaxisType, "\n", - "axis pair:", axispair, "\n", - "ARO position x:", xaroPos, "\n", - "ARO position y:", yaroPos, "\n", + "axis pair:", xid, yid, "\n", + "ARO position x:", JSON.stringify(xaroPos), "\n", + "ARO position y:", JSON.stringify(yaroPos), "\n", ].join(' '); } @@ -582,70 +621,14 @@ function testShapeCombo(combo, assert, gd) { var yaroPos = combo[4]; var xAxNum = axispair[0].substr(1); var yAxNum = axispair[1].substr(1); - return Plotly.newPlot(gd, testMock) + return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { - return testShape(gd,xAxNum,xaxisType,xaroPos,yAxNum,yaxisType,yaroPos,'shape'); + return testShape(gd, xAxNum, xaxisType, xaroPos, yAxNum, yaxisType, yaroPos, 'shape'); }).then(function(test_ret) { assert(test_ret); }); } -var testDomRefAROCombo = function(combo) { - var xAxNum = combo[0]; - var xaxisType = combo[1]; - var xaroPos = combo[2]; - var yAxNum = combo[3]; - var yaxisType = combo[4]; - var yaroPos = combo[5]; - var aroType = combo[6]; - it('should draw a ' + aroType + - ' for x' + xAxNum + ' of type ' + - xaxisType + - ' with a value referencing ' + - xaroPos.ref + - ' and for y' + yAxNum + ' of type ' + - yaxisType + - ' with a value referencing ' + - yaroPos.ref, - function(done) { - var gd = createGraphDiv(); - var mock = testMock; - if (DEBUG) { - console.log(combo); - } - Plotly.newPlot(gd, mock) - var aro = { - type: aroType, - line: { - color: aroColor - } - }; - aroFromAROPos(aro, 'x', xAxNum, xaroPos); - aroFromAROPos(aro, 'y', yAxNum, yaroPos); - var layout = { - shapes: [aro] - }; - // change to log axes if need be - logAxisIfAxType(gd.layout, layout, 'x' + xAxNum, xaxisType); - logAxisIfAxType(gd.layout, layout, 'y' + yAxNum, yaxisType); - Plotly.relayout(gd, layout); - console.log(checkAROPosition(gd, aro)); - destroyGraphDiv(); - }); -} - -// Test correct aro positions -function test_correct_aro_positions() { - // for both x and y axes - var testCombos = [...iterable.cartesianProduct([ - axNum, axisTypes, aroPositionsX, axNum, axisTypes, - aroPositionsY, aroType - ])]; - // map all the combinations to a aro definition and check this aro is - // placed properly - testCombos.forEach(testDomRefAROCombo); -} - function describeImageComboTest(combo) { var axistypex = combo[0]; var axistypey = combo[1]; @@ -661,14 +644,13 @@ function describeImageComboTest(combo) { var yref = makeAxRef(yid, aroposy.ref); // TODO Add image combo test description return [ - "layout image with graph ID", - gd_id, - "with parameters:", + "#", gd_id, + "should create a plot with parameters:", "\n", "x-axis type:", axistypex, "\n", "y-axis type:", axistypey, "\n", - "axis pair:", axispair, "\n", - "ARO position x:", aroposx, "\n", - "ARO position y:", aroposy, "\n", + "axis pair:", xid, yid, "\n", + "ARO position x:", JSON.stringify(aroposx), "\n", + "ARO position y:", JSON.stringify(aroposy), "\n", "xanchor:", xanchor, "\n", "yanchor:", yanchor, "\n", "xref:", xref, "\n", @@ -689,7 +671,7 @@ function testImageCombo(combo, assert, gd) { var yid = axispair[1]; var xref = makeAxRef(xid, aroposx.ref); var yref = makeAxRef(yid, aroposy.ref); - return Plotly.newPlot(gd, testMock) + return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { return imageTest(gd, {}, axistypex, axistypey, aroposx.value[0], aroposy.value[0], aroposx.size, @@ -713,14 +695,13 @@ function describeAnnotationComboTest(combo) { var xref = makeAxRef(xid, aroposx.ref); var yref = makeAxRef(yid, aroposy.ref); return [ - "should create a plot with graph ID", - gd_id, - " with parameters:", "\n", + "#", gd_id, + "should create a plot with parameters:", "\n", "x-axis type:", axistypex, "\n", "y-axis type:", axistypey, "\n", - "axis pair:", axispair, "\n", - "ARO position x:", aroposx, "\n", - "ARO position y:", aroposy, "\n", + "axis pair:", xid, yid, "\n", + "ARO position x:", JSON.stringify(aroposx), "\n", + "ARO position y:", JSON.stringify(aroposy), "\n", "arrow axis pair:", arrowaxispair, "\n", "xref:", xref, "\n", "yref:", yref, "\n", @@ -745,7 +726,7 @@ function testAnnotationCombo(combo, assert, gd) { var y0 = aroposy.value[0]; var ax = axref === 'pixel' ? aroposx.pixel : aroposx.value[1]; var ay = ayref === 'pixel' ? aroposy.pixel : aroposy.value[1]; - return Plotly.newPlot(gd, testMock) + return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { return annotationTest(gd, {}, x0, y0, ax, ay, xref, yref, axref, ayref, axistypex, axistypey, xid, yid); @@ -859,4 +840,4 @@ module.exports = { }, // utilities findAROByColor: findAROByColor -}; +}; \ No newline at end of file diff --git a/test/jasmine/assets/svg_tools.js b/test/jasmine/assets/svg_tools.js index e5bb7a235f3..075324a26b9 100644 --- a/test/jasmine/assets/svg_tools.js +++ b/test/jasmine/assets/svg_tools.js @@ -1,15 +1,25 @@ 'use strict'; module.exports = { - findParentSVG: findParentSVG + findParentSVG: findParentSVG, + SVGRectToObj: SVGRectToObj }; function findParentSVG(node) { var parentNode = node.parentNode; - if(parentNode.tagName === 'svg') { + if (parentNode.tagName === 'svg') { return parentNode; } else { return findParentSVG(parentNode); } } + +function SVGRectToObj(svgrect) { + var obj = {}; + obj.x = svgrect.x; + obj.y = svgrect.y; + obj.width = svgrect.width; + obj.height = svgrect.height; + return obj; +} \ No newline at end of file diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 76d9d3716d3..72db4b72744 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -16,16 +16,21 @@ function makeTests(component, filter) { var descriptions = component.descriptions().filter(filter); var tests = component.tests().filter(filter); var gd; - beforeEach(function () { gd = createGraphDiv(); }); - afterEach(function () { + beforeEach(function() { + gd = createGraphDiv(); + }); + afterEach(function() { Plotly.purge(gd); destroyGraphDiv(gd); + gd = null; }); descriptions.forEach(function(d, i) { it(d, function(done) { + console.log("testing " + d); + gd.id = "graph-" + i; tests[i](function(v) { expect(v).toBe(true); - },gd) + }, gd) .catch(failTest) .then(done); }); @@ -35,18 +40,24 @@ function makeTests(component, filter) { describe('Test annotations', makeTests(domainRefComponents.annotations, function(f, i) { - if (testNumber === undefined) { return true; } + if (testNumber === undefined) { + return true; + } return i == testNumber; })); describe('Test images', makeTests(domainRefComponents.images, function(f, i) { - if (testNumber === undefined) { return true; } + if (testNumber === undefined) { + return true; + } return i == testNumber; })); -fdescribe('Test shapes', makeTests(domainRefComponents.shapes, +describe('Test shapes', makeTests(domainRefComponents.shapes, function(f, i) { - if (testNumber === undefined) { return true; } + if (testNumber === undefined) { + return true; + } return i == testNumber; - })); + })); \ No newline at end of file From c9103dfff0b456d6f963cff9d4119e2fd4a6b351 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 14 Sep 2020 12:31:00 -0400 Subject: [PATCH 072/112] Domain referenced shapes are visible outside the plotting area This is like paper referenced images --- src/components/images/draw.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 29289daa116..14e80f5f65a 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -11,6 +11,7 @@ var d3 = require('d3'); var Drawing = require('../drawing'); var Axes = require('../../plots/cartesian/axes'); +var axisIds = require('../../plots/cartesian/axis_ids'); var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); module.exports = function draw(gd) { @@ -27,7 +28,7 @@ module.exports = function draw(gd) { if(img.visible) { if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') { - subplot = img.xref + img.yref; + subplot = axisIds.ref2id(img.xref) + axisIds.ref2id(img.yref); var plotinfo = fullLayout._plots[subplot]; @@ -205,8 +206,8 @@ module.exports = function draw(gd) { // Set proper clipping on images - var xId = xa ? xa._id : ''; - var yId = ya ? ya._id : ''; + var xId = xa && (Axes.getRefType(d.xref) != 'domain') ? xa._id : ''; + var yId = ya && (Axes.getRefType(d.yref) != 'domain') ? ya._id : ''; var clipAxes = xId + yId; Drawing.setClipUrl( From 92496845ec86b09799c7c2308a1f1c213351d895 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 15 Sep 2020 10:22:24 -0400 Subject: [PATCH 073/112] fixed syntax, removed scraps --- src/components/annotations/defaults.js | 2 +- src/components/images/attributes.js | 8 +- src/components/images/draw.js | 4 +- src/plots/cartesian/axes.js | 4 +- test/all_layout_annotations_test.js | 5 - test/all_layout_images_test.js | 6 - test/domain_ref_shapes_test.html | 9 - test/domain_ref_test.html | 18 - test/domain_ref_test.js | 133 ---- test/domain_ref_tests.html | 765 ------------------- test/jasmine/assets/domain_ref/components.js | 6 +- test/jasmine/assets/domain_ref/testnumber.js | 2 +- test/jasmine/assets/pixel_calc.js | 22 +- test/jasmine/assets/svg_tools.js | 4 +- test/jasmine/tests/domain_ref_test.js | 27 +- test/jasmine/tests/shapes_test.js | 142 ---- test/run_js_script.sh | 39 - 17 files changed, 40 insertions(+), 1156 deletions(-) delete mode 100644 test/all_layout_annotations_test.js delete mode 100644 test/all_layout_images_test.js delete mode 100644 test/domain_ref_shapes_test.html delete mode 100644 test/domain_ref_test.html delete mode 100644 test/domain_ref_test.js delete mode 100644 test/domain_ref_tests.html delete mode 100644 test/run_js_script.sh diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index f3dccfac328..75fa9bbbc2f 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -61,7 +61,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { var arrowPosAttr = 'a' + axLetter; // axref, ayref var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, '', - ['paper','pixel'], true); + ['paper', 'pixel'], true); // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index 0e916396345..ad8c2cbbdcc 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -58,7 +58,9 @@ module.exports = templatedArray('image', { 'Sets the image container size horizontally.', 'The image will be sized based on the `position` value.', 'When `xref` is set to `paper`, units are sized relative', - 'to the plot width.' + 'to the plot width.', + 'When `xref` ends with ` domain`, units are sized relative', + 'to the axis width.', ].join(' ') }, @@ -71,7 +73,9 @@ module.exports = templatedArray('image', { 'Sets the image container size vertically.', 'The image will be sized based on the `position` value.', 'When `yref` is set to `paper`, units are sized relative', - 'to the plot height.' + 'to the plot height.', + 'When `yref` ends with ` domain`, units are sized relative', + 'to the axis height.' ].join(' ') }, diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 14e80f5f65a..781e0910df8 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -206,8 +206,8 @@ module.exports = function draw(gd) { // Set proper clipping on images - var xId = xa && (Axes.getRefType(d.xref) != 'domain') ? xa._id : ''; - var yId = ya && (Axes.getRefType(d.yref) != 'domain') ? ya._id : ''; + var xId = xa && (Axes.getRefType(d.xref) !== 'domain') ? xa._id : ''; + var yId = ya && (Axes.getRefType(d.yref) !== 'domain') ? ya._id : ''; var clipAxes = xId + yId; Drawing.setClipUrl( diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index bbfaa404816..1ec31c3bf14 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -97,8 +97,8 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption attrDef[refAttr] = { valType: 'enumerated', values: axlist.concat(extraOption ? - (typeof extraOption === 'string' ? [extraOption] : extraOption) - : []), + (typeof extraOption === 'string' ? [extraOption] : extraOption) : + []), dflt: dflt }; diff --git a/test/all_layout_annotations_test.js b/test/all_layout_annotations_test.js deleted file mode 100644 index a152e25b52f..00000000000 --- a/test/all_layout_annotations_test.js +++ /dev/null @@ -1,5 +0,0 @@ -var domainRefTests = require('./domain_ref_shapes_test'); -domainRefTests.runAnnotationTests( -{start:8,stop:9}, -); - diff --git a/test/all_layout_images_test.js b/test/all_layout_images_test.js deleted file mode 100644 index 08eeb1a3712..00000000000 --- a/test/all_layout_images_test.js +++ /dev/null @@ -1,6 +0,0 @@ -var domainRefTests = require('./domain_ref_shapes_test'); -domainRefTests.runImageTests( -{start:0,stop:10}, -function (combo) { - return combo[1] === 'log'; -}); diff --git a/test/domain_ref_shapes_test.html b/test/domain_ref_shapes_test.html deleted file mode 100644 index 635ea367bc2..00000000000 --- a/test/domain_ref_shapes_test.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/test/domain_ref_test.html b/test/domain_ref_test.html deleted file mode 100644 index 365767cbdb8..00000000000 --- a/test/domain_ref_test.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/test/domain_ref_test.js b/test/domain_ref_test.js deleted file mode 100644 index 9bfe19485ac..00000000000 --- a/test/domain_ref_test.js +++ /dev/null @@ -1,133 +0,0 @@ -var Plotly = require('../lib/index'); -var d3 = require('d3'); -var createGraphDiv = require('../test/jasmine/assets/create_graph_div'); -var pixelCalc = require('../test/jasmine/assets/pixel_calc'); -var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_bbox'); -var Lib = require('../src/lib'); -var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; - -function findPathWithColor(c) { - var aroPath = d3.selectAll('path').filter(function() { - return this.style.stroke === c; - }).node(); - return aroPath; -} - -var dothething = function() { - var arrowColor0 = 'rgb(10, 20, 30)'; - var arrowColor1 = 'rgb(10, 20, 31)'; - var shapeColor = 'rgb(50, 150, 250)'; - var arrowX0 = .75; - var arrowY0 = 3; - var arrowX1 = 1.; - var arrowY1 = 4; - var gd = createGraphDiv(); - var mock = Lib.extendDeep({}, require('../test/image/mocks/domain_ref_base.json')); - var shape = { - type: 'rect', - xref: 'x2', - yref: 'y2', - x0: 2, - x1: 3, - y0: 1, - y1: 2, - line: { - color: shapeColor - } - }; - var anno0 = { - width: 10, - height: 10, - x: arrowX0, - y: arrowY0, - xref: 'x domain', - yref: 'y', - ax: arrowX1, - ay: arrowY1, - axref: 'x domain', - ayref: 'y', - showarrow: true, - arrowhead: 0, - arrowcolor: arrowColor0, - bgcolor: 'rgb(10,100,30)', - }; - var anno1 = {...anno0}; - anno1.ax += arrowX1 - arrowX0; - anno1.ay += arrowY1 - arrowY0; - anno1.arrowcolor = arrowColor1; - Plotly.newPlot(gd, mock) - .then(function() { - var xaxis2 = { - ...gd.layout.xaxis2 - }; - var yaxis2 = { - ...gd.layout.yaxis2 - }; - xaxis2.type = 'log'; - xaxis2.range = xaxis2.range.map(Math.log10); - yaxis2.type = 'log'; - yaxis2.range = yaxis2.range.map(Math.log10); - var layout = { - shapes: [shape], - xaxis2: xaxis2, - yaxis2: yaxis2, - // adding image for test - images: [{ - x: 0.25, - y: 0.1, - sizex: 0.7, - sizey: 0.7, - source: testImage, - xanchor: "left", - xref: "x domain", - yanchor: "bottom", - yref: "y domain", - sizing: "stretch" - }], - // adding annotation for test - annotations: [anno0,anno1] - } - return layout; - }) - .then(function(layout) { - return Plotly.relayout(gd, layout); - }) - .then(function() { - var shapePath = d3.selectAll('path').filter(function() { - return this.style.stroke === shapeColor; - }).node(); - var bbox = getSVGElemScreenBBox(shapePath) - console.log(bbox); - console.log('property names', Object.keys(bbox)); - console.log('x0', pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0)); - console.log('x1', pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x1)); - console.log('y0', pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y0)); - console.log('y1', pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y1)); - console.log('bbox.x0 - shape.x0', - bbox.x - - pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0) - ); - // here we check to see the annotation arrow length is as expected - // by comparing 2 annotations, one of which has arrow dimensions - // twice the other one. - var annoPath0 = d3.selectAll('path').filter(function() { - return this.style.stroke === arrowColor0; - }).node(); - var annoPath1 = d3.selectAll('path').filter(function() { - return this.style.stroke === arrowColor1; - }).node(); - var anbbox0 = getSVGElemScreenBBox(annoPath0); - var anbbox1 = getSVGElemScreenBBox(annoPath1); - console.log(anbbox0); - console.log(anbbox1); - var arrowXest = ((anbbox1.x+anbbox1.width) - (anbbox0.x+anbbox0.width)); - console.log("Annotation 0 ax " + arrowXest + " correct: ", - pixelCalc.mapDomainToPixel(gd.layout, 'xaxis', arrowX1)-pixelCalc.mapDomainToPixel(gd.layout, 'xaxis', arrowX0) == arrowXest); - // SVG's y is the top of the box - var arrowYest = (anbbox1.y - anbbox0.y); - console.log("Annotation 1 ay " + arrowYest + " correct: ", - pixelCalc.mapRangeToPixel(gd.layout, 'yaxis', arrowY1)-pixelCalc.mapRangeToPixel(gd.layout, 'yaxis', arrowY0) == arrowYest); - }); -} - -dothething(); diff --git a/test/domain_ref_tests.html b/test/domain_ref_tests.html deleted file mode 100644 index 3e8cc6b7223..00000000000 --- a/test/domain_ref_tests.html +++ /dev/null @@ -1,765 +0,0 @@ - - - - - - - - -
- - - - - diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index 0be73dc2eff..9f89d6e350e 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -294,9 +294,7 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, function logAxisIfAxType(layoutIn, layoutOut, axid, axtype, nologrange) { var axname = axisIds.id2name(axid); if ((axtype === 'log') && (axid !== undefined)) { - var axis = { - ...layoutIn[axname] - }; + var axis = Lib.extendDeep({}, layoutIn[axname]); axis.type = 'log'; axis.range = nologrange ? axis.range : axis.range.map(Math.log10); layoutOut[axname] = axis; @@ -840,4 +838,4 @@ module.exports = { }, // utilities findAROByColor: findAROByColor -}; \ No newline at end of file +}; diff --git a/test/jasmine/assets/domain_ref/testnumber.js b/test/jasmine/assets/domain_ref/testnumber.js index 7950d000264..8fa2dc16313 100644 --- a/test/jasmine/assets/domain_ref/testnumber.js +++ b/test/jasmine/assets/domain_ref/testnumber.js @@ -1,3 +1,3 @@ // module.exports can be set to a specific test number to run just a single // test, otherwise if set to undefined, runs all the tests. -module.exports=undefined; +module.exports = undefined; diff --git a/test/jasmine/assets/pixel_calc.js b/test/jasmine/assets/pixel_calc.js index 7a6ec5ca14c..d214c56826b 100644 --- a/test/jasmine/assets/pixel_calc.js +++ b/test/jasmine/assets/pixel_calc.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; // Calculate the pixel values from various objects @@ -19,20 +19,20 @@ function mapToPixelHelper(layout, axis, domain, d) { var dim; var lower; var upper; - if (axis === 'x') { + if(axis === 'x') { dim = 'width'; lower = 'l'; upper = 'r'; - } else if (axis === 'y') { + } else if(axis === 'y') { dim = 'height'; lower = 'b'; upper = 't'; } else { - throw "Bad axis letter: " + axis; + throw 'Bad axis letter: ' + axis; } var plotwidth = layout[dim] - layout.margin[lower] - layout.margin[upper]; var domwidth = (domain[1] - domain[0]) * plotwidth; - if (dim === 'height') { + if(dim === 'height') { // y-axes relative to bottom of plot in plotly.js return layout[dim] - (layout.margin[lower] + domain[0] * plotwidth + domwidth * d); } @@ -41,12 +41,12 @@ function mapToPixelHelper(layout, axis, domain, d) { // axis must be single letter, e.g., x or y function mapPaperToPixel(layout, axis, d) { - return mapToPixelHelper(layout,axis,[0,1],d); + return mapToPixelHelper(layout, axis, [0, 1], d); } // Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc. function mapDomainToPixel(layout, axis, d) { - return mapToPixelHelper(layout,axis[0],layout[axis].domain,d); + return mapToPixelHelper(layout, axis[0], layout[axis].domain, d); } // Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc. @@ -55,7 +55,7 @@ function mapDomainToPixel(layout, axis, d) { // and dimensions are specified in powers of 10, e.g., if the corner's x // coordinate is at data 10, then the x value passed is 1 function mapRangeToPixel(layout, axis, r, nolog) { - if ((!nolog)&&(layout[axis].type === 'log')) { + if((!nolog) && (layout[axis].type === 'log')) { r = Math.log10(r); } var d = (r - layout[axis].range[0]) / (layout[axis].range[1] - layout[axis].range[0]); @@ -63,7 +63,7 @@ function mapRangeToPixel(layout, axis, r, nolog) { } module.exports = { -mapPaperToPixel: mapPaperToPixel, -mapDomainToPixel: mapDomainToPixel, -mapRangeToPixel: mapRangeToPixel + mapPaperToPixel: mapPaperToPixel, + mapDomainToPixel: mapDomainToPixel, + mapRangeToPixel: mapRangeToPixel }; diff --git a/test/jasmine/assets/svg_tools.js b/test/jasmine/assets/svg_tools.js index 075324a26b9..72eca80abe9 100644 --- a/test/jasmine/assets/svg_tools.js +++ b/test/jasmine/assets/svg_tools.js @@ -8,7 +8,7 @@ module.exports = { function findParentSVG(node) { var parentNode = node.parentNode; - if (parentNode.tagName === 'svg') { + if(parentNode.tagName === 'svg') { return parentNode; } else { return findParentSVG(parentNode); @@ -22,4 +22,4 @@ function SVGRectToObj(svgrect) { obj.width = svgrect.width; obj.height = svgrect.height; return obj; -} \ No newline at end of file +} diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 72db4b72744..6fde13eb2ed 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -1,17 +1,16 @@ -'use strict' +'use strict'; var failTest = require('../assets/fail_test'); var domainRefComponents = require('../assets/domain_ref/components'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var Plotly = require('../../../lib/index'); -var delay = require('../assets/delay'); // optionally specify a test number in a file to run just a single test var testNumber = require('../assets/domain_ref/testnumber'); function makeTests(component, filter) { return function() { filter = filter === undefined ? function() { - return true + return true; } : filter; var descriptions = component.descriptions().filter(filter); var tests = component.tests().filter(filter); @@ -26,11 +25,11 @@ function makeTests(component, filter) { }); descriptions.forEach(function(d, i) { it(d, function(done) { - console.log("testing " + d); - gd.id = "graph-" + i; + console.log('testing ' + d); + gd.id = 'graph-' + i; tests[i](function(v) { - expect(v).toBe(true); - }, gd) + expect(v).toBe(true); + }, gd) .catch(failTest) .then(done); }); @@ -40,24 +39,24 @@ function makeTests(component, filter) { describe('Test annotations', makeTests(domainRefComponents.annotations, function(f, i) { - if (testNumber === undefined) { + if(testNumber === undefined) { return true; } - return i == testNumber; + return i === testNumber; })); describe('Test images', makeTests(domainRefComponents.images, function(f, i) { - if (testNumber === undefined) { + if(testNumber === undefined) { return true; } - return i == testNumber; + return i === testNumber; })); describe('Test shapes', makeTests(domainRefComponents.shapes, function(f, i) { - if (testNumber === undefined) { + if(testNumber === undefined) { return true; } - return i == testNumber; - })); \ No newline at end of file + return i === testNumber; + })); diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index 4f754824473..1a0950088c7 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -12,9 +12,6 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var failTest = require('../assets/fail_test'); var drag = require('../assets/drag'); -var mouseEvent = require('../assets/mouse_event'); -var delay = require('../assets/delay'); -var pixelCalc = require('../assets/pixel_calc.js'); var customAssertions = require('../assets/custom_assertions'); var assertElemRightTo = customAssertions.assertElemRightTo; @@ -1653,142 +1650,3 @@ describe('Test shapes', function() { return coordinates; } }); - -// Domain referenced shapes tests -// Check shapes are drawn indeed where they should be -// Possibilities: for xref and yref are 'range', 'paper', and 'domain', and each -// can or cannot be a log axis, so there are 36 possibilities x the number of -// shapes. -// The test should be simple: for paper and domain, a shape is drawn in pixels -// based on the chosen plot or axis size and checked against the one produced by -// Plotly. For 'range', data should be produced and the shape checked against -// this data. -// Check that shapes are still drawn where they should be after the domain size -// changes (just change the domain sizes and run the same test). -// Also for each of the above cases, check that we can move the shape properly. -jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; - -// delay time in milliseconds -DELAYTIME = 2500; - -// acts on an Object representing a shape which could be a line or a rect -function shapeFromShapePos(shape,axletter,axnum,shapepos) { - shape[axletter+'0'] = shapepos.value[0]; - shape[axletter+'1'] = shapepos.value[1]; - if (shapepos.ref === 'range') { - shape[axletter+'ref'] = axletter + axnum; - } else if (shapepos.ref === 'domain') { - shape[axletter+'ref'] = axletter + axnum + ' domain'; - } else if (shapepos.ref === 'paper') { - shape[axletter+'ref'] = 'paper'; - } -} - -// -function checkShapePosition(gd) {} - -fdescribe('Test correct shape positions', function() { - beforeEach(function () { - gd = createGraphDiv(); - mock = Lib.extendDeep({}, require('@mocks/domain_ref_base.json')); - }); - var iterable = require('extra-iterable'); - // some made-up values for testing - var shapePositions = [ - { - // shapes referring to data - ref: 'range', - // two values for rects - value: [2,3] - }, - { - // shapes referring to domains - ref: 'domain', - value: [0.2,0.75], - }, - { - // shapes referring to paper - ref: 'paper', - value: [0.25, 0.8] - } - ]; - var axisTypes = [ 'linear', 'log' ]; - // Test on 'x', 'y', 'x2', 'y2' axes - // TODO the 'paper' position references are tested twice when once would - // suffice. - var axNum = ['','2']; - // only test line and rect for now - var shapeType = ['line','rect']; - // for both x and y axes - var testCombos = iterable.cartesianProduct([ - axNum,axisTypes,shapePositions,axNum,axisTypes,shapePositions,shapeType - ]); - // map all the combinations to a shape definition and check this shape is - // placed properly - testCombos.forEach(function(combo) { - var xAxNum = combo[0]; - var xaxisType = combo[1]; - var xshapePos = combo[2]; - var yAxNum = combo[3]; - var yaxisType = combo[4]; - var yshapePos = combo[5]; - var shapeType = combo[6]; - it('should draw a ' + shapeType - + ' for x' + xAxNum + ' of type ' - + xaxisType - + ' with a value referencing ' - + xshapePos.ref - + ' and for y' + yAxNum + ' of type ' - + yaxisType - + ' with a value referencing ' - + yshapePos.ref, - function (done) { - var gd = createGraphDiv(); - var mock = Lib.extendDeep({}, require('@mocks/domain_ref_base.json')); - Plotly.newPlot(gd, mock) - .then(function() { - var shape = { - type: shapeType, - // this color chosen so it can easily be found with d3 - line: { color: 'rgb(100, 200, 300)' } - }; - shapeFromShapePos(shape,'x',xAxNum,xshapePos); - shapeFromShapePos(shape,'y',yAxNum,yshapePos); - return shape; - }) - .then(function (shape) { - return Plotly.relayout(gd,{shapes: [shape]}); - }).then(function () { - checkShapePosition(gd, - .catch(failTest) - .then(done); - } - }); - ); -}); - -describe('Test shape move', function() { - beforeEach(function() { - gd = createGraphDiv(); - mock = Lib.extendDeep({}, require('@mocks/domain_refs_editable.json')); - }); - - // afterAll(function (done) { setTimeout(done,1000); }); - - it('should work, no?', function(done) { - Plotly.newPlot(gd, mock) - .then(function() {mouseEvent('mousemove', 350, 280);}) - .then(function() {mouseEvent('mousedown', 350, 280);}) - .then(delay(DELAYTIME)) - .then(function() {mouseEvent('mousemove', 350, 200);}) - .then(delay(DELAYTIME)) - .then(function() {mouseEvent('mousemove', 450, 200);}) - .then(function() { - mouseEvent('mouseup', 450, 200); - expect(1).toEqual(1); - }) - .then(delay(DELAYTIME)) - .catch(failTest) - .then(done); - }); -}); diff --git a/test/run_js_script.sh b/test/run_js_script.sh deleted file mode 100644 index 072f7a3d8d6..00000000000 --- a/test/run_js_script.sh +++ /dev/null @@ -1,39 +0,0 @@ -# wraps a JS in a simple webpage and runs it in a browser - -[ -z "$1" ] && (echo "Provide path to script as argument" && exit -1) -[ -z "$BROWSER_BIN" ] && BROWSER_BIN=firefox -# delay time in seconds before launching browser -[ -z "$D" ] && D=10 - -stripped_script="$(basename ${1%.*})" - -html_out="/tmp/$stripped_script.html" - -cat > "$html_out" < - - - - - - - - -HEREDOC - -node_modules/.bin/watchify "$1" -o "/tmp/$stripped_script-min.js" -v -d& -watchify_proc=$! - -_kill_bg_and_exit () { - kill -9 "$watchify_proc" && exit 0 -} - -sleep "$D" - -echo "Opening browser at URL:" -echo "$html_out" -$BROWSER_BIN "$html_out" - -trap _kill_bg_and_exit SIGINT - -while [ 1 ]; do sleep 1; done From f9d4b0376ff7aaacc1f22ba3917d008b3c1dcc8e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 15 Sep 2020 11:23:40 -0400 Subject: [PATCH 074/112] Removed axis pairs and lambda-style function These axis pairs would only be used in esoteric situations and this makes the tests a bit faster. Not sure if lambda-style functions are supported so we'll just use inline functions. --- test/jasmine/assets/domain_ref/components.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index 9f89d6e350e..3f7da2e9f52 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -410,8 +410,8 @@ function coordsEq(a, b) { function compareBBoxes(a, b) { return ['x', 'y', 'width', 'height'].map( - (k, ) => coordsEq(a[k], b[k])).reduce( - (l, r) => l && r, + function (k) { return coordsEq(a[k], b[k]); }).reduce( + function (l, r) { return l && r; }, true); } @@ -537,8 +537,6 @@ var axisTypes = ['linear', 'log']; // suffice. var axisPairs = [ ['x', 'y'], - ['x2', 'y'], - ['x', 'y2'], ['x2', 'y2'] ]; // For annotations: if arrow coordinate is in the same coordinate system 's', if From e6c623595116a70f857d877f283f57dfddcb5b7f Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 15 Sep 2020 11:25:59 -0400 Subject: [PATCH 075/112] Doubled all the hard-coded timeout intervals in the Jasmine tests --- test/jasmine/tests/gl2d_plot_interact_test.js | 6 +++--- test/jasmine/tests/gl3d_hover_click_test.js | 2 +- test/jasmine/tests/gl3d_plot_interact_test.js | 6 +++--- test/jasmine/tests/polar_test.js | 4 ++-- test/jasmine/tests/scatter3d_test.js | 2 +- test/jasmine/tests/scattergl_test.js | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js index 60eca4fdfe4..945345b51d0 100644 --- a/test/jasmine/tests/gl2d_plot_interact_test.js +++ b/test/jasmine/tests/gl2d_plot_interact_test.js @@ -21,7 +21,7 @@ describe('Test removal of gl contexts', function() { var gd; beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; gd = createGraphDiv(); }); @@ -94,7 +94,7 @@ describe('Test gl plot side effects', function() { var gd; beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; gd = createGraphDiv(); }); @@ -389,7 +389,7 @@ describe('Test gl2d plot interactions:', function() { var mock = require('@mocks/gl2d_10.json'); beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; gd = createGraphDiv(); }); diff --git a/test/jasmine/tests/gl3d_hover_click_test.js b/test/jasmine/tests/gl3d_hover_click_test.js index 57f74f1b03f..42a7aa0ecd2 100644 --- a/test/jasmine/tests/gl3d_hover_click_test.js +++ b/test/jasmine/tests/gl3d_hover_click_test.js @@ -35,7 +35,7 @@ describe('Test gl3d trace click/hover:', function() { beforeEach(function() { gd = createGraphDiv(); ptData = {}; - jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 12000; }); afterEach(function() { diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index b77003fb8d1..eee0e2a2395 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -19,7 +19,7 @@ describe('Test gl3d before/after plot', function() { var mock = require('@mocks/gl3d_marker-arrays.json'); beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 4000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 8000; }); afterEach(function() { @@ -144,7 +144,7 @@ describe('Test gl3d plots', function() { beforeEach(function() { gd = createGraphDiv(); - jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 12000; }); afterEach(function() { @@ -918,7 +918,7 @@ describe('Test gl3d drag and wheel interactions', function() { beforeEach(function() { gd = createGraphDiv(); - jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000; }); afterEach(function() { diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index e087f2495e4..4262b87e41d 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -675,7 +675,7 @@ describe('Test polar interactions:', function() { ]; beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; eventData = ''; eventCnts = {}; gd = createGraphDiv(); @@ -1367,7 +1367,7 @@ describe('Test polar *gridshape linear* interactions', function() { var gd; beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; gd = createGraphDiv(); }); diff --git a/test/jasmine/tests/scatter3d_test.js b/test/jasmine/tests/scatter3d_test.js index abe1a04bde4..27cdea117bf 100644 --- a/test/jasmine/tests/scatter3d_test.js +++ b/test/jasmine/tests/scatter3d_test.js @@ -106,7 +106,7 @@ describe('Test scatter3d interactions:', function() { beforeEach(function() { gd = createGraphDiv(); - jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 12000; }); afterEach(function() { diff --git a/test/jasmine/tests/scattergl_test.js b/test/jasmine/tests/scattergl_test.js index 6dc35e2cb36..4e1c60d10d9 100644 --- a/test/jasmine/tests/scattergl_test.js +++ b/test/jasmine/tests/scattergl_test.js @@ -624,7 +624,7 @@ describe('Test scattergl autorange:', function() { var gd; beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; gd = createGraphDiv(); }); @@ -675,7 +675,7 @@ describe('Test scattergl autorange:', function() { var gd; beforeEach(function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; gd = createGraphDiv(); // to avoid expansive draw calls (which could be problematic on CI) spyOn(ScatterGl, 'plot').and.callFake(function(gd) { From 44862fa74b83480e8e3572cfb7675105e1cd4f9d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 15 Sep 2020 12:20:50 -0400 Subject: [PATCH 076/112] Clean up and syntax of test --- test/jasmine/assets/domain_ref/components.js | 323 ++++++++----------- test/jasmine/assets/svg_tools.js | 4 +- 2 files changed, 139 insertions(+), 188 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index 3f7da2e9f52..c79df7b42a2 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -8,12 +8,10 @@ // itself. In this case, it needs to tell jasmine if it passed or failed, so we // pass in an assert function that the promise can call. Then in jasmine, the // promise is followed by .catch(failTest).then(done) -'use strict' +'use strict'; var Plotly = require('../../../../lib/index'); var d3 = require('d3'); -var createGraphDiv = require('../../assets/create_graph_div'); -var destroyGraphDiv = require('../../assets/destroy_graph_div'); var pixelCalc = require('../../assets/pixel_calc'); var getSVGElemScreenBBox = require( '../../assets/get_svg_elem_screen_bbox'); @@ -24,73 +22,32 @@ var Axes = require('../../../../src/plots/cartesian/axes'); var axisIds = require('../../../../src/plots/cartesian/axis_ids'); var testImage = 'https://images.plot.ly/language-icons/api-home/js-logo.png'; var iterable = require('extra-iterable'); -var delay = require('../../assets/delay'); var testMock = require('../../../image/mocks/domain_ref_base.json'); -var defaultLayout = { - "xaxis": { - "domain": [0, 0.75], - "range": [1, 3] - }, - "yaxis": { - "domain": [0, 0.4], - "range": [1, 4] - }, - "xaxis2": { - "domain": [0.75, 1], - "range": [1, 4], - "anchor": "y2" - }, - "yaxis2": { - "domain": [0.4, 1], - "range": [1, 3], - "anchor": "x2" - }, - "margin": { - "l": 100, - "r": 100, - "t": 100, - "b": 100, - "autoexpand": false - }, - "width": 400, - "height": 400 -}; - // NOTE: this tolerance is in pixels var EQUALITY_TOLERANCE = 1e-2; -var DEBUG = true; - -var it = function(s, f) { - console.log('testing ' + s); - f(function() { - console.log(s + ' is done.'); - }); -} - // acts on an Object representing a aro which could be a line or a rect // DEPRECATED function aroFromAROPos(aro, axletter, axnum, aropos) { aro[axletter + '0'] = aropos.value[0]; aro[axletter + '1'] = aropos.value[1]; - if (aropos.ref === 'range') { + if(aropos.ref === 'range') { aro[axletter + 'ref'] = axletter + axnum; - } else if (aropos.ref === 'domain') { + } else if(aropos.ref === 'domain') { aro[axletter + 'ref'] = axletter + axnum + ' domain'; - } else if (aropos.ref === 'paper') { + } else if(aropos.ref === 'paper') { aro[axletter + 'ref'] = 'paper'; } } - // {axid} is the axis id, e.g., x2, y, etc. // {ref} is ['range'|'domain'|'paper'] function makeAxRef(axid, ref) { var axref; - switch (ref) { + switch(ref) { case 'range': axref = axid; break; @@ -106,16 +63,6 @@ function makeAxRef(axid, ref) { return axref; } -// set common parameters of an ARO -// {aro} is the object whose parameters to set -// {coordletter} the letter of the coordinate to assign -// {axref} is the axis the coordinate refers to -// {value} is the value of the first coordinate (e.g., x0 if axletter is x) -function aroSetCommonParams(aro, coordletter, axref, value) { - aro[coordletter + '0'] = value; - aro.axref = axref; -} - // shape, annotation and image all take x0, y0, xref, yref, color parameters // x0, y0 are numerical values, xref, yref are strings that could be passed to // the xref field of an ANO (e.g., 'x2 domain' or 'paper'), color should be @@ -133,7 +80,7 @@ function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { xref: xref, yref: yref }; - switch (arotype) { + switch(arotype) { case 'shape': aro.x0 = x0; aro.y0 = y0; @@ -147,7 +94,7 @@ function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { case 'annotation': aro.x = x0; aro.y = y0; - aro.text = "A"; + aro.text = 'A'; aro.ax = opts.ax; aro.ay = opts.ay; aro.axref = opts.axref; @@ -163,24 +110,19 @@ function aroFromParams(arotype, x0, y0, xref, yref, color, opts) { aro.sizey = opts.sizey; aro.xanchor = opts.xanchor; aro.yanchor = opts.yanchor; - aro.sizing = "stretch"; + aro.sizing = 'stretch'; aro.source = testImage; break; default: - throw "Bad arotype: " + arotype; + throw 'Bad arotype: ' + arotype; } return aro; } -function setAxType(layout, axref, axtype) { - axname = axisIds.id2name(axref); - layout[axname].type = axtype; -} - // Calculate the ax value of an annotation given a particular desired scaling K // This also works with log axes by taking logs of each part of the sum, so that // the length in pixels is multiplied by the scalar -function annaxscale(ac, c0, K) { +function annaxscale(ac, c0) { var ret; ret = c0 + 2 * (ac - c0); return ret; @@ -211,15 +153,13 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, // if xref != axref or axref === 'pixel' then ax is a value relative to // x0 but in pixels. Same for yref var axpixels = false; - if ((axreftype === 'pixel') || (axreftype != xreftype)) { + if((axreftype === 'pixel') || (axreftype !== xreftype)) { axpixels = true; } var aypixels = false; - if ((ayreftype === 'pixel') || (ayreftype != yreftype)) { + if((ayreftype === 'pixel') || (ayreftype !== yreftype)) { aypixels = true; } - var xaxname; - var yaxname; logAxisIfAxType(gd.layout, layout, xid, xaxistype); logAxisIfAxType(gd.layout, layout, yid, yaxistype); var xpixels; @@ -231,8 +171,8 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, ayref: ayref, }; var opts1 = { - ax: axpixels ? 2 * ax : annaxscale(ax, x0, 2), - ay: aypixels ? 2 * ay : annaxscale(ay, y0, 2), + ax: axpixels ? 2 * ax : annaxscale(ax, x0), + ay: aypixels ? 2 * ay : annaxscale(ay, y0), axref: axref, ayref: ayref, }; @@ -247,7 +187,7 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, // the choice of anno1 or anno0 is arbitrary var xabspixels = mapAROCoordToPixel(gd.layout, 'xref', anno1, 'x', 0, true); var yabspixels = mapAROCoordToPixel(gd.layout, 'yref', anno1, 'y', 0, true); - if (axpixels) { + if(axpixels) { // no need to map the specified values to pixels (because that's what // they are already) xpixels = ax; @@ -255,7 +195,7 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, xpixels = mapAROCoordToPixel(gd.layout, 'xref', anno0, 'ax', 0, true) - xabspixels; } - if (aypixels) { + if(aypixels) { // no need to map the specified values to pixels (because that's what // they are already) ypixels = ay; @@ -270,15 +210,15 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, .width)); var arrowLenY; var yabspixelscmp; - if (aypixels) { + if(aypixels) { // for annotations whose arrows are specified in relative pixels, // positive pixel values on the y axis mean moving down the page like // SVG coordinates, so we have to add height - var arrowLenY = (annobbox1.y + annobbox1.height) - + arrowLenY = (annobbox1.y + annobbox1.height) - (annobbox0.y + annobbox0.height); yabspixelscmp = annobbox0.y; } else { - var arrowLenY = annobbox1.y - annobbox0.y; + arrowLenY = annobbox1.y - annobbox0.y; yabspixelscmp = annobbox0.y + annobbox0.height; } var ret = coordsEq(arrowLenX, xpixels) && @@ -293,7 +233,7 @@ function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, // if nologrange is true, log of range is not taken function logAxisIfAxType(layoutIn, layoutOut, axid, axtype, nologrange) { var axname = axisIds.id2name(axid); - if ((axtype === 'log') && (axid !== undefined)) { + if((axtype === 'log') && (axid !== undefined)) { var axis = Lib.extendDeep({}, layoutIn[axname]); axis.type = 'log'; axis.range = nologrange ? axis.range : axis.range.map(Math.log10); @@ -314,18 +254,18 @@ function logAxisIfAxType(layoutIn, layoutOut, axid, axtype, nologrange) { // later this was the case for images and annotations :'). function mapAROCoordToPixel(layout, axref, aro, c, offset, nolog) { var reftype = Axes.getRefType(aro[axref]); - var axletter = axref[0]; var ret; offset = (offset === undefined) ? 0 : offset; var val = aro[c] + offset; - if (reftype === 'range') { - var axis = axisIds.id2name(aro[axref]); + var axis; + if(reftype === 'range') { + axis = axisIds.id2name(aro[axref]); ret = pixelCalc.mapRangeToPixel(layout, axis, val, nolog); - } else if (reftype === 'domain') { - var axis = axisIds.id2name(aro[axref]); + } else if(reftype === 'domain') { + axis = axisIds.id2name(aro[axref]); ret = pixelCalc.mapDomainToPixel(layout, axis, val); - } else if (reftype === 'paper') { - var axis = axref[0]; + } else if(reftype === 'paper') { + axis = axref[0]; ret = pixelCalc.mapPaperToPixel(layout, axis, val); } return ret; @@ -358,7 +298,7 @@ function imageToBBox(layout, img) { var x1; var y0; var y1; - switch (img.xanchor) { + switch(img.xanchor) { case 'left': x0 = mapAROCoordToPixel(layout, 'xref', img, 'x', undefined, true); x1 = mapAROCoordToPixel(layout, 'xref', img, 'x', img.sizex, true); @@ -374,7 +314,7 @@ function imageToBBox(layout, img) { default: throw 'Bad xanchor: ' + img.xanchor; } - switch (img.yanchor) { + switch(img.yanchor) { case 'bottom': y0 = mapAROCoordToPixel(layout, 'yref', img, 'y', undefined, true); y1 = mapAROCoordToPixel(layout, 'yref', img, 'y', img.sizey, true); @@ -402,7 +342,7 @@ function imageToBBox(layout, img) { function coordsEq(a, b) { - if (a && b) { + if(a && b) { return Math.abs(a - b) < EQUALITY_TOLERANCE; } return false; @@ -410,8 +350,8 @@ function coordsEq(a, b) { function compareBBoxes(a, b) { return ['x', 'y', 'width', 'height'].map( - function (k) { return coordsEq(a[k], b[k]); }).reduce( - function (l, r) { return l && r; }, + function(k) { return coordsEq(a[k], b[k]); }).reduce( + function(l, r) { return l && r; }, true); } @@ -443,10 +383,8 @@ function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, yanchor: yanchor, xref: xref, yref: yref, - sizing: "stretch" + sizing: 'stretch' }; - var xreftype = Axes.getRefType(xref); - var yreftype = Axes.getRefType(yref); var ret; // we pass xid, yid because we possibly want to change some axes to log, // even if we refer to paper in the end @@ -455,7 +393,7 @@ function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, layout.images = [image]; return Plotly.relayout(gd, layout) .then(function(gd) { - var imageElem = findImage("#" + gd.id); + var imageElem = findImage('#' + gd.id); var svgImageBBox = getSVGElemScreenBBox(imageElem); var imageBBox = imageToBBox(gd.layout, image); ret = compareBBoxes(svgImageBBox, imageBBox); @@ -466,12 +404,12 @@ function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, // gets the SVG bounding box of the aro and checks it against what mapToPixel // gives function checkAROPosition(gd, aro) { - var aroPath = findAROByColor(aro.line.color, "#" + gd.id); + var aroPath = findAROByColor(aro.line.color, '#' + gd.id); var aroPathBBox = getSVGElemScreenBBox(aroPath); var aroBBox = shapeToBBox(gd.layout, aro); var ret = compareBBoxes(aroBBox, aroPathBBox); - console.log("aroBBox: " + JSON.stringify(aroBBox)) - console.log("aroPathBBox: " + JSON.stringify(SVGTools.SVGRectToObj(aroPathBBox))) + console.log('aroBBox: ' + JSON.stringify(aroBBox)); + console.log('aroPathBBox: ' + JSON.stringify(SVGTools.svgRectToObj(aroPathBBox))); return ret; } @@ -483,54 +421,53 @@ function checkAROPosition(gd, aro) { // annotations, the second having arrow components twice as long as the first. var aroPositionsX = [{ // aros referring to data - ref: 'range', - value: [2, 3], + ref: 'range', + value: [2, 3], // for objects that need a size (i.e., images) - size: 1.5, + size: 1.5, // for the case when annotations specifies arrow in pixels, this value // is read instead of value[1] - pixel: 25 - }, - { + pixel: 25 +}, +{ // aros referring to domains - ref: 'domain', - value: [0.2, 0.75], - size: 0.3, - pixel: 30 - }, - { + ref: 'domain', + value: [0.2, 0.75], + size: 0.3, + pixel: 30 +}, +{ // aros referring to paper - ref: 'paper', - value: [0.25, 0.8], - size: 0.35, - pixel: 35 - }, + ref: 'paper', + value: [0.25, 0.8], + size: 0.35, + pixel: 35 +}, ]; var aroPositionsY = [{ // aros referring to data - ref: 'range', + ref: 'range', // two values for rects - value: [1, 2], - pixel: 30, - size: 1.2 - }, - { + value: [1, 2], + pixel: 30, + size: 1.2 +}, +{ // aros referring to domains - ref: 'domain', - value: [0.25, 0.7], - pixel: 40, - size: .2 - }, - { + ref: 'domain', + value: [0.25, 0.7], + pixel: 40, + size: 0.2 +}, +{ // aros referring to paper - ref: 'paper', - value: [0.2, 0.85], - pixel: 45, - size: .3 - } + ref: 'paper', + value: [0.2, 0.85], + pixel: 45, + size: 0.3 +} ]; -var aroTypes = ['shape', 'annotation', 'image']; var axisTypes = ['linear', 'log']; // Test on 'x', 'y', 'x2', 'y2' axes // TODO the 'paper' position references are tested twice when once would @@ -548,7 +485,7 @@ var arrowAxis = [ ['p', 'p'] ]; // only test the shapes line and rect for now -var shapeType = ['line', 'rect']; +var shapeTypes = ['line', 'rect']; // anchor positions for images var xAnchors = ['left', 'center', 'right']; var yAnchors = ['top', 'middle', 'bottom']; @@ -595,17 +532,19 @@ function describeShapeComboTest(combo) { var axispair = combo[2]; var xaroPos = combo[3]; var yaroPos = combo[4]; - var gd_id = combo[5]; + var shapeType = combo[5]; + var gdId = combo[6]; var xid = axispair[0]; var yid = axispair[1]; return [ - "#", gd_id, - "should create a plot with parameters:", "\n", - "x-axis type:", xaxisType, "\n", - "y-axis type:", yaxisType, "\n", - "axis pair:", xid, yid, "\n", - "ARO position x:", JSON.stringify(xaroPos), "\n", - "ARO position y:", JSON.stringify(yaroPos), "\n", + '#', gdId, + 'should create a plot with parameters:', '\n', + 'x-axis type:', xaxisType, '\n', + 'y-axis type:', yaxisType, '\n', + 'axis pair:', xid, yid, '\n', + 'ARO position x:', JSON.stringify(xaroPos), '\n', + 'ARO position y:', JSON.stringify(yaroPos), '\n', + 'shape type:', shapeType, '\n', ].join(' '); } @@ -615,13 +554,15 @@ function testShapeCombo(combo, assert, gd) { var axispair = combo[2]; var xaroPos = combo[3]; var yaroPos = combo[4]; + var shapeType = combo[5]; var xAxNum = axispair[0].substr(1); var yAxNum = axispair[1].substr(1); return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { - return testShape(gd, xAxNum, xaxisType, xaroPos, yAxNum, yaxisType, yaroPos, 'shape'); - }).then(function(test_ret) { - assert(test_ret); + return testShape(gd, xAxNum, xaxisType, xaroPos, yAxNum, yaxisType, yaroPos, + shapeType); + }).then(function(testRet) { + assert(testRet); }); } @@ -633,24 +574,24 @@ function describeImageComboTest(combo) { var aroposy = combo[4]; var xanchor = combo[5]; var yanchor = combo[6]; - var gd_id = combo[7]; + var gdId = combo[7]; var xid = axispair[0]; var yid = axispair[1]; var xref = makeAxRef(xid, aroposx.ref); var yref = makeAxRef(yid, aroposy.ref); // TODO Add image combo test description return [ - "#", gd_id, - "should create a plot with parameters:", "\n", - "x-axis type:", axistypex, "\n", - "y-axis type:", axistypey, "\n", - "axis pair:", xid, yid, "\n", - "ARO position x:", JSON.stringify(aroposx), "\n", - "ARO position y:", JSON.stringify(aroposy), "\n", - "xanchor:", xanchor, "\n", - "yanchor:", yanchor, "\n", - "xref:", xref, "\n", - "yref:", yref, "\n", + '#', gdId, + 'should create a plot with parameters:', '\n', + 'x-axis type:', axistypex, '\n', + 'y-axis type:', axistypey, '\n', + 'axis pair:', xid, yid, '\n', + 'ARO position x:', JSON.stringify(aroposx), '\n', + 'ARO position y:', JSON.stringify(aroposy), '\n', + 'xanchor:', xanchor, '\n', + 'yanchor:', yanchor, '\n', + 'xref:', xref, '\n', + 'yref:', yref, '\n', ].join(' '); } @@ -662,7 +603,6 @@ function testImageCombo(combo, assert, gd) { var aroposy = combo[4]; var xanchor = combo[5]; var yanchor = combo[6]; - var gd_id = combo[7]; var xid = axispair[0]; var yid = axispair[1]; var xref = makeAxRef(xid, aroposx.ref); @@ -673,8 +613,8 @@ function testImageCombo(combo, assert, gd) { aroposx.value[0], aroposy.value[0], aroposx.size, aroposy.size, xanchor, yanchor, xref, yref, xid, yid); - }).then(function(test_ret) { - assert(test_ret); + }).then(function(testRet) { + assert(testRet); }); } @@ -685,22 +625,22 @@ function describeAnnotationComboTest(combo) { var aroposx = combo[3]; var aroposy = combo[4]; var arrowaxispair = combo[5]; - var gd_id = combo[6]; + var gdId = combo[6]; var xid = axispair[0]; var yid = axispair[1]; var xref = makeAxRef(xid, aroposx.ref); var yref = makeAxRef(yid, aroposy.ref); return [ - "#", gd_id, - "should create a plot with parameters:", "\n", - "x-axis type:", axistypex, "\n", - "y-axis type:", axistypey, "\n", - "axis pair:", xid, yid, "\n", - "ARO position x:", JSON.stringify(aroposx), "\n", - "ARO position y:", JSON.stringify(aroposy), "\n", - "arrow axis pair:", arrowaxispair, "\n", - "xref:", xref, "\n", - "yref:", yref, "\n", + '#', gdId, + 'should create a plot with parameters:', '\n', + 'x-axis type:', axistypex, '\n', + 'y-axis type:', axistypey, '\n', + 'axis pair:', xid, yid, '\n', + 'ARO position x:', JSON.stringify(aroposx), '\n', + 'ARO position y:', JSON.stringify(aroposy), '\n', + 'arrow axis pair:', arrowaxispair, '\n', + 'xref:', xref, '\n', + 'yref:', yref, '\n', ].join(' '); } @@ -711,7 +651,6 @@ function testAnnotationCombo(combo, assert, gd) { var aroposx = combo[3]; var aroposy = combo[4]; var arrowaxispair = combo[5]; - var gd_id = combo[6]; var xid = axispair[0]; var yid = axispair[1]; var xref = makeAxRef(xid, aroposx.ref); @@ -726,8 +665,8 @@ function testAnnotationCombo(combo, assert, gd) { .then(function(gd) { return annotationTest(gd, {}, x0, y0, ax, ay, xref, yref, axref, ayref, axistypex, axistypey, xid, yid); - }).then(function(test_ret) { - assert(test_ret); + }).then(function(testRet) { + assert(testRet); }); } @@ -743,7 +682,7 @@ function comboTests(testCombos, test) { var ret = testCombos.map(function(combo) { return function(assert, gd) { return test(combo, assert, gd); - } + }; }); return ret; } @@ -757,10 +696,14 @@ function comboTestDescriptions(testCombos, desribe) { } function annotationTestCombos() { - var testCombos = [...iterable.cartesianProduct([ + var testCombos = Array.from(iterable.cartesianProduct([ axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis - ])]; - testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); + ])); + testCombos = testCombos.map( + function(c, i) { + return c.concat(['graph-' + i]); + } + ); return testCombos; } @@ -776,15 +719,19 @@ function annotationTestDescriptions() { function imageTestCombos() { - var testCombos = [...iterable.cartesianProduct( + var testCombos = Array.from(iterable.cartesianProduct( [ axisTypes, axisTypes, axisPairs, // axis reference types are contained in here aroPositionsX, aroPositionsY, xAnchors, yAnchors ] - )]; - testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); + )); + testCombos = testCombos.map( + function(c, i) { + return c.concat(['graph-' + i]); + } + ); return testCombos; } @@ -799,14 +746,18 @@ function imageTestDescriptions() { } function shapeTestCombos() { - var testCombos = [...iterable.cartesianProduct( + var testCombos = Array.from(iterable.cartesianProduct( [ axisTypes, axisTypes, axisPairs, // axis reference types are contained in here - aroPositionsX, aroPositionsY, + aroPositionsX, aroPositionsY, shapeTypes ] - )]; - testCombos = testCombos.map((c, i) => c.concat(['graph-' + i])); + )); + testCombos = testCombos.map( + function(c, i) { + return c.concat(['graph-' + i]); + } + ); return testCombos; } diff --git a/test/jasmine/assets/svg_tools.js b/test/jasmine/assets/svg_tools.js index 72eca80abe9..72ba4e44f86 100644 --- a/test/jasmine/assets/svg_tools.js +++ b/test/jasmine/assets/svg_tools.js @@ -2,7 +2,7 @@ module.exports = { findParentSVG: findParentSVG, - SVGRectToObj: SVGRectToObj + svgRectToObj: svgRectToObj }; function findParentSVG(node) { @@ -15,7 +15,7 @@ function findParentSVG(node) { } } -function SVGRectToObj(svgrect) { +function svgRectToObj(svgrect) { var obj = {}; obj.x = svgrect.x; obj.y = svgrect.y; From 00edf56dea9e191050eeefbb56552d16a2148da1 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 15 Sep 2020 16:26:22 -0400 Subject: [PATCH 077/112] Removing unused custom graph id fixes axes tests Let's see about the other tests --- test/jasmine/assets/create_graph_div.js | 5 ++--- test/jasmine/assets/destroy_graph_div.js | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/jasmine/assets/create_graph_div.js b/test/jasmine/assets/create_graph_div.js index 3db6073f61a..9791d46018c 100644 --- a/test/jasmine/assets/create_graph_div.js +++ b/test/jasmine/assets/create_graph_div.js @@ -1,9 +1,8 @@ 'use strict'; -module.exports = function createGraphDiv(id) { - id = (id === undefined) ? 'graph' : id; +module.exports = function createGraphDiv() { var gd = document.createElement('div'); - gd.id = id; + gd.id = 'graph'; document.body.appendChild(gd); // force the graph to be at position 0,0 no matter what diff --git a/test/jasmine/assets/destroy_graph_div.js b/test/jasmine/assets/destroy_graph_div.js index eb891513020..a1bd18b9741 100644 --- a/test/jasmine/assets/destroy_graph_div.js +++ b/test/jasmine/assets/destroy_graph_div.js @@ -1,8 +1,7 @@ 'use strict'; -module.exports = function destroyGraphDiv(id) { - id = (id === undefined) ? 'graph' : id; - var gd = document.getElementById(id); +module.exports = function destroyGraphDiv() { + var gd = document.getElementById('graph'); if(gd) document.body.removeChild(gd); }; From 93284dfe7399e47ccb6ff581c91f64912caf75cb Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 16 Sep 2020 16:40:42 -0400 Subject: [PATCH 078/112] Fixed click test --- src/components/annotations/defaults.js | 4 ++-- src/plots/cartesian/axes.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 75fa9bbbc2f..0bc933b79b1 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -60,8 +60,8 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { if(showArrow) { var arrowPosAttr = 'a' + axLetter; // axref, ayref - var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, '', - ['paper', 'pixel'], true); + var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel', + ['pixel', 'paper'], true); // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index b6eb861afe4..576148f2734 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -93,7 +93,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption var refAttr = attr + 'ref'; var attrDef = {}; - if(!dflt) dflt = axlist[0] || extraOption; + if(!dflt) dflt = axlist[0] || (typeof extraOption === 'string' ? extraOption : extraOption[0]); if(!extraOption) extraOption = dflt; if(domainRef) axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); From 6e26db3193b70ebc261c2546afad6ef2ec20aa3d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 16 Sep 2020 20:02:16 -0400 Subject: [PATCH 079/112] Starting to add interact tests for domain referenced objects --- test/image/mocks/domain_ref_base.json | 4 +- test/image/mocks/domain_refs_editable.json | 179 ++++++++++++++---- test/jasmine/assets/domain_ref/components.js | 163 ++++++++-------- .../jasmine/tests/domain_ref_interact_test.js | 56 ++++++ 4 files changed, 285 insertions(+), 117 deletions(-) create mode 100644 test/jasmine/tests/domain_ref_interact_test.js diff --git a/test/image/mocks/domain_ref_base.json b/test/image/mocks/domain_ref_base.json index f8f205899b9..8e759334d43 100644 --- a/test/image/mocks/domain_ref_base.json +++ b/test/image/mocks/domain_ref_base.json @@ -18,7 +18,7 @@ "layout": { "xaxis": { "domain": [0,0.75], - "range": [1, 3] + "range": [1, 4] }, "yaxis": { "domain": [0,0.4], @@ -26,7 +26,7 @@ }, "xaxis2": { "domain": [0.75,1], - "range": [1, 4], + "range": [1, 5], "anchor": "y2" }, "yaxis2": { diff --git a/test/image/mocks/domain_refs_editable.json b/test/image/mocks/domain_refs_editable.json index ad56f4b0394..950de603da8 100644 --- a/test/image/mocks/domain_refs_editable.json +++ b/test/image/mocks/domain_refs_editable.json @@ -1,39 +1,150 @@ { - "data": [{ - "x": [1, 5], - "y": [1, 10], - "type": "scatter" - }, { - "x": [1, 5], - "y": [0.1, 1.0], - "type": "scatter", - "yaxis": "y2" - }], - "layout": { - "title": "Axis domain referenced shape", - "width": 0.3, - "height": 0.2, - "xaxis": { - "domain": [0.2, 0.9] - }, - "yaxis": { - "domain": [0.1, 0.8] - }, - "yaxis2": { - "overlaying": "y", - "side": "right" - }, - "shapes": [{ - "type": "rect", - "xref": "x domain", - "yref": "y domain", - "layer": "above", - "x0": 0.1, - "y0": 0.2, - "x1": 0.8, - "y1": 0.5 - }] + "data": [ + { + "type": "scatter", + "x": [], + "xaxis": "x", + "y": [], + "yaxis": "y" + }, + { + "type": "scatter", + "x": [], + "xaxis": "x2", + "y": [], + "yaxis": "y2" + }, + { + "type": "scatter", + "x": [], + "xaxis": "x3", + "y": [], + "yaxis": "y3" + }, + { + "type": "scatter", + "x": [], + "xaxis": "x4", + "y": [], + "yaxis": "y4" + } + ], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [ + 0.0, + 0.45 + ] + }, + "xaxis2": { + "anchor": "y2", + "domain": [ + 0.55, + 1.0 + ] + }, + "xaxis3": { + "anchor": "y3", + "domain": [ + 0.0, + 0.45 + ], + "range": [ + 1, + 10 + ] }, + "xaxis4": { + "anchor": "y4", + "domain": [ + 0.55, + 1.0 + ], + "range": [ + 0, + 1 + ] + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0.575, + 1.0 + ] + }, + "yaxis2": { + "anchor": "x2", + "domain": [ + 0.575, + 1.0 + ] + }, + "yaxis3": { + "anchor": "x3", + "domain": [ + 0.0, + 0.425 + ], + "range": [ + 0, + 1 + ] + }, + "yaxis4": { + "anchor": "x4", + "domain": [ + 0.0, + 0.425 + ], + "range": [ + 1, + 10 + ] + }, + "shapes": [{ + "type": "rect", + "xref": "x3 domain", + "yref": "y3 domain", + "x0": 0.1, + "y0": 0.2, + "x1": 0.3, + "y1": 0.4, + "line": { "color": "rgb(10, 20, 30)" } + },{ + "type": "rect", + "xref": "x4 domain", + "yref": "y4 domain", + "x0": 0.4, + "y0": 0.6, + "x1": 0.5, + "y1": 0.7, + "line": { "color": "rgb(10, 20, 31)" } + }], + "annotations": [{ + "text": "A", + "bgcolor": "rgb(100, 200, 232)", + "xref": "x3 domain", + "yref": "y3 domain", + "x": 0.4, + "y": 0.6, + "axref": "x3 domain", + "ayref": "y3 domain", + "ax": 0.5, + "ay": 0.7 + },{ + "text": "B", + "bgcolor": "rgb(200, 200, 232)", + "xref": "x4 domain", + "yref": "y4 domain", + "x": 0.3, + "y": 0.4, + "axref": "x4 domain", + "ayref": "y4 domain", + "ax": 0.1, + "ay": 0.2 + }] + }, "config": { "editable": true } diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index c79df7b42a2..f5f2bd91e50 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -28,6 +28,88 @@ var testMock = require('../../../image/mocks/domain_ref_base.json'); // NOTE: this tolerance is in pixels var EQUALITY_TOLERANCE = 1e-2; +// some made-up values for testing +// NOTE: The pixel values are intentionally set so that 2*pixel is never greater +// than the mock's margin. This is so that annotations are not unintentionally +// clipped out because they exceed the plotting area. The reason for using twice +// the pixel value is because the annotation test requires plotting 2 +// annotations, the second having arrow components twice as long as the first. +var aroPositionsX = [{ + // aros referring to data + ref: 'range', + value: [2, 3], + // for objects that need a size (i.e., images) + size: 1.5, + // for the case when annotations specifies arrow in pixels, this value + // is read instead of value[1] + pixel: 25 +}, +{ + // aros referring to domains + ref: 'domain', + value: [0.2, 0.75], + size: 0.3, + pixel: 30 +}, +{ + // aros referring to paper + ref: 'paper', + value: [0.25, 0.8], + size: 0.35, + pixel: 35 +}, +]; +var aroPositionsY = [{ + // aros referring to data + ref: 'range', + // two values for rects + value: [1, 2], + pixel: 30, + size: 1.2 +}, +{ + // aros referring to domains + ref: 'domain', + value: [0.25, 0.7], + pixel: 40, + size: 0.2 +}, +{ + // aros referring to paper + ref: 'paper', + value: [0.2, 0.85], + pixel: 45, + size: 0.3 +} +]; + +var axisTypes = ['linear', 'log']; +// Test on 'x', 'y', 'x2', 'y2' axes +// TODO the 'paper' position references are tested twice when once would +// suffice. +var axisPairs = [ + ['x', 'y'], + ['x2', 'y2'] +]; +// For annotations: if arrow coordinate is in the same coordinate system 's', if +// pixel then 'p' +var arrowAxis = [ + ['s', 's'], + ['p', 's'], + ['s', 'p'], + ['p', 'p'] +]; +// only test the shapes line and rect for now +var shapeTypes = ['line', 'rect']; +// anchor positions for images +var xAnchors = ['left', 'center', 'right']; +var yAnchors = ['top', 'middle', 'bottom']; +// this color chosen so it can easily be found with d3 +// NOTE: for images color cannot be set but it will be the only image in the +// plot so you can use d3.select('g image').node() +var aroColor = 'rgb(50, 100, 150)'; + + // acts on an Object representing a aro which could be a line or a rect // DEPRECATED function aroFromAROPos(aro, axletter, axnum, aropos) { @@ -413,87 +495,6 @@ function checkAROPosition(gd, aro) { return ret; } -// some made-up values for testing -// NOTE: The pixel values are intentionally set so that 2*pixel is never greater -// than the mock's margin. This is so that annotations are not unintentionally -// clipped out because they exceed the plotting area. The reason for using twice -// the pixel value is because the annotation test requires plotting 2 -// annotations, the second having arrow components twice as long as the first. -var aroPositionsX = [{ - // aros referring to data - ref: 'range', - value: [2, 3], - // for objects that need a size (i.e., images) - size: 1.5, - // for the case when annotations specifies arrow in pixels, this value - // is read instead of value[1] - pixel: 25 -}, -{ - // aros referring to domains - ref: 'domain', - value: [0.2, 0.75], - size: 0.3, - pixel: 30 -}, -{ - // aros referring to paper - ref: 'paper', - value: [0.25, 0.8], - size: 0.35, - pixel: 35 -}, -]; -var aroPositionsY = [{ - // aros referring to data - ref: 'range', - // two values for rects - value: [1, 2], - pixel: 30, - size: 1.2 -}, -{ - // aros referring to domains - ref: 'domain', - value: [0.25, 0.7], - pixel: 40, - size: 0.2 -}, -{ - // aros referring to paper - ref: 'paper', - value: [0.2, 0.85], - pixel: 45, - size: 0.3 -} -]; - -var axisTypes = ['linear', 'log']; -// Test on 'x', 'y', 'x2', 'y2' axes -// TODO the 'paper' position references are tested twice when once would -// suffice. -var axisPairs = [ - ['x', 'y'], - ['x2', 'y2'] -]; -// For annotations: if arrow coordinate is in the same coordinate system 's', if -// pixel then 'p' -var arrowAxis = [ - ['s', 's'], - ['p', 's'], - ['s', 'p'], - ['p', 'p'] -]; -// only test the shapes line and rect for now -var shapeTypes = ['line', 'rect']; -// anchor positions for images -var xAnchors = ['left', 'center', 'right']; -var yAnchors = ['top', 'middle', 'bottom']; -// this color chosen so it can easily be found with d3 -// NOTE: for images color cannot be set but it will be the only image in the -// plot so you can use d3.select('g image').node() -var aroColor = 'rgb(50, 100, 150)'; - function testShape( gd, xAxNum, diff --git a/test/jasmine/tests/domain_ref_interact_test.js b/test/jasmine/tests/domain_ref_interact_test.js new file mode 100644 index 00000000000..2c9d7c5308c --- /dev/null +++ b/test/jasmine/tests/domain_ref_interact_test.js @@ -0,0 +1,56 @@ +'use strict'; +var failTest = require('../assets/fail_test'); +var domainRefComponents = require('../assets/domain_ref/components'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var Plotly = require('../../../lib/index'); +var Lib = require('../../../src/lib'); +var getSVGElemScreenBBox = require( + '../assets/get_svg_elem_screen_bbox'); +var testMock = require('../../image/mocks/domain_refs_editable.json'); +var delay = require('../assets/delay'); +var mouseEvent = require('../assets/mouse_event'); + +// color of first rectangle +var rectColor1 = "rgb(10, 20, 30)"; + +var DELAY_TIME = 0; + +describe("Shapes referencing domain", function () { + var gd; + beforeEach(function() { + gd = createGraphDiv(); + }); + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(gd); + gd = null; + }); + it("should move to the proper position", function(done) { + Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) + .then(delay(DELAY_TIME)) + .then(function () { + var rectPos1before = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(rectColor1) + ); + var pos = { + mouseStartX: rectPos1before.x + rectPos1before.width * 0.5, + mouseStartY: rectPos1before.y + rectPos1before.height * 0.5, + }; + pos.mouseEndX = pos.mouseStartX + 100; + pos.mouseEndY = pos.mouseStartY + -300; + mouseEvent('mousemove', pos.mouseStartX, pos.mouseStartY); + mouseEvent('mousedown', pos.mouseStartX, pos.mouseStartY); + mouseEvent('mousemove', pos.mouseEndX, pos.mouseEndY); + mouseEvent('mouseup', pos.mouseEndX, pos.mouseEndY); + var rectPos1after = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(rectColor1) + ); + expect(rectPos1after.x).toBeCloseTo(rectPos1before.x + 100, 2); + expect(rectPos1after.y).toBeCloseTo(rectPos1before.y - 300, 2); + }) + .then(delay(DELAY_TIME)) + .catch(failTest) + .then(done); + }); +}); From 156b73bed697ffd91ab3b54b17ff8c208b1d0c8b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 17 Sep 2020 15:49:25 -0400 Subject: [PATCH 080/112] Added test for interaction with domain reference objects --- test/image/mocks/domain_refs_editable.json | 10 +- test/jasmine/assets/domain_ref/components.js | 5 +- .../jasmine/tests/domain_ref_interact_test.js | 107 +++++++++++++----- 3 files changed, 86 insertions(+), 36 deletions(-) diff --git a/test/image/mocks/domain_refs_editable.json b/test/image/mocks/domain_refs_editable.json index 950de603da8..769c6b21974 100644 --- a/test/image/mocks/domain_refs_editable.json +++ b/test/image/mocks/domain_refs_editable.json @@ -56,6 +56,7 @@ ] }, "xaxis4": { + "type": "log", "anchor": "y4", "domain": [ 0.55, @@ -81,6 +82,7 @@ ] }, "yaxis3": { + "type": "log", "anchor": "x3", "domain": [ 0.0, @@ -116,14 +118,14 @@ "xref": "x4 domain", "yref": "y4 domain", "x0": 0.4, - "y0": 0.6, - "x1": 0.5, + "y0": 0.5, + "x1": 0.6, "y1": 0.7, "line": { "color": "rgb(10, 20, 31)" } }], "annotations": [{ "text": "A", - "bgcolor": "rgb(100, 200, 232)", + "bordercolor": "rgb(100, 200, 232)", "xref": "x3 domain", "yref": "y3 domain", "x": 0.4, @@ -134,7 +136,7 @@ "ay": 0.7 },{ "text": "B", - "bgcolor": "rgb(200, 200, 232)", + "bordercolor": "rgb(200, 200, 232)", "xref": "x4 domain", "yref": "y4 domain", "x": 0.3, diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index f5f2bd91e50..5875c50d28e 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -437,9 +437,10 @@ function compareBBoxes(a, b) { true); } -function findAROByColor(color, id) { +function findAROByColor(color, id, type) { id = (id === undefined) ? '' : id + ' '; - var selector = id + 'path'; + type = (type === undefined) ? 'path' : type; + var selector = id + type; var ret = d3.selectAll(selector).filter(function() { return this.style.stroke === color; }).node(); diff --git a/test/jasmine/tests/domain_ref_interact_test.js b/test/jasmine/tests/domain_ref_interact_test.js index 2c9d7c5308c..730d437ebd5 100644 --- a/test/jasmine/tests/domain_ref_interact_test.js +++ b/test/jasmine/tests/domain_ref_interact_test.js @@ -10,13 +10,57 @@ var getSVGElemScreenBBox = require( var testMock = require('../../image/mocks/domain_refs_editable.json'); var delay = require('../assets/delay'); var mouseEvent = require('../assets/mouse_event'); +var drag = require('../assets/drag'); -// color of first rectangle -var rectColor1 = "rgb(10, 20, 30)"; +// color of the rectangles +var rectColor1 = 'rgb(10, 20, 30)'; +var rectColor2 = 'rgb(10, 20, 31)'; +var rectColor3 = 'rgb(100, 200, 232)'; +var rectColor4 = 'rgb(200, 200, 232)'; var DELAY_TIME = 0; -describe("Shapes referencing domain", function () { +function testObjectMove(objectColor, moveX, moveY, type) { + var bboxBefore = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(objectColor, undefined, type) + ); + var pos = { + mouseStartX: bboxBefore.x + bboxBefore.width * 0.5, + mouseStartY: bboxBefore.y + bboxBefore.height * 0.5, + }; + pos.mouseEndX = pos.mouseStartX + moveX; + pos.mouseEndY = pos.mouseStartY + moveY; + mouseEvent('mousemove', pos.mouseStartX, pos.mouseStartY); + mouseEvent('mousedown', pos.mouseStartX, pos.mouseStartY); + mouseEvent('mousemove', pos.mouseEndX, pos.mouseEndY); + mouseEvent('mouseup', pos.mouseEndX, pos.mouseEndY); + var bboxAfter = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(objectColor, undefined, type) + ); + expect(bboxAfter.x).toBeCloseTo(bboxBefore.x + moveX, 2); + expect(bboxAfter.y).toBeCloseTo(bboxBefore.y + moveY, 2); +} + +function testAnnotationMove(objectColor, moveX, moveY, type) { + var bboxBefore = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(objectColor, undefined, type) + ); + var opt = { + pos0: [ bboxBefore.x + bboxBefore.width * 0.5, + bboxBefore.y + bboxBefore.height * 0.5 ], + }; + opt.dpos = [moveX, moveY]; + return (new Promise(function() { drag(opt); })) + .then(function() { + var bboxAfter = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(objectColor, undefined, type) + ); + expect(bboxAfter.x).toBeCloseTo(bboxBefore.x + moveX, 2); + expect(bboxAfter.y).toBeCloseTo(bboxBefore.y + moveY, 2); + }); +} + +describe('Shapes referencing domain', function() { var gd; beforeEach(function() { gd = createGraphDiv(); @@ -26,31 +70,34 @@ describe("Shapes referencing domain", function () { destroyGraphDiv(gd); gd = null; }); - it("should move to the proper position", function(done) { - Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) - .then(delay(DELAY_TIME)) - .then(function () { - var rectPos1before = getSVGElemScreenBBox( - domainRefComponents.findAROByColor(rectColor1) - ); - var pos = { - mouseStartX: rectPos1before.x + rectPos1before.width * 0.5, - mouseStartY: rectPos1before.y + rectPos1before.height * 0.5, - }; - pos.mouseEndX = pos.mouseStartX + 100; - pos.mouseEndY = pos.mouseStartY + -300; - mouseEvent('mousemove', pos.mouseStartX, pos.mouseStartY); - mouseEvent('mousedown', pos.mouseStartX, pos.mouseStartY); - mouseEvent('mousemove', pos.mouseEndX, pos.mouseEndY); - mouseEvent('mouseup', pos.mouseEndX, pos.mouseEndY); - var rectPos1after = getSVGElemScreenBBox( - domainRefComponents.findAROByColor(rectColor1) - ); - expect(rectPos1after.x).toBeCloseTo(rectPos1before.x + 100, 2); - expect(rectPos1after.y).toBeCloseTo(rectPos1before.y - 300, 2); - }) - .then(delay(DELAY_TIME)) - .catch(failTest) - .then(done); - }); + function testObjectMoveItFun(color, x, y, type) { + return function(done) { + Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) + .then(delay(DELAY_TIME)) + .then(function() { + testObjectMove(color, x, y, type); + }) + .then(delay(DELAY_TIME)) + .catch(failTest) + .then(done); + }; + } + function testAnnotationMoveItFun(color, x, y, type) { + return function(done) { + Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) + .then(delay(DELAY_TIME)) + .then(testAnnotationMove(color, x, y, type)) + .then(delay(DELAY_TIME)) + .catch(failTest) + .then(done); + }; + } + it('should move box on linear x axis and log y to the proper position', + testObjectMoveItFun(rectColor1, 100, -300, 'path')); + it('should move box on log x axis and linear y to the proper position', + testObjectMoveItFun(rectColor2, -400, -200, 'path')); + it('should move annotation box on linear x axis and log y to the proper position', + testAnnotationMoveItFun(rectColor3, 50, -100, 'rect')); + it('should move annotation box on log x axis and linear y to the proper position', + testAnnotationMoveItFun(rectColor4, -75, -150, 'rect')); }); From fd07290d8ae98e86babc1c9a2e5803f9396ea516 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 17 Sep 2020 20:48:31 -0400 Subject: [PATCH 081/112] Test dragging the whole annotation --- test/image/mocks/domain_refs_editable.json | 16 ++- test/jasmine/assets/domain_ref/components.js | 5 +- .../jasmine/tests/domain_ref_interact_test.js | 133 +++++++++++++++--- 3 files changed, 126 insertions(+), 28 deletions(-) diff --git a/test/image/mocks/domain_refs_editable.json b/test/image/mocks/domain_refs_editable.json index 769c6b21974..f5cb8f1b56e 100644 --- a/test/image/mocks/domain_refs_editable.json +++ b/test/image/mocks/domain_refs_editable.json @@ -132,8 +132,10 @@ "y": 0.6, "axref": "x3 domain", "ayref": "y3 domain", - "ax": 0.5, - "ay": 0.7 + "ax": 0.7, + "ay": 0.8, + "arrowcolor": "rgb(231, 200, 100)", + "arrowwidth": 3 },{ "text": "B", "bordercolor": "rgb(200, 200, 232)", @@ -144,10 +146,14 @@ "axref": "x4 domain", "ayref": "y4 domain", "ax": 0.1, - "ay": 0.2 - }] + "ay": 0.2, + "arrowcolor": "rgb(231, 200, 200)", + "arrowwidth": 3 + }], + "dragmode": "drawopenpath" }, "config": { - "editable": true + "editable": true, + "modeBarButtonsToAdd": ["drawopenpath"] } } diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index 5875c50d28e..56cbe819ed5 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -437,12 +437,13 @@ function compareBBoxes(a, b) { true); } -function findAROByColor(color, id, type) { +function findAROByColor(color, id, type, colorAttribute) { id = (id === undefined) ? '' : id + ' '; type = (type === undefined) ? 'path' : type; + colorAttribute = (colorAttribute === undefined) ? 'stroke' : colorAttribute; var selector = id + type; var ret = d3.selectAll(selector).filter(function() { - return this.style.stroke === color; + return this.style[colorAttribute] === color; }).node(); return ret; } diff --git a/test/jasmine/tests/domain_ref_interact_test.js b/test/jasmine/tests/domain_ref_interact_test.js index 730d437ebd5..a88472a4ff6 100644 --- a/test/jasmine/tests/domain_ref_interact_test.js +++ b/test/jasmine/tests/domain_ref_interact_test.js @@ -10,15 +10,36 @@ var getSVGElemScreenBBox = require( var testMock = require('../../image/mocks/domain_refs_editable.json'); var delay = require('../assets/delay'); var mouseEvent = require('../assets/mouse_event'); +// we have to use drag to move annotations for some reason var drag = require('../assets/drag'); +var SVGTools = require('../assets/svg_tools'); // color of the rectangles var rectColor1 = 'rgb(10, 20, 30)'; var rectColor2 = 'rgb(10, 20, 31)'; var rectColor3 = 'rgb(100, 200, 232)'; var rectColor4 = 'rgb(200, 200, 232)'; +var arrowColor1 = 'rgb(231, 200, 100)'; +var arrowColor2 = 'rgb(231, 200, 200)'; -var DELAY_TIME = 0; +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000; + +var DELAY_TIME = 1000; + +function svgRectToJSON(svgrect) { + return JSON.stringify(SVGTools.svgRectToObj(svgrect)); +} + +function checkBBox(bboxBefore, bboxAfter, moveX, moveY) { + // We print out the objects for sanity, because sometimes Jasmine says a + // test passed when it actually did nothing! + console.log('bboxBefore', svgRectToJSON(bboxBefore)); + console.log('bboxAfter', svgRectToJSON(bboxAfter)); + console.log('moveX', moveX); + console.log('moveY', moveY); + expect(bboxAfter.x).toBeCloseTo(bboxBefore.x + moveX, 2); + expect(bboxAfter.y).toBeCloseTo(bboxBefore.y + moveY, 2); +} function testObjectMove(objectColor, moveX, moveY, type) { var bboxBefore = getSVGElemScreenBBox( @@ -37,27 +58,83 @@ function testObjectMove(objectColor, moveX, moveY, type) { var bboxAfter = getSVGElemScreenBBox( domainRefComponents.findAROByColor(objectColor, undefined, type) ); - expect(bboxAfter.x).toBeCloseTo(bboxBefore.x + moveX, 2); - expect(bboxAfter.y).toBeCloseTo(bboxBefore.y + moveY, 2); + checkBBox(bboxBefore, bboxAfter, moveX, moveY); +} + +function dragPos0(bbox, corner) { + if(corner === 'bl') { + return [ bbox.x + bbox.width * 0.5, + bbox.y + bbox.height * 0.5 - 10 ]; + } else if(corner === 'tr') { + return [ bbox.x + bbox.width * 0.5, + bbox.y + bbox.height * 0.5 + 10 ]; + } else { + return [ bbox.x + bbox.width * 0.5, + bbox.y + bbox.height * 0.5]; + } } -function testAnnotationMove(objectColor, moveX, moveY, type) { +// Tests moving the annotation label +function testAnnotationMoveLabel(objectColor, moveX, moveY) { + var bboxAfter; + // Get where the text box (label) is before dragging it var bboxBefore = getSVGElemScreenBBox( - domainRefComponents.findAROByColor(objectColor, undefined, type) + domainRefComponents.findAROByColor(objectColor, undefined, 'rect') ); - var opt = { - pos0: [ bboxBefore.x + bboxBefore.width * 0.5, - bboxBefore.y + bboxBefore.height * 0.5 ], + // we have to use drag to move annotations for some reason + var optLabelDrag = { + pos0: dragPos0(bboxBefore) }; - opt.dpos = [moveX, moveY]; - return (new Promise(function() { drag(opt); })) + optLabelDrag.dpos = [moveX, moveY]; + console.log('optLabelDrag', optLabelDrag); + // drag the label, this will make the arrow rotate around the arrowhead + return (new Promise(function(resolve) { + drag(optLabelDrag); resolve(); + })) + .then(delay(DELAY_TIME)) .then(function() { - var bboxAfter = getSVGElemScreenBBox( - domainRefComponents.findAROByColor(objectColor, undefined, type) + // then check it's position + bboxAfter = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(objectColor, undefined, 'rect') ); - expect(bboxAfter.x).toBeCloseTo(bboxBefore.x + moveX, 2); - expect(bboxAfter.y).toBeCloseTo(bboxBefore.y + moveY, 2); - }); + checkBBox(bboxBefore, bboxAfter, moveX, moveY); + }) + .then(delay(DELAY_TIME)); +} + +// Tests moving the whole annotation +function testAnnotationMoveWhole(objectColor, arrowColor, moveX, moveY, corner) { + var bboxAfter; + var arrowBBoxAfter; + // Get where the text box (label) is before dragging it + var bboxBefore = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(objectColor, undefined, 'rect') + ); + var arrowBBoxBefore = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(arrowColor, undefined, 'path', 'fill') + ); + var optArrowDrag = { + pos0: dragPos0(arrowBBoxBefore, corner) + }; + optArrowDrag.dpos = [moveX, moveY]; + console.log('optArrowDrag', optArrowDrag); + // drag the whole annotation + (new Promise(function(resolve) { + drag(optArrowDrag); resolve(); + })) + .then(delay(DELAY_TIME)) + .then(function() { + // check the new position of the arrow and label + arrowBBoxAfter = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(arrowColor, undefined, 'path', 'fill') + ); + bboxAfter = getSVGElemScreenBBox( + domainRefComponents.findAROByColor(objectColor, undefined, 'rect') + ); + checkBBox(arrowBBoxBefore, arrowBBoxAfter, moveX, moveY); + checkBBox(bboxBefore, bboxAfter, moveX, moveY); + }) + .then(delay(DELAY_TIME)); } describe('Shapes referencing domain', function() { @@ -82,11 +159,21 @@ describe('Shapes referencing domain', function() { .then(done); }; } - function testAnnotationMoveItFun(color, x, y, type) { + function testAnnotationMoveLabelItFun(color, x, y) { + return function(done) { + Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) + .then(delay(DELAY_TIME)) + .then(testAnnotationMoveLabel(color, x, y)) + .then(delay(DELAY_TIME)) + .catch(failTest) + .then(done); + }; + } + function testAnnotationMoveWholeItFun(color, arrowColor, x, y, corner) { return function(done) { Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(delay(DELAY_TIME)) - .then(testAnnotationMove(color, x, y, type)) + .then(testAnnotationMoveWhole(color, arrowColor, x, y, corner)) .then(delay(DELAY_TIME)) .catch(failTest) .then(done); @@ -96,8 +183,12 @@ describe('Shapes referencing domain', function() { testObjectMoveItFun(rectColor1, 100, -300, 'path')); it('should move box on log x axis and linear y to the proper position', testObjectMoveItFun(rectColor2, -400, -200, 'path')); - it('should move annotation box on linear x axis and log y to the proper position', - testAnnotationMoveItFun(rectColor3, 50, -100, 'rect')); - it('should move annotation box on log x axis and linear y to the proper position', - testAnnotationMoveItFun(rectColor4, -75, -150, 'rect')); + it('should move annotation label on linear x axis and log y to the proper position', + testAnnotationMoveLabelItFun(rectColor3, 50, -100, 'rect')); + it('should move annotation label on log x axis and linear y to the proper position', + testAnnotationMoveLabelItFun(rectColor4, -75, -150, 'rect')); + it('should move whole annotation on linear x axis and log y to the proper position', + testAnnotationMoveWholeItFun(rectColor3, arrowColor1, 50, -100, 'bl')); + it('should move whole annotation on log x axis and linear y to the proper position', + testAnnotationMoveWholeItFun(rectColor4, arrowColor2, -75, -150, 'tr')); }); From bd2b34db8fe7ac0cae7bef6a7c89bb84d370fb63 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 18 Sep 2020 14:12:50 -0400 Subject: [PATCH 082/112] Domain refercing objects image test --- test/image/baselines/domain_refs.png | Bin 0 -> 38305 bytes test/image/mocks/domain_refs.json | 221 ++++++++++++++++++ test/jasmine/assets/domain_ref/components.js | 2 +- .../assets/domain_ref}/domain_ref_base.json | 0 .../domain_ref}/domain_refs_editable.json | 0 .../jasmine/tests/domain_ref_interact_test.js | 6 +- 6 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 test/image/baselines/domain_refs.png create mode 100644 test/image/mocks/domain_refs.json rename test/{image/mocks => jasmine/assets/domain_ref}/domain_ref_base.json (100%) rename test/{image/mocks => jasmine/assets/domain_ref}/domain_refs_editable.json (100%) diff --git a/test/image/baselines/domain_refs.png b/test/image/baselines/domain_refs.png new file mode 100644 index 0000000000000000000000000000000000000000..7347652f2236a35f665df8634340a045b995eb23 GIT binary patch literal 38305 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

;#Xp5 zFVh>17#kDi8Wa54V+G2bqeZz+S{tp}@|J|W9M?6&Susa#1*Na25+P^b4k8u`!rL1CH98B!L6MaIM5=8ZnCh5z^hqlZ6ZxLhRoZW8|9IA|G5-Wh{EYp%8g0nvSj)Eo~YXG+aF?LX5}M(rOzD~ zDrA}IW`t0d5$_*DXANAfI6N&&5KBdv!^|cW8}fLM}GtW7Sf+$*Btcbp?D5R!zm?kx&#gst%^?AyGhHzwsV(CpWWK0MLin~ z&3)Au%QDG*6A#-P?bpXI0?iNJ6vGdXf2L6I3B+PtMsV}4p%bJ1{wxQf`B5BV{q=K+ zM4UM2=#XZ z0|UzdPh?v@2|~3hN=o3WKft1#njZOZ%;_AILpGlmfi7b9C_zJJUZ0E zX>&pj#M~*oUN6*c)u)4<^D>N4Na#~h-N8$F`B_Ezac=jjAy7rCcy_x>LsM5~vc|yh zY3t}pgA^o*Wbfz%O5onVxVFL*?Q`HzNP$8juI;Gi^&C3cvIUE_K*44j zsBntH#l_8V6x;u)b{`a!-4e8+69dJs_pK%b0TM{R7G9#hyEKWaV%n*G^&JCMp_{ou z*GLJrET3nxejQ+ZULF0GEw~>p?sxpN63l3A6Gik=l3fWpc^C@)Jv&~81MA|$OH2nf z66)RL9rX*fK?8$%DAL#u>7X(<;V5_muzPEDyPwV95#KJ;!$#I$M7Jw zfwe}gX)O!6b9%#zcS-B{9sdUG?Xbc;Ka{-R0~Ngk)l117EFvbo$%-p0D;rJ-fPbKf z#zK{n1|hh)CC^J2{EYgFfZfW;7=<`jO)5VZ&wJ zby!zanQ1Q#J(Pv)BG$*$`G_D1ulFUH+Eo?*ad-{1orpG+i~E~YS$MH{}8X$b?BGb>ysin zJ}_BYMH^FZ6~D6m`5^dCGV8GDC!QuZa20!~QI7cOI^Xu9fMGLs&3b89^H?PhMY8Iw zRw%&CshpK{(!LYj?dW<7dpSl!bpa}a6}y-9K$Xfi{dUPLwB8Ci^WA`&<%E;|tNXEC zNd$c3lR|OhE;r{HWn;shi!LN`7DR~RQ5yg91l}MD?u6n+N~2dUypHn#H5-4y6`4q` zD?|%sCy6*$_yxCoj=#6PpXYDXE3zfJI$l|9mJ%<3bn}v3D6E_!`341v2Nu1W_MOmK zPOyC|-E|9-8qiDkb}C1{QY0(R?LQa&rmdy*CJb3hw=FCZClx2@xod)Mb-u!0;4(L= zbnyWJRIC@dbMG3tR`QG>K-9$+T^`Lvw%GCN z9sV<@e2~o0r?Bom3uq*I}_`bI?*g_=@DwbQG=CJWW@2Wx*4M1+1LB;}JL`K9U z=yHSTQPFW1PAFHXq>D>+7143F$dJH3UDy{(+A&=2;_!L+$i>>h(Ra}cH0pb=pAZLn zKNO;C6d&A;iUlaYHn{uFd5^e1R$+m5WdvkH$SZg;W$n(>k29xY)o%t_;2_nT?J=d{ zBR(idi@K%pO<$^H#rE!%S7&-H``g`Ymkj3RNN!PRb7SW{7QN`MuF<1Vbg%mDB`sMC zNVW2r%)15&xA=N_ZrgYBZjUNo1fK!Y;85abuu<6%CEFCFT%sI6rdbnn zBd{&ra6r$#F^D5!%3Ah_z}+L$P9O{P+~0#)TnjaY9)Wm3qQ4NtanXB;yUZRxQP9z2dmf6 zopD}iQXWDcK_-n>8?D1PO-K`9DZ=IxEvgm1%RR89S=uR@M2rPQuB-Ok*LM|njg0N`N}U>oEXig z5g3!#9?C4!VYA|KJH?iGf>cK#UlSP}8U`J)Fuw*r2 zJM>Y$E{u)Q3HS`R*yn~U(GdF~cUP|hgTV(4VC1f`QZD0SzhHXLTHE=e*^uov%mRnv zoh0hvMDQ@8o63~{<0+l0Q|$J=abi90l{BmWSQ}{Q8{ju%YTktZSSa8N1Ym8}n00>= zV4(W<_a`+8#E9H*`+nOge;F*?0DM)K5n6+K+wTv`ZZ=m&7yjo-@MVS>$dz~pRe}E) zLf+m^z~0(mlfe4tN$>> Date: Mon, 21 Sep 2020 17:04:55 -0400 Subject: [PATCH 083/112] Fixed up docstring generation for axis placeable objects --- src/constants/axis_placeable_objects.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/constants/axis_placeable_objects.js b/src/constants/axis_placeable_objects.js index 32f2dd50dec..1982b035a42 100644 --- a/src/constants/axis_placeable_objects.js +++ b/src/constants/axis_placeable_objects.js @@ -12,19 +12,19 @@ module.exports = { axisRefDescription: function(axisname, lower, upper) { return [ - 'If set to a ', axisname, ' axis id (e.g. *', axisname, '* or', - '*', axisname, '2*), the `', axisname, '` position refers to a', - '', axisname, ' coordinate. If set to *paper*, the `', axisname, '`', - 'position refers to the distance from the ', lower, ' of the plotting', + 'If set to a', axisname, 'axis id (e.g. *' + axisname + '* or', + '*' + axisname + '2*), the `' + axisname + '` position refers to a', + axisname, 'coordinate. If set to *paper*, the `' + axisname + '`', + 'position refers to the distance from the', lower, 'of the plotting', 'area in normalized coordinates where *0* (*1*) corresponds to the', - '', lower, ' (', upper, '). If set to a ', axisname, ' axis ID followed by', + lower, '(' + upper + '). If set to a', axisname, 'axis ID followed by', '*domain* (separated by a space), the position behaves like for', '*paper*, but refers to the distance in fractions of the domain', - 'length from the ', lower, ' of the domain of that axis: e.g.,', - '*', axisname, '2 domain* refers to the domain of the second', - '', axisname, ' axis and a ', axisname, ' position of 0.5 refers to the', - 'point between the ', lower, ' and the ', upper, ' of the domain of the', - 'second ', axisname, ' axis.', + 'length from the', lower, 'of the domain of that axis: e.g.,', + '*' + axisname + '2 domain* refers to the domain of the second', + axisname, ' axis and a', axisname, 'position of 0.5 refers to the', + 'point between the', lower, 'and the', upper, 'of the domain of the', + 'second', axisname, 'axis.', ].join(' '); } }; From 65104151d1b4973ad0a5d8b709293586d7991e8c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 21 Sep 2020 17:41:13 -0400 Subject: [PATCH 084/112] Moved extra-iterable to dev-dependencies --- package-lock.json | 7 ++++--- package.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e16367f1f22..088fbbda16d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4098,9 +4098,10 @@ } }, "extra-iterable": { - "version": "2.5.12", - "resolved": "https://registry.npmjs.org/extra-iterable/-/extra-iterable-2.5.12.tgz", - "integrity": "sha512-bNfNKrxHvPPtfL5R8khTLSZuH737J4eDDU8Rtse81oRV8sGhk+gJJ7T+2upbqOU/a369rO/ioWwHKyfXOrTsIw==" + "version": "2.5.13", + "resolved": "https://registry.npmjs.org/extra-iterable/-/extra-iterable-2.5.13.tgz", + "integrity": "sha512-6K+KLLptYou+973HnqDjGZp15joUkEO/LCbo212V0fTl/PENmMttQGQLof5EAmeX5xLNnXAu0qkV7cH/rxZuRA==", + "dev": true }, "extract-frustum-planes": { "version": "1.0.0", diff --git a/package.json b/package.json index c374346755b..dc4497dde40 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "d3-time-format": "^2.2.3", "delaunay-triangulate": "^1.1.6", "es6-promise": "^4.2.8", - "extra-iterable": "^2.5.12", "fast-isnumeric": "^1.1.4", "gl-cone3d": "^1.5.2", "gl-contour2d": "^1.1.7", @@ -137,6 +136,7 @@ "elliptic": "^6.5.3", "eslint": "^7.7.0", "espree": "^7.3.0", + "extra-iterable": "^2.5.13", "falafel": "^2.2.4", "fs-extra": "^9.0.1", "fuse.js": "^6.4.1", From aa2d6eb7f915f50d023ed016ca43fc1c9f8a24a9 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 21 Sep 2020 17:54:23 -0400 Subject: [PATCH 085/112] Removed .js from require --- src/components/annotations/attributes.js | 2 +- src/components/images/attributes.js | 2 +- src/components/shapes/attributes.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 8ea67e4ab2e..9053a81e150 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -12,7 +12,7 @@ var ARROWPATHS = require('./arrow_paths'); var fontAttrs = require('../../plots/font_attributes'); var cartesianConstants = require('../../plots/cartesian/constants'); var templatedArray = require('../../plot_api/plot_template').templatedArray; -var axisPlaceableObjs = require('../../constants/axis_placeable_objects.js'); +var axisPlaceableObjs = require('../../constants/axis_placeable_objects'); module.exports = templatedArray('annotation', { diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index ad8c2cbbdcc..c14afa9e7d8 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -10,7 +10,7 @@ var cartesianConstants = require('../../plots/cartesian/constants'); var templatedArray = require('../../plot_api/plot_template').templatedArray; -var axisPlaceableObjs = require('../../constants/axis_placeable_objects.js'); +var axisPlaceableObjs = require('../../constants/axis_placeable_objects'); module.exports = templatedArray('image', { diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index 1bf9fef20b4..9e445831897 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -13,7 +13,7 @@ var scatterLineAttrs = require('../../traces/scatter/attributes').line; var dash = require('../drawing/attributes').dash; var extendFlat = require('../../lib/extend').extendFlat; var templatedArray = require('../../plot_api/plot_template').templatedArray; -var axisPlaceableObjs = require('../../constants/axis_placeable_objects.js'); +var axisPlaceableObjs = require('../../constants/axis_placeable_objects'); module.exports = templatedArray('shape', { visible: { From 4203088f782f3dddfcbd07cedc3c6546a0d3ba8d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 21 Sep 2020 19:59:55 -0400 Subject: [PATCH 086/112] PR review integration --- src/components/annotations/draw.js | 36 +++++++++++++----------------- src/components/images/draw.js | 32 ++++++++++---------------- src/components/shapes/helpers.js | 5 +++-- src/plot_api/helpers.js | 2 +- src/plots/cartesian/axes.js | 2 +- src/plots/cartesian/axis_ids.js | 15 +++++-------- 6 files changed, 37 insertions(+), 55 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 0cc3fdebea9..7644a02552e 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -78,7 +78,8 @@ function drawOne(gd, index) { // the plot. // axDomainRef: if true and axa defined, draws relative to axis domain, // otherwise draws relative to data (if axa defined) or paper (if not). -function shiftPosition(axa, optAx, dAx, gsDim, vertical, axDomainRef) { +function shiftPosition(axa, optAx, dAx, gsDim, vertical, axRef) { + var axDomainRef = Axes.getRefType(axRef) === 'domain'; if(axa) { if(axDomainRef) { // here optAx normalized to length of axis (e.g., normally in range @@ -339,20 +340,17 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { basePx = ax._offset + ax.r2p(options[axLetter]); autoAlignFraction = 0.5; } else { + var axRefTypeEqDomain = axRefType === 'domain'; if(axLetter === 'x') { alignPosition = options[axLetter]; - if(axRefType === 'domain') { - basePx = ax._offset + ax._length * alignPosition; - } else { + basePx = axRefTypeEqDomain ? + ax._offset + ax._length * alignPosition : basePx = gs.l + gs.w * alignPosition; - } } else { alignPosition = 1 - options[axLetter]; - if(axRefType === 'domain') { - basePx = ax._offset + ax._length * alignPosition; - } else { + basePx = axRefTypeEqDomain ? + ax._offset + ax._length * alignPosition : basePx = gs.t + gs.h * alignPosition; - } } autoAlignFraction = options.showarrow ? 0.5 : alignPosition; } @@ -612,24 +610,22 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var ycenter = annxy0[1] + dy; annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); - var xRefType = Axes.getRefType(options.xref); - var yRefType = Axes.getRefType(options.yref); modifyItem('x', - shiftPosition(xa, options.x, dx, gs.w, false, xRefType === 'domain')); + shiftPosition(xa, options.x, dx, gs.w, false, options.xref)); modifyItem('y', - shiftPosition(ya, options.y, dy, gs.h, true, yRefType === 'domain')); + shiftPosition(ya, options.y, dy, gs.h, true, options.yref)); // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.w, false, - xRefType === 'domain')); + options.xref)); } if(options.ayref === options.yref) { modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.h, true, - yRefType === 'domain')); + options.yref)); } arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); @@ -663,22 +659,20 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { }, moveFn: function(dx, dy) { var csr = 'pointer'; - var xRefType = Axes.getRefType(options.xref); - var yRefType = Axes.getRefType(options.yref); if(options.showarrow) { // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.h, false, - xRefType === 'domain')); + options.xref)); } else { modifyItem('ax', options.ax + dx); } if(options.ayref === options.yref) { modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.w, true, - yRefType === 'domain')); + options.yref)); } else { modifyItem('ay', options.ay + dy); } @@ -690,7 +684,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // shiftPosition will not execute code where xa was // undefined, so we use to calculate xUpdate too xUpdate = shiftPosition(xa, options.x, dx, gs.h, false, - xRefType === 'domain'); + options.xref); } else { var widthFraction = options._xsize / gs.w; var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2; @@ -703,7 +697,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // shiftPosition will not execute code where ya was // undefined, so we use to calculate yUpdate too yUpdate = shiftPosition(ya, options.y, dy, gs.w, true, - yRefType === 'domain'); + options.yref); } else { var heightFraction = options._ysize / gs.h; var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2; diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 781e0910df8..0567fbecdbb 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -135,20 +135,16 @@ module.exports = function draw(gd) { var size = fullLayout._size; var width, height; if(xa !== undefined) { - if((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) { - width = xa._length * d.sizex; - } else { - width = Math.abs(xa.l2p(d.sizex) - xa.l2p(0)); - } + width = ((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) ? + xa._length * d.sizex : + Math.abs(xa.l2p(d.sizex) - xa.l2p(0)); } else { width = d.sizex * size.w; } if(ya !== undefined) { - if((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) { - height = ya._length * d.sizey; - } else { - height = Math.abs(ya.l2p(d.sizey) - ya.l2p(0)); - } + height = ((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) ? + ya._length * d.sizey : + Math.abs(ya.l2p(d.sizey) - ya.l2p(0)); } else { height = d.sizey * size.h; } @@ -162,23 +158,19 @@ module.exports = function draw(gd) { // Final positions var xPos, yPos; if(xa !== undefined) { - if((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) { - xPos = xa._length * d.x + xa._offset; - } else { - xPos = xa.r2p(d.x) + xa._offset; - } + xPos = ((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) ? + xa._length * d.x + xa._offset : + xa.r2p(d.x) + xa._offset; } else { xPos = d.x * size.w + size.l; } xPos += xOffset; if(ya !== undefined) { - if((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) { + yPos = ((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) ? // consistent with "paper" yref value, where positive values // move up the page - yPos = ya._length * (1 - d.y) + ya._offset; - } else { - yPos = ya.r2p(d.y) + ya._offset; - } + ya._length * (1 - d.y) + ya._offset : + ya.r2p(d.y) + ya._offset; } else { yPos = size.h - d.y * size.h + size.t; } diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index cfcf08c117b..539b2949748 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -92,9 +92,10 @@ exports.getPixelToData = function(gd, axis, isVertical, opt) { if(axis) { if(opt === 'domain') { pixelToData = function(p) { + var q = p - axis._offset; return ((isVertical ? - (1 - (p - axis._offset) / axis._length) : - (p - axis._offset) / axis._length)); + (1 - q / axis._length) : + q / axis._length)); }; } else { var r2d = exports.rangeToShapePosition(axis); diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index dc688ce8600..5a5eeecca59 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -174,7 +174,7 @@ exports.cleanLayout = function(layout) { cleanAxRef(shape, 'yref'); } - var imagesLen = Array.isArray(layout.images) ? layout.images.length : 0; + var imagesLen = isArray(layout.images) ? layout.images.length : 0; for(i = 0; i < imagesLen; i++) { var image = layout.images[i]; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 576148f2734..7fb1e71eb27 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -95,7 +95,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption if(!dflt) dflt = axlist[0] || (typeof extraOption === 'string' ? extraOption : extraOption[0]); if(!extraOption) extraOption = dflt; - if(domainRef) axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); + if(domainRef) axlist.push(axlist.map(function(x) { return x + ' domain'; })); // data-ref annotations are not supported in gl2d yet diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 57deb8b073a..dff56784fe3 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -37,15 +37,13 @@ exports.name2id = function name2id(name) { * ' domain' part is kept at the end of the axis ID string. */ exports.cleanId = function cleanId(id, axLetter, domainId) { + var domainTest = /( domain)$/.test(id); if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; if(axLetter && id.charAt(0) !== axLetter) return; - if(/( domain)$/.test(id) && (!domainId)) return; + if(domainTest && (!domainId)) return; var axNum = id.split(' ')[0].substr(1).replace(/^0+/, ''); if(axNum === '1') axNum = ''; - if(/( domain)$/.test(id) && domainId) { - return id.charAt(0) + axNum + ' domain'; - } - return id.charAt(0) + axNum; + return id.charAt(0) + axNum + (domainTest && domainId ? ' domain' : ''); }; // get all axis objects, as restricted in listNames @@ -92,7 +90,7 @@ exports.listIds = function(gd, axLetter) { exports.getFromId = function(gd, id, type) { var fullLayout = gd._fullLayout; // remove "domain" suffix - id = ((id === undefined) || (typeof(id) !== 'string')) ? id : id.replace(/ *domain/, ''); + id = ((id === undefined) || (typeof(id) !== 'string')) ? id : id.replace(' domain', ''); if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); @@ -146,8 +144,5 @@ exports.ref2id = function(ar) { // This assumes ar has been coerced via coerceRef, and uses the shortcut of // checking if the first letter matches [xyz] to determine if it should // return the axis ID. Otherwise it returns false. - if(/^[xyz]/.test(ar)) { - return ar.split(' ')[0]; - } - return false; + return (/^[xyz]/.test(ar)) ? ar.split(' ')[0] : false; }; From b8dc27f6ddcc313e2672e19e702e414ed7e5898f Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 22 Sep 2020 12:05:07 -0400 Subject: [PATCH 087/112] Using Array.isArray unbreaks the mathjax bundle test --- src/plot_api/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 5a5eeecca59..dc688ce8600 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -174,7 +174,7 @@ exports.cleanLayout = function(layout) { cleanAxRef(shape, 'yref'); } - var imagesLen = isArray(layout.images) ? layout.images.length : 0; + var imagesLen = Array.isArray(layout.images) ? layout.images.length : 0; for(i = 0; i < imagesLen; i++) { var image = layout.images[i]; From 099bf52638591f63bb6ca49f6756ea49b4674183 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 22 Sep 2020 12:42:14 -0400 Subject: [PATCH 088/112] It's too complicated to use push like concat so we'll just leave it the way it is. --- src/plots/cartesian/axes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 7fb1e71eb27..576148f2734 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -95,7 +95,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption if(!dflt) dflt = axlist[0] || (typeof extraOption === 'string' ? extraOption : extraOption[0]); if(!extraOption) extraOption = dflt; - if(domainRef) axlist.push(axlist.map(function(x) { return x + ' domain'; })); + if(domainRef) axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); // data-ref annotations are not supported in gl2d yet From f3531dcc28a53450f7f3eeca6a24feef42e2d461 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 22 Sep 2020 13:15:04 -0400 Subject: [PATCH 089/112] Added domain_ref mock to mock_test --- test/jasmine/tests/mock_test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jasmine/tests/mock_test.js b/test/jasmine/tests/mock_test.js index d68c3604bd2..b587ed12ffe 100644 --- a/test/jasmine/tests/mock_test.js +++ b/test/jasmine/tests/mock_test.js @@ -260,6 +260,7 @@ var list = [ 'date_histogram', 'dendrogram', 'display-text_zero-number', + 'domain_refs', 'earth_heatmap', 'electric_heatmap', 'empty', From 93890d0a1d353540b91547e64362b304bcc6bbbb Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 22 Sep 2020 13:39:22 -0400 Subject: [PATCH 090/112] Added domain_refs to mock_test but it doesn't pass the validator --- test/jasmine/tests/mock_test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jasmine/tests/mock_test.js b/test/jasmine/tests/mock_test.js index b587ed12ffe..2c9739e3c19 100644 --- a/test/jasmine/tests/mock_test.js +++ b/test/jasmine/tests/mock_test.js @@ -1313,6 +1313,7 @@ figs['date_axes_period_breaks_automargin'] = require('@mocks/date_axes_period_br figs['date_histogram'] = require('@mocks/date_histogram'); // figs['dendrogram'] = require('@mocks/dendrogram'); figs['display-text_zero-number'] = require('@mocks/display-text_zero-number'); +figs['domain_refs'] = require("@mocks/domain_refs"); figs['earth_heatmap'] = require('@mocks/earth_heatmap'); figs['electric_heatmap'] = require('@mocks/electric_heatmap'); figs['empty'] = require('@mocks/empty'); From 6eba309ac6a535bdf7849c070be56e8f5ac4ec07 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 23 Sep 2020 16:29:17 -0400 Subject: [PATCH 091/112] Made shiftPosition clearer and more modular --- src/components/annotations/draw.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 7644a02552e..c61536c31ed 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -78,8 +78,12 @@ function drawOne(gd, index) { // the plot. // axDomainRef: if true and axa defined, draws relative to axis domain, // otherwise draws relative to data (if axa defined) or paper (if not). -function shiftPosition(axa, optAx, dAx, gsDim, vertical, axRef) { +function shiftPosition(axa, options, dAx, axLetter, gs) { + var optAx = options[axLetter]; + var axRef = options[axLetter + 'ref']; + var vertical = axLetter.includes('y'); var axDomainRef = Axes.getRefType(axRef) === 'domain'; + var gsDim = vertical ? gs.h : gs.w; if(axa) { if(axDomainRef) { // here optAx normalized to length of axis (e.g., normally in range @@ -611,21 +615,19 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); modifyItem('x', - shiftPosition(xa, options.x, dx, gs.w, false, options.xref)); + shiftPosition(xa, options, dx, 'x', gs)); modifyItem('y', - shiftPosition(ya, options.y, dy, gs.h, true, options.yref)); + shiftPosition(ya, options, dy, 'y', gs)); // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.w, false, - options.xref)); + modifyItem('ax', shiftPosition(xa, options, dx, 'ax', gs)); } if(options.ayref === options.yref) { - modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.h, true, - options.yref)); + modifyItem('ay', shiftPosition(ya, options, dy, 'ay', gs)); } arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); @@ -664,15 +666,13 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', shiftPosition(xa, options.ax, dx, gs.h, false, - options.xref)); + modifyItem('ax', shiftPosition(xa, options, dx, 'ax', gs)); } else { modifyItem('ax', options.ax + dx); } if(options.ayref === options.yref) { - modifyItem('ay', shiftPosition(ya, options.ay, dy, gs.w, true, - options.yref)); + modifyItem('ay', shiftPosition(ya, options, dy, 'ay', gs.w)); } else { modifyItem('ay', options.ay + dy); } @@ -683,8 +683,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { if(xa) { // shiftPosition will not execute code where xa was // undefined, so we use to calculate xUpdate too - xUpdate = shiftPosition(xa, options.x, dx, gs.h, false, - options.xref); + xUpdate = shiftPosition(xa, options, dx, 'x', gs); } else { var widthFraction = options._xsize / gs.w; var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2; @@ -696,8 +695,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { if(ya) { // shiftPosition will not execute code where ya was // undefined, so we use to calculate yUpdate too - yUpdate = shiftPosition(ya, options.y, dy, gs.w, true, - options.yref); + yUpdate = shiftPosition(ya, options, dy, 'y', gs); } else { var heightFraction = options._ysize / gs.h; var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2; From 2d25857bbe5ee314f7da07669ca4158f072bc831 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 23 Sep 2020 16:45:44 -0400 Subject: [PATCH 092/112] Change function parameter name and syntax fix --- src/components/shapes/helpers.js | 4 ++-- test/jasmine/tests/mock_test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index 539b2949748..d7144109876 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -58,12 +58,12 @@ exports.extractPathCoords = function(path, paramsToUse) { return extractedCoordinates; }; -exports.getDataToPixel = function(gd, axis, isVertical, opt) { +exports.getDataToPixel = function(gd, axis, isVertical, refType) { var gs = gd._fullLayout._size; var dataToPixel; if(axis) { - if(opt === 'domain') { + if(refType === 'domain') { dataToPixel = function(v) { return axis._length * (isVertical ? (1 - v) : v) + axis._offset; }; diff --git a/test/jasmine/tests/mock_test.js b/test/jasmine/tests/mock_test.js index 2c9739e3c19..102480ff2d5 100644 --- a/test/jasmine/tests/mock_test.js +++ b/test/jasmine/tests/mock_test.js @@ -1313,7 +1313,7 @@ figs['date_axes_period_breaks_automargin'] = require('@mocks/date_axes_period_br figs['date_histogram'] = require('@mocks/date_histogram'); // figs['dendrogram'] = require('@mocks/dendrogram'); figs['display-text_zero-number'] = require('@mocks/display-text_zero-number'); -figs['domain_refs'] = require("@mocks/domain_refs"); +figs['domain_refs'] = require('@mocks/domain_refs'); figs['earth_heatmap'] = require('@mocks/earth_heatmap'); figs['electric_heatmap'] = require('@mocks/electric_heatmap'); figs['empty'] = require('@mocks/empty'); From 35ab9dad6d345f9020b9fd2e813eca9aa998465d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 23 Sep 2020 17:15:29 -0400 Subject: [PATCH 093/112] Fixed domain_ref mock_test --- test/image/mocks/domain_refs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/image/mocks/domain_refs.json b/test/image/mocks/domain_refs.json index 59ae7fcade2..af56005cc3a 100644 --- a/test/image/mocks/domain_refs.json +++ b/test/image/mocks/domain_refs.json @@ -193,8 +193,8 @@ }], "images": [{ "source": "https://images.plot.ly/language-icons/api-home/js-logo.png", - "xref": "x1 domain", - "yref": "y1 domain", + "xref": "x domain", + "yref": "y domain", "x": 0, "y": 0, "xanchor": "left", From 759d37bf9e1eac56cef5d1e4df44e899e20ffadc Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 23 Sep 2020 17:35:37 -0400 Subject: [PATCH 094/112] Changed order of arguments to shiftPosition Also avoided using String.includes. --- src/components/annotations/draw.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index c61536c31ed..478a12141d0 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -78,10 +78,10 @@ function drawOne(gd, index) { // the plot. // axDomainRef: if true and axa defined, draws relative to axis domain, // otherwise draws relative to data (if axa defined) or paper (if not). -function shiftPosition(axa, options, dAx, axLetter, gs) { +function shiftPosition(axa, dAx, axLetter, gs, options) { var optAx = options[axLetter]; var axRef = options[axLetter + 'ref']; - var vertical = axLetter.includes('y'); + var vertical = axLetter.indexOf('y') !== -1; var axDomainRef = Axes.getRefType(axRef) === 'domain'; var gsDim = vertical ? gs.h : gs.w; if(axa) { @@ -615,19 +615,19 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); modifyItem('x', - shiftPosition(xa, options, dx, 'x', gs)); + shiftPosition(xa, dx, 'x', gs, options)); modifyItem('y', - shiftPosition(ya, options, dy, 'y', gs)); + shiftPosition(ya, dy, 'y', gs, options)); // for these 2 calls to shiftPosition, it is assumed xa, ya are // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', shiftPosition(xa, options, dx, 'ax', gs)); + modifyItem('ax', shiftPosition(xa, dx, 'ax', gs, options)); } if(options.ayref === options.yref) { - modifyItem('ay', shiftPosition(ya, options, dy, 'ay', gs)); + modifyItem('ay', shiftPosition(ya, dy, 'ay', gs, options)); } arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); @@ -666,13 +666,13 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // defined, so gsDim will not be used, but we put it in // anyways for consistency if(options.axref === options.xref) { - modifyItem('ax', shiftPosition(xa, options, dx, 'ax', gs)); + modifyItem('ax', shiftPosition(xa, dx, 'ax', gs, options)); } else { modifyItem('ax', options.ax + dx); } if(options.ayref === options.yref) { - modifyItem('ay', shiftPosition(ya, options, dy, 'ay', gs.w)); + modifyItem('ay', shiftPosition(ya, dy, 'ay', gs.w, options)); } else { modifyItem('ay', options.ay + dy); } @@ -683,7 +683,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { if(xa) { // shiftPosition will not execute code where xa was // undefined, so we use to calculate xUpdate too - xUpdate = shiftPosition(xa, options, dx, 'x', gs); + xUpdate = shiftPosition(xa, dx, 'x', gs, options); } else { var widthFraction = options._xsize / gs.w; var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2; @@ -695,7 +695,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { if(ya) { // shiftPosition will not execute code where ya was // undefined, so we use to calculate yUpdate too - yUpdate = shiftPosition(ya, options, dy, 'y', gs); + yUpdate = shiftPosition(ya, dy, 'y', gs, options); } else { var heightFraction = options._ysize / gs.h; var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2; From d70f4e9c79bb1e8bd5a28002847c60b502da6884 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 23 Sep 2020 17:49:43 -0400 Subject: [PATCH 095/112] DRY pixelToData --- src/components/shapes/helpers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index d7144109876..3a64919d813 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -92,10 +92,10 @@ exports.getPixelToData = function(gd, axis, isVertical, opt) { if(axis) { if(opt === 'domain') { pixelToData = function(p) { - var q = p - axis._offset; + var q = (p - axis._offset) / axis._length; return ((isVertical ? - (1 - q / axis._length) : - q / axis._length)); + (1 - q) : + q)); }; } else { var r2d = exports.rangeToShapePosition(axis); From 9055503a6a9c19aec642e31cb8fa3c9f19c79aed Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 24 Sep 2020 12:55:00 -0400 Subject: [PATCH 096/112] Made pixelToData shorter --- src/components/shapes/helpers.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index 3a64919d813..aac28b48d8c 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -93,9 +93,7 @@ exports.getPixelToData = function(gd, axis, isVertical, opt) { if(opt === 'domain') { pixelToData = function(p) { var q = (p - axis._offset) / axis._length; - return ((isVertical ? - (1 - q) : - q)); + return isVertical ? 1 - q : q; }; } else { var r2d = exports.rangeToShapePosition(axis); From e458c865a5c24d0f8ce4fff8a662672eab43a870 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 24 Sep 2020 18:15:18 -0400 Subject: [PATCH 097/112] Syntax --- src/components/shapes/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index aac28b48d8c..b22ea96a60d 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -93,7 +93,7 @@ exports.getPixelToData = function(gd, axis, isVertical, opt) { if(opt === 'domain') { pixelToData = function(p) { var q = (p - axis._offset) / axis._length; - return isVertical ? 1 - q : q; + return isVertical ? 1 - q : q; }; } else { var r2d = exports.rangeToShapePosition(axis); From df0560cfa46f076dc16646053a83cf403fd59baf Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 25 Sep 2020 16:50:13 -0400 Subject: [PATCH 098/112] Don't coerce pixel, paper or domain references the same as range references --- src/plots/cartesian/axes.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 576148f2734..783b1af27db 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -173,13 +173,13 @@ axes.addAxRefDomainCoerceRefExtra = function(container, axLetter, coerceRefExtra * - for other types: coerce them to numbers */ axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) { - var cleanPos, pos; - - if(axRef === 'paper' || axRef === 'pixel') { + var cleanPos, pos, axRefType; + axRefType = axes.getRefType(axRef); + if(axRefType === 'paper' || axRefType === 'pixel' || axRefType === 'domain') { cleanPos = Lib.ensureNumber; pos = coerce(attr, dflt); } else { - // TODO: This doesn't seem to work for dates + // if axRef is 'range' or undefined we will end up here var ax = axes.getFromId(gd, axRef); dflt = ax.fraction2r(dflt); pos = coerce(attr, dflt); From 676d6c5e08eb661198419f73de41a692161b13bc Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 28 Sep 2020 13:40:34 -0400 Subject: [PATCH 099/112] Improved 'axref' 'ayref' descriptions For annotations --- src/components/annotations/attributes.js | 60 ++++++++++++++---------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 9053a81e150..eacade0b0f0 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -14,6 +14,32 @@ var cartesianConstants = require('../../plots/cartesian/constants'); var templatedArray = require('../../plot_api/plot_template').templatedArray; var axisPlaceableObjs = require('../../constants/axis_placeable_objects'); +function arrowAxisRefDescription(axis) { + return [ + 'In order for absolute positioning of the arrow to work, *a'+axis+ + 'ref* must be exactly the same as *'+axis+'ref*, otherwise *a'+axis+ + 'ref* will revert to *pixel* (explained next).', + 'For relative positioning, *a'+axis+'ref* can be set to *pixel*,', + 'in which case the *a'+axis+'* value is specified in pixels', + 'relative to *'+axis+'*.', + 'Absolute positioning is useful', + 'for trendline annotations which should continue to indicate', + 'the correct trend when zoomed. Relative positioning is useful', + 'for specifying the text offset for an annotated point.' + ].join(' '); +} + +function arrowCoordinateDescription(axis,lower,upper) { + return [ + 'Sets the', axis, 'component of the arrow tail about the arrow head.', + 'If `a'+axis+'ref` is `pixel`, a positive (negative)', + 'component corresponds to an arrow pointing', + 'from', upper, 'to', lower, '('+lower, 'to', upper+').', + 'If `a'+axis+'ref` is not `pixel` and is exactly the same as `'+axis+'ref`,', + 'this is an absolute value on that axis,', + 'like `'+axis+'`, specified in the same coordinates as `'+axis+'ref`.' + ].join(' '); +} module.exports = templatedArray('annotation', { visible: { @@ -255,12 +281,7 @@ module.exports = templatedArray('annotation', { role: 'info', editType: 'calc+arraydraw', description: [ - 'Sets the x component of the arrow tail about the arrow head.', - 'If `axref` is `pixel`, a positive (negative) ', - 'component corresponds to an arrow pointing', - 'from right to left (left to right).', - 'If `axref` is an axis, this is an absolute value on that axis,', - 'like `x`, NOT a relative value.' + arrowCoordinateDescription('x','left','right') ].join(' ') }, ay: { @@ -268,12 +289,7 @@ module.exports = templatedArray('annotation', { role: 'info', editType: 'calc+arraydraw', description: [ - 'Sets the y component of the arrow tail about the arrow head.', - 'If `ayref` is `pixel`, a positive (negative) ', - 'component corresponds to an arrow pointing', - 'from bottom to top (top to bottom).', - 'If `ayref` is an axis, this is an absolute value on that axis,', - 'like `y`, NOT a relative value.' + arrowCoordinateDescription('y','top','bottom') ].join(' ') }, axref: { @@ -286,12 +302,10 @@ module.exports = templatedArray('annotation', { role: 'info', editType: 'calc', description: [ - 'Indicates in what terms the tail of the annotation (ax,ay) ', - 'is specified. If `pixel`, `ax` is a relative offset in pixels ', - 'from `x`. If set to an x axis id (e.g. *x* or *x2*), `ax` is ', - 'specified in the same terms as that axis. This is useful ', - 'for trendline annotations which should continue to indicate ', - 'the correct trend when zoomed.' + 'Indicates in what coordinates the tail of the', + 'annotation (ax,ay) is specified.', + axisPlaceableObjs.axisRefDescription('ax', 'left', 'right'), + arrowAxisRefDescription('x') ].join(' ') }, ayref: { @@ -304,12 +318,10 @@ module.exports = templatedArray('annotation', { role: 'info', editType: 'calc', description: [ - 'Indicates in what terms the tail of the annotation (ax,ay) ', - 'is specified. If `pixel`, `ay` is a relative offset in pixels ', - 'from `y`. If set to a y axis id (e.g. *y* or *y2*), `ay` is ', - 'specified in the same terms as that axis. This is useful ', - 'for trendline annotations which should continue to indicate ', - 'the correct trend when zoomed.' + 'Indicates in what coordinates the tail of the', + 'annotation (ax,ay) is specified.', + axisPlaceableObjs.axisRefDescription('ay', 'bottom', 'top'), + arrowAxisRefDescription('y') ].join(' ') }, // positioning From 69acd5540fbc2f0cf9a576d073bb910f4c6142eb Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 28 Sep 2020 17:12:34 -0400 Subject: [PATCH 100/112] Added domain ref baseline for all types of axes --- .../image/baselines/domain_ref_axis_types.png | Bin 0 -> 51650 bytes test/image/mocks/domain_ref_axis_types.json | 1767 +++++++++++++++++ 2 files changed, 1767 insertions(+) create mode 100644 test/image/baselines/domain_ref_axis_types.png create mode 100644 test/image/mocks/domain_ref_axis_types.json diff --git a/test/image/baselines/domain_ref_axis_types.png b/test/image/baselines/domain_ref_axis_types.png new file mode 100644 index 0000000000000000000000000000000000000000..88a884fe3ea44f4f6764238e90971fedb9d2bfce GIT binary patch literal 51650 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 Date: Tue, 29 Sep 2020 16:40:49 -0400 Subject: [PATCH 101/112] newline --- test/image/mocks/domain_ref_axis_types.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/image/mocks/domain_ref_axis_types.json b/test/image/mocks/domain_ref_axis_types.json index f8693d5f918..ca1c9d789a4 100644 --- a/test/image/mocks/domain_ref_axis_types.json +++ b/test/image/mocks/domain_ref_axis_types.json @@ -1764,4 +1764,4 @@ "type": "category" } } -} \ No newline at end of file +} From 8b63e38fb907a1dafacf6cd337d830b322decb87 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 29 Sep 2020 17:25:29 -0400 Subject: [PATCH 102/112] Removed some layout information from domain_ref_axis_types.json --- test/image/mocks/domain_ref_axis_types.json | 533 -------------------- 1 file changed, 533 deletions(-) diff --git a/test/image/mocks/domain_ref_axis_types.json b/test/image/mocks/domain_ref_axis_types.json index ca1c9d789a4..7f32664abcc 100644 --- a/test/image/mocks/domain_ref_axis_types.json +++ b/test/image/mocks/domain_ref_axis_types.json @@ -766,531 +766,9 @@ }, "type": "bar" } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } ] }, "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, "coloraxis": { "colorbar": { "outlinewidth": 0, @@ -1444,21 +922,10 @@ "font": { "color": "#2a3f5f" }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "#E5ECF6", - "showlakes": true, - "showland": true, - "subunitcolor": "white" - }, "hoverlabel": { "align": "left" }, "hovermode": "closest", - "mapbox": { - "style": "light" - }, "paper_bgcolor": "white", "plot_bgcolor": "#E5ECF6", "polar": { From 7d2129c2fecba36c8adaee255f3cfb8d4b6598b8 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 29 Sep 2020 18:20:28 -0400 Subject: [PATCH 103/112] Removed console.logs from tests --- test/jasmine/assets/domain_ref/components.js | 33 ++++++++++++------- .../jasmine/tests/domain_ref_interact_test.js | 20 +++++------ test/jasmine/tests/domain_ref_test.js | 2 +- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index 8aad0fe0882..b322b6439f9 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -13,10 +13,8 @@ var Plotly = require('../../../../lib/index'); var d3 = require('d3'); var pixelCalc = require('../../assets/pixel_calc'); -var getSVGElemScreenBBox = require( - '../../assets/get_svg_elem_screen_bbox'); -var SVGTools = require( - '../../assets/svg_tools'); +var getSVGElemScreenBBox = require('../../assets/get_svg_elem_screen_bbox'); +// var SVGTools = require('../../assets/svg_tools'); var Lib = require('../../../../src/lib'); var Axes = require('../../../../src/plots/cartesian/axes'); var axisIds = require('../../../../src/plots/cartesian/axis_ids'); @@ -28,6 +26,19 @@ var testMock = require('./domain_ref_base.json'); // NOTE: this tolerance is in pixels var EQUALITY_TOLERANCE = 1e-2; +// Make an array from a finite iterable (for environments not having +// Array.from) +function iterToArray(iter) { + var a = []; + do { + var v = iter.next(); + // when done is true v.value is undefined + if(v.done) { return a; } + a.push(v.value); + } while(true); + return a; +} + // some made-up values for testing // NOTE: The pixel values are intentionally set so that 2*pixel is never greater // than the mock's margin. This is so that annotations are not unintentionally @@ -492,8 +503,8 @@ function checkAROPosition(gd, aro) { var aroPathBBox = getSVGElemScreenBBox(aroPath); var aroBBox = shapeToBBox(gd.layout, aro); var ret = compareBBoxes(aroBBox, aroPathBBox); - console.log('aroBBox: ' + JSON.stringify(aroBBox)); - console.log('aroPathBBox: ' + JSON.stringify(SVGTools.svgRectToObj(aroPathBBox))); + // console.log('aroBBox: ' + JSON.stringify(aroBBox)); + // console.log('aroPathBBox: ' + JSON.stringify(SVGTools.svgRectToObj(aroPathBBox))); return ret; } @@ -507,7 +518,7 @@ function testShape( yaroPos, aroType ) { - console.log('gd.layout: ', JSON.stringify(gd.layout)); + // console.log('gd.layout: ', JSON.stringify(gd.layout)); var aro = { type: aroType, line: { @@ -522,7 +533,7 @@ function testShape( // change to log axes if need be logAxisIfAxType(gd.layout, layout, 'x' + xAxNum, xaxisType); logAxisIfAxType(gd.layout, layout, 'y' + yAxNum, yaxisType); - console.log('layout: ', JSON.stringify(layout)); + // console.log('layout: ', JSON.stringify(layout)); return Plotly.relayout(gd, layout) .then(function(gd) { return checkAROPosition(gd, aro); @@ -699,7 +710,7 @@ function comboTestDescriptions(testCombos, desribe) { } function annotationTestCombos() { - var testCombos = Array.from(iterable.cartesianProduct([ + var testCombos = iterToArray(iterable.cartesianProduct([ axisTypes, axisTypes, axisPairs, aroPositionsX, aroPositionsY, arrowAxis ])); testCombos = testCombos.map( @@ -722,7 +733,7 @@ function annotationTestDescriptions() { function imageTestCombos() { - var testCombos = Array.from(iterable.cartesianProduct( + var testCombos = iterToArray(iterable.cartesianProduct( [ axisTypes, axisTypes, axisPairs, // axis reference types are contained in here @@ -749,7 +760,7 @@ function imageTestDescriptions() { } function shapeTestCombos() { - var testCombos = Array.from(iterable.cartesianProduct( + var testCombos = iterToArray(iterable.cartesianProduct( [ axisTypes, axisTypes, axisPairs, // axis reference types are contained in here diff --git a/test/jasmine/tests/domain_ref_interact_test.js b/test/jasmine/tests/domain_ref_interact_test.js index effa40389ec..6d79887ca2d 100644 --- a/test/jasmine/tests/domain_ref_interact_test.js +++ b/test/jasmine/tests/domain_ref_interact_test.js @@ -12,7 +12,7 @@ var delay = require('../assets/delay'); var mouseEvent = require('../assets/mouse_event'); // we have to use drag to move annotations for some reason var drag = require('../assets/drag'); -var SVGTools = require('../assets/svg_tools'); +// var SVGTools = require('../assets/svg_tools'); // color of the rectangles var rectColor1 = 'rgb(10, 20, 30)'; @@ -24,17 +24,17 @@ var arrowColor2 = 'rgb(231, 200, 200)'; var DELAY_TIME = 10; -function svgRectToJSON(svgrect) { - return JSON.stringify(SVGTools.svgRectToObj(svgrect)); -} +// function svgRectToJSON(svgrect) { +// return JSON.stringify(SVGTools.svgRectToObj(svgrect)); +// } function checkBBox(bboxBefore, bboxAfter, moveX, moveY) { // We print out the objects for sanity, because sometimes Jasmine says a // test passed when it actually did nothing! - console.log('bboxBefore', svgRectToJSON(bboxBefore)); - console.log('bboxAfter', svgRectToJSON(bboxAfter)); - console.log('moveX', moveX); - console.log('moveY', moveY); + // console.log('bboxBefore', svgRectToJSON(bboxBefore)); + // console.log('bboxAfter', svgRectToJSON(bboxAfter)); + // console.log('moveX', moveX); + // console.log('moveY', moveY); expect(bboxAfter.x).toBeCloseTo(bboxBefore.x + moveX, 2); expect(bboxAfter.y).toBeCloseTo(bboxBefore.y + moveY, 2); } @@ -84,7 +84,7 @@ function testAnnotationMoveLabel(objectColor, moveX, moveY) { pos0: dragPos0(bboxBefore) }; optLabelDrag.dpos = [moveX, moveY]; - console.log('optLabelDrag', optLabelDrag); + // console.log('optLabelDrag', optLabelDrag); // drag the label, this will make the arrow rotate around the arrowhead return (new Promise(function(resolve) { drag(optLabelDrag); resolve(); @@ -115,7 +115,7 @@ function testAnnotationMoveWhole(objectColor, arrowColor, moveX, moveY, corner) pos0: dragPos0(arrowBBoxBefore, corner) }; optArrowDrag.dpos = [moveX, moveY]; - console.log('optArrowDrag', optArrowDrag); + // console.log('optArrowDrag', optArrowDrag); // drag the whole annotation (new Promise(function(resolve) { drag(optArrowDrag); resolve(); diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 6fde13eb2ed..ddaaacbebc4 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -25,7 +25,7 @@ function makeTests(component, filter) { }); descriptions.forEach(function(d, i) { it(d, function(done) { - console.log('testing ' + d); + // console.log('testing ' + d); gd.id = 'graph-' + i; tests[i](function(v) { expect(v).toBe(true); From 1c51d72e484e93ab23d5c67612a96972edf8b379 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 29 Sep 2020 19:20:35 -0400 Subject: [PATCH 104/112] Removed domainRef argument from axes.coerceRef because it is always true wherever this is called. --- src/components/annotations/defaults.js | 4 ++-- src/components/images/defaults.js | 3 +-- src/components/shapes/defaults.js | 2 +- src/plots/cartesian/axes.js | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 0bc933b79b1..ed791e4a1cf 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -47,7 +47,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { var axLetter = axLetters[i]; // xref, yref - var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper', true); + var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper'); if(axRef !== 'paper') { var ax = Axes.getFromId(gdMock, axRef); @@ -61,7 +61,7 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { var arrowPosAttr = 'a' + axLetter; // axref, ayref var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel', - ['pixel', 'paper'], true); + ['pixel', 'paper']); // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 5f3ea0d8503..7e85e6508f8 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -49,8 +49,7 @@ function imageDefaults(imageIn, imageOut, fullLayout) { for(var i = 0; i < 2; i++) { // 'paper' is the fallback axref var axLetter = axLetters[i]; - var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper', undefined, - true); + var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper', undefined); if(axRef !== 'paper') { var ax = Axes.getFromId(gdMock, axRef); diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 46519e006b8..c6ab8ea3b6c 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -64,7 +64,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { // xref, yref var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, - 'paper', true); + 'paper'); var axRefType = Axes.getRefType(axRef); if(axRefType === 'range') { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 783b1af27db..6b7beb12c03 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -87,7 +87,7 @@ function expandRange(range) { * domainRef: true if ' domain' should be appended to the axis items in the list * of possible values for this axis reference. */ -axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption, domainRef) { +axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) { var axLetter = attr.charAt(attr.length - 1); var axlist = gd._fullLayout._subplots[axLetter + 'axis']; var refAttr = attr + 'ref'; @@ -95,7 +95,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption if(!dflt) dflt = axlist[0] || (typeof extraOption === 'string' ? extraOption : extraOption[0]); if(!extraOption) extraOption = dflt; - if(domainRef) axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); + axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); // data-ref annotations are not supported in gl2d yet From 71b0cc49a800a112def013781d1c25391dc2865d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 30 Sep 2020 10:03:47 -0400 Subject: [PATCH 105/112] Syntax --- src/components/annotations/attributes.js | 24 ++++++++++---------- test/jasmine/assets/domain_ref/components.js | 9 ++++---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index eacade0b0f0..2dd2fe8b6a3 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -16,12 +16,12 @@ var axisPlaceableObjs = require('../../constants/axis_placeable_objects'); function arrowAxisRefDescription(axis) { return [ - 'In order for absolute positioning of the arrow to work, *a'+axis+ - 'ref* must be exactly the same as *'+axis+'ref*, otherwise *a'+axis+ + 'In order for absolute positioning of the arrow to work, *a' + axis + + 'ref* must be exactly the same as *' + axis + 'ref*, otherwise *a' + axis + 'ref* will revert to *pixel* (explained next).', - 'For relative positioning, *a'+axis+'ref* can be set to *pixel*,', - 'in which case the *a'+axis+'* value is specified in pixels', - 'relative to *'+axis+'*.', + 'For relative positioning, *a' + axis + 'ref* can be set to *pixel*,', + 'in which case the *a' + axis + '* value is specified in pixels', + 'relative to *' + axis + '*.', 'Absolute positioning is useful', 'for trendline annotations which should continue to indicate', 'the correct trend when zoomed. Relative positioning is useful', @@ -29,15 +29,15 @@ function arrowAxisRefDescription(axis) { ].join(' '); } -function arrowCoordinateDescription(axis,lower,upper) { +function arrowCoordinateDescription(axis, lower, upper) { return [ 'Sets the', axis, 'component of the arrow tail about the arrow head.', - 'If `a'+axis+'ref` is `pixel`, a positive (negative)', + 'If `a' + axis + 'ref` is `pixel`, a positive (negative)', 'component corresponds to an arrow pointing', - 'from', upper, 'to', lower, '('+lower, 'to', upper+').', - 'If `a'+axis+'ref` is not `pixel` and is exactly the same as `'+axis+'ref`,', + 'from', upper, 'to', lower, '(' + lower, 'to', upper + ').', + 'If `a' + axis + 'ref` is not `pixel` and is exactly the same as `' + axis + 'ref`,', 'this is an absolute value on that axis,', - 'like `'+axis+'`, specified in the same coordinates as `'+axis+'ref`.' + 'like `' + axis + '`, specified in the same coordinates as `' + axis + 'ref`.' ].join(' '); } @@ -281,7 +281,7 @@ module.exports = templatedArray('annotation', { role: 'info', editType: 'calc+arraydraw', description: [ - arrowCoordinateDescription('x','left','right') + arrowCoordinateDescription('x', 'left', 'right') ].join(' ') }, ay: { @@ -289,7 +289,7 @@ module.exports = templatedArray('annotation', { role: 'info', editType: 'calc+arraydraw', description: [ - arrowCoordinateDescription('y','top','bottom') + arrowCoordinateDescription('y', 'top', 'bottom') ].join(' ') }, axref: { diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index b322b6439f9..e56042d7fd8 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -30,12 +30,11 @@ var EQUALITY_TOLERANCE = 1e-2; // Array.from) function iterToArray(iter) { var a = []; - do { - var v = iter.next(); - // when done is true v.value is undefined - if(v.done) { return a; } + var v; + // when done is true v.value is undefined + for(v = iter.next(); !v.done; v = iter.next()) { a.push(v.value); - } while(true); + } return a; } From a31a94aef38325d4984df10a0de0a5d557a80a5d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 1 Oct 2020 14:47:37 -0400 Subject: [PATCH 106/112] Removed testnumber file You can set testnumber in test/jasmine/tests/domain_ref_test.js if you want to run a single test. --- test/jasmine/assets/domain_ref/testnumber.js | 3 --- test/jasmine/tests/domain_ref_test.js | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 test/jasmine/assets/domain_ref/testnumber.js diff --git a/test/jasmine/assets/domain_ref/testnumber.js b/test/jasmine/assets/domain_ref/testnumber.js deleted file mode 100644 index 8fa2dc16313..00000000000 --- a/test/jasmine/assets/domain_ref/testnumber.js +++ /dev/null @@ -1,3 +0,0 @@ -// module.exports can be set to a specific test number to run just a single -// test, otherwise if set to undefined, runs all the tests. -module.exports = undefined; diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index ddaaacbebc4..31b0d895493 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -4,8 +4,8 @@ var domainRefComponents = require('../assets/domain_ref/components'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var Plotly = require('../../../lib/index'); -// optionally specify a test number in a file to run just a single test -var testNumber = require('../assets/domain_ref/testnumber'); +// optionally specify a test number to run just a single test +var testNumber = undefined; function makeTests(component, filter) { return function() { From 472c1358621c950b1239b9bae6adde3cc63b4c23 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 1 Oct 2020 18:00:11 -0400 Subject: [PATCH 107/112] changed shape / annotation / image function prototypes now they use opt instead of many arguments --- test/jasmine/assets/domain_ref/components.js | 57 +++++++++++++------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index e56042d7fd8..d6cee2977f8 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -119,7 +119,6 @@ var yAnchors = ['top', 'middle', 'bottom']; // plot so you can use d3.select('g image').node() var aroColor = 'rgb(50, 100, 150)'; - // acts on an Object representing a aro which could be a line or a rect // DEPRECATED function aroFromAROPos(aro, axletter, axnum, aropos) { @@ -134,7 +133,6 @@ function aroFromAROPos(aro, axletter, axnum, aropos) { } } - // {axid} is the axis id, e.g., x2, y, etc. // {ref} is ['range'|'domain'|'paper'] function makeAxRef(axid, ref) { @@ -228,8 +226,20 @@ function annaxscale(ac, c0) { // of the arrow doesn't change where the arrow meets the text box. // xaxistype can be linear|log, only used if xref has type 'range' or 'domain', // same for yaxistype and yref -function annotationTest(gd, layout, x0, y0, ax, ay, xref, yref, axref, ayref, - xaxistype, yaxistype, xid, yid) { +function annotationTest(gd, layout, opt) { + var x0 = opt.x0; + var y0 = opt.y0; + var ax = opt.ax; + var ay = opt.ay; + var xref = opt.xref; + var yref = opt.yref; + var axref = opt.axref; + var ayref = opt.ayref; + var xaxistype = opt.xaxistype; + var yaxistype = opt.yaxistype; + var xid = opt.xid; + var yid = opt.yid; + // Take the log of values corresponding to log axes. This is because the // test is designed to make predicting the pixel positions easy, and it's // easiest when we work with the logarithm of values on log axes (doubling @@ -432,7 +442,6 @@ function imageToBBox(layout, img) { return bbox; } - function coordsEq(a, b) { if(a && b) { return Math.abs(a - b) < EQUALITY_TOLERANCE; @@ -465,8 +474,20 @@ function findImage(id) { return ret; } -function imageTest(gd, layout, xaxtype, yaxtype, x, y, sizex, sizey, xanchor, - yanchor, xref, yref, xid, yid) { +function imageTest(gd, layout, opt) { + var xaxtype = opt.xaxtype; + var yaxtype = opt.yaxtype; + var x = opt.x; + var y = opt.y; + var sizex = opt.sizex; + var sizey = opt.sizey; + var xanchor = opt.xanchor; + var yanchor = opt.yanchor; + var xref = opt.xref; + var yref = opt.yref; + var xid = opt.xid; + var yid = opt.yid; + var image = { x: x, y: y, @@ -507,16 +528,17 @@ function checkAROPosition(gd, aro) { return ret; } -function testShape( +function shapeTest( gd, - xAxNum, - xaxisType, - xaroPos, - yAxNum, - yaxisType, - yaroPos, - aroType -) { + opt) { + var xAxNum = opt.xAxNum; + var xaxisType = opt.xaxisType; + var xaroPos = opt.xaroPos; + var yAxNum = opt.yAxNum; + var yaxisType = opt.yaxisType; + var yaroPos = opt.yaroPos; + var aroType = opt.aroType; + // console.log('gd.layout: ', JSON.stringify(gd.layout)); var aro = { type: aroType, @@ -572,7 +594,7 @@ function testShapeCombo(combo, assert, gd) { var yAxNum = axispair[1].substr(1); return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { - return testShape(gd, xAxNum, xaxisType, xaroPos, yAxNum, yaxisType, yaroPos, + return shapeTest(gd, xAxNum, xaxisType, xaroPos, yAxNum, yaxisType, yaroPos, shapeType); }).then(function(testRet) { assert(testRet); @@ -730,7 +752,6 @@ function annotationTestDescriptions() { return comboTestDescriptions(testCombos, describeAnnotationComboTest); } - function imageTestCombos() { var testCombos = iterToArray(iterable.cartesianProduct( [ From 5d586dd2396f8256e89bc8d7996c34a768f0769b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 2 Oct 2020 09:39:58 -0400 Subject: [PATCH 108/112] Use dicts in the function calls with many parameters --- test/jasmine/assets/domain_ref/components.js | 47 ++++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/test/jasmine/assets/domain_ref/components.js b/test/jasmine/assets/domain_ref/components.js index d6cee2977f8..bb481cf7842 100644 --- a/test/jasmine/assets/domain_ref/components.js +++ b/test/jasmine/assets/domain_ref/components.js @@ -594,8 +594,15 @@ function testShapeCombo(combo, assert, gd) { var yAxNum = axispair[1].substr(1); return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { - return shapeTest(gd, xAxNum, xaxisType, xaroPos, yAxNum, yaxisType, yaroPos, - shapeType); + return shapeTest(gd, + {xAxNum: xAxNum, + xaxisType: xaxisType, + xaroPos: xaroPos, + yAxNum: yAxNum, + yaxisType: yaxisType, + yaroPos: yaroPos, + aroType: shapeType, + }); }).then(function(testRet) { assert(testRet); }); @@ -644,10 +651,21 @@ function testImageCombo(combo, assert, gd) { var yref = makeAxRef(yid, aroposy.ref); return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { - return imageTest(gd, {}, axistypex, axistypey, - aroposx.value[0], aroposy.value[0], aroposx.size, - aroposy.size, - xanchor, yanchor, xref, yref, xid, yid); + return imageTest(gd, {}, + { + xaxtype: axistypex, + yaxtype: axistypey, + x: aroposx.value[0], + y: aroposy.value[0], + sizex: aroposx.size, + sizey: aroposy.size, + xanchor: xanchor, + yanchor: yanchor, + xref: xref, + yref: yref, + xid: xid, + yid: yid, + }); }).then(function(testRet) { assert(testRet); }); @@ -698,8 +716,21 @@ function testAnnotationCombo(combo, assert, gd) { var ay = ayref === 'pixel' ? aroposy.pixel : aroposy.value[1]; return Plotly.newPlot(gd, Lib.extendDeep({}, testMock)) .then(function(gd) { - return annotationTest(gd, {}, x0, y0, ax, ay, xref, yref, axref, - ayref, axistypex, axistypey, xid, yid); + return annotationTest(gd, {}, + { + x0: x0, + y0: y0, + ax: ax, + ay: ay, + xref: xref, + yref: yref, + axref: axref, + ayref: ayref, + axistypex: axistypex, + axistypey: axistypey, + xid: xid, + yid: yid, + }); }).then(function(testRet) { assert(testRet); }); From 7f5300c064386d9ebdd14524af79b446968296d0 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 2 Oct 2020 10:13:22 -0400 Subject: [PATCH 109/112] Fixed syntax error This wasn't picked up by a local run of `npm run test-syntax` --- test/jasmine/tests/domain_ref_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 31b0d895493..0e8f52c048e 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -5,7 +5,7 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var Plotly = require('../../../lib/index'); // optionally specify a test number to run just a single test -var testNumber = undefined; +var testNumber; function makeTests(component, filter) { return function() { From 35712fe5e689558746eae0319527ad88a824699c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 5 Oct 2020 17:25:04 -0400 Subject: [PATCH 110/112] use xIsDomain, yIsDomain, no more axes.addAxRefDomainCoerceRefExtra --- src/components/images/draw.js | 10 ++++++---- src/plots/cartesian/axes.js | 26 -------------------------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 0567fbecdbb..50378e37802 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -131,18 +131,20 @@ module.exports = function draw(gd) { // Axes if specified var xa = Axes.getFromId(gd, d.xref); var ya = Axes.getFromId(gd, d.yref); + var xIsDomain = Axes.getRefType(d.xref) === 'domain'; + var yIsDomain = Axes.getRefType(d.yref) === 'domain'; var size = fullLayout._size; var width, height; if(xa !== undefined) { - width = ((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) ? + width = ((typeof(d.xref) === 'string') && xIsDomain) ? xa._length * d.sizex : Math.abs(xa.l2p(d.sizex) - xa.l2p(0)); } else { width = d.sizex * size.w; } if(ya !== undefined) { - height = ((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) ? + height = ((typeof(d.yref) === 'string') && yIsDomain) ? ya._length * d.sizey : Math.abs(ya.l2p(d.sizey) - ya.l2p(0)); } else { @@ -158,7 +160,7 @@ module.exports = function draw(gd) { // Final positions var xPos, yPos; if(xa !== undefined) { - xPos = ((typeof(d.xref) === 'string') && d.xref.endsWith(' domain')) ? + xPos = ((typeof(d.xref) === 'string') && xIsDomain) ? xa._length * d.x + xa._offset : xa.r2p(d.x) + xa._offset; } else { @@ -166,7 +168,7 @@ module.exports = function draw(gd) { } xPos += xOffset; if(ya !== undefined) { - yPos = ((typeof(d.yref) === 'string') && d.yref.endsWith(' domain')) ? + yPos = ((typeof(d.yref) === 'string') && yIsDomain) ? // consistent with "paper" yref value, where positive values // move up the page ya._length * (1 - d.y) + ya._offset : diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 0fe5bde84c8..120c35e5acb 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -84,8 +84,6 @@ function expandRange(range) { * extraOption if there is no axis) * extraOption: aside from existing axes with this letter, what non-axis value is allowed? * Only required if it's different from `dflt` - * domainRef: true if ' domain' should be appended to the axis items in the list - * of possible values for this axis reference. */ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) { var axLetter = attr.charAt(attr.length - 1); @@ -126,30 +124,6 @@ axes.getRefType = function(ar) { if(/( domain)$/.test(ar)) { return 'domain'; } else { return 'range'; } }; -/* - * Add the specified axis letter and number + " domain" to the extras for - * coerceRef. - * - * container: the object holding the [xyz]ref keys, e.g., a shape. - * axLetter: the letter of the axis of interest. - * coerceRefExtras: the current list of extras for coerceRef that will be - * appended to. - * - */ -axes.addAxRefDomainCoerceRefExtra = function(container, axLetter, coerceRefExtras) { - var axNumMatch = ( - container[axLetter + 'ref'] ? - container[axLetter + 'ref'].match(/[xyz]([0-9]*)/) : - undefined - ); - if(axNumMatch) { - var axNum = axNumMatch[1]; - coerceRefExtras = coerceRefExtras.concat( - (axNum !== undefined) ? [axLetter + axNum + ' domain'] : []); - } - return coerceRefExtras; -}; - /* * coerce position attributes (range-type) that can be either on axes or absolute * (paper or pixel) referenced. The biggest complication here is that we don't know From a3d561c90b038b39ce496d73c89d312760894000 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 5 Oct 2020 17:33:17 -0400 Subject: [PATCH 111/112] Just check that axRefType isn't range in axes.coercePosition --- src/plots/cartesian/axes.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 120c35e5acb..1c4937e1812 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -147,19 +147,17 @@ axes.getRefType = function(ar) { * - for other types: coerce them to numbers */ axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) { - var cleanPos, pos, axRefType; - axRefType = axes.getRefType(axRef); - if(axRefType === 'paper' || axRefType === 'pixel' || axRefType === 'domain') { + var cleanPos, pos; + var axRefType = axes.getRefType(axRef); + if(axRefType !== 'range') { cleanPos = Lib.ensureNumber; pos = coerce(attr, dflt); } else { - // if axRef is 'range' or undefined we will end up here var ax = axes.getFromId(gd, axRef); dflt = ax.fraction2r(dflt); pos = coerce(attr, dflt); cleanPos = ax.cleanPos; } - containerOut[attr] = cleanPos(pos); }; From dcc27f405a33692cdb7851c8d41427eef063f93e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 5 Oct 2020 18:09:52 -0400 Subject: [PATCH 112/112] Loop over component names in domain_ref_test --- test/jasmine/tests/domain_ref_test.js | 32 ++++++++------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/test/jasmine/tests/domain_ref_test.js b/test/jasmine/tests/domain_ref_test.js index 0e8f52c048e..79ab84b101e 100644 --- a/test/jasmine/tests/domain_ref_test.js +++ b/test/jasmine/tests/domain_ref_test.js @@ -37,26 +37,12 @@ function makeTests(component, filter) { }; } -describe('Test annotations', makeTests(domainRefComponents.annotations, - function(f, i) { - if(testNumber === undefined) { - return true; - } - return i === testNumber; - })); - -describe('Test images', makeTests(domainRefComponents.images, - function(f, i) { - if(testNumber === undefined) { - return true; - } - return i === testNumber; - })); - -describe('Test shapes', makeTests(domainRefComponents.shapes, - function(f, i) { - if(testNumber === undefined) { - return true; - } - return i === testNumber; - })); +['annotations', 'images', 'shapes'].forEach(function(componentName) { + describe('Test ' + componentName, makeTests(domainRefComponents[componentName], + function(f, i) { + if(testNumber === undefined) { + return true; + } + return i === testNumber; + })); +});