From b9a5d35f55cc40464334b694ec7ce80424eae45b Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Tue, 29 Sep 2020 10:49:26 +1000 Subject: [PATCH 01/63] 888: transform hover event coords to fit css scaling transform --- src/components/fx/hover.js | 7 ++++ src/lib/dom.js | 77 +++++++++++++++++++++++++++++++++++++- src/lib/index.js | 6 +++ src/lib/matrix.js | 49 ++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 3b43a069bc3..4ffe44cbbc0 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -72,6 +72,8 @@ var HOVERTEXTPAD = constants.HOVERTEXTPAD; exports.hover = function hover(gd, evt, subplot, noHoverEvent) { gd = Lib.getGraphDiv(gd); + evt.inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(evt.target)); + Lib.throttle( gd._fullLayout._uid + constants.HOVERID, constants.HOVERMINTIME, @@ -336,6 +338,11 @@ function _hover(gd, evt, subplot, noHoverEvent) { xpx = evt.clientX - dbb.left; ypx = evt.clientY - dbb.top; + var transformedCoords = Lib.apply2DTransform(evt.inverseTransform)(xpx, ypx); + + xpx = transformedCoords[0]; + ypx = transformedCoords[1]; + // in case hover was called from mouseout into hovertext, // it's possible you're not actually over the plot anymore if(xpx < 0 || xpx > xaArray[0]._length || ypx < 0 || ypx > yaArray[0]._length) { diff --git a/src/lib/dom.js b/src/lib/dom.js index 6bc7760d253..fda2e625143 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -10,6 +10,7 @@ var d3 = require('d3'); var loggers = require('./loggers'); +var matrix = require('./matrix'); /** * Allow referencing a graph DOM element either directly @@ -91,11 +92,85 @@ function deleteRelatedStyleRule(uid) { if(style) removeElement(style); } +function getFullTransformMatrix(element) { + var ancestors = getElementAncestors(element); + // the identity matrix + var transform = [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1] + ]; + ancestors.forEach((ancestor) => { + var ancestor_transform = getElementTransformMatrix(ancestor); + if (ancestor_transform) + transform = matrix.dot(transform, matrix.convertCssMatrix(ancestor_transform)); + }); + return transform; +} + +/** + * transforms a rect with {left, top, right?, bottom?, width?, height?} according to an element's css transform styles + */ +function transformRectToNode(element, rect) { + var inverse = matrix.inverseTransformMatrix(getFullTransformMatrix(element)); + var at = matrix.apply2DTransform2(inverse); + var rectArray = [ + rect.left, + rect.top, + rect.hasOwnProperty('right') ? rect.right : rect.left + rect.width, + rect.hasOwnProperty('bottom') ? rect.bottom : rect.top + rect.height + ]; + var transformed = at(rectArray); + return { + left: transformed[0], + top: transformed[1], + right: transformed[2], + bottom: transformed[3], + width: transformed[2] - transformed[0], + height: transformed[3] - transformed[1] + }; +} + +/** + * extracts and parses the 2d css style transform matrix from some element + */ +function getElementTransformMatrix(element) { + const style = window.getComputedStyle(element, null); + const transform = style.getPropertyValue("-webkit-transform") || + style.getPropertyValue("-moz-transform") || + style.getPropertyValue("-ms-transform") || + style.getPropertyValue("-o-transform") || + style.getPropertyValue("transform"); + if (transform === 'none') + return null; + // the slice is because the transform string returns eg "matrix(0.5, 0, 1, 0, 1, 1)" + return transform.slice(7, -1).split(',').map(n => parseFloat(n)); +} +/** + * retrieve all DOM elements that are ancestors of the specified one (including itself) + */ +function getElementAncestors(element) { + const elements = []; + while (isTransformableElement(element)) { + elements.push(element); + element = element.parentElement; + } + return elements; +} + +function isTransformableElement(element) { + return element && (element instanceof Element || element instanceof HTMLElement); +} + module.exports = { getGraphDiv: getGraphDiv, isPlotDiv: isPlotDiv, removeElement: removeElement, addStyleRule: addStyleRule, addRelatedStyleRule: addRelatedStyleRule, - deleteRelatedStyleRule: deleteRelatedStyleRule + deleteRelatedStyleRule: deleteRelatedStyleRule, + getFullTransformMatrix: getFullTransformMatrix, + getElementTransformMatrix: getElementTransformMatrix, + getElementAncestors: getElementAncestors, + transformRectToNode: transformRectToNode, }; diff --git a/src/lib/index.js b/src/lib/index.js index 157dd15542b..ea60422c085 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -90,6 +90,8 @@ lib.rotationMatrix = matrixModule.rotationMatrix; lib.rotationXYMatrix = matrixModule.rotationXYMatrix; lib.apply2DTransform = matrixModule.apply2DTransform; lib.apply2DTransform2 = matrixModule.apply2DTransform2; +lib.convertCssMatrix = matrixModule.convertCssMatrix; +lib.inverseTransformMatrix = matrixModule.inverseTransformMatrix; var anglesModule = require('./angles'); lib.deg2rad = anglesModule.deg2rad; @@ -145,6 +147,10 @@ lib.removeElement = domModule.removeElement; lib.addStyleRule = domModule.addStyleRule; lib.addRelatedStyleRule = domModule.addRelatedStyleRule; lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule; +lib.getFullTransformMatrix = domModule.getFullTransformMatrix; +lib.getElementTransformMatrix = domModule.getElementTransformMatrix; +lib.getElementAncestors = domModule.getElementAncestors; +lib.transformRectToNode = domModule.transformRectToNode; lib.clearResponsive = require('./clear_responsive'); diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 80f5a87e508..34c7269fb86 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -103,3 +103,52 @@ exports.apply2DTransform2 = function(transform) { return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4))); }; }; + +// applies a 2D transform to something with either the form {left, top, right, bottom} +// or {left, top, width, height}, and returns it in the same format +exports.apply2DTransformToRect = function(transform) { + var at = exports.apply2DTransform2(transform); + return function(rect) { + var rectArray = [ + rect.left, + rect.top, + rect.hasOwnProperty('right') ? rect.right : rect.left + rect.width, + rect.hasOwnProperty('bottom') ? rect.bottom : rect.top + rect.height + ]; + var transformed = at(rectArray); + return { + left: transformed[0], + top: transformed[1], + right: transformed[2], + bottom: transformed[3], + width: transformed[2] - transformed[0], + height: transformed[3] - transformed[1] + }; + } +} + +// converts a 2x3 css transform matrix, represented as a length 6 array, to a 3x3 matrix. +exports.convertCssMatrix = function(m) { + if (m.length != 6) + throw new Error("Css transform matrix not of length 6"); + return [ + [m[0], m[2], m[4]], + [m[1], m[3], m[5]], + [0, 0, 1 ] + ]; +} + +// find the inverse for a 3x3 affine transform matrix +exports.inverseTransformMatrix = function(m) { + const determinant = m[0][0] * m[1][1] - m[1][0] * m[0][1]; + if (Math.abs(determinant) < Number.EPSILON) + throw new Error("Matrix is singular"); + var inv = 1.0 / determinant; + var invTranslateX = inv * (-m[1][1] * m[0][2] + m[0][1] * m[1][2]); + var invTranslateY = inv * (m[1][0] * m[0][2] + -m[0][0] * m[1][2]); + return [ + [inv * m[1][1], inv * -m[0][1], invTranslateX], + [inv * -m[1][0], inv * m[0][0], invTranslateY], + [0, 0, 1] + ]; +} \ No newline at end of file From 5e6eee9675720b98b45db6776e94d8bb2d501f3a Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Mon, 12 Oct 2020 15:24:08 +1100 Subject: [PATCH 02/63] 888: fix cartesian dragbox --- src/plots/cartesian/dragbox.js | 74 +++++++++++++++++----------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 573362f0047..beb5393f105 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -326,7 +326,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var dragBBox = dragger.getBoundingClientRect(); x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; - box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; + box = Lib.transformRectToNode(gd, {left: x0, right: x0, w: 0, top: y0, bottom: y0, height: 0}); lum = gd._hmpixcount ? (gd._hmlumcount / gd._hmpixcount) : tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); @@ -348,15 +348,17 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var dx = Math.abs(x1 - x0); var dy = Math.abs(y1 - y0); - box.l = Math.min(x0, x1); - box.r = Math.max(x0, x1); - box.t = Math.min(y0, y1); - box.b = Math.max(y0, y1); + box.left = Math.min(x0, x1); + box.right = Math.max(x0, x1); + box.top = Math.min(y0, y1); + box.bottom = Math.max(y0, y1); + + box = Lib.transformRectToNode(gd, box); function noZoom() { zoomMode = ''; - box.r = box.l; - box.t = box.b; + box.right = box.left; + box.top = box.bottom; corners.attr('d', 'M0,0Z'); } @@ -365,12 +367,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { zoomMode = 'xy'; if(dx / pw > dy / ph) { dy = dx * ph / pw; - if(y0 > y1) box.t = y0 - dy; - else box.b = y0 + dy; + if(y0 > y1) box.top = y0 - dy; + else box.bottom = y0 + dy; } else { dx = dy * pw / ph; - if(x0 > x1) box.l = x0 - dx; - else box.r = x0 + dx; + if(x0 > x1) box.left = x0 - dx; + else box.right = x0 + dx; } corners.attr('d', xyCorners(box)); } else { @@ -380,13 +382,13 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(dx > MINZOOM || dy > MINZOOM) { zoomMode = 'xy'; - var r0 = Math.min(box.l / pw, (ph - box.b) / ph); - var r1 = Math.max(box.r / pw, (ph - box.t) / ph); + var r0 = Math.min(box.left / pw, (ph - box.bottom) / ph); + var r1 = Math.max(box.right / pw, (ph - box.top) / ph); - box.l = r0 * pw; - box.r = r1 * pw; - box.b = (1 - r0) * ph; - box.t = (1 - r1) * ph; + box.left = r0 * pw; + box.right = r1 * pw; + box.bottom = (1 - r0) * ph; + box.top = (1 - r1) * ph; corners.attr('d', xyCorners(box)); } else { noZoom(); @@ -398,22 +400,22 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(dx < MINDRAG || !xActive) { noZoom(); } else { - box.t = 0; - box.b = ph; + box.top = 0; + box.bottom = ph; zoomMode = 'x'; corners.attr('d', xCorners(box, y0)); } } else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) { - box.l = 0; - box.r = pw; + box.left = 0; + box.right = pw; zoomMode = 'y'; corners.attr('d', yCorners(box, x0)); } else { zoomMode = 'xy'; corners.attr('d', xyCorners(box)); } - box.w = box.r - box.l; - box.h = box.b - box.t; + box.width = box.right - box.left; + box.height = box.bottom - box.top; if(zoomMode) zoomDragged = true; gd._dragged = zoomDragged; @@ -429,11 +431,11 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // TODO: edit linked axes in zoomAxRanges and in dragTail if(zoomMode === 'xy' || zoomMode === 'x') { - zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes); + zoomAxRanges(xaxes, box.left / pw, box.right / pw, updates, links.xaxes); updateMatchedAxRange('x', updates); } if(zoomMode === 'xy' || zoomMode === 'y') { - zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes); + zoomAxRanges(yaxes, (ph - box.bottom) / ph, (ph - box.top) / ph, updates, links.yaxes); updateMatchedAxRange('y', updates); } } @@ -1091,8 +1093,8 @@ function makeCorners(zoomlayer, xs, ys) { function updateZoombox(zb, corners, box, path0, dimmed, lum) { zb.attr('d', - path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) + - 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z'); + path0 + 'M' + (box.left) + ',' + (box.top) + 'v' + (box.height) + + 'h' + (box.width) + 'v-' + (box.height) + 'h-' + (box.width) + 'Z'); transitionZoombox(zb, corners, dimmed, lum); } @@ -1123,30 +1125,30 @@ function showDoubleClickNotifier(gd) { function xCorners(box, y0) { return 'M' + - (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) + + (box.left - 0.5) + ',' + (y0 - MINZOOM - 0.5) + 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' + - (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) + + (box.right + 0.5) + ',' + (y0 - MINZOOM - 0.5) + 'h3v' + (2 * MINZOOM + 1) + 'h-3Z'; } function yCorners(box, x0) { return 'M' + - (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) + + (x0 - MINZOOM - 0.5) + ',' + (box.top - 0.5) + 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' + - (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) + + (x0 - MINZOOM - 0.5) + ',' + (box.bottom + 0.5) + 'v3h' + (2 * MINZOOM + 1) + 'v-3Z'; } function xyCorners(box) { - var clen = Math.floor(Math.min(box.b - box.t, box.r - box.l, MINZOOM) / 2); + var clen = Math.floor(Math.min(box.bottom - box.top, box.right - box.left, MINZOOM) / 2); return 'M' + - (box.l - 3.5) + ',' + (box.t - 0.5 + clen) + 'h3v' + (-clen) + + (box.left - 3.5) + ',' + (box.top - 0.5 + clen) + 'h3v' + (-clen) + 'h' + clen + 'v-3h-' + (clen + 3) + 'ZM' + - (box.r + 3.5) + ',' + (box.t - 0.5 + clen) + 'h-3v' + (-clen) + + (box.right + 3.5) + ',' + (box.top - 0.5 + clen) + 'h-3v' + (-clen) + 'h' + (-clen) + 'v-3h' + (clen + 3) + 'ZM' + - (box.r + 3.5) + ',' + (box.b + 0.5 - clen) + 'h-3v' + clen + + (box.right + 3.5) + ',' + (box.bottom + 0.5 - clen) + 'h-3v' + clen + 'h' + (-clen) + 'v3h' + (clen + 3) + 'ZM' + - (box.l - 3.5) + ',' + (box.b + 0.5 - clen) + 'h3v' + clen + + (box.left - 3.5) + ',' + (box.bottom + 0.5 - clen) + 'h3v' + clen + 'h' + clen + 'v3h-' + (clen + 3) + 'Z'; } From 1741a4a5c1b167282105efcb3519c44235a3b3c5 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 15:00:41 -0400 Subject: [PATCH 03/63] fix syntax in new files --- src/lib/dom.js | 49 +++++++++++++++++++++++++---------------------- src/lib/matrix.js | 34 +++++++++++++++++--------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/lib/dom.js b/src/lib/dom.js index fda2e625143..d0d5047314b 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -96,27 +96,28 @@ function getFullTransformMatrix(element) { var ancestors = getElementAncestors(element); // the identity matrix var transform = [ - [1, 0, 0], + [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; - ancestors.forEach((ancestor) => { - var ancestor_transform = getElementTransformMatrix(ancestor); - if (ancestor_transform) - transform = matrix.dot(transform, matrix.convertCssMatrix(ancestor_transform)); + ancestors.forEach(function(ancestor) { + var ancestorTransform = getElementTransformMatrix(ancestor); + if(ancestorTransform) { + transform = matrix.dot(transform, matrix.convertCssMatrix(ancestorTransform)); + } }); return transform; } /** * transforms a rect with {left, top, right?, bottom?, width?, height?} according to an element's css transform styles - */ + */ function transformRectToNode(element, rect) { var inverse = matrix.inverseTransformMatrix(getFullTransformMatrix(element)); var at = matrix.apply2DTransform2(inverse); var rectArray = [ - rect.left, - rect.top, + rect.left, + rect.top, rect.hasOwnProperty('right') ? rect.right : rect.left + rect.width, rect.hasOwnProperty('bottom') ? rect.bottom : rect.top + rect.height ]; @@ -133,27 +134,29 @@ function transformRectToNode(element, rect) { /** * extracts and parses the 2d css style transform matrix from some element - */ + */ function getElementTransformMatrix(element) { - const style = window.getComputedStyle(element, null); - const transform = style.getPropertyValue("-webkit-transform") || - style.getPropertyValue("-moz-transform") || - style.getPropertyValue("-ms-transform") || - style.getPropertyValue("-o-transform") || - style.getPropertyValue("transform"); - if (transform === 'none') - return null; + var style = window.getComputedStyle(element, null); + var transform = ( + style.getPropertyValue('-webkit-transform') || + style.getPropertyValue('-moz-transform') || + style.getPropertyValue('-ms-transform') || + style.getPropertyValue('-o-transform') || + style.getPropertyValue('transform') + ); + + if(transform === 'none') return null; // the slice is because the transform string returns eg "matrix(0.5, 0, 1, 0, 1, 1)" - return transform.slice(7, -1).split(',').map(n => parseFloat(n)); + return transform.slice(7, -1).split(',').map(function(n) {return parseFloat(n);}); } /** * retrieve all DOM elements that are ancestors of the specified one (including itself) - */ + */ function getElementAncestors(element) { - const elements = []; - while (isTransformableElement(element)) { - elements.push(element); - element = element.parentElement; + var elements = []; + while(isTransformableElement(element)) { + elements.push(element); + element = element.parentElement; } return elements; } diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 34c7269fb86..5a3af26f8c4 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -90,7 +90,7 @@ exports.apply2DTransform = function(transform) { var args = arguments; if(args.length === 3) { args = args[0]; - }// from map + } // from map var xy = arguments.length === 1 ? args[0] : [args[0], args[1]]; return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2); }; @@ -110,8 +110,8 @@ exports.apply2DTransformToRect = function(transform) { var at = exports.apply2DTransform2(transform); return function(rect) { var rectArray = [ - rect.left, - rect.top, + rect.left, + rect.top, rect.hasOwnProperty('right') ? rect.right : rect.left + rect.width, rect.hasOwnProperty('bottom') ? rect.bottom : rect.top + rect.height ]; @@ -124,25 +124,29 @@ exports.apply2DTransformToRect = function(transform) { width: transformed[2] - transformed[0], height: transformed[3] - transformed[1] }; - } -} + }; +}; // converts a 2x3 css transform matrix, represented as a length 6 array, to a 3x3 matrix. exports.convertCssMatrix = function(m) { - if (m.length != 6) - throw new Error("Css transform matrix not of length 6"); + if(m.length !== 6) { + throw new Error('Css transform matrix not of length 6'); + } + return [ - [m[0], m[2], m[4]], - [m[1], m[3], m[5]], - [0, 0, 1 ] + [m[0], m[2], m[4]], + [m[1], m[3], m[5]], + [0, 0, 1] ]; -} +}; // find the inverse for a 3x3 affine transform matrix exports.inverseTransformMatrix = function(m) { - const determinant = m[0][0] * m[1][1] - m[1][0] * m[0][1]; - if (Math.abs(determinant) < Number.EPSILON) - throw new Error("Matrix is singular"); + var determinant = m[0][0] * m[1][1] - m[1][0] * m[0][1]; + if(Math.abs(determinant) < Number.EPSILON) { + throw new Error('Matrix is singular'); + } + var inv = 1.0 / determinant; var invTranslateX = inv * (-m[1][1] * m[0][2] + m[0][1] * m[1][2]); var invTranslateY = inv * (m[1][0] * m[0][2] + -m[0][0] * m[1][2]); @@ -151,4 +155,4 @@ exports.inverseTransformMatrix = function(m) { [inv * -m[1][0], inv * m[0][0], invTranslateY], [0, 0, 1] ]; -} \ No newline at end of file +}; From f0232c4a95a7008e34d8252d43697868c8b5d2a3 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 16:18:11 -0400 Subject: [PATCH 04/63] some minor changes --- src/lib/dom.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/dom.js b/src/lib/dom.js index d0d5047314b..2f62a98943c 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -93,14 +93,14 @@ function deleteRelatedStyleRule(uid) { } function getFullTransformMatrix(element) { - var ancestors = getElementAncestors(element); + var allAncestors = getElementAncestors(element); // the identity matrix var transform = [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; - ancestors.forEach(function(ancestor) { + allAncestors.forEach(function(ancestor) { var ancestorTransform = getElementTransformMatrix(ancestor); if(ancestorTransform) { transform = matrix.dot(transform, matrix.convertCssMatrix(ancestorTransform)); @@ -147,18 +147,18 @@ function getElementTransformMatrix(element) { if(transform === 'none') return null; // the slice is because the transform string returns eg "matrix(0.5, 0, 1, 0, 1, 1)" - return transform.slice(7, -1).split(',').map(function(n) {return parseFloat(n);}); + return transform.slice(7, -1).split(',').map(function(n) {return +n;}); } /** * retrieve all DOM elements that are ancestors of the specified one (including itself) */ function getElementAncestors(element) { - var elements = []; + var allElements = []; while(isTransformableElement(element)) { - elements.push(element); + allElements.push(element); element = element.parentElement; } - return elements; + return allElements; } function isTransformableElement(element) { From 3b63fe6629a2fd53dc0d24749813ec81f0ee9a1f Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 16:35:28 -0400 Subject: [PATCH 05/63] renaming variables and method since list of all elements includes the base element too --- src/lib/dom.js | 14 +++++++------- src/lib/index.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/dom.js b/src/lib/dom.js index 2f62a98943c..4f99b4a9cff 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -93,17 +93,17 @@ function deleteRelatedStyleRule(uid) { } function getFullTransformMatrix(element) { - var allAncestors = getElementAncestors(element); + var allElements = getElementAndAncestors(element); // the identity matrix var transform = [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; - allAncestors.forEach(function(ancestor) { - var ancestorTransform = getElementTransformMatrix(ancestor); - if(ancestorTransform) { - transform = matrix.dot(transform, matrix.convertCssMatrix(ancestorTransform)); + allElements.forEach(function(e) { + var t = getElementTransformMatrix(e); + if(t) { + transform = matrix.dot(transform, matrix.convertCssMatrix(t)); } }); return transform; @@ -152,7 +152,7 @@ function getElementTransformMatrix(element) { /** * retrieve all DOM elements that are ancestors of the specified one (including itself) */ -function getElementAncestors(element) { +function getElementAndAncestors(element) { var allElements = []; while(isTransformableElement(element)) { allElements.push(element); @@ -174,6 +174,6 @@ module.exports = { deleteRelatedStyleRule: deleteRelatedStyleRule, getFullTransformMatrix: getFullTransformMatrix, getElementTransformMatrix: getElementTransformMatrix, - getElementAncestors: getElementAncestors, + getElementAndAncestors: getElementAndAncestors, transformRectToNode: transformRectToNode, }; diff --git a/src/lib/index.js b/src/lib/index.js index ea60422c085..8c0e527dcef 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -149,7 +149,7 @@ lib.addRelatedStyleRule = domModule.addRelatedStyleRule; lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule; lib.getFullTransformMatrix = domModule.getFullTransformMatrix; lib.getElementTransformMatrix = domModule.getElementTransformMatrix; -lib.getElementAncestors = domModule.getElementAncestors; +lib.getElementAndAncestors = domModule.getElementAndAncestors; lib.transformRectToNode = domModule.transformRectToNode; lib.clearResponsive = require('./clear_responsive'); From 5c60d5e8e5c06604c9854169741d20d451274c49 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 19:34:27 -0400 Subject: [PATCH 06/63] lets not touch dragbox keys --- src/lib/dom.js | 12 +++--- src/plots/cartesian/dragbox.js | 74 +++++++++++++++++----------------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/lib/dom.js b/src/lib/dom.js index 4f99b4a9cff..b9837657831 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -123,12 +123,12 @@ function transformRectToNode(element, rect) { ]; var transformed = at(rectArray); return { - left: transformed[0], - top: transformed[1], - right: transformed[2], - bottom: transformed[3], - width: transformed[2] - transformed[0], - height: transformed[3] - transformed[1] + l: transformed[0], + t: transformed[1], + r: transformed[2], + b: transformed[3], + w: transformed[2] - transformed[0], + h: transformed[3] - transformed[1] }; } diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index beb5393f105..573362f0047 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -326,7 +326,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var dragBBox = dragger.getBoundingClientRect(); x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; - box = Lib.transformRectToNode(gd, {left: x0, right: x0, w: 0, top: y0, bottom: y0, height: 0}); + box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; lum = gd._hmpixcount ? (gd._hmlumcount / gd._hmpixcount) : tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); @@ -348,17 +348,15 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var dx = Math.abs(x1 - x0); var dy = Math.abs(y1 - y0); - box.left = Math.min(x0, x1); - box.right = Math.max(x0, x1); - box.top = Math.min(y0, y1); - box.bottom = Math.max(y0, y1); - - box = Lib.transformRectToNode(gd, box); + box.l = Math.min(x0, x1); + box.r = Math.max(x0, x1); + box.t = Math.min(y0, y1); + box.b = Math.max(y0, y1); function noZoom() { zoomMode = ''; - box.right = box.left; - box.top = box.bottom; + box.r = box.l; + box.t = box.b; corners.attr('d', 'M0,0Z'); } @@ -367,12 +365,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { zoomMode = 'xy'; if(dx / pw > dy / ph) { dy = dx * ph / pw; - if(y0 > y1) box.top = y0 - dy; - else box.bottom = y0 + dy; + if(y0 > y1) box.t = y0 - dy; + else box.b = y0 + dy; } else { dx = dy * pw / ph; - if(x0 > x1) box.left = x0 - dx; - else box.right = x0 + dx; + if(x0 > x1) box.l = x0 - dx; + else box.r = x0 + dx; } corners.attr('d', xyCorners(box)); } else { @@ -382,13 +380,13 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(dx > MINZOOM || dy > MINZOOM) { zoomMode = 'xy'; - var r0 = Math.min(box.left / pw, (ph - box.bottom) / ph); - var r1 = Math.max(box.right / pw, (ph - box.top) / ph); + var r0 = Math.min(box.l / pw, (ph - box.b) / ph); + var r1 = Math.max(box.r / pw, (ph - box.t) / ph); - box.left = r0 * pw; - box.right = r1 * pw; - box.bottom = (1 - r0) * ph; - box.top = (1 - r1) * ph; + box.l = r0 * pw; + box.r = r1 * pw; + box.b = (1 - r0) * ph; + box.t = (1 - r1) * ph; corners.attr('d', xyCorners(box)); } else { noZoom(); @@ -400,22 +398,22 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(dx < MINDRAG || !xActive) { noZoom(); } else { - box.top = 0; - box.bottom = ph; + box.t = 0; + box.b = ph; zoomMode = 'x'; corners.attr('d', xCorners(box, y0)); } } else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) { - box.left = 0; - box.right = pw; + box.l = 0; + box.r = pw; zoomMode = 'y'; corners.attr('d', yCorners(box, x0)); } else { zoomMode = 'xy'; corners.attr('d', xyCorners(box)); } - box.width = box.right - box.left; - box.height = box.bottom - box.top; + box.w = box.r - box.l; + box.h = box.b - box.t; if(zoomMode) zoomDragged = true; gd._dragged = zoomDragged; @@ -431,11 +429,11 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // TODO: edit linked axes in zoomAxRanges and in dragTail if(zoomMode === 'xy' || zoomMode === 'x') { - zoomAxRanges(xaxes, box.left / pw, box.right / pw, updates, links.xaxes); + zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes); updateMatchedAxRange('x', updates); } if(zoomMode === 'xy' || zoomMode === 'y') { - zoomAxRanges(yaxes, (ph - box.bottom) / ph, (ph - box.top) / ph, updates, links.yaxes); + zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes); updateMatchedAxRange('y', updates); } } @@ -1093,8 +1091,8 @@ function makeCorners(zoomlayer, xs, ys) { function updateZoombox(zb, corners, box, path0, dimmed, lum) { zb.attr('d', - path0 + 'M' + (box.left) + ',' + (box.top) + 'v' + (box.height) + - 'h' + (box.width) + 'v-' + (box.height) + 'h-' + (box.width) + 'Z'); + path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) + + 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z'); transitionZoombox(zb, corners, dimmed, lum); } @@ -1125,30 +1123,30 @@ function showDoubleClickNotifier(gd) { function xCorners(box, y0) { return 'M' + - (box.left - 0.5) + ',' + (y0 - MINZOOM - 0.5) + + (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) + 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' + - (box.right + 0.5) + ',' + (y0 - MINZOOM - 0.5) + + (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) + 'h3v' + (2 * MINZOOM + 1) + 'h-3Z'; } function yCorners(box, x0) { return 'M' + - (x0 - MINZOOM - 0.5) + ',' + (box.top - 0.5) + + (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) + 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' + - (x0 - MINZOOM - 0.5) + ',' + (box.bottom + 0.5) + + (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) + 'v3h' + (2 * MINZOOM + 1) + 'v-3Z'; } function xyCorners(box) { - var clen = Math.floor(Math.min(box.bottom - box.top, box.right - box.left, MINZOOM) / 2); + var clen = Math.floor(Math.min(box.b - box.t, box.r - box.l, MINZOOM) / 2); return 'M' + - (box.left - 3.5) + ',' + (box.top - 0.5 + clen) + 'h3v' + (-clen) + + (box.l - 3.5) + ',' + (box.t - 0.5 + clen) + 'h3v' + (-clen) + 'h' + clen + 'v-3h-' + (clen + 3) + 'ZM' + - (box.right + 3.5) + ',' + (box.top - 0.5 + clen) + 'h-3v' + (-clen) + + (box.r + 3.5) + ',' + (box.t - 0.5 + clen) + 'h-3v' + (-clen) + 'h' + (-clen) + 'v-3h' + (clen + 3) + 'ZM' + - (box.right + 3.5) + ',' + (box.bottom + 0.5 - clen) + 'h-3v' + clen + + (box.r + 3.5) + ',' + (box.b + 0.5 - clen) + 'h-3v' + clen + 'h' + (-clen) + 'v3h' + (clen + 3) + 'ZM' + - (box.left - 3.5) + ',' + (box.bottom + 0.5 - clen) + 'h3v' + clen + + (box.l - 3.5) + ',' + (box.b + 0.5 - clen) + 'h3v' + clen + 'h' + clen + 'v3h-' + (clen + 3) + 'Z'; } From 252947424ac881782e7d5e3fc9d830e360afcc47 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 19:35:45 -0400 Subject: [PATCH 07/63] drop unused functions --- src/lib/dom.js | 24 ------------------------ src/lib/index.js | 1 - 2 files changed, 25 deletions(-) diff --git a/src/lib/dom.js b/src/lib/dom.js index b9837657831..1fa0ae55476 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -109,29 +109,6 @@ function getFullTransformMatrix(element) { return transform; } -/** - * transforms a rect with {left, top, right?, bottom?, width?, height?} according to an element's css transform styles - */ -function transformRectToNode(element, rect) { - var inverse = matrix.inverseTransformMatrix(getFullTransformMatrix(element)); - var at = matrix.apply2DTransform2(inverse); - var rectArray = [ - rect.left, - rect.top, - rect.hasOwnProperty('right') ? rect.right : rect.left + rect.width, - rect.hasOwnProperty('bottom') ? rect.bottom : rect.top + rect.height - ]; - var transformed = at(rectArray); - return { - l: transformed[0], - t: transformed[1], - r: transformed[2], - b: transformed[3], - w: transformed[2] - transformed[0], - h: transformed[3] - transformed[1] - }; -} - /** * extracts and parses the 2d css style transform matrix from some element */ @@ -175,5 +152,4 @@ module.exports = { getFullTransformMatrix: getFullTransformMatrix, getElementTransformMatrix: getElementTransformMatrix, getElementAndAncestors: getElementAndAncestors, - transformRectToNode: transformRectToNode, }; diff --git a/src/lib/index.js b/src/lib/index.js index 8c0e527dcef..40ec6da6f97 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -150,7 +150,6 @@ lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule; lib.getFullTransformMatrix = domModule.getFullTransformMatrix; lib.getElementTransformMatrix = domModule.getElementTransformMatrix; lib.getElementAndAncestors = domModule.getElementAndAncestors; -lib.transformRectToNode = domModule.transformRectToNode; lib.clearResponsive = require('./clear_responsive'); From b8e7c6a319fcd8dea76f907ed53d0cf45d6ac550 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 20:19:04 -0400 Subject: [PATCH 08/63] declare isMiddle variable in hover --- src/components/fx/hover.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 4ffe44cbbc0..afb216ed4d9 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1500,7 +1500,8 @@ function alignHoverText(hoverLabels, rotateLabels) { var offsetX = 0; var offsetY = d.offset; - if(anchor === 'middle') { + var isMiddle = anchor === 'middle'; + if(isMiddle) { txx -= d.tx2width / 2; tx2x += d.txwidth / 2 + HOVERTEXTPAD; } @@ -1509,7 +1510,8 @@ function alignHoverText(hoverLabels, rotateLabels) { offsetX = d.offset * YSHIFTX; } - g.select('path').attr('d', anchor === 'middle' ? + g.select('path') + .attr('d', isMiddle ? // middle aligned: rect centered on data ('M-' + (d.bx / 2 + d.tx2width / 2) + ',' + (offsetY - d.by / 2) + 'h' + d.bx + 'v' + d.by + 'h-' + d.bx + 'Z') : @@ -1529,12 +1531,12 @@ function alignHoverText(hoverLabels, rotateLabels) { if(textAlign !== 'auto') { if(textAlign === 'left' && anchor !== 'start') { tx.attr('text-anchor', 'start'); - posX = anchor === 'middle' ? + posX = isMiddle ? -d.bx / 2 - d.tx2width / 2 + HOVERTEXTPAD : -d.bx - HOVERTEXTPAD; } else if(textAlign === 'right' && anchor !== 'end') { tx.attr('text-anchor', 'end'); - posX = anchor === 'middle' ? + posX = isMiddle ? d.bx / 2 - d.tx2width / 2 - HOVERTEXTPAD : d.bx + HOVERTEXTPAD; } From 0b4f74a881a04a93f44e3eba494c4dcb307ef039 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 20:58:52 -0400 Subject: [PATCH 09/63] correct size of hover box and position of text --- src/components/fx/hover.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index afb216ed4d9..c1bc8611cc4 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -194,7 +194,7 @@ exports.loneHover = function loneHover(hoverItems, opts) { d.offset -= anchor; }); - alignHoverText(hoverLabel, fullOpts.rotateLabels); + alignHoverText(hoverLabel, fullOpts.rotateLabels, false); return multiHover ? hoverLabel : hoverLabel.node(); }; @@ -723,7 +723,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { if(!helpers.isUnifiedHover(hovermode)) { hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); - alignHoverText(hoverLabels, rotateLabels); + alignHoverText(hoverLabels, rotateLabels, evt); } // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true @@ -1484,7 +1484,12 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) { } } -function alignHoverText(hoverLabels, rotateLabels) { +function alignHoverText(hoverLabels, rotateLabels, evt) { + var scaleX = evt ? evt.inverseTransform[0][0] : 1; + var scaleY = evt ? evt.inverseTransform[1][1] : 1; + var pX = function(x) { return x * scaleX; }; + var pY = function(y) { return y * scaleY; }; + // finally set the text positioning relative to the data and draw the // box around it hoverLabels.each(function(d) { @@ -1513,19 +1518,19 @@ function alignHoverText(hoverLabels, rotateLabels) { g.select('path') .attr('d', isMiddle ? // middle aligned: rect centered on data - ('M-' + (d.bx / 2 + d.tx2width / 2) + ',' + (offsetY - d.by / 2) + - 'h' + d.bx + 'v' + d.by + 'h-' + d.bx + 'Z') : + ('M-' + pX(d.bx / 2 + d.tx2width / 2) + ',' + pY(offsetY - d.by / 2) + + 'h' + pX(d.bx) + 'v' + pY(d.by) + 'h-' + pX(d.bx) + 'Z') : // left or right aligned: side rect with arrow to data - ('M0,0L' + (horzSign * HOVERARROWSIZE + offsetX) + ',' + (HOVERARROWSIZE + offsetY) + - 'v' + (d.by / 2 - HOVERARROWSIZE) + - 'h' + (horzSign * d.bx) + - 'v-' + d.by + - 'H' + (horzSign * HOVERARROWSIZE + offsetX) + - 'V' + (offsetY - HOVERARROWSIZE) + + ('M0,0L' + pX(horzSign * HOVERARROWSIZE + offsetX) + ',' + pY(HOVERARROWSIZE + offsetY) + + 'v' + pY(d.by / 2 - HOVERARROWSIZE) + + 'h' + pX(horzSign * d.bx) + + 'v-' + pY(d.by) + + 'H' + pX(horzSign * HOVERARROWSIZE + offsetX) + + 'V' + pY(offsetY - HOVERARROWSIZE) + 'Z')); - var posX = txx + offsetX; - var posY = offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD; + var posX = pX(offsetX + txx); + var posY = pY(offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD); var textAlign = d.textAlign || 'auto'; if(textAlign !== 'auto') { From a6aeafae4c2ecdbeab86640f808780008feac31c Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 21 Oct 2020 21:35:37 -0400 Subject: [PATCH 10/63] decompose scales when there is also rotation --- src/components/fx/hover.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index c1bc8611cc4..3270fc042ee 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1485,8 +1485,13 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) { } function alignHoverText(hoverLabels, rotateLabels, evt) { - var scaleX = evt ? evt.inverseTransform[0][0] : 1; - var scaleY = evt ? evt.inverseTransform[1][1] : 1; + var scaleX = 1; + var scaleY = 1; + if(evt) { + var m = evt.inverseTransform; + scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + } var pX = function(x) { return x * scaleX; }; var pY = function(y) { return y * scaleY; }; From 7e8a0e8dba7c14271c847eb1b8db7e062ba1a344 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 10:43:22 -0400 Subject: [PATCH 11/63] drop unused function --- src/lib/matrix.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 5a3af26f8c4..e2e4bb05c7a 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -104,29 +104,6 @@ exports.apply2DTransform2 = function(transform) { }; }; -// applies a 2D transform to something with either the form {left, top, right, bottom} -// or {left, top, width, height}, and returns it in the same format -exports.apply2DTransformToRect = function(transform) { - var at = exports.apply2DTransform2(transform); - return function(rect) { - var rectArray = [ - rect.left, - rect.top, - rect.hasOwnProperty('right') ? rect.right : rect.left + rect.width, - rect.hasOwnProperty('bottom') ? rect.bottom : rect.top + rect.height - ]; - var transformed = at(rectArray); - return { - left: transformed[0], - top: transformed[1], - right: transformed[2], - bottom: transformed[3], - width: transformed[2] - transformed[0], - height: transformed[3] - transformed[1] - }; - }; -}; - // converts a 2x3 css transform matrix, represented as a length 6 array, to a 3x3 matrix. exports.convertCssMatrix = function(m) { if(m.length !== 6) { From 41820ceed964911639922bc948c48b08881f6870 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 10:54:54 -0400 Subject: [PATCH 12/63] refactor matrix --- src/lib/matrix.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/matrix.js b/src/lib/matrix.js index e2e4bb05c7a..9ae4fe09069 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -125,11 +125,11 @@ exports.inverseTransformMatrix = function(m) { } var inv = 1.0 / determinant; - var invTranslateX = inv * (-m[1][1] * m[0][2] + m[0][1] * m[1][2]); - var invTranslateY = inv * (m[1][0] * m[0][2] + -m[0][0] * m[1][2]); + var invTranslateX = inv * (m[0][1] * m[1][2] - m[1][1] * m[0][2]); + var invTranslateY = inv * (m[1][0] * m[0][2] - m[0][0] * m[1][2]); return [ - [inv * m[1][1], inv * -m[0][1], invTranslateX], - [inv * -m[1][0], inv * m[0][0], invTranslateY], + [inv * m[1][1], -inv * m[0][1], invTranslateX], + [-inv * m[1][0], inv * m[0][0], invTranslateY], [0, 0, 1] ]; }; From d7665f30d8ea683c2467f4f4e0fe730d00b1bbcd Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 11:04:43 -0400 Subject: [PATCH 13/63] fixup translate within inverseTransformMatrix function --- src/lib/matrix.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 9ae4fe09069..e639e6b05d2 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -128,8 +128,8 @@ exports.inverseTransformMatrix = function(m) { var invTranslateX = inv * (m[0][1] * m[1][2] - m[1][1] * m[0][2]); var invTranslateY = inv * (m[1][0] * m[0][2] - m[0][0] * m[1][2]); return [ - [inv * m[1][1], -inv * m[0][1], invTranslateX], - [-inv * m[1][0], inv * m[0][0], invTranslateY], - [0, 0, 1] + [inv * m[1][1], -inv * m[0][1], 0], + [-inv * m[1][0], inv * m[0][0], 0], + [invTranslateX, invTranslateY, 1] ]; }; From c0868c22868bda9c9a97849b23c98e54675eabd7 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 13:02:54 -0400 Subject: [PATCH 14/63] replace invert function with gl-mat3 to compute determinant etc. correctly --- package-lock.json | 13 ++++++++++--- package.json | 1 + src/lib/matrix.js | 22 +++++++++++----------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ac9254245b..b4f4648c379 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4840,9 +4840,9 @@ } }, "gl-mat3": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gl-mat3/-/gl-mat3-1.0.0.tgz", - "integrity": "sha1-iWMyGcpCk3mha5GF2V1BcTRTuRI=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gl-mat3/-/gl-mat3-2.0.0.tgz", + "integrity": "sha512-/RfKyizhztkG+gH07lA0/OI9uXVEqDrvjza1U8kZc3Sjvn/iT1a99jyL1WtLzXMn/BYp0geE2/DsNp9GCXp28Q==" }, "gl-mat4": { "version": "1.2.0", @@ -4932,6 +4932,13 @@ "gl-mat3": "^1.0.0", "gl-vec3": "^1.0.3", "gl-vec4": "^1.0.0" + }, + "dependencies": { + "gl-mat3": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gl-mat3/-/gl-mat3-1.0.0.tgz", + "integrity": "sha1-iWMyGcpCk3mha5GF2V1BcTRTuRI=" + } } }, "gl-scatter3d": { diff --git a/package.json b/package.json index cc5d7801cee..873d6c1ec34 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "gl-error3d": "^1.0.16", "gl-heatmap2d": "^1.1.0", "gl-line3d": "1.2.1", + "gl-mat3": "^2.0.0", "gl-mat4": "^1.2.0", "gl-mesh3d": "^2.3.1", "gl-plot2d": "^1.4.5", diff --git a/src/lib/matrix.js b/src/lib/matrix.js index e639e6b05d2..83c796f09e6 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -9,6 +9,7 @@ 'use strict'; +var mat3X3 = require('gl-mat3'); exports.init2dArray = function(rowLength, colLength) { var array = new Array(rowLength); @@ -119,17 +120,16 @@ exports.convertCssMatrix = function(m) { // find the inverse for a 3x3 affine transform matrix exports.inverseTransformMatrix = function(m) { - var determinant = m[0][0] * m[1][1] - m[1][0] * m[0][1]; - if(Math.abs(determinant) < Number.EPSILON) { - throw new Error('Matrix is singular'); - } - - var inv = 1.0 / determinant; - var invTranslateX = inv * (m[0][1] * m[1][2] - m[1][1] * m[0][2]); - var invTranslateY = inv * (m[1][0] * m[0][2] - m[0][0] * m[1][2]); + var out = []; + mat3X3.invert(out, [ + m[0][0], m[0][1], m[0][2], + m[1][0], m[1][1], m[1][2], + m[2][0], m[2][1], m[2][2] + ]); + mat3X3.transpose(out, out); return [ - [inv * m[1][1], -inv * m[0][1], 0], - [-inv * m[1][0], inv * m[0][0], 0], - [invTranslateX, invTranslateY, 1] + [out[0], out[1], out[2]], + [out[3], out[4], out[5]], + [out[6], out[7], out[8]] ]; }; From 7625a678df05ed6c2649bdd8494ae7f1f396b91e Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 13:18:12 -0400 Subject: [PATCH 15/63] use parentNode instead of parentElement for IE support --- src/lib/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/dom.js b/src/lib/dom.js index 1fa0ae55476..a85f1049d72 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -133,7 +133,7 @@ function getElementAndAncestors(element) { var allElements = []; while(isTransformableElement(element)) { allElements.push(element); - element = element.parentElement; + element = element.parentNode; } return allElements; } From 46ed093d0fffdf0b7135c772e00da9c254843629 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 13:18:55 -0400 Subject: [PATCH 16/63] increase computed style calls - new one added in hover --- tasks/test_syntax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/test_syntax.js b/tasks/test_syntax.js index 14c20675120..8730f6bb531 100644 --- a/tasks/test_syntax.js +++ b/tasks/test_syntax.js @@ -213,7 +213,7 @@ function assertSrcContents() { * - If you use conforms to these rules, you may update * KNOWN_GET_COMPUTED_STYLE_CALLS to count the new use. */ - var KNOWN_GET_COMPUTED_STYLE_CALLS = 5; + var KNOWN_GET_COMPUTED_STYLE_CALLS = 6; if(getComputedStyleCnt !== KNOWN_GET_COMPUTED_STYLE_CALLS) { logs.push('Expected ' + KNOWN_GET_COMPUTED_STYLE_CALLS + ' window.getComputedStyle calls, found ' + getComputedStyleCnt + From 44769fa0e56724217754a4fc450d7929b1c37dee Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 13:30:21 -0400 Subject: [PATCH 17/63] bypass and return default matrix if encountered a problem instead of throwing an error --- src/lib/matrix.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 83c796f09e6..7d9e514c77c 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -107,8 +107,12 @@ exports.apply2DTransform2 = function(transform) { // converts a 2x3 css transform matrix, represented as a length 6 array, to a 3x3 matrix. exports.convertCssMatrix = function(m) { - if(m.length !== 6) { - throw new Error('Css transform matrix not of length 6'); + if(!m || m.length !== 6) { + return [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1] + ]; } return [ From 463a0ab66605d194663cb0e24a59419a8aab7b43 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 13:41:01 -0400 Subject: [PATCH 18/63] transform drag start position --- src/plots/cartesian/dragbox.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 573362f0047..6417cbeee1b 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -326,6 +326,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var dragBBox = dragger.getBoundingClientRect(); x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; + + e.inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); + var transformedCoords = Lib.apply2DTransform(e.inverseTransform)(x0, y0); + x0 = transformedCoords[0]; + y0 = transformedCoords[1]; + box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; lum = gd._hmpixcount ? (gd._hmlumcount / gd._hmpixcount) : From e7d9a531b1e987c104b29a06e0efc05c66aea28b Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 13:59:21 -0400 Subject: [PATCH 19/63] adjust cartesian drag box endpoint in respect to scales --- src/plots/cartesian/dragbox.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 6417cbeee1b..6cbdc2c5dc8 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -332,7 +332,11 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { x0 = transformedCoords[0]; y0 = transformedCoords[1]; - box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; + var m = e.inverseTransform; + var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + + box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0, scaleX: scaleX, scaleY: scaleY}; lum = gd._hmpixcount ? (gd._hmlumcount / gd._hmpixcount) : tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); @@ -349,8 +353,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { return false; } - var x1 = Math.max(0, Math.min(pw, dx0 + x0)); - var y1 = Math.max(0, Math.min(ph, dy0 + y0)); + var x1 = Math.max(0, Math.min(pw, box.scaleX * dx0 + x0)); + var y1 = Math.max(0, Math.min(ph, box.scaleY * dy0 + y0)); var dx = Math.abs(x1 - x0); var dy = Math.abs(y1 - y0); From 080034b9c7b54216dbe40ecbfc74a52ed4e50fd5 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Oct 2020 14:11:36 -0400 Subject: [PATCH 20/63] adjust cartesian select start and end point according to parent transforms --- src/plots/cartesian/select.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index a61bbbcb23c..bdc32d7d808 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -67,6 +67,15 @@ function prepSelect(e, startX, startY, dragOptions, mode) { var transform = getTransform(plotinfo); var x0 = startX - dragBBox.left; var y0 = startY - dragBBox.top; + + e.inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); + var transformedCoords = Lib.apply2DTransform(e.inverseTransform)(x0, y0); + x0 = transformedCoords[0]; + y0 = transformedCoords[1]; + var m = e.inverseTransform; + var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + var x1 = x0; var y1 = y0; var path0 = 'M' + x0 + ',' + y0; @@ -156,8 +165,8 @@ function prepSelect(e, startX, startY, dragOptions, mode) { } dragOptions.moveFn = function(dx0, dy0) { - x1 = Math.max(0, Math.min(pw, dx0 + x0)); - y1 = Math.max(0, Math.min(ph, dy0 + y0)); + x1 = Math.max(0, Math.min(pw, scaleX * dx0 + x0)); + y1 = Math.max(0, Math.min(ph, scaleY * dy0 + y0)); var dx = Math.abs(x1 - x0); var dy = Math.abs(y1 - y0); From 2bfc471c04d5c24e3398ddfb316d5e86e8eb9815 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 23 Oct 2020 13:47:15 -0400 Subject: [PATCH 21/63] adjust trace name hover text and box --- src/components/fx/hover.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 3270fc042ee..547b83b2970 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1557,13 +1557,13 @@ function alignHoverText(hoverLabels, rotateLabels, evt) { if(d.tx2width) { g.select('text.name') .call(svgTextUtils.positionText, - tx2x + alignShift * HOVERTEXTPAD + offsetX, - offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD); + pX(tx2x + alignShift * HOVERTEXTPAD + offsetX), + pY(offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD)); g.select('rect') .call(Drawing.setRect, - tx2x + (alignShift - 1) * d.tx2width / 2 + offsetX, - offsetY - d.by / 2 - 1, - d.tx2width, d.by + 2); + pX(tx2x + (alignShift - 1) * d.tx2width / 2 + offsetX), + pY(offsetY - d.by / 2 - 1), + pX(d.tx2width), pY(d.by + 2)); } }); } From 1b78028028a0bcd8822c2706c5e319dabad77a96 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 23 Oct 2020 14:09:23 -0400 Subject: [PATCH 22/63] adjust left and right alignments --- src/components/fx/hover.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 547b83b2970..ec8ef40b0e9 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1534,8 +1534,8 @@ function alignHoverText(hoverLabels, rotateLabels, evt) { 'V' + pY(offsetY - HOVERARROWSIZE) + 'Z')); - var posX = pX(offsetX + txx); - var posY = pY(offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD); + var posX = offsetX + txx; + var posY = offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD; var textAlign = d.textAlign || 'auto'; if(textAlign !== 'auto') { @@ -1552,7 +1552,7 @@ function alignHoverText(hoverLabels, rotateLabels, evt) { } } - tx.call(svgTextUtils.positionText, posX, posY); + tx.call(svgTextUtils.positionText, pX(posX), pY(posY)); if(d.tx2width) { g.select('text.name') From f4121753d81c53ddb3d9d84e5529349ac2b4841f Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 23 Oct 2020 14:44:34 -0400 Subject: [PATCH 23/63] adjust vertical offset between multiple hover boxes --- src/components/fx/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index ec8ef40b0e9..71e4984c26a 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1508,7 +1508,7 @@ function alignHoverText(hoverLabels, rotateLabels, evt) { var txx = alignShift * (HOVERARROWSIZE + HOVERTEXTPAD); var tx2x = txx + alignShift * (d.txwidth + HOVERTEXTPAD); var offsetX = 0; - var offsetY = d.offset; + var offsetY = pY(d.offset); var isMiddle = anchor === 'middle'; if(isMiddle) { From 825d9622e904b7fdb385c34bebf577301d1fe625 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 23 Oct 2020 15:25:53 -0400 Subject: [PATCH 24/63] compute inverse transform at graph main div level not the event target --- src/components/fx/hover.js | 26 ++++++++++++++------------ src/plots/cartesian/dragbox.js | 8 +++++--- src/plots/cartesian/select.js | 9 ++++++--- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 71e4984c26a..417fccb0487 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -72,7 +72,9 @@ var HOVERTEXTPAD = constants.HOVERTEXTPAD; exports.hover = function hover(gd, evt, subplot, noHoverEvent) { gd = Lib.getGraphDiv(gd); - evt.inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(evt.target)); + if(gd._inverseTransform === undefined) { + gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + } Lib.throttle( gd._fullLayout._uid + constants.HOVERID, @@ -194,7 +196,11 @@ exports.loneHover = function loneHover(hoverItems, opts) { d.offset -= anchor; }); - alignHoverText(hoverLabel, fullOpts.rotateLabels, false); + var gd = opts.gd; + if(gd._inverseTransform === undefined) { + gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + } + alignHoverText(hoverLabel, fullOpts.rotateLabels, gd._inverseTransform); return multiHover ? hoverLabel : hoverLabel.node(); }; @@ -338,7 +344,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { xpx = evt.clientX - dbb.left; ypx = evt.clientY - dbb.top; - var transformedCoords = Lib.apply2DTransform(evt.inverseTransform)(xpx, ypx); + var transformedCoords = Lib.apply2DTransform(gd._inverseTransform)(xpx, ypx); xpx = transformedCoords[0]; ypx = transformedCoords[1]; @@ -723,7 +729,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { if(!helpers.isUnifiedHover(hovermode)) { hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); - alignHoverText(hoverLabels, rotateLabels, evt); + alignHoverText(hoverLabels, rotateLabels, gd._inverseTransform); } // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true @@ -1484,14 +1490,10 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) { } } -function alignHoverText(hoverLabels, rotateLabels, evt) { - var scaleX = 1; - var scaleY = 1; - if(evt) { - var m = evt.inverseTransform; - scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); - scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); - } +function alignHoverText(hoverLabels, rotateLabels, inverseTransform) { + var m = inverseTransform; + var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); var pX = function(x) { return x * scaleX; }; var pY = function(y) { return y * scaleY; }; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 6cbdc2c5dc8..dc353e9f414 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -327,12 +327,14 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; - e.inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); - var transformedCoords = Lib.apply2DTransform(e.inverseTransform)(x0, y0); + if(gd._inverseTransform === undefined) { + gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); + } + var transformedCoords = Lib.apply2DTransform(gd._inverseTransform)(x0, y0); x0 = transformedCoords[0]; y0 = transformedCoords[1]; - var m = e.inverseTransform; + var m = gd._inverseTransform; var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index bdc32d7d808..a047c5ba934 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -68,11 +68,14 @@ function prepSelect(e, startX, startY, dragOptions, mode) { var x0 = startX - dragBBox.left; var y0 = startY - dragBBox.top; - e.inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); - var transformedCoords = Lib.apply2DTransform(e.inverseTransform)(x0, y0); + if(gd._inverseTransform === undefined) { + gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); + } + + var transformedCoords = Lib.apply2DTransform(gd._inverseTransform)(x0, y0); x0 = transformedCoords[0]; y0 = transformedCoords[1]; - var m = e.inverseTransform; + var m = gd._inverseTransform; var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); From 7e26e91b0cebada7f80eb34ed9b454cf53844a7e Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 23 Oct 2020 15:46:03 -0400 Subject: [PATCH 25/63] compute inverse transform for gd at makePlotFramwork and stash it on fullLayout --- src/components/fx/hover.js | 14 +++----------- src/plot_api/plot_api.js | 3 +++ src/plots/cartesian/dragbox.js | 7 ++----- src/plots/cartesian/select.js | 8 ++------ 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 417fccb0487..1c70379ea33 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -72,10 +72,6 @@ var HOVERTEXTPAD = constants.HOVERTEXTPAD; exports.hover = function hover(gd, evt, subplot, noHoverEvent) { gd = Lib.getGraphDiv(gd); - if(gd._inverseTransform === undefined) { - gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); - } - Lib.throttle( gd._fullLayout._uid + constants.HOVERID, constants.HOVERMINTIME, @@ -196,11 +192,7 @@ exports.loneHover = function loneHover(hoverItems, opts) { d.offset -= anchor; }); - var gd = opts.gd; - if(gd._inverseTransform === undefined) { - gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); - } - alignHoverText(hoverLabel, fullOpts.rotateLabels, gd._inverseTransform); + alignHoverText(hoverLabel, fullOpts.rotateLabels, opts.gd._fullLayout._inverseTransform); return multiHover ? hoverLabel : hoverLabel.node(); }; @@ -344,7 +336,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { xpx = evt.clientX - dbb.left; ypx = evt.clientY - dbb.top; - var transformedCoords = Lib.apply2DTransform(gd._inverseTransform)(xpx, ypx); + var transformedCoords = Lib.apply2DTransform(fullLayout._inverseTransform)(xpx, ypx); xpx = transformedCoords[0]; ypx = transformedCoords[1]; @@ -729,7 +721,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { if(!helpers.isUnifiedHover(hovermode)) { hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); - alignHoverText(hoverLabels, rotateLabels, gd._inverseTransform); + alignHoverText(hoverLabels, rotateLabels, fullLayout._inverseTransform); } // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 893e8b9a68e..90e9fd089a0 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3714,6 +3714,9 @@ function purge(gd) { function makePlotFramework(gd) { var gd3 = d3.select(gd); var fullLayout = gd._fullLayout; + if(fullLayout._inverseTransform === undefined) { + fullLayout._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + } // Plot container fullLayout._container = gd3.selectAll('.plot-container').data([0]); diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index dc353e9f414..b990ada9974 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -327,14 +327,11 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; - if(gd._inverseTransform === undefined) { - gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); - } - var transformedCoords = Lib.apply2DTransform(gd._inverseTransform)(x0, y0); + var transformedCoords = Lib.apply2DTransform(gd._fullLayout._inverseTransform)(x0, y0); x0 = transformedCoords[0]; y0 = transformedCoords[1]; - var m = gd._inverseTransform; + var m = gd._fullLayout._inverseTransform; var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index a047c5ba934..ab779aab0c3 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -68,14 +68,10 @@ function prepSelect(e, startX, startY, dragOptions, mode) { var x0 = startX - dragBBox.left; var y0 = startY - dragBBox.top; - if(gd._inverseTransform === undefined) { - gd._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(e.target)); - } - - var transformedCoords = Lib.apply2DTransform(gd._inverseTransform)(x0, y0); + var transformedCoords = Lib.apply2DTransform(fullLayout._inverseTransform)(x0, y0); x0 = transformedCoords[0]; y0 = transformedCoords[1]; - var m = gd._inverseTransform; + var m = fullLayout._inverseTransform; var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); From c137f144845de83c7c30775da7f660e782b1bb40 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 23 Oct 2020 15:57:02 -0400 Subject: [PATCH 26/63] no need for extra adjustment for y offset --- src/components/fx/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 1c70379ea33..405b473fcb6 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1502,7 +1502,7 @@ function alignHoverText(hoverLabels, rotateLabels, inverseTransform) { var txx = alignShift * (HOVERARROWSIZE + HOVERTEXTPAD); var tx2x = txx + alignShift * (d.txwidth + HOVERTEXTPAD); var offsetX = 0; - var offsetY = pY(d.offset); + var offsetY = d.offset; var isMiddle = anchor === 'middle'; if(isMiddle) { From bedbee7a3ef4fb8531f08a077490c3eeded614e5 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 23 Oct 2020 19:45:37 -0400 Subject: [PATCH 27/63] adjust scale of svg hover container in respect to transform for gl3d subplots --- src/plots/gl3d/scene.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index e45fb6e932a..54d141df0b2 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -296,8 +296,11 @@ proto.render = function() { // update size of svg container var svgContainer = scene.svgContainer; var clientRect = scene.container.getBoundingClientRect(); - var width = clientRect.width; - var height = clientRect.height; + var m = gd._fullLayout._inverseTransform; + var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + var width = clientRect.width * scaleX; + var height = clientRect.height * scaleY; svgContainer.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height); svgContainer.setAttributeNS(null, 'width', width); svgContainer.setAttributeNS(null, 'height', height); From 2c810fcf066d3f706097427942888de8c5780b8e Mon Sep 17 00:00:00 2001 From: archmoj Date: Sun, 25 Oct 2020 22:22:13 -0400 Subject: [PATCH 28/63] handle cases where css transform has a length of 16 using 4x4 matrices --- package-lock.json | 5 --- package.json | 1 - src/components/fx/hover.js | 2 +- src/lib/dom.js | 15 ++++---- src/lib/index.js | 1 + src/lib/matrix.js | 63 ++++++++++++++++++++++------------ src/plots/cartesian/dragbox.js | 6 ++-- src/plots/cartesian/select.js | 6 ++-- 8 files changed, 58 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index b4f4648c379..a2b6a492c06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4839,11 +4839,6 @@ "ndarray": "^1.0.18" } }, - "gl-mat3": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gl-mat3/-/gl-mat3-2.0.0.tgz", - "integrity": "sha512-/RfKyizhztkG+gH07lA0/OI9uXVEqDrvjza1U8kZc3Sjvn/iT1a99jyL1WtLzXMn/BYp0geE2/DsNp9GCXp28Q==" - }, "gl-mat4": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.2.0.tgz", diff --git a/package.json b/package.json index 873d6c1ec34..cc5d7801cee 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "gl-error3d": "^1.0.16", "gl-heatmap2d": "^1.1.0", "gl-line3d": "1.2.1", - "gl-mat3": "^2.0.0", "gl-mat4": "^1.2.0", "gl-mesh3d": "^2.3.1", "gl-plot2d": "^1.4.5", diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 405b473fcb6..c65e0090ec9 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -336,7 +336,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { xpx = evt.clientX - dbb.left; ypx = evt.clientY - dbb.top; - var transformedCoords = Lib.apply2DTransform(fullLayout._inverseTransform)(xpx, ypx); + var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(xpx, ypx); xpx = transformedCoords[0]; ypx = transformedCoords[1]; diff --git a/src/lib/dom.js b/src/lib/dom.js index a85f1049d72..879d39e1cf3 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -11,6 +11,7 @@ var d3 = require('d3'); var loggers = require('./loggers'); var matrix = require('./matrix'); +var mat4X4 = require('gl-mat4'); /** * Allow referencing a graph DOM element either directly @@ -95,18 +96,20 @@ function deleteRelatedStyleRule(uid) { function getFullTransformMatrix(element) { var allElements = getElementAndAncestors(element); // the identity matrix - var transform = [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] + var out = [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 ]; allElements.forEach(function(e) { var t = getElementTransformMatrix(e); if(t) { - transform = matrix.dot(transform, matrix.convertCssMatrix(t)); + var m = matrix.convertCssMatrix(t); + out = mat4X4.multiply(out, out, m); } }); - return transform; + return out; } /** diff --git a/src/lib/index.js b/src/lib/index.js index 40ec6da6f97..f73666037c2 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -88,6 +88,7 @@ lib.dot = matrixModule.dot; lib.translationMatrix = matrixModule.translationMatrix; lib.rotationMatrix = matrixModule.rotationMatrix; lib.rotationXYMatrix = matrixModule.rotationXYMatrix; +lib.apply3DTransform = matrixModule.apply3DTransform; lib.apply2DTransform = matrixModule.apply2DTransform; lib.apply2DTransform2 = matrixModule.apply2DTransform2; lib.convertCssMatrix = matrixModule.convertCssMatrix; diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 7d9e514c77c..7a23053a1ea 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -9,7 +9,7 @@ 'use strict'; -var mat3X3 = require('gl-mat3'); +var mat4X4 = require('gl-mat4'); exports.init2dArray = function(rowLength, colLength) { var array = new Array(rowLength); @@ -85,6 +85,16 @@ exports.rotationXYMatrix = function(a, x, y) { exports.translationMatrix(-x, -y)); }; +// applies a 3D transformation matrix to either x, y and z params +// Note: z is optional +exports.apply3DTransform = function(transform) { + return function() { + var args = arguments; + var xyz = arguments.length === 1 ? args[0] : [args[0], args[1], args[2] || 0]; + return exports.dot(transform, [xyz[0], xyz[1], xyz[2], 1]).slice(0, 3); + }; +}; + // applies a 2D transformation matrix to either x and y params or an [x,y] array exports.apply2DTransform = function(transform) { return function() { @@ -105,35 +115,44 @@ exports.apply2DTransform2 = function(transform) { }; }; -// converts a 2x3 css transform matrix, represented as a length 6 array, to a 3x3 matrix. exports.convertCssMatrix = function(m) { - if(!m || m.length !== 6) { - return [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ]; + if(m) { + var len = m.length; + if(len === 16) { + // validate values + return [ + m[0] || 1, m[1] || 0, m[2] || 0, m[3] || 0, + m[4] || 0, m[5] || 1, m[6] || 0, m[7] || 0, + m[8] || 0, m[9] || 0, m[10] || 1, m[11] || 0, + m[12] || 0, m[13] || 0, m[14] || 0, m[15] || 1 + ]; + } + if(len === 6) { + // converts a 2x3 css transform matrix to a 4x4 matrix see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix + return [ + m[0], m[1], 0, 0, + m[2], m[3], 0, 0, + 0, 0, 1, 0, + m[4], m[5], 0, 1 + ]; + } } - return [ - [m[0], m[2], m[4]], - [m[1], m[3], m[5]], - [0, 0, 1] + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 ]; }; -// find the inverse for a 3x3 affine transform matrix +// find the inverse for a 4x4 affine transform matrix exports.inverseTransformMatrix = function(m) { var out = []; - mat3X3.invert(out, [ - m[0][0], m[0][1], m[0][2], - m[1][0], m[1][1], m[1][2], - m[2][0], m[2][1], m[2][2] - ]); - mat3X3.transpose(out, out); + mat4X4.invert(out, m); return [ - [out[0], out[1], out[2]], - [out[3], out[4], out[5]], - [out[6], out[7], out[8]] + [out[0], out[4], out[8], out[12]], + [out[1], out[5], out[9], out[13]], + [out[2], out[6], out[10], out[14]], + [out[3], out[7], out[11], out[15]], ]; }; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index b990ada9974..00d756d594f 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -327,13 +327,13 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; - var transformedCoords = Lib.apply2DTransform(gd._fullLayout._inverseTransform)(x0, y0); + var transformedCoords = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(x0, y0); x0 = transformedCoords[0]; y0 = transformedCoords[1]; var m = gd._fullLayout._inverseTransform; - var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); - var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); + var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0, scaleX: scaleX, scaleY: scaleY}; lum = gd._hmpixcount ? diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index ab779aab0c3..415eddd3b19 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -68,12 +68,12 @@ function prepSelect(e, startX, startY, dragOptions, mode) { var x0 = startX - dragBBox.left; var y0 = startY - dragBBox.top; - var transformedCoords = Lib.apply2DTransform(fullLayout._inverseTransform)(x0, y0); + var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(x0, y0); x0 = transformedCoords[0]; y0 = transformedCoords[1]; var m = fullLayout._inverseTransform; - var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); - var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); + var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); var x1 = x0; var y1 = y0; From c409a28fd91d27ae4d845dc5f4d64919a420e36b Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 29 Oct 2020 09:48:02 -0400 Subject: [PATCH 29/63] fixup matrix for translate --- src/lib/matrix.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 7a23053a1ea..4ca500d0b63 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -150,9 +150,9 @@ exports.inverseTransformMatrix = function(m) { var out = []; mat4X4.invert(out, m); return [ - [out[0], out[4], out[8], out[12]], - [out[1], out[5], out[9], out[13]], - [out[2], out[6], out[10], out[14]], - [out[3], out[7], out[11], out[15]], + [out[0], out[1], out[2], out[3]], + [out[4], out[5], out[6], out[7]], + [out[8], out[9], out[10], out[11]], + [out[12], out[13], out[14], out[15]] ]; }; From 68f5addd86e7ba04022b7c43e8492c842d709747 Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Mon, 2 Nov 2020 17:15:47 +1100 Subject: [PATCH 30/63] 888: cartesian hover test --- test/jasmine/tests/cartesian_interact_test.js | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 2fdd891f83b..527858dc075 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -12,14 +12,17 @@ var mouseEvent = require('../assets/mouse_event'); var failTest = require('../assets/fail_test'); var selectButton = require('../assets/modebar_button'); var drag = require('../assets/drag'); +var click = require('../assets/click'); var doubleClick = require('../assets/double_click'); var getNodeCoords = require('../assets/get_node_coords'); var delay = require('../assets/delay'); var customAssertions = require('../assets/custom_assertions'); var assertNodeDisplay = customAssertions.assertNodeDisplay; +var assertHoverLabelContent = customAssertions.assertHoverLabelContent; var MODEBAR_DELAY = 500; +var HOVERMINTIME = require('@src/components/fx').constants.HOVERMINTIME; describe('zoom box element', function() { var mock = require('@mocks/14.json'); @@ -2244,3 +2247,100 @@ describe('Event data:', function() { .then(done); }); }); + +describe('Cartesian plots with css transforms', function() { + var gd; + var eventRecordings = {}; + + beforeEach(function() { + eventRecordings = {}; + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + function _getLocalPos(element, point) { + var bb = element.getBoundingClientRect(); + return [ + bb.left + point[0], + bb.top + point[1] + ]; + } + + function _hover(pos) { + return new Promise(function(resolve, reject) { + var localPos = _getLocalPos(gd, pos); + gd.once('plotly_hover', function(d) { + Lib.clearThrottle(); + resolve(d); + }); + + mouseEvent('mousemove', localPos[0], localPos[1]); + + setTimeout(function() { + reject('plotly_hover did not get called!'); + }, 100); + }); + } + + function _unhover(pos) { + var localPos = _getLocalPos(gd, pos); + mouseEvent('mouseout', localPos[0], localPos[1]); + } + + function _hoverAndAssertEventOccurred(point, label) { + return _hover(point) + .then(function() { + expect(eventRecordings[label]).toBe(1); + }) + .then(function() { + _unhover(point); + }); + } + + function transformPlot(gd, scale, transX, transY) { + var transformString = `scale(${scale}) translate(${transX}, ${transY})`; + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + function recalculateInverse(gd) { + var inverse = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + gd._fullLayout._inverseTransform = inverse; + } + + var points = [[50, 180], [150, 180], [250, 180]]; + var xLabels = ['one', 'two', 'three']; + var data = [{ + x: xLabels, + y: [1, 2, 3], + type: 'bar' + }]; + var layout = { + width: 600, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0} + }; + + it('hover behaves correctly after css transform', function(done) { + Plotly.plot(gd, data, layout) + .then(function() { + gd.on('plotly_hover', function(d) { + eventRecordings[d.points[0].x] = 1; + }); + }) + .then(function() { + transformPlot(gd, '0.5', '0px', '0px'); + recalculateInverse(gd); + }) + .then(function() {_hoverAndAssertEventOccurred(points[0], xLabels[0]);}) + .then(function() {_hoverAndAssertEventOccurred(points[1], xLabels[1]);}) + .then(function() {_hoverAndAssertEventOccurred(points[2], xLabels[2]);}) + .catch(failTest) + .then(done); + }); + +}); \ No newline at end of file From 8e6428174b7dae3bc45a58646c9fadf8e633e28c Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Tue, 3 Nov 2020 17:24:04 +1100 Subject: [PATCH 31/63] 888: changed transformPlot fn, added dragzoom test --- test/jasmine/tests/cartesian_interact_test.js | 113 ++++++++++++------ 1 file changed, 79 insertions(+), 34 deletions(-) diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 527858dc075..85ed4ec36aa 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -2267,39 +2267,7 @@ describe('Cartesian plots with css transforms', function() { ]; } - function _hover(pos) { - return new Promise(function(resolve, reject) { - var localPos = _getLocalPos(gd, pos); - gd.once('plotly_hover', function(d) { - Lib.clearThrottle(); - resolve(d); - }); - - mouseEvent('mousemove', localPos[0], localPos[1]); - - setTimeout(function() { - reject('plotly_hover did not get called!'); - }, 100); - }); - } - - function _unhover(pos) { - var localPos = _getLocalPos(gd, pos); - mouseEvent('mouseout', localPos[0], localPos[1]); - } - - function _hoverAndAssertEventOccurred(point, label) { - return _hover(point) - .then(function() { - expect(eventRecordings[label]).toBe(1); - }) - .then(function() { - _unhover(point); - }); - } - - function transformPlot(gd, scale, transX, transY) { - var transformString = `scale(${scale}) translate(${transX}, ${transY})`; + function transformPlot(gd, transformString) { gd.style.webkitTransform = transformString; gd.style.MozTransform = transformString; gd.style.msTransform = transformString; @@ -2326,6 +2294,38 @@ describe('Cartesian plots with css transforms', function() { }; it('hover behaves correctly after css transform', function(done) { + + function _hover(pos) { + return new Promise(function(resolve, reject) { + var localPos = _getLocalPos(gd, pos); + gd.once('plotly_hover', function(d) { + Lib.clearThrottle(); + resolve(d); + }); + + mouseEvent('mousemove', localPos[0], localPos[1]); + + setTimeout(function() { + reject('plotly_hover did not get called!'); + }, 100); + }); + } + + function _unhover(pos) { + var localPos = _getLocalPos(gd, pos); + mouseEvent('mouseout', localPos[0], localPos[1]); + } + + function _hoverAndAssertEventOccurred(point, label) { + return _hover(point) + .then(function() { + expect(eventRecordings[label]).toBe(1); + }) + .then(function() { + _unhover(point); + }); + } + Plotly.plot(gd, data, layout) .then(function() { gd.on('plotly_hover', function(d) { @@ -2333,7 +2333,7 @@ describe('Cartesian plots with css transforms', function() { }); }) .then(function() { - transformPlot(gd, '0.5', '0px', '0px'); + transformPlot(gd, 'scale(0.5)'); recalculateInverse(gd); }) .then(function() {_hoverAndAssertEventOccurred(points[0], xLabels[0]);}) @@ -2343,4 +2343,49 @@ describe('Cartesian plots with css transforms', function() { .then(done); }); + it('drag-zoom behaves correctly after css transform', function(done) { + + // asserts that the zoombox path must go from the start to end positions, + // in css-transformed coordinates. + function _assertTransformedZoombox(startPos, endPos) { + startPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(startPos[0], startPos[1]); + endPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(endPos[0], endPos[1]); + var size = [endPos[0] - startPos[0], endPos[1] - startPos[1]]; + var zb = d3.select(gd).select('g.zoomlayer > path.zoombox'); + var d = zb.attr('d'); + var v = Number(d.split('v')[1].split('h')[0]); + var h = Number(d.split('h')[1].split('v')[0]); + var startCoordsString = d.split('M')[2].split('v')[0]; + var startX = Number(startCoordsString.split(',')[0]); + var startY = Number(startCoordsString.split(',')[1]); + expect(startX).toBeCloseTo(startPos[0]); + expect(startY).toBeCloseTo(startPos[1]); + expect(h).toBeCloseTo(size[0]); + expect(v).toBeCloseTo(size[1]); + } + + function _dragRaw(start, end) { + var localStart = _getLocalPos(gd, start); + var localEnd = _getLocalPos(gd, end); + mouseEvent('mousemove', localStart[0], localStart[1]); + mouseEvent('mousedown', localStart[0], localStart[1]); + mouseEvent('mousemove', localEnd[0], localEnd[1]); + } + + var start = [50, 50]; + var end = [150, 150] + + Plotly.plot(gd, data, layout) + .then(function() { + transformPlot(gd, 'scale(0.5)'); + recalculateInverse(gd); + }) + .then(function() {_dragRaw(start, end); }) + .then(function() { + _assertTransformedZoombox(start, end); + }) + .catch(failTest) + .then(done); + }); + }); \ No newline at end of file From dbc523214d831eeb5a600e497bb781b655945a9e Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Wed, 4 Nov 2020 17:59:33 +1100 Subject: [PATCH 32/63] 888: select test --- test/jasmine/tests/cartesian_interact_test.js | 169 ++++++++++++------ 1 file changed, 118 insertions(+), 51 deletions(-) diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 85ed4ec36aa..26deedd9860 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -2280,41 +2280,73 @@ describe('Cartesian plots with css transforms', function() { gd._fullLayout._inverseTransform = inverse; } + function _drag(start, end) { + var localStart = _getLocalPos(gd, start); + var localEnd = _getLocalPos(gd, end); + Lib.clearThrottle(); + mouseEvent('mousemove', localStart[0], localStart[1]); + mouseEvent('mousedown', localStart[0], localStart[1]); + mouseEvent('mousemove', localEnd[0], localEnd[1]); + } + + function _dragRelease(start, end) { + var localEnd = _getLocalPos(gd, end); + _drag(start, end); + mouseEvent('mouseup',localEnd[0], localEnd[1]); + } + + function _hover(pos) { + return new Promise(function(resolve, reject) { + var localPos = _getLocalPos(gd, pos); + gd.once('plotly_hover', function(d) { + Lib.clearThrottle(); + resolve(d); + }); + + mouseEvent('mousemove', localPos[0], localPos[1]); + + setTimeout(function() { + reject('plotly_hover did not get called!'); + }, 100); + }); + } + + function _unhover(pos) { + var localPos = _getLocalPos(gd, pos); + mouseEvent('mouseout', localPos[0], localPos[1]); + } + var points = [[50, 180], [150, 180], [250, 180]]; var xLabels = ['one', 'two', 'three']; - var data = [{ - x: xLabels, - y: [1, 2, 3], - type: 'bar' - }]; - var layout = { - width: 600, - height: 400, - margin: {l: 0, t: 0, r: 0, b: 0} + var mock = { + data: [{ + x: xLabels, + y: [1, 2, 3], + type: 'bar' + }], + layout: { + width: 600, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0} + } }; + // var data = [{ + // x: xLabels, + // y: [1, 2, 3], + // type: 'bar' + // }]; + // var layout = { + // width: 600, + // height: 400, + // margin: {l: 0, t: 0, r: 0, b: 0} + // }; + var transforms = ['scale(0.5)']; + + transforms.forEach(function(transform) { - it('hover behaves correctly after css transform', function(done) { + }); - function _hover(pos) { - return new Promise(function(resolve, reject) { - var localPos = _getLocalPos(gd, pos); - gd.once('plotly_hover', function(d) { - Lib.clearThrottle(); - resolve(d); - }); - - mouseEvent('mousemove', localPos[0], localPos[1]); - - setTimeout(function() { - reject('plotly_hover did not get called!'); - }, 100); - }); - } - - function _unhover(pos) { - var localPos = _getLocalPos(gd, pos); - mouseEvent('mouseout', localPos[0], localPos[1]); - } + it('hover behaves correctly after css transform', function(done) { function _hoverAndAssertEventOccurred(point, label) { return _hover(point) @@ -2326,7 +2358,7 @@ describe('Cartesian plots with css transforms', function() { }); } - Plotly.plot(gd, data, layout) + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { gd.on('plotly_hover', function(d) { eventRecordings[d.points[0].x] = 1; @@ -2345,6 +2377,18 @@ describe('Cartesian plots with css transforms', function() { it('drag-zoom behaves correctly after css transform', function(done) { + // return a rect of form {left, top, width, height} from the zoomlayer + // svg path. + function _getZoomlayerPathRect(pathStr) { + var rect = {}; + rect.height = Number(pathStr.split('v')[1].split('h')[0]); + rect.width = Number(pathStr.split('h')[1].split('v')[0]); + var startCoordsString = pathStr.split('M')[2].split('v')[0]; + rect.left = Number(startCoordsString.split(',')[0]); + rect.top = Number(startCoordsString.split(',')[1]); + return rect; + } + // asserts that the zoombox path must go from the start to end positions, // in css-transformed coordinates. function _assertTransformedZoombox(startPos, endPos) { @@ -2352,38 +2396,61 @@ describe('Cartesian plots with css transforms', function() { endPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(endPos[0], endPos[1]); var size = [endPos[0] - startPos[0], endPos[1] - startPos[1]]; var zb = d3.select(gd).select('g.zoomlayer > path.zoombox'); - var d = zb.attr('d'); - var v = Number(d.split('v')[1].split('h')[0]); - var h = Number(d.split('h')[1].split('v')[0]); - var startCoordsString = d.split('M')[2].split('v')[0]; - var startX = Number(startCoordsString.split(',')[0]); - var startY = Number(startCoordsString.split(',')[1]); - expect(startX).toBeCloseTo(startPos[0]); - expect(startY).toBeCloseTo(startPos[1]); - expect(h).toBeCloseTo(size[0]); - expect(v).toBeCloseTo(size[1]); - } - - function _dragRaw(start, end) { - var localStart = _getLocalPos(gd, start); - var localEnd = _getLocalPos(gd, end); - mouseEvent('mousemove', localStart[0], localStart[1]); - mouseEvent('mousedown', localStart[0], localStart[1]); - mouseEvent('mousemove', localEnd[0], localEnd[1]); + var zoomboxRect = _getZoomlayerPathRect(zb.attr('d')); + expect(zoomboxRect.left).toBeCloseTo(startPos[0]); + expect(zoomboxRect.top).toBeCloseTo(startPos[1]); + expect(zoomboxRect.width).toBeCloseTo(size[0]); + expect(zoomboxRect.height).toBeCloseTo(size[1]); } var start = [50, 50]; var end = [150, 150] - Plotly.plot(gd, data, layout) + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { transformPlot(gd, 'scale(0.5)'); recalculateInverse(gd); }) - .then(function() {_dragRaw(start, end); }) + .then(function() {_drag(start, end); }) .then(function() { _assertTransformedZoombox(start, end); }) + .then(function() { mouseEvent('mouseup', 0, 0); }) + .catch(failTest) + .then(done); + }); + + it('select behaves correctly after css transform', function(done) { + + function _assertSelected(expectation) { + var data = gd._fullData[0]; + var points = data.selectedpoints; + expect(typeof(points) !== 'undefined').toBeTrue(); + if (expectation.numPoints) + expect(points.length).toBe(expectation.numPoints); + if (expectation.selectedLabels) { + var selectedLabels = points.map(function(i) { return data.x[i]; }) + expect(selectedLabels).toEqual(expectation.selectedLabels); + } + } + + var start = [10, 10]; + var end = [200, 200]; + + Plotly.plot(gd, Lib.extendDeep({}, mock)) + .then(function() { + transformPlot(gd, 'scale(0.5)'); + recalculateInverse(gd); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + _dragRelease(start, end); + }) + .then(function() { + _assertSelected({numPoints: 2, selectedLabels: ["one", "two"]}); + }) .catch(failTest) .then(done); }); From 07b934b6f48b2188e150dc8b002d4f52732aa9c7 Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Wed, 4 Nov 2020 18:03:41 +1100 Subject: [PATCH 33/63] 888: more flexible transform testing --- test/jasmine/tests/cartesian_interact_test.js | 215 +++++++++--------- 1 file changed, 102 insertions(+), 113 deletions(-) diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 26deedd9860..33d894b940c 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -2330,129 +2330,118 @@ describe('Cartesian plots with css transforms', function() { margin: {l: 0, t: 0, r: 0, b: 0} } }; - // var data = [{ - // x: xLabels, - // y: [1, 2, 3], - // type: 'bar' - // }]; - // var layout = { - // width: 600, - // height: 400, - // margin: {l: 0, t: 0, r: 0, b: 0} - // }; var transforms = ['scale(0.5)']; transforms.forEach(function(transform) { - }); - - it('hover behaves correctly after css transform', function(done) { + it(`hover behaves correctly after css transform: ${transform}`, function(done) { + + function _hoverAndAssertEventOccurred(point, label) { + return _hover(point) + .then(function() { + expect(eventRecordings[label]).toBe(1); + }) + .then(function() { + _unhover(point); + }); + } - function _hoverAndAssertEventOccurred(point, label) { - return _hover(point) + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { - expect(eventRecordings[label]).toBe(1); + gd.on('plotly_hover', function(d) { + eventRecordings[d.points[0].x] = 1; + }); }) .then(function() { - _unhover(point); - }); - } - - Plotly.plot(gd, Lib.extendDeep({}, mock)) - .then(function() { - gd.on('plotly_hover', function(d) { - eventRecordings[d.points[0].x] = 1; - }); - }) - .then(function() { - transformPlot(gd, 'scale(0.5)'); - recalculateInverse(gd); - }) - .then(function() {_hoverAndAssertEventOccurred(points[0], xLabels[0]);}) - .then(function() {_hoverAndAssertEventOccurred(points[1], xLabels[1]);}) - .then(function() {_hoverAndAssertEventOccurred(points[2], xLabels[2]);}) - .catch(failTest) - .then(done); - }); - - it('drag-zoom behaves correctly after css transform', function(done) { - - // return a rect of form {left, top, width, height} from the zoomlayer - // svg path. - function _getZoomlayerPathRect(pathStr) { - var rect = {}; - rect.height = Number(pathStr.split('v')[1].split('h')[0]); - rect.width = Number(pathStr.split('h')[1].split('v')[0]); - var startCoordsString = pathStr.split('M')[2].split('v')[0]; - rect.left = Number(startCoordsString.split(',')[0]); - rect.top = Number(startCoordsString.split(',')[1]); - return rect; - } - - // asserts that the zoombox path must go from the start to end positions, - // in css-transformed coordinates. - function _assertTransformedZoombox(startPos, endPos) { - startPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(startPos[0], startPos[1]); - endPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(endPos[0], endPos[1]); - var size = [endPos[0] - startPos[0], endPos[1] - startPos[1]]; - var zb = d3.select(gd).select('g.zoomlayer > path.zoombox'); - var zoomboxRect = _getZoomlayerPathRect(zb.attr('d')); - expect(zoomboxRect.left).toBeCloseTo(startPos[0]); - expect(zoomboxRect.top).toBeCloseTo(startPos[1]); - expect(zoomboxRect.width).toBeCloseTo(size[0]); - expect(zoomboxRect.height).toBeCloseTo(size[1]); - } - - var start = [50, 50]; - var end = [150, 150] + transformPlot(gd, transform); + recalculateInverse(gd); + }) + .then(function() {_hoverAndAssertEventOccurred(points[0], xLabels[0]);}) + .then(function() {_hoverAndAssertEventOccurred(points[1], xLabels[1]);}) + .then(function() {_hoverAndAssertEventOccurred(points[2], xLabels[2]);}) + .catch(failTest) + .then(done); + }); - Plotly.plot(gd, Lib.extendDeep({}, mock)) - .then(function() { - transformPlot(gd, 'scale(0.5)'); - recalculateInverse(gd); - }) - .then(function() {_drag(start, end); }) - .then(function() { - _assertTransformedZoombox(start, end); - }) - .then(function() { mouseEvent('mouseup', 0, 0); }) - .catch(failTest) - .then(done); - }); - - it('select behaves correctly after css transform', function(done) { - - function _assertSelected(expectation) { - var data = gd._fullData[0]; - var points = data.selectedpoints; - expect(typeof(points) !== 'undefined').toBeTrue(); - if (expectation.numPoints) - expect(points.length).toBe(expectation.numPoints); - if (expectation.selectedLabels) { - var selectedLabels = points.map(function(i) { return data.x[i]; }) - expect(selectedLabels).toEqual(expectation.selectedLabels); + it(`drag-zoom behaves correctly after css transform: ${transform}`, function(done) { + + // return a rect of form {left, top, width, height} from the zoomlayer + // svg path. + function _getZoomlayerPathRect(pathStr) { + var rect = {}; + rect.height = Number(pathStr.split('v')[1].split('h')[0]); + rect.width = Number(pathStr.split('h')[1].split('v')[0]); + var startCoordsString = pathStr.split('M')[2].split('v')[0]; + rect.left = Number(startCoordsString.split(',')[0]); + rect.top = Number(startCoordsString.split(',')[1]); + return rect; } - } - - var start = [10, 10]; - var end = [200, 200]; - - Plotly.plot(gd, Lib.extendDeep({}, mock)) - .then(function() { - transformPlot(gd, 'scale(0.5)'); - recalculateInverse(gd); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - _dragRelease(start, end); - }) - .then(function() { - _assertSelected({numPoints: 2, selectedLabels: ["one", "two"]}); - }) - .catch(failTest) - .then(done); + + // asserts that the zoombox path must go from the start to end positions, + // in css-transformed coordinates. + function _assertTransformedZoombox(startPos, endPos) { + startPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(startPos[0], startPos[1]); + endPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(endPos[0], endPos[1]); + var size = [endPos[0] - startPos[0], endPos[1] - startPos[1]]; + var zb = d3.select(gd).select('g.zoomlayer > path.zoombox'); + var zoomboxRect = _getZoomlayerPathRect(zb.attr('d')); + expect(zoomboxRect.left).toBeCloseTo(startPos[0]); + expect(zoomboxRect.top).toBeCloseTo(startPos[1]); + expect(zoomboxRect.width).toBeCloseTo(size[0]); + expect(zoomboxRect.height).toBeCloseTo(size[1]); + } + + var start = [50, 50]; + var end = [150, 150] + + Plotly.plot(gd, Lib.extendDeep({}, mock)) + .then(function() { + transformPlot(gd, transform); + recalculateInverse(gd); + }) + .then(function() {_drag(start, end); }) + .then(function() { + _assertTransformedZoombox(start, end); + }) + .then(function() { mouseEvent('mouseup', 0, 0); }) + .catch(failTest) + .then(done); + }); + + it(`select behaves correctly after css transform: ${transform}`, function(done) { + + function _assertSelected(expectation) { + var data = gd._fullData[0]; + var points = data.selectedpoints; + expect(typeof(points) !== 'undefined').toBeTrue(); + if (expectation.numPoints) + expect(points.length).toBe(expectation.numPoints); + if (expectation.selectedLabels) { + var selectedLabels = points.map(function(i) { return data.x[i]; }) + expect(selectedLabels).toEqual(expectation.selectedLabels); + } + } + + var start = [10, 10]; + var end = [200, 200]; + + Plotly.plot(gd, Lib.extendDeep({}, mock)) + .then(function() { + transformPlot(gd, transform); + recalculateInverse(gd); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + _dragRelease(start, end); + }) + .then(function() { + _assertSelected({numPoints: 2, selectedLabels: ["one", "two"]}); + }) + .catch(failTest) + .then(done); + }); }); }); \ No newline at end of file From d75c018ff07701921f66a39c33d99769083bcc4f Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Thu, 5 Nov 2020 16:39:04 +1100 Subject: [PATCH 34/63] 888: polar hover test --- test/jasmine/tests/polar_test.js | 100 +++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index e087f2495e4..c02c4158b25 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1595,3 +1595,103 @@ describe('Test polar *gridshape linear* interactions', function() { .then(done); }); }); + + +describe('Polar plots with css transforms', function() { + var gd; + + beforeEach(function() { + eventRecordings = {}; + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + function _getLocalPos(element, point) { + var bb = element.getBoundingClientRect(); + return [ + bb.left + point[0], + bb.top + point[1] + ]; + } + + function transformPlot(gd, transformString) { + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + function recalculateInverse(gd) { + var inverse = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + gd._fullLayout._inverseTransform = inverse; + } + + function _hover(pos) { + return new Promise(function(resolve, reject) { + var localPos = _getLocalPos(gd, pos); + gd.once('plotly_hover', function(d) { + Lib.clearThrottle(); + resolve(d); + }); + + mouseEvent('mousemove', localPos[0], localPos[1]); + + setTimeout(function() { + reject('plotly_hover did not get called!'); + }, 100); + }); + } + + var rVals = [100, 50, 50, 100]; + var thetaVals = [135, 135, 315, 315]; + var plotSize = [400, 400]; + var mock = { + data: [{ + type: 'scatterpolar', + r: rVals, + theta: thetaVals, + mode: 'markers', + marker: { + size: 20, + } + }], + layout: { + width: plotSize[0], + height: plotSize[1], + margin: {l: 0, t: 0, r: 0, b: 0}, + hovermode: 'closest' + } + }; + var transforms = ['scale(0.5)']; + + transforms.forEach(function(transform) { + + it(`@transform_test @alex_test hover behaves correctly after css transform: ${transform}`, function(done) { + + var hoverEvents = {}; + + Plotly.plot(gd, Lib.extendDeep({}, mock)) + .then(function() { + gd.on('plotly_hover', function(d) { + hoverEvents[d.points[0].pointIndex] = true; + }); + }) + .then(function() { + transformPlot(gd, transform); + recalculateInverse(gd); + }) + .then(function() { _hover([32, 32]); }) + .then(function() { _hover([65, 65]); }) + .then(function() { _hover([132, 132]); }) + .then(function() { _hover([165, 165]); }) + .then(function() { + expect(Object.keys(hoverEvents).length).toBe(4); + }) + .catch(failTest) + .then(done); + }); + }); + +}); From d31244a3e4312c73fe9bbf37fac9e72763f3bb99 Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Mon, 9 Nov 2020 17:03:46 +1100 Subject: [PATCH 35/63] 888: fix polar dragzoom --- src/plots/polar/polar.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index ebeed62ab25..c6496289fb8 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -837,8 +837,15 @@ proto.updateMainDrag = function(fullLayout) { } function zoomMove(dx, dy) { + + var inverse = gd._fullLayout._inverseTransform; + var transformedDelta = Lib.apply3DTransform(inverse)(dx, dy); + dx = transformedDelta[0]; + dy = transformedDelta[1]; + var x1 = x0 + dx; var y1 = y0 + dy; + var rr0 = xy2r(x0, y0); var rr1 = Math.min(xy2r(x1, y1), radius); var a0 = xy2a(x0, y0); @@ -934,8 +941,10 @@ proto.updateMainDrag = function(fullLayout) { var dragModeNow = gd._fullLayout.dragmode; var bbox = mainDrag.getBoundingClientRect(); - x0 = startX - bbox.left; - y0 = startY - bbox.top; + var inverse = gd._fullLayout._inverseTransform; + var transformedCoords = Lib.apply3DTransform(inverse)(startX - bbox.left, startY - bbox.top); + x0 = transformedCoords[0]; + y0 = transformedCoords[1]; // need to offset x/y as bbox center does not // match origin for asymmetric polygons From 94f685f195d699b5f1075f0f5769be9246142d07 Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Tue, 10 Nov 2020 11:41:02 +1100 Subject: [PATCH 36/63] 888: fixed cartesian pan --- src/plots/cartesian/dragbox.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 00d756d594f..cbc63ed3f5d 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -90,6 +90,9 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var hasScatterGl, hasSplom, hasSVG; // collected changes to be made to the plot by relayout at the end var updates; + // scaling factors from css transform + var scaleX; + var scaleY; function recomputeAxisLists() { xa0 = plotinfo.xaxis; @@ -161,6 +164,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { recomputeAxisLists(); + var m = gd._fullLayout._inverseTransform; + scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + if(!allFixedRanges) { if(isMainDrag) { // main dragger handles all drag modes, and changes @@ -331,11 +338,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { x0 = transformedCoords[0]; y0 = transformedCoords[1]; - var m = gd._fullLayout._inverseTransform; - var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); - var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); - - box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0, scaleX: scaleX, scaleY: scaleY}; + box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; lum = gd._hmpixcount ? (gd._hmlumcount / gd._hmpixcount) : tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); @@ -352,8 +355,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { return false; } - var x1 = Math.max(0, Math.min(pw, box.scaleX * dx0 + x0)); - var y1 = Math.max(0, Math.min(ph, box.scaleY * dy0 + y0)); + var x1 = Math.max(0, Math.min(pw, scaleX * dx0 + x0)); + var y1 = Math.max(0, Math.min(ph, scaleY * dy0 + y0)); var dx = Math.abs(x1 - x0); var dy = Math.abs(y1 - y0); @@ -553,6 +556,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // plotDrag: move the plot in response to a drag function plotDrag(dx, dy) { + dx = dx * scaleX; + dy = dy * scaleY; // If a transition is in progress, then disable any behavior: if(gd._transitioningWithDuration) { return; From 435945b764a706adfd9a9bc42d9597c0a0a0056c Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Tue, 10 Nov 2020 12:11:33 +1100 Subject: [PATCH 37/63] 888: use scale factors in polar too for performance --- src/plots/polar/polar.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index c6496289fb8..e8bde9662e4 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -678,6 +678,10 @@ proto.updateMainDrag = function(fullLayout) { var chw = constants.cornerHalfWidth; var chl = constants.cornerLen / 2; + var m = gd._fullLayout._inverseTransform; + var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + var mainDrag = dragBox.makeDragger(layers, 'path', 'maindrag', 'crosshair'); d3.select(mainDrag) @@ -838,10 +842,8 @@ proto.updateMainDrag = function(fullLayout) { function zoomMove(dx, dy) { - var inverse = gd._fullLayout._inverseTransform; - var transformedDelta = Lib.apply3DTransform(inverse)(dx, dy); - dx = transformedDelta[0]; - dy = transformedDelta[1]; + dx = dx * scaleX; + dy = dy * scaleY; var x1 = x0 + dx; var y1 = y0 + dy; From f3c03cd655f380bf4e5a3db12c12daa537702b4a Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Mon, 16 Nov 2020 15:08:42 +1100 Subject: [PATCH 38/63] 888: assign scales at makePlotFramework --- src/components/fx/hover.js | 15 ++++++--------- src/plot_api/plot_api.js | 3 +++ src/plots/cartesian/dragbox.js | 5 ++--- src/plots/cartesian/select.js | 5 ++--- src/plots/gl3d/scene.js | 5 ++--- src/plots/polar/polar.js | 5 ++--- src/plots/ternary/ternary.js | 14 ++++++++++++-- 7 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index c65e0090ec9..1ca765f1dbf 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -192,7 +192,9 @@ exports.loneHover = function loneHover(hoverItems, opts) { d.offset -= anchor; }); - alignHoverText(hoverLabel, fullOpts.rotateLabels, opts.gd._fullLayout._inverseTransform); + var scaleX = opts.gd._fullLayout._inverseScaleX; + var scaleY = opts.gd._fullLayout._inverseScaleY; + alignHoverText(hoverLabel, fullOpts.rotateLabels, scaleX, scaleY); return multiHover ? hoverLabel : hoverLabel.node(); }; @@ -721,10 +723,8 @@ function _hover(gd, evt, subplot, noHoverEvent) { if(!helpers.isUnifiedHover(hovermode)) { hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); - alignHoverText(hoverLabels, rotateLabels, fullLayout._inverseTransform); - } - - // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true + alignHoverText(hoverLabels, rotateLabels, fullLayout._inverseScaleX, fullLayout._inverseScaleY); + } // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true // we should improve the "fx" API so other plots can use it without these hack. if(evt.target && evt.target.tagName) { var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata); @@ -1482,10 +1482,7 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) { } } -function alignHoverText(hoverLabels, rotateLabels, inverseTransform) { - var m = inverseTransform; - var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); - var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); +function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) { var pX = function(x) { return x * scaleX; }; var pY = function(y) { return y * scaleY; }; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 90e9fd089a0..050061932c1 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3716,6 +3716,9 @@ function makePlotFramework(gd) { var fullLayout = gd._fullLayout; if(fullLayout._inverseTransform === undefined) { fullLayout._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + var m = fullLayout._inverseTransform; + fullLayout._inverseScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); + fullLayout._inverseScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); } // Plot container diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index cbc63ed3f5d..9190aa709c5 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -164,9 +164,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { recomputeAxisLists(); - var m = gd._fullLayout._inverseTransform; - scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); - scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + scaleX = gd._fullLayout._inverseScaleX; + scaleY = gd._fullLayout._inverseScaleY; if(!allFixedRanges) { if(isMainDrag) { diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 415eddd3b19..029f32815e6 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -71,9 +71,8 @@ function prepSelect(e, startX, startY, dragOptions, mode) { var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(x0, y0); x0 = transformedCoords[0]; y0 = transformedCoords[1]; - var m = fullLayout._inverseTransform; - var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); - var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); + var scaleX = fullLayout._inverseScaleX; + var scaleY = fullLayout._inverseScaleY; var x1 = x0; var y1 = y0; diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 54d141df0b2..886fb14c7f1 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -296,9 +296,8 @@ proto.render = function() { // update size of svg container var svgContainer = scene.svgContainer; var clientRect = scene.container.getBoundingClientRect(); - var m = gd._fullLayout._inverseTransform; - var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); - var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; var width = clientRect.width * scaleX; var height = clientRect.height * scaleY; svgContainer.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height); diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index e8bde9662e4..95217427fd7 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -678,9 +678,8 @@ proto.updateMainDrag = function(fullLayout) { var chw = constants.cornerHalfWidth; var chl = constants.cornerLen / 2; - var m = gd._fullLayout._inverseTransform; - var scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); - var scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; var mainDrag = dragBox.makeDragger(layers, 'path', 'maindrag', 'crosshair'); diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 5610ceff51e..584a226c75c 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -502,6 +502,8 @@ proto.initInteractions = function() { var dragger = _this.layers.plotbg.select('path').node(); var gd = _this.graphDiv; var zoomLayer = gd._fullLayout._zoomlayer; + var scaleX; + var scaleY; // use plotbg for the main interactions this.dragOptions = { @@ -520,6 +522,9 @@ proto.initInteractions = function() { _this.dragOptions.xaxes = [_this.xaxis]; _this.dragOptions.yaxes = [_this.yaxis]; + scaleX = gd._fullLayout._inverseScaleX; + scaleY = gd._fullLayout._inverseScaleY; + var dragModeNow = _this.dragOptions.dragmode = gd._fullLayout.dragmode; if(freeMode(dragModeNow)) _this.dragOptions.minDrag = 1; @@ -573,8 +578,13 @@ proto.initInteractions = function() { function zoomPrep(e, startX, startY) { var dragBBox = dragger.getBoundingClientRect(); + var inverse = gd._fullLayout._inverseTransform; x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; + var transformedCoords = Lib.apply3DTransform(inverse)(x0, y0); + x0 = transformedCoords[0]; + y0 = transformedCoords[1]; + mins0 = { a: _this.aaxis.range[0], b: _this.baxis.range[1], @@ -614,8 +624,8 @@ proto.initInteractions = function() { function getCFrac(x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w); } function zoomMove(dx0, dy0) { - var x1 = x0 + dx0; - var y1 = y0 + dy0; + var x1 = x0 + dx0 * scaleX; + var y1 = y0 + dy0 * scaleY; var afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1))); var bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1))); var cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1))); From 23b4d3ce5cec796782e042cc808eccde046abce3 Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Mon, 16 Nov 2020 17:03:28 +1100 Subject: [PATCH 39/63] 888: fix polar plot rotation --- src/plots/polar/polar.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 95217427fd7..b34a336239a 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -1197,6 +1197,9 @@ proto.updateAngularDrag = function(fullLayout) { var fullLayoutNow = _this.gd._fullLayout; var polarLayoutNow = fullLayoutNow[_this.id]; + dx *= fullLayout._inverseScaleX; + dy *= fullLayout._inverseScaleY; + var x1 = x0 + dx; var y1 = y0 + dy; var a1 = xy2a(x1, y1); @@ -1291,6 +1294,11 @@ proto.updateAngularDrag = function(fullLayout) { var bbox = angularDrag.getBoundingClientRect(); x0 = startX - bbox.left; y0 = startY - bbox.top; + + var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(x0, y0); + x0 = transformedCoords[0]; + y0 = transformedCoords[1]; + a0 = xy2a(x0, y0); dragOpts.moveFn = moveFn; From 84ede3191af35a2fa6bf2e4dcb0ccc1ac1c78649 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 11:10:21 -0500 Subject: [PATCH 40/63] 888: fix alignHTMLWith --- src/lib/svg_text_utils.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index 93194a14145..46937c692d0 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -743,9 +743,17 @@ function alignHTMLWith(_base, container, options) { return function() { thisRect = this.node().getBoundingClientRect(); + + var x0 = getLeft() - cRect.left; + var y0 = getTop() - cRect.top; + var gd = options.gd || {}; + var transformedCoords = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(x0, y0); + x0 = transformedCoords[0]; + y0 = transformedCoords[1]; + this.style({ - top: (getTop() - cRect.top) + 'px', - left: (getLeft() - cRect.left) + 'px', + top: y0 + 'px', + left: x0 + 'px', 'z-index': 1000 }); return this; From 4b1bf6240fae052d2d971ac1ef97e80c63055bc6 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 11:33:06 -0500 Subject: [PATCH 41/63] 888: various syntax fixes --- src/plots/polar/polar.js | 1 - test/jasmine/tests/cartesian_interact_test.js | 51 ++++++++----------- test/jasmine/tests/polar_test.js | 12 ++--- 3 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index b34a336239a..8f89ab816cb 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -840,7 +840,6 @@ proto.updateMainDrag = function(fullLayout) { } function zoomMove(dx, dy) { - dx = dx * scaleX; dy = dy * scaleY; diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 33d894b940c..3552142486d 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -12,17 +12,14 @@ var mouseEvent = require('../assets/mouse_event'); var failTest = require('../assets/fail_test'); var selectButton = require('../assets/modebar_button'); var drag = require('../assets/drag'); -var click = require('../assets/click'); var doubleClick = require('../assets/double_click'); var getNodeCoords = require('../assets/get_node_coords'); var delay = require('../assets/delay'); var customAssertions = require('../assets/custom_assertions'); var assertNodeDisplay = customAssertions.assertNodeDisplay; -var assertHoverLabelContent = customAssertions.assertHoverLabelContent; var MODEBAR_DELAY = 500; -var HOVERMINTIME = require('@src/components/fx').constants.HOVERMINTIME; describe('zoom box element', function() { var mock = require('@mocks/14.json'); @@ -2292,7 +2289,7 @@ describe('Cartesian plots with css transforms', function() { function _dragRelease(start, end) { var localEnd = _getLocalPos(gd, end); _drag(start, end); - mouseEvent('mouseup',localEnd[0], localEnd[1]); + mouseEvent('mouseup', localEnd[0], localEnd[1]); } function _hover(pos) { @@ -2333,9 +2330,7 @@ describe('Cartesian plots with css transforms', function() { var transforms = ['scale(0.5)']; transforms.forEach(function(transform) { - - it(`hover behaves correctly after css transform: ${transform}`, function(done) { - + it('hover behaves correctly after css transform: ' + transform, function(done) { function _hoverAndAssertEventOccurred(point, label) { return _hover(point) .then(function() { @@ -2345,7 +2340,7 @@ describe('Cartesian plots with css transforms', function() { _unhover(point); }); } - + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { gd.on('plotly_hover', function(d) { @@ -2362,9 +2357,8 @@ describe('Cartesian plots with css transforms', function() { .catch(failTest) .then(done); }); - - it(`drag-zoom behaves correctly after css transform: ${transform}`, function(done) { - + + it('drag-zoom behaves correctly after css transform: ' + transform, function(done) { // return a rect of form {left, top, width, height} from the zoomlayer // svg path. function _getZoomlayerPathRect(pathStr) { @@ -2376,7 +2370,7 @@ describe('Cartesian plots with css transforms', function() { rect.top = Number(startCoordsString.split(',')[1]); return rect; } - + // asserts that the zoombox path must go from the start to end positions, // in css-transformed coordinates. function _assertTransformedZoombox(startPos, endPos) { @@ -2390,41 +2384,41 @@ describe('Cartesian plots with css transforms', function() { expect(zoomboxRect.width).toBeCloseTo(size[0]); expect(zoomboxRect.height).toBeCloseTo(size[1]); } - + var start = [50, 50]; - var end = [150, 150] - + var end = [150, 150]; + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { transformPlot(gd, transform); recalculateInverse(gd); }) - .then(function() {_drag(start, end); }) - .then(function() { - _assertTransformedZoombox(start, end); + .then(function() {_drag(start, end); }) + .then(function() { + _assertTransformedZoombox(start, end); }) .then(function() { mouseEvent('mouseup', 0, 0); }) .catch(failTest) .then(done); }); - - it(`select behaves correctly after css transform: ${transform}`, function(done) { - + + it('select behaves correctly after css transform: ' + transform, function(done) { function _assertSelected(expectation) { var data = gd._fullData[0]; var points = data.selectedpoints; expect(typeof(points) !== 'undefined').toBeTrue(); - if (expectation.numPoints) + if(expectation.numPoints) { expect(points.length).toBe(expectation.numPoints); - if (expectation.selectedLabels) { - var selectedLabels = points.map(function(i) { return data.x[i]; }) + } + if(expectation.selectedLabels) { + var selectedLabels = points.map(function(i) { return data.x[i]; }); expect(selectedLabels).toEqual(expectation.selectedLabels); } } - + var start = [10, 10]; var end = [200, 200]; - + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { transformPlot(gd, transform); @@ -2437,11 +2431,10 @@ describe('Cartesian plots with css transforms', function() { _dragRelease(start, end); }) .then(function() { - _assertSelected({numPoints: 2, selectedLabels: ["one", "two"]}); + _assertSelected({numPoints: 2, selectedLabels: ['one', 'two']}); }) .catch(failTest) .then(done); }); }); - -}); \ No newline at end of file +}); diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index c02c4158b25..cd68d940581 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1601,7 +1601,6 @@ describe('Polar plots with css transforms', function() { var gd; beforeEach(function() { - eventRecordings = {}; gd = createGraphDiv(); }); @@ -1667,11 +1666,9 @@ describe('Polar plots with css transforms', function() { var transforms = ['scale(0.5)']; transforms.forEach(function(transform) { - - it(`@transform_test @alex_test hover behaves correctly after css transform: ${transform}`, function(done) { - + it('@transform_test @alex_test hover behaves correctly after css transform: ' + transform, function(done) { var hoverEvents = {}; - + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { gd.on('plotly_hover', function(d) { @@ -1686,12 +1683,11 @@ describe('Polar plots with css transforms', function() { .then(function() { _hover([65, 65]); }) .then(function() { _hover([132, 132]); }) .then(function() { _hover([165, 165]); }) - .then(function() { - expect(Object.keys(hoverEvents).length).toBe(4); + .then(function() { + expect(Object.keys(hoverEvents).length).toBe(4); }) .catch(failTest) .then(done); }); }); - }); From d3b548f527f45e1a1604043982c618464265599e Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 13:54:11 -0500 Subject: [PATCH 42/63] 888: adjust scatter mapbox hover when css transition is present --- src/plots/mapbox/mapbox.js | 8 ++++++-- src/traces/scattermapbox/hover.js | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index b3268b9a5cb..9a23e0dcd5c 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -382,14 +382,18 @@ proto.createFramework = function(fullLayout) { div.style.position = 'absolute'; self.container.appendChild(div); + var gd = self.gd; + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; + // create mock x/y axes for hover routine self.xaxis = { _id: 'x', - c2p: function(v) { return self.project(v).x; } + c2p: function(v) { return self.project(v).x * scaleX; } }; self.yaxis = { _id: 'y', - c2p: function(v) { return self.project(v).y; } + c2p: function(v) { return self.project(v).y * scaleY; } }; self.updateFramework(fullLayout); diff --git a/src/traces/scattermapbox/hover.js b/src/traces/scattermapbox/hover.js index d1bd61d3a3e..7f80b05ba11 100644 --- a/src/traces/scattermapbox/hover.js +++ b/src/traces/scattermapbox/hover.js @@ -22,6 +22,10 @@ module.exports = function hoverPoints(pointData, xval, yval) { var ya = pointData.ya; var subplot = pointData.subplot; + var gd = subplot.gd; + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; + // compute winding number about [-180, 180] globe var winding = (xval >= 0) ? Math.floor((xval + 180) / 360) : @@ -52,11 +56,12 @@ module.exports = function hoverPoints(pointData, xval, yval) { var di = cd[pointData.index]; var lonlat = di.lonlat; + var lonlatShifted = [Lib.modHalf(lonlat[0], 360) + lonShift, lonlat[1]]; // shift labels back to original winded globe - var xc = xa.c2p(lonlatShifted); - var yc = ya.c2p(lonlatShifted); + var xc = xa.c2p(lonlatShifted) / scaleX; + var yc = ya.c2p(lonlatShifted) / scaleY; var rad = di.mrc || 1; pointData.x0 = xc - rad; From 4e1fd5feb80962e2d495852982ae59e9ff0a8574 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 14:28:54 -0500 Subject: [PATCH 43/63] drop unused argument --- src/traces/choropleth/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/choropleth/hover.js b/src/traces/choropleth/hover.js index 3d1609ef627..b44f2027c2c 100644 --- a/src/traces/choropleth/hover.js +++ b/src/traces/choropleth/hover.js @@ -49,7 +49,7 @@ module.exports = function hoverPoints(pointData, xval, yval) { pointData.zLabel = Axes.tickText(geo.mockAxis, geo.mockAxis.c2l(pt.z), 'hover').text; pointData.hovertemplate = pt.hovertemplate; - makeHoverInfo(pointData, trace, pt, geo.mockAxis); + makeHoverInfo(pointData, trace, pt); return [pointData]; }; From 76afc6c6ed76327a17cdc0faa4a349112e4a6738 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 14:48:14 -0500 Subject: [PATCH 44/63] a bit of optimization in choropleth/hover - avoid creating the same array multiple times --- src/traces/choropleth/hover.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/traces/choropleth/hover.js b/src/traces/choropleth/hover.js index b44f2027c2c..1ebe905c22e 100644 --- a/src/traces/choropleth/hover.js +++ b/src/traces/choropleth/hover.js @@ -19,17 +19,20 @@ module.exports = function hoverPoints(pointData, xval, yval) { var pt, i, j, isInside; + var xy = [xval, yval]; + var altXy = [xval + 360, yval]; + for(i = 0; i < cd.length; i++) { pt = cd[i]; isInside = false; if(pt._polygons) { for(j = 0; j < pt._polygons.length; j++) { - if(pt._polygons[j].contains([xval, yval])) { + if(pt._polygons[j].contains(xy)) { isInside = !isInside; } // for polygons that cross antimeridian as xval is in [-180, 180] - if(pt._polygons[j].contains([xval + 360, yval])) { + if(pt._polygons[j].contains(altXy)) { isInside = !isInside; } } From 0117b418bcb30b249b3322a51e954daf997b5f00 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 15:15:21 -0500 Subject: [PATCH 45/63] 888: fixup scattermapbox both select and hover --- src/plots/mapbox/mapbox.js | 8 ++------ src/traces/scattermapbox/hover.js | 8 ++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index 9a23e0dcd5c..b3268b9a5cb 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -382,18 +382,14 @@ proto.createFramework = function(fullLayout) { div.style.position = 'absolute'; self.container.appendChild(div); - var gd = self.gd; - var scaleX = gd._fullLayout._inverseScaleX; - var scaleY = gd._fullLayout._inverseScaleY; - // create mock x/y axes for hover routine self.xaxis = { _id: 'x', - c2p: function(v) { return self.project(v).x * scaleX; } + c2p: function(v) { return self.project(v).x; } }; self.yaxis = { _id: 'y', - c2p: function(v) { return self.project(v).y * scaleY; } + c2p: function(v) { return self.project(v).y; } }; self.updateFramework(fullLayout); diff --git a/src/traces/scattermapbox/hover.js b/src/traces/scattermapbox/hover.js index 7f80b05ba11..6d96b7b2294 100644 --- a/src/traces/scattermapbox/hover.js +++ b/src/traces/scattermapbox/hover.js @@ -42,8 +42,8 @@ module.exports = function hoverPoints(pointData, xval, yval) { var lon = Lib.modHalf(lonlat[0], 360); var lat = lonlat[1]; var pt = subplot.project([lon, lat]); - var dx = pt.x - xa.c2p([xval2, lat]); - var dy = pt.y - ya.c2p([lon, yval]); + var dx = pt.x - xa.c2p([xval2, lat]) * scaleX; + var dy = pt.y - ya.c2p([lon, yval]) * scaleY; var rad = Math.max(3, d.mrc || 0); return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); @@ -60,8 +60,8 @@ module.exports = function hoverPoints(pointData, xval, yval) { var lonlatShifted = [Lib.modHalf(lonlat[0], 360) + lonShift, lonlat[1]]; // shift labels back to original winded globe - var xc = xa.c2p(lonlatShifted) / scaleX; - var yc = ya.c2p(lonlatShifted) / scaleY; + var xc = xa.c2p(lonlatShifted); + var yc = ya.c2p(lonlatShifted); var rad = di.mrc || 1; pointData.x0 = xc - rad; From 4980e24fd517f39e86bd7671768d3b1c7b3d2d9e Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 18:44:54 -0500 Subject: [PATCH 46/63] return correct latitude and longitude from mapbox p2c - fix choroplethmapbox hover and remove scattermapbox hack --- src/plots/mapbox/mapbox.js | 13 +++++++------ src/traces/scattermapbox/hover.js | 9 ++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index b3268b9a5cb..46246fc1784 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -451,14 +451,15 @@ proto.initFx = function(calcData, fullLayout) { map.on('mousemove', function(evt) { var bb = self.div.getBoundingClientRect(); - // some hackery to get Fx.hover to work - evt.clientX = evt.point.x + bb.left; - evt.clientY = evt.point.y + bb.top; - evt.target.getBoundingClientRect = function() { return bb; }; - self.xaxis.p2c = function() { return evt.lngLat.lng; }; - self.yaxis.p2c = function() { return evt.lngLat.lat; }; + var xy = [ + evt.originalEvent.offsetX, + evt.originalEvent.offsetY + ]; + + self.xaxis.p2c = function() { return map.unproject(xy).lng; }; + self.yaxis.p2c = function() { return map.unproject(xy).lat; }; gd._fullLayout._rehover = function() { if(gd._fullLayout._hoversubplot === self.id && gd._fullLayout[self.id]) { diff --git a/src/traces/scattermapbox/hover.js b/src/traces/scattermapbox/hover.js index 6d96b7b2294..d1bd61d3a3e 100644 --- a/src/traces/scattermapbox/hover.js +++ b/src/traces/scattermapbox/hover.js @@ -22,10 +22,6 @@ module.exports = function hoverPoints(pointData, xval, yval) { var ya = pointData.ya; var subplot = pointData.subplot; - var gd = subplot.gd; - var scaleX = gd._fullLayout._inverseScaleX; - var scaleY = gd._fullLayout._inverseScaleY; - // compute winding number about [-180, 180] globe var winding = (xval >= 0) ? Math.floor((xval + 180) / 360) : @@ -42,8 +38,8 @@ module.exports = function hoverPoints(pointData, xval, yval) { var lon = Lib.modHalf(lonlat[0], 360); var lat = lonlat[1]; var pt = subplot.project([lon, lat]); - var dx = pt.x - xa.c2p([xval2, lat]) * scaleX; - var dy = pt.y - ya.c2p([lon, yval]) * scaleY; + var dx = pt.x - xa.c2p([xval2, lat]); + var dy = pt.y - ya.c2p([lon, yval]); var rad = Math.max(3, d.mrc || 0); return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); @@ -56,7 +52,6 @@ module.exports = function hoverPoints(pointData, xval, yval) { var di = cd[pointData.index]; var lonlat = di.lonlat; - var lonlatShifted = [Lib.modHalf(lonlat[0], 360) + lonShift, lonlat[1]]; // shift labels back to original winded globe From 88b24f6ae26f6d97f6cc2c0f812c713b4597e7ec Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 16 Nov 2020 19:23:24 -0500 Subject: [PATCH 47/63] offsetX and offsetY are now implemented in FireFox - remove from black list --- tasks/test_syntax.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tasks/test_syntax.js b/tasks/test_syntax.js index 8730f6bb531..48b28537fc1 100644 --- a/tasks/test_syntax.js +++ b/tasks/test_syntax.js @@ -112,9 +112,6 @@ function assertSrcContents() { // Forbidden in IE in any context var IE_BLACK_LIST = ['classList']; - // not implemented in FF, or inconsistent with others - var FF_BLACK_LIST = ['offsetX', 'offsetY']; - // require'd built-in modules var BUILTINS = ['events']; @@ -151,8 +148,6 @@ function assertSrcContents() { if(!(isSunburstOrTreemap && isLinkedToObject)) { logs.push(file + ' : contains .' + lastPart + ' (IE failure in SVG)'); } - } else if(FF_BLACK_LIST.indexOf(lastPart) !== -1) { - logs.push(file + ' : contains .' + lastPart + ' (FF failure)'); } } else if(node.type === 'Identifier' && node.source() === 'getComputedStyle') { if(node.parent.source() !== 'window.getComputedStyle') { From b2eed5249c9c915f99a0a56bde78a2d4b332e3ab Mon Sep 17 00:00:00 2001 From: Alex Hartstone Date: Tue, 17 Nov 2020 17:34:21 +1100 Subject: [PATCH 48/63] 888: added polar drag and select tests --- test/jasmine/tests/cartesian_interact_test.js | 6 +- test/jasmine/tests/polar_test.js | 70 ++++++++++++++++++- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 3552142486d..4ac36bc0538 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -2273,8 +2273,10 @@ describe('Cartesian plots with css transforms', function() { } function recalculateInverse(gd) { - var inverse = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); - gd._fullLayout._inverseTransform = inverse; + var m = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + gd._fullLayout._inverseTransform = m; + gd._fullLayout._inverseScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); + gd._fullLayout._inverseScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); } function _drag(start, end) { diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index cd68d940581..1986e49e673 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1623,8 +1623,17 @@ describe('Polar plots with css transforms', function() { } function recalculateInverse(gd) { - var inverse = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); - gd._fullLayout._inverseTransform = inverse; + var m = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); + gd._fullLayout._inverseTransform = m; + gd._fullLayout._inverseScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); + gd._fullLayout._inverseScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); + } + + + function _drag(start, dp) { + var node = d3.select('.polar > .draglayer > .maindrag').node(); + var localStart = _getLocalPos(gd, start); + return drag({node: node, dpos: dp, pos0: localStart}); } function _hover(pos) { @@ -1643,6 +1652,15 @@ describe('Polar plots with css transforms', function() { }); } + function _getVisiblePointsData(gd) { + return Array.from( + document.querySelectorAll('.point').entries(), + ([key, value]) => value, + ) + .filter(e => window.getComputedStyle(e).display !== 'none') + .map(e => e.__data__); + } + var rVals = [100, 50, 50, 100]; var thetaVals = [135, 135, 315, 315]; var plotSize = [400, 400]; @@ -1666,7 +1684,7 @@ describe('Polar plots with css transforms', function() { var transforms = ['scale(0.5)']; transforms.forEach(function(transform) { - it('@transform_test @alex_test hover behaves correctly after css transform: ' + transform, function(done) { + it('hover behaves correctly after css transform: ' + transform, function(done) { var hoverEvents = {}; Plotly.plot(gd, Lib.extendDeep({}, mock)) @@ -1689,5 +1707,51 @@ describe('Polar plots with css transforms', function() { .catch(failTest) .then(done); }); + + it(`drag-zoom behaves correctly after css transform: ${transform}`, function(done) { + + Plotly.plot(gd, Lib.extendDeep({}, mock)) + .then(function() { + transformPlot(gd, transform); + recalculateInverse(gd); + }) + .then(function() { + return _drag([30, 30], [50, 50]); + }) + .then(function() { + var points = _getVisiblePointsData(gd); + expect(points.length).toBe(2); + expect(points[0].i).toBe(0); + expect(points[1].i).toBe(3); + }) + .catch(failTest) + .then(done); + }); + + it(`select behaves correctly after css transform: ${transform}`, function(done) { + + function _assertSelected(expectation) { + var data = gd._fullData[0]; + var points = data.selectedpoints; + expect(typeof(points) !== 'undefined').toBeTrue(); + if (expectation.numPoints) + expect(points.length).toBe(expectation.numPoints); + } + + Plotly.plot(gd, Lib.extendDeep({}, mock)) + .then(function() { + transformPlot(gd, transform); + recalculateInverse(gd); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { return _drag([30, 30], [130, 130]) }) + .then(function() { + _assertSelected({numPoints: 3}); + }) + .catch(failTest) + .then(done); + }); }); }); From 8cc496c63e08cdcd01e7116cf2a748efbe3dd8da Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 10:20:36 -0500 Subject: [PATCH 49/63] fixup polar test --- test/jasmine/tests/polar_test.js | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index 1986e49e673..81911a4e854 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1652,13 +1652,13 @@ describe('Polar plots with css transforms', function() { }); } - function _getVisiblePointsData(gd) { + function _getVisiblePointsData() { return Array.from( - document.querySelectorAll('.point').entries(), - ([key, value]) => value, + document.querySelectorAll('.point').entries(), + function(e) { return e[1]; } ) - .filter(e => window.getComputedStyle(e).display !== 'none') - .map(e => e.__data__); + .filter(function(e) { return window.getComputedStyle(e).display !== 'none'; }) + .map(function(e) { return e.__data__; }); } var rVals = [100, 50, 50, 100]; @@ -1707,19 +1707,18 @@ describe('Polar plots with css transforms', function() { .catch(failTest) .then(done); }); - - it(`drag-zoom behaves correctly after css transform: ${transform}`, function(done) { - + + it('drag-zoom behaves correctly after css transform: ' + transform, function(done) { Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { transformPlot(gd, transform); recalculateInverse(gd); }) - .then(function() { + .then(function() { return _drag([30, 30], [50, 50]); }) .then(function() { - var points = _getVisiblePointsData(gd); + var points = _getVisiblePointsData(); expect(points.length).toBe(2); expect(points[0].i).toBe(0); expect(points[1].i).toBe(3); @@ -1727,17 +1726,17 @@ describe('Polar plots with css transforms', function() { .catch(failTest) .then(done); }); - - it(`select behaves correctly after css transform: ${transform}`, function(done) { - + + it('select behaves correctly after css transform: ' + transform, function(done) { function _assertSelected(expectation) { var data = gd._fullData[0]; var points = data.selectedpoints; expect(typeof(points) !== 'undefined').toBeTrue(); - if (expectation.numPoints) + if(expectation.numPoints) { expect(points.length).toBe(expectation.numPoints); + } } - + Plotly.plot(gd, Lib.extendDeep({}, mock)) .then(function() { transformPlot(gd, transform); @@ -1746,7 +1745,7 @@ describe('Polar plots with css transforms', function() { .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) - .then(function() { return _drag([30, 30], [130, 130]) }) + .then(function() { return _drag([30, 30], [130, 130]); }) .then(function() { _assertSelected({numPoints: 3}); }) From 1dee019654e49014a8a5f390b937c962470ce9e8 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 11:18:31 -0500 Subject: [PATCH 50/63] expand and fix tests - should not depend on calculating inverse matrix in test - add test case for various transforms e.g. scale and translate --- test/jasmine/tests/cartesian_interact_test.js | 51 ++++++++-------- test/jasmine/tests/polar_test.js | 60 +++++++++---------- 2 files changed, 53 insertions(+), 58 deletions(-) diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 4ac36bc0538..819708b2de3 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -2272,13 +2272,6 @@ describe('Cartesian plots with css transforms', function() { gd.style.transform = transformString; } - function recalculateInverse(gd) { - var m = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); - gd._fullLayout._inverseTransform = m; - gd._fullLayout._inverseScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); - gd._fullLayout._inverseScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); - } - function _drag(start, end) { var localStart = _getLocalPos(gd, start); var localEnd = _getLocalPos(gd, end); @@ -2329,30 +2322,40 @@ describe('Cartesian plots with css transforms', function() { margin: {l: 0, t: 0, r: 0, b: 0} } }; - var transforms = ['scale(0.5)']; - transforms.forEach(function(transform) { + [{ + transform: 'scaleX(0.5)', + hovered: 1, + selected: {numPoints: 1, selectedLabels: ['two']} + }, { + transform: 'scale(0.5)', + hovered: 1, + selected: {numPoints: 2, selectedLabels: ['one', 'two']} + }, { + transform: 'scale(0.25) translate(150px, 25%) scaleY(2)', + hovered: 1, + selected: {numPoints: 3, selectedLabels: ['one', 'two', 'three']} + }].forEach(function(t) { + var transform = t.transform; + it('hover behaves correctly after css transform: ' + transform, function(done) { function _hoverAndAssertEventOccurred(point, label) { return _hover(point) .then(function() { - expect(eventRecordings[label]).toBe(1); + expect(eventRecordings[label]).toBe(t.hovered); }) .then(function() { _unhover(point); }); } - Plotly.plot(gd, Lib.extendDeep({}, mock)) + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) .then(function() { gd.on('plotly_hover', function(d) { eventRecordings[d.points[0].x] = 1; }); }) - .then(function() { - transformPlot(gd, transform); - recalculateInverse(gd); - }) .then(function() {_hoverAndAssertEventOccurred(points[0], xLabels[0]);}) .then(function() {_hoverAndAssertEventOccurred(points[1], xLabels[1]);}) .then(function() {_hoverAndAssertEventOccurred(points[2], xLabels[2]);}) @@ -2381,7 +2384,7 @@ describe('Cartesian plots with css transforms', function() { var size = [endPos[0] - startPos[0], endPos[1] - startPos[1]]; var zb = d3.select(gd).select('g.zoomlayer > path.zoombox'); var zoomboxRect = _getZoomlayerPathRect(zb.attr('d')); - expect(zoomboxRect.left).toBeCloseTo(startPos[0]); + expect(zoomboxRect.left).toBeCloseTo(startPos[0], -1); expect(zoomboxRect.top).toBeCloseTo(startPos[1]); expect(zoomboxRect.width).toBeCloseTo(size[0]); expect(zoomboxRect.height).toBeCloseTo(size[1]); @@ -2390,11 +2393,8 @@ describe('Cartesian plots with css transforms', function() { var start = [50, 50]; var end = [150, 150]; - Plotly.plot(gd, Lib.extendDeep({}, mock)) - .then(function() { - transformPlot(gd, transform); - recalculateInverse(gd); - }) + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) .then(function() {_drag(start, end); }) .then(function() { _assertTransformedZoombox(start, end); @@ -2421,11 +2421,8 @@ describe('Cartesian plots with css transforms', function() { var start = [10, 10]; var end = [200, 200]; - Plotly.plot(gd, Lib.extendDeep({}, mock)) - .then(function() { - transformPlot(gd, transform); - recalculateInverse(gd); - }) + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) @@ -2433,7 +2430,7 @@ describe('Cartesian plots with css transforms', function() { _dragRelease(start, end); }) .then(function() { - _assertSelected({numPoints: 2, selectedLabels: ['one', 'two']}); + _assertSelected(t.selected); }) .catch(failTest) .then(done); diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index 81911a4e854..483e62d2591 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1622,14 +1622,6 @@ describe('Polar plots with css transforms', function() { gd.style.transform = transformString; } - function recalculateInverse(gd) { - var m = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); - gd._fullLayout._inverseTransform = m; - gd._fullLayout._inverseScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); - gd._fullLayout._inverseScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); - } - - function _drag(start, dp) { var node = d3.select('.polar > .draglayer > .maindrag').node(); var localStart = _getLocalPos(gd, start); @@ -1681,47 +1673,56 @@ describe('Polar plots with css transforms', function() { hovermode: 'closest' } }; - var transforms = ['scale(0.5)']; - transforms.forEach(function(transform) { + [{ + transform: 'scaleX(0.75)', + hovered: 1, + zoomed: [0, 1, 2, 3], + selected: {numPoints: 2} + }, { + transform: 'scale(0.5)', + hovered: 4, + zoomed: [0, 3], + selected: {numPoints: 3} + }, { + transform: 'scale(0.5) translate(-200px, 25%)', + hovered: 4, + zoomed: [0, 3], + selected: {numPoints: 3} + }].forEach(function(t) { + var transform = t.transform; + it('hover behaves correctly after css transform: ' + transform, function(done) { var hoverEvents = {}; - Plotly.plot(gd, Lib.extendDeep({}, mock)) + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) .then(function() { gd.on('plotly_hover', function(d) { hoverEvents[d.points[0].pointIndex] = true; }); }) - .then(function() { - transformPlot(gd, transform); - recalculateInverse(gd); - }) .then(function() { _hover([32, 32]); }) .then(function() { _hover([65, 65]); }) .then(function() { _hover([132, 132]); }) .then(function() { _hover([165, 165]); }) .then(function() { - expect(Object.keys(hoverEvents).length).toBe(4); + expect(Object.keys(hoverEvents).length).toBe(t.hovered); }) .catch(failTest) .then(done); }); it('drag-zoom behaves correctly after css transform: ' + transform, function(done) { - Plotly.plot(gd, Lib.extendDeep({}, mock)) - .then(function() { - transformPlot(gd, transform); - recalculateInverse(gd); - }) + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) + .then(function() { - return _drag([30, 30], [50, 50]); + return _drag([10, 10], [50, 50]); }) .then(function() { var points = _getVisiblePointsData(); - expect(points.length).toBe(2); - expect(points[0].i).toBe(0); - expect(points[1].i).toBe(3); + expect(points.map(function(e) { return e.i; })).toEqual(t.zoomed); }) .catch(failTest) .then(done); @@ -1737,17 +1738,14 @@ describe('Polar plots with css transforms', function() { } } - Plotly.plot(gd, Lib.extendDeep({}, mock)) - .then(function() { - transformPlot(gd, transform); - recalculateInverse(gd); - }) + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) .then(function() { return _drag([30, 30], [130, 130]); }) .then(function() { - _assertSelected({numPoints: 3}); + _assertSelected(t.selected); }) .catch(failTest) .then(done); From 02b6f79f358f3d5cac5c7da56eabc00f33a099b5 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 17:09:51 -0500 Subject: [PATCH 51/63] removed hacky clientX and clientY from scattermaobox events --- src/plots/mapbox/mapbox.js | 5 ++--- test/jasmine/tests/scattermapbox_test.js | 14 -------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index 46246fc1784..71d48193187 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -450,14 +450,13 @@ proto.initFx = function(calcData, fullLayout) { map.on('mousemove', function(evt) { var bb = self.div.getBoundingClientRect(); - - evt.target.getBoundingClientRect = function() { return bb; }; - var xy = [ evt.originalEvent.offsetX, evt.originalEvent.offsetY ]; + evt.target.getBoundingClientRect = function() { return bb; }; + self.xaxis.p2c = function() { return map.unproject(xy).lng; }; self.yaxis.p2c = function() { return map.unproject(xy).lat; }; diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 298d64f7b98..796800d48ba 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -951,7 +951,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { click(pointPos[0], pointPos[1]); var pt = futureData.points[0]; - var evt = futureData.event; expect(Object.keys(pt)).toEqual([ 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' @@ -963,9 +962,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { expect(pt.lat).toEqual(10, 'points[0].lat'); expect(pt.lon).toEqual(10, 'points[0].lon'); expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - - expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); - expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); }); }); @@ -1013,8 +1009,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { // expect(pt.lon).toEqual(10, 'points[0].lon'); // expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - // expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); - // expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); // Object.getOwnPropertyNames(clickOpts).forEach(function(opt) { // expect(evt[opt]).toEqual(clickOpts[opt], 'event.' + opt); // }); @@ -1035,7 +1029,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { mouseEvent('mousemove', pointPos[0], pointPos[1]); var pt = futureData.points[0]; - var evt = futureData.event; expect(Object.keys(pt)).toEqual([ 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' @@ -1047,9 +1040,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { expect(pt.lat).toEqual(10, 'points[0].lat'); expect(pt.lon).toEqual(10, 'points[0].lon'); expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - - expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); - expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); }); }); @@ -1065,7 +1055,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { it('@gl should contain the correct fields', function(done) { move(pointPos[0], pointPos[1], nearPos[0], nearPos[1], HOVERMINTIME + 10).then(function() { var pt = futureData.points[0]; - var evt = futureData.event; expect(Object.keys(pt)).toEqual([ 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' @@ -1077,9 +1066,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { expect(pt.lat).toEqual(10, 'points[0].lat'); expect(pt.lon).toEqual(10, 'points[0].lon'); expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - - expect(evt.clientX).toEqual(nearPos[0], 'event.clientX'); - expect(evt.clientY).toEqual(nearPos[1], 'event.clientY'); }).then(done); }); }); From 805d97b93b42659a868c0314d730da459cdc69fa Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 17:36:01 -0500 Subject: [PATCH 52/63] add scattermapbox tests when css transform is present --- test/jasmine/tests/scattermapbox_test.js | 123 +++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 796800d48ba..63d3df59230 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -1070,3 +1070,126 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { }); }); }); + +describe('@noCI Test plotly events on a scattermapbox plot when css transform is present:', function() { + var mock = require('@mocks/mapbox_0.json'); + var pointPos = [440 / 2, 290 / 2]; + var nearPos = [460 / 2, 290 / 2]; + var blankPos = [10 / 2, 10 / 2]; + var mockCopy; + var gd; + + function transformPlot(gd, transformString) { + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + beforeAll(function() { + Plotly.setPlotConfig({ + mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN + }); + }); + + beforeEach(function(done) { + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.width = 800; + mockCopy.layout.height = 500; + + transformPlot(gd, 'translate(-25%, -25%) scale(0.5)'); + Plotly.plot(gd, mockCopy).then(done); + }); + + afterEach(destroyGraphDiv); + + describe('click events', function() { + var futureData; + + beforeEach(function() { + futureData = undefined; + gd.on('plotly_click', function(data) { + futureData = data; + }); + }); + + it('@gl should not be trigged when not on data points', function() { + click(blankPos[0], blankPos[1]); + expect(futureData).toBe(undefined); + }); + + it('@gl should contain the correct fields', function() { + click(pointPos[0], pointPos[1]); + + var pt = futureData.points[0]; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(10, 'points[0].lat'); + expect(pt.lon).toEqual(10, 'points[0].lon'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + }); + }); + + describe('hover events', function() { + var futureData; + + beforeEach(function() { + gd.on('plotly_hover', function(data) { + futureData = data; + }); + }); + + it('@gl should contain the correct fields', function() { + mouseEvent('mousemove', blankPos[0], blankPos[1]); + mouseEvent('mousemove', pointPos[0], pointPos[1]); + + var pt = futureData.points[0]; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(10, 'points[0].lat'); + expect(pt.lon).toEqual(10, 'points[0].lon'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + }); + }); + + describe('unhover events', function() { + var futureData; + + beforeEach(function() { + gd.on('plotly_unhover', function(data) { + futureData = data; + }); + }); + + it('@gl should contain the correct fields', function(done) { + move(pointPos[0], pointPos[1], nearPos[0], nearPos[1], HOVERMINTIME + 10).then(function() { + var pt = futureData.points[0]; + + expect(Object.keys(pt)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' + ]); + + expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); + expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); + expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); + expect(pt.lat).toEqual(10, 'points[0].lat'); + expect(pt.lon).toEqual(10, 'points[0].lon'); + expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); + }).then(done); + }); + }); +}); From 127f17120adac7e44da647efcb8f878e8935d5b6 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 18:47:09 -0500 Subject: [PATCH 53/63] expand choropleth hover tests to cover css transform --- test/jasmine/tests/choropleth_test.js | 234 +++++++++++++++----------- 1 file changed, 137 insertions(+), 97 deletions(-) diff --git a/test/jasmine/tests/choropleth_test.js b/test/jasmine/tests/choropleth_test.js index 9aee3409e56..9e8a6a173eb 100644 --- a/test/jasmine/tests/choropleth_test.js +++ b/test/jasmine/tests/choropleth_test.js @@ -156,8 +156,21 @@ describe('Test choropleth hover:', function() { afterEach(destroyGraphDiv); - function run(pos, fig, content, style) { + function transformPlot(gd, transformString) { + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + function run(hasCssTransform, pos, fig, content, style) { gd = createGraphDiv(); + var scale = 1; + if(hasCssTransform) { + scale = 0.5; + transformPlot(gd, 'translate(-25%, -25%) scale(0.5)'); + } style = style || { bgcolor: 'rgb(68, 68, 68)', @@ -168,7 +181,7 @@ describe('Test choropleth hover:', function() { }; return Plotly.plot(gd, fig).then(function() { - mouseEvent('mousemove', pos[0], pos[1]); + mouseEvent('mousemove', scale * pos[0], scale * pos[1]); assertHoverLabelContent({ nums: content[0], name: content[1] @@ -180,102 +193,123 @@ describe('Test choropleth hover:', function() { }); } - it('should generate hover label info (base)', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); - - run( - [400, 160], - fig, - ['RUS\n10', 'trace 1'] - ) - .then(done); + [false, true].forEach(function(hasCssTransform) { + it('should generate hover label info (base), hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); + + run( + hasCssTransform, + [400, 160], + fig, + ['RUS\n10', 'trace 1'] + ) + .then(done); + }); }); - it('should use the hovertemplate', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); - fig.data[1].hovertemplate = 'tpl %{z}x'; - - run( - [400, 160], - fig, - ['tpl 10', 'x'] - ) - .then(done); + [false, true].forEach(function(hasCssTransform) { + it('should use the hovertemplate, hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); + fig.data[1].hovertemplate = 'tpl %{z}x'; + + run( + hasCssTransform, + [400, 160], + fig, + ['tpl 10', 'x'] + ) + .then(done); + }); }); - it('should generate hover label info (\'text\' single value case)', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); - fig.data[1].text = 'tExT'; - fig.data[1].hoverinfo = 'text'; - - run( - [400, 160], - fig, - ['tExT', null] - ) - .then(done); + [false, true].forEach(function(hasCssTransform) { + it('should generate hover label info (\'text\' single value case), hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); + fig.data[1].text = 'tExT'; + fig.data[1].hoverinfo = 'text'; + + run( + hasCssTransform, + [400, 160], + fig, + ['tExT', null] + ) + .then(done); + }); }); - it('should generate hover label info (\'text\' array case)', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); - fig.data[1].text = ['tExT', 'TeXt', '-text-']; - fig.data[1].hoverinfo = 'text'; - - run( - [400, 160], - fig, - ['-text-', null] - ) - .then(done); + [false, true].forEach(function(hasCssTransform) { + it('should generate hover label info (\'text\' array case), hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); + fig.data[1].text = ['tExT', 'TeXt', '-text-']; + fig.data[1].hoverinfo = 'text'; + + run( + hasCssTransform, + [400, 160], + fig, + ['-text-', null] + ) + .then(done); + }); }); - it('should generate hover labels from `hovertext`', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); - fig.data[1].hovertext = ['tExT', 'TeXt', '-text-']; - fig.data[1].text = ['N', 'O', 'P']; - fig.data[1].hoverinfo = 'text'; - - run( - [400, 160], - fig, - ['-text-', null] - ) - .then(done); + [false, true].forEach(function(hasCssTransform) { + it('should generate hover labels from `hovertext`, hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); + fig.data[1].hovertext = ['tExT', 'TeXt', '-text-']; + fig.data[1].text = ['N', 'O', 'P']; + fig.data[1].hoverinfo = 'text'; + + run( + hasCssTransform, + [400, 160], + fig, + ['-text-', null] + ) + .then(done); + }); }); - it('should generate hover label with custom styling', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); - fig.data[1].hoverlabel = { - bgcolor: 'red', - bordercolor: ['blue', 'black', 'green'], - font: {family: 'Roboto'} - }; + [false, true].forEach(function(hasCssTransform) { + it('should generate hover label with custom styling, hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); + fig.data[1].hoverlabel = { + bgcolor: 'red', + bordercolor: ['blue', 'black', 'green'], + font: {family: 'Roboto'} + }; - run( - [400, 160], - fig, - ['RUS\n10', 'trace 1'], - { - bgcolor: 'rgb(255, 0, 0)', - bordercolor: 'rgb(0, 128, 0)', - fontColor: 'rgb(0, 128, 0)', - fontSize: 13, - fontFamily: 'Roboto' - } - ) - .then(done); + run( + hasCssTransform, + [400, 160], + fig, + ['RUS\n10', 'trace 1'], + { + bgcolor: 'rgb(255, 0, 0)', + bordercolor: 'rgb(0, 128, 0)', + fontColor: 'rgb(0, 128, 0)', + fontSize: 13, + fontFamily: 'Roboto' + } + ) + .then(done); + }); }); - it('should generate hover label with arrayOk \'hoverinfo\' settings', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); - fig.data[1].hoverinfo = ['location', 'z', 'location+name']; - - run( - [400, 160], - fig, - ['RUS', 'trace 1'] - ) - .then(done); + [false, true].forEach(function(hasCssTransform) { + it('should generate hover label with arrayOk \'hoverinfo\' settings, hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_first.json')); + fig.data[1].hoverinfo = ['location', 'z', 'location+name']; + + run( + hasCssTransform, + [400, 160], + fig, + ['RUS', 'trace 1'] + ) + .then(done); + }); }); describe('should preserve z formatting hovetemplate equivalence', function() { @@ -292,24 +326,30 @@ describe('Test choropleth hover:', function() { var pos = [400, 160]; var exp = ['10.02132', 'RUS']; - it('- base case (truncate z decimals)', function(done) { - run(pos, base(), exp).then(done); + [false, true].forEach(function(hasCssTransform) { + it('- base case (truncate z decimals), hasCssTransform: ' + hasCssTransform, function(done) { + run(hasCssTransform, pos, base(), exp).then(done); + }); }); - it('- hovertemplate case (same z truncation)', function(done) { - var fig = base(); - fig.hovertemplate = '%{z}%{location}'; - run(pos, fig, exp).then(done); + [false, true].forEach(function(hasCssTransform) { + it('- hovertemplate case (same z truncation), hasCssTransform: ' + hasCssTransform, function(done) { + var fig = base(); + fig.hovertemplate = '%{z}%{location}'; + run(hasCssTransform, pos, fig, exp).then(done); + }); }); }); - it('should include *properties* from input custom geojson', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/geo_custom-geojson.json')); - fig.data = [fig.data[1]]; - fig.data[0].hovertemplate = '%{properties.name}%{ct[0]:.1f} | %{ct[1]:.1f}'; - fig.layout.geo.projection = {scale: 20}; + [false, true].forEach(function(hasCssTransform) { + it('should include *properties* from input custom geojson, hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_custom-geojson.json')); + fig.data = [fig.data[1]]; + fig.data[0].hovertemplate = '%{properties.name}%{ct[0]:.1f} | %{ct[1]:.1f}'; + fig.layout.geo.projection = {scale: 20}; - run([300, 200], fig, ['New York', '-75.1 | 42.6']).then(done); + run(hasCssTransform, [300, 200], fig, ['New York', '-75.1 | 42.6']).then(done); + }); }); }); From 12a57c96dfba7d34086fd70758f9175a564aa173 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 18:55:27 -0500 Subject: [PATCH 54/63] expand choroplethmapbox hover tests to cover css transform --- test/jasmine/tests/choroplethmapbox_test.js | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/jasmine/tests/choroplethmapbox_test.js b/test/jasmine/tests/choroplethmapbox_test.js index 1940e983e6f..7e5fd3aaa3b 100644 --- a/test/jasmine/tests/choroplethmapbox_test.js +++ b/test/jasmine/tests/choroplethmapbox_test.js @@ -525,8 +525,21 @@ describe('@noCI Test choroplethmapbox hover:', function() { setTimeout(done, 200); }); - function run(s, done) { + function transformPlot(gd, transformString) { + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + function run(hasCssTransform, s, done) { gd = createGraphDiv(); + var scale = 1; + if(hasCssTransform) { + scale = 0.5; + transformPlot(gd, 'translate(-25%, -25%) scale(0.5)'); + } var fig = Lib.extendDeep({}, s.mock || require('@mocks/mapbox_choropleth0.json') @@ -569,7 +582,7 @@ describe('@noCI Test choroplethmapbox hover:', function() { setTimeout(done, 0); }); - mouseEvent('mousemove', pos[0], pos[1]); + mouseEvent('mousemove', scale * pos[0], scale * pos[1]); }) .catch(failTest); } @@ -631,8 +644,10 @@ describe('@noCI Test choroplethmapbox hover:', function() { }]; specs.forEach(function(s) { - it('@gl should generate correct hover labels ' + s.desc, function(done) { - run(s, done); + [false, true].forEach(function(hasCssTransform) { + it('@gl should generate correct hover labels ' + s.desc + ', hasCssTransform: ' + hasCssTransform, function(done) { + run(hasCssTransform, s, done); + }); }); }); }); From 34845ecd80631c4e6996b09fd407408a32ce6c92 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 19:56:50 -0500 Subject: [PATCH 55/63] expand select tests for various traces to cover css transform --- test/jasmine/tests/select_test.js | 2164 +++++++++++++++-------------- 1 file changed, 1123 insertions(+), 1041 deletions(-) diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index fbcc5261fa3..47f0a3ecbad 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -1836,10 +1836,32 @@ describe('Test select box and lasso per trace:', function() { }; } - function _run(dragPath, afterDragFn, dblClickPos, eventCounts, msg) { + function transformPlot(gd, transformString) { + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + var cssTransform = 'translate(-25%, -25%) scale(0.5)'; + + function _run(hasCssTransform, dragPath, afterDragFn, dblClickPos, eventCounts, msg) { afterDragFn = afterDragFn || function() {}; dblClickPos = dblClickPos || [250, 200]; + var scale = 1; + if(hasCssTransform) { + scale = 0.5; + } + dblClickPos[0] *= scale; + dblClickPos[1] *= scale; + for(var i = 0; i < dragPath.length; i++) { + for(var j = 0; j < dragPath[i].length; j++) { + dragPath[i][j] *= scale; + } + } + resetEvents(gd); assertSelectionNodes(0, 0); @@ -1862,1018 +1884,989 @@ describe('Test select box and lasso per trace:', function() { }); } - it('@flaky should work on scatterternary traces', function(done) { - var assertPoints = makeAssertPoints(['a', 'b', 'c']); - var assertSelectedPoints = makeAssertSelectedPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on scatterternary traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['a', 'b', 'c']); + var assertSelectedPoints = makeAssertSelectedPoints(); - var fig = Lib.extendDeep({}, require('@mocks/ternary_simple')); - fig.layout.width = 800; - fig.layout.dragmode = 'select'; - addInvisible(fig); + var fig = Lib.extendDeep({}, require('@mocks/ternary_simple')); + fig.layout.width = 800; + fig.layout.dragmode = 'select'; + addInvisible(fig); - Plotly.plot(gd, fig).then(function() { - return _run( - [[400, 200], [445, 235]], - function() { - assertPoints([[0.5, 0.25, 0.25]]); - assertSelectedPoints({0: [0]}); - }, - [380, 180], - BOXEVENTS, 'scatterternary select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[400, 200], [445, 200], [445, 235], [400, 235], [400, 200]], - function() { - assertPoints([[0.5, 0.25, 0.25]]); - assertSelectedPoints({0: [0]}); - }, - [380, 180], - LASSOEVENTS, 'scatterternary lasso' - ); - }) - .then(function() { - // should work after a relayout too - return Plotly.relayout(gd, 'width', 400); - }) - .then(function() { - return _run( - [[200, 200], [230, 200], [230, 230], [200, 230], [200, 200]], - function() { - assertPoints([[0.5, 0.25, 0.25]]); - assertSelectedPoints({0: [0]}); - }, - [180, 180], - LASSOEVENTS, 'scatterternary lasso after relayout' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig).then(function() { + return _run(hasCssTransform, + [[400, 200], [445, 235]], + function() { + assertPoints([[0.5, 0.25, 0.25]]); + assertSelectedPoints({0: [0]}); + }, + [380, 180], + BOXEVENTS, 'scatterternary select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[400, 200], [445, 200], [445, 235], [400, 235], [400, 200]], + function() { + assertPoints([[0.5, 0.25, 0.25]]); + assertSelectedPoints({0: [0]}); + }, + [380, 180], + LASSOEVENTS, 'scatterternary lasso' + ); + }) + .then(function() { + // should work after a relayout too + return Plotly.relayout(gd, 'width', 400); + }) + .then(function() { + return _run(hasCssTransform, + [[200, 200], [230, 200], [230, 230], [200, 230], [200, 200]], + function() { + assertPoints([[0.5, 0.25, 0.25]]); + assertSelectedPoints({0: [0]}); + }, + [180, 180], + LASSOEVENTS, 'scatterternary lasso after relayout' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work on scattercarpet traces', function(done) { - var assertPoints = makeAssertPoints(['a', 'b']); - var assertSelectedPoints = makeAssertSelectedPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on scattercarpet traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['a', 'b']); + var assertSelectedPoints = makeAssertSelectedPoints(); - var fig = Lib.extendDeep({}, require('@mocks/scattercarpet')); - delete fig.data[6].selectedpoints; - fig.layout.dragmode = 'select'; - addInvisible(fig); + var fig = Lib.extendDeep({}, require('@mocks/scattercarpet')); + delete fig.data[6].selectedpoints; + fig.layout.dragmode = 'select'; + addInvisible(fig); - Plotly.plot(gd, fig).then(function() { - return _run( - [[300, 200], [400, 250]], - function() { - assertPoints([[0.2, 1.5]]); - assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []}); - }, - null, BOXEVENTS, 'scattercarpet select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[300, 200], [400, 200], [400, 250], [300, 250], [300, 200]], - function() { - assertPoints([[0.2, 1.5]]); - assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []}); - }, - null, LASSOEVENTS, 'scattercarpet lasso' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig).then(function() { + return _run(hasCssTransform, + [[300, 200], [400, 250]], + function() { + assertPoints([[0.2, 1.5]]); + assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []}); + }, + null, BOXEVENTS, 'scattercarpet select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[300, 200], [400, 200], [400, 250], [300, 250], [300, 200]], + function() { + assertPoints([[0.2, 1.5]]); + assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []}); + }, + null, LASSOEVENTS, 'scattercarpet lasso' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@noCI @gl should work on scattermapbox traces', function(done) { - var assertPoints = makeAssertPoints(['lon', 'lat']); - var assertRanges = makeAssertRanges('mapbox'); - var assertLassoPoints = makeAssertLassoPoints('mapbox'); - var assertSelectedPoints = makeAssertSelectedPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@noCI @gl should work on scattermapbox traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['lon', 'lat']); + var assertRanges = makeAssertRanges('mapbox'); + var assertLassoPoints = makeAssertLassoPoints('mapbox'); + var assertSelectedPoints = makeAssertSelectedPoints(); - var fig = Lib.extendDeep({}, require('@mocks/mapbox_bubbles-text')); + var fig = Lib.extendDeep({}, require('@mocks/mapbox_bubbles-text')); - fig.data[0].lon.push(null); - fig.data[0].lat.push(null); + fig.data[0].lon.push(null); + fig.data[0].lat.push(null); - fig.layout.dragmode = 'select'; - fig.config = { - mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN - }; - addInvisible(fig); + fig.layout.dragmode = 'select'; + fig.config = { + mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN + }; + addInvisible(fig); - Plotly.plot(gd, fig).then(function() { - return _run( - [[370, 120], [500, 200]], - function() { - assertPoints([[30, 30]]); - assertRanges([[21.99, 34.55], [38.14, 25.98]]); - assertSelectedPoints({0: [2]}); - }, - null, BOXEVENTS, 'scattermapbox select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], - function() { - assertPoints([[20, 20]]); - assertSelectedPoints({0: [1]}); - assertLassoPoints([ - [13.28, 25.97], [13.28, 14.33], [25.71, 14.33], [25.71, 25.97], [13.28, 25.97] - ]); - }, - null, LASSOEVENTS, 'scattermapbox lasso' - ); - }) - .then(function() { - // make selection handlers don't get called in 'pan' dragmode - return Plotly.relayout(gd, 'dragmode', 'pan'); - }) - .then(function() { - return _run( - [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattermapbox pan' - ); - }) - .catch(failTest) - .then(done); - }, LONG_TIMEOUT_INTERVAL); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig).then(function() { + return _run(hasCssTransform, + [[370, 120], [500, 200]], + function() { + assertPoints([[30, 30]]); + assertRanges([[21.99, 34.55], [38.14, 25.98]]); + assertSelectedPoints({0: [2]}); + }, + null, BOXEVENTS, 'scattermapbox select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], + function() { + assertPoints([[20, 20]]); + assertSelectedPoints({0: [1]}); + assertLassoPoints([ + [13.28, 25.97], [13.28, 14.33], [25.71, 14.33], [25.71, 25.97], [13.28, 25.97] + ]); + }, + null, LASSOEVENTS, 'scattermapbox lasso' + ); + }) + .then(function() { + // make selection handlers don't get called in 'pan' dragmode + return Plotly.relayout(gd, 'dragmode', 'pan'); + }) + .then(function() { + return _run(hasCssTransform, + [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattermapbox pan' + ); + }) + .catch(failTest) + .then(done); + }, LONG_TIMEOUT_INTERVAL); + }); - it('@noCI @gl should work on choroplethmapbox traces', function(done) { - var assertPoints = makeAssertPoints(['location', 'z']); - var assertRanges = makeAssertRanges('mapbox'); - var assertLassoPoints = makeAssertLassoPoints('mapbox'); - var assertSelectedPoints = makeAssertSelectedPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@noCI @gl should work on choroplethmapbox traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['location', 'z']); + var assertRanges = makeAssertRanges('mapbox'); + var assertLassoPoints = makeAssertLassoPoints('mapbox'); + var assertSelectedPoints = makeAssertSelectedPoints(); - var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth0.json')); + var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth0.json')); - fig.data[0].locations.push(null); + fig.data[0].locations.push(null); - fig.layout.dragmode = 'select'; - fig.config = { - mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN - }; - addInvisible(fig); + fig.layout.dragmode = 'select'; + fig.config = { + mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN + }; + addInvisible(fig); - Plotly.plot(gd, fig).then(function() { - return _run( - [[150, 150], [300, 300]], - function() { - assertPoints([['NY', 10]]); - assertRanges([[-83.29, 46.13], [-73.97, 39.29]]); - assertSelectedPoints({0: [0]}); - }, - null, BOXEVENTS, 'choroplethmapbox select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], - function() { - assertPoints([['MA', 20]]); - assertSelectedPoints({0: [1]}); - assertLassoPoints([ - [-73.97, 43.936], [-73.97, 39.293], [-67.756, 39.293], - [-67.756, 43.936], [-73.971, 43.936] - ]); - }, - null, LASSOEVENTS, 'choroplethmapbox lasso' - ); - }) - .catch(failTest) - .then(done); - }, LONG_TIMEOUT_INTERVAL); - - it('@flaky should work on scattergeo traces', function(done) { - var assertPoints = makeAssertPoints(['lon', 'lat']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges('geo'); - var assertLassoPoints = makeAssertLassoPoints('geo'); - - function assertNodeOpacity(exp) { - var traces = d3.select(gd).selectAll('.scatterlayer > .trace'); - expect(traces.size()).toBe(Object.keys(exp).length, 'correct # of trace '); - - traces.each(function(_, i) { - d3.select(this).selectAll('path.point').each(function(_, j) { - expect(Number(this.style.opacity)) - .toBe(exp[i][j], 'node opacity - trace ' + i + ' pt ' + j); - }); - }); - } + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig).then(function() { + return _run(hasCssTransform, + [[150, 150], [300, 300]], + function() { + assertPoints([['NY', 10]]); + assertRanges([[-83.29, 46.13], [-73.97, 39.29]]); + assertSelectedPoints({0: [0]}); + }, + null, BOXEVENTS, 'choroplethmapbox select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], + function() { + assertPoints([['MA', 20]]); + assertSelectedPoints({0: [1]}); + assertLassoPoints([ + [-73.97, 43.936], [-73.97, 39.293], [-67.756, 39.293], + [-67.756, 43.936], [-73.971, 43.936] + ]); + }, + null, LASSOEVENTS, 'choroplethmapbox lasso' + ); + }) + .catch(failTest) + .then(done); + }, LONG_TIMEOUT_INTERVAL); + }); - var fig = { - data: [{ - type: 'scattergeo', - lon: [10, 20, 30, null], - lat: [10, 20, 30, null] - }, { - type: 'scattergeo', - lon: [-10, -20, -30], - lat: [10, 20, 30] - }], - layout: { - showlegend: false, - dragmode: 'select', - width: 800, - height: 600 + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on scattergeo traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['lon', 'lat']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges('geo'); + var assertLassoPoints = makeAssertLassoPoints('geo'); + + function assertNodeOpacity(exp) { + var traces = d3.select(gd).selectAll('.scatterlayer > .trace'); + expect(traces.size()).toBe(Object.keys(exp).length, 'correct # of trace '); + + traces.each(function(_, i) { + d3.select(this).selectAll('path.point').each(function(_, j) { + expect(Number(this.style.opacity)) + .toBe(exp[i][j], 'node opacity - trace ' + i + ' pt ' + j); + }); + }); } - }; - addInvisible(fig); - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[350, 200], [450, 400]], - function() { - assertPoints([[10, 10], [20, 20], [-10, 10], [-20, 20]]); - assertSelectedPoints({0: [0, 1], 1: [0, 1]}); - assertNodeOpacity({0: [1, 1, 0.2], 1: [1, 1, 0.2]}); - assertRanges([[-28.13, 61.88], [28.13, -50.64]]); - }, - null, BOXEVENTS, 'scattergeo select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], - function() { - assertPoints([[-10, 10], [-20, 20], [-30, 30]]); - assertSelectedPoints({0: [], 1: [0, 1, 2]}); - assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]}); - assertLassoPoints([ - [-56.25, 61.88], [-56.24, 5.63], [0, 5.63], [0, 61.88], [-56.25, 61.88] - ]); - }, - null, LASSOEVENTS, 'scattergeo lasso' - ); - }) - .then(function() { - // some projection types can't handle BADNUM during c2p, - // make they are skipped here - return Plotly.relayout(gd, 'geo.projection.type', 'robinson'); - }) - .then(function() { - return _run( - [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], - function() { - assertPoints([[-10, 10], [-20, 20], [-30, 30]]); - assertSelectedPoints({0: [], 1: [0, 1, 2]}); - assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]}); - assertLassoPoints([ - [-67.40, 55.07], [-56.33, 4.968], [0, 4.968], [0, 55.07], [-67.40, 55.07] - ]); - }, - null, LASSOEVENTS, 'scattergeo lasso (on robinson projection)' - ); - }) - .then(function() { - // make sure selection handlers don't get called in 'pan' dragmode - return Plotly.relayout(gd, 'dragmode', 'pan'); - }) - .then(function() { - return _run( - [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattergeo pan' - ); - }) - .catch(failTest) - .then(done); - }, LONG_TIMEOUT_INTERVAL); + var fig = { + data: [{ + type: 'scattergeo', + lon: [10, 20, 30, null], + lat: [10, 20, 30, null] + }, { + type: 'scattergeo', + lon: [-10, -20, -30], + lat: [10, 20, 30] + }], + layout: { + showlegend: false, + dragmode: 'select', + width: 800, + height: 600 + } + }; + addInvisible(fig); - it('@flaky should work on scatterpolar traces', function(done) { - var assertPoints = makeAssertPoints(['r', 'theta']); - var assertSelectedPoints = makeAssertSelectedPoints(); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[350, 200], [450, 400]], + function() { + assertPoints([[10, 10], [20, 20], [-10, 10], [-20, 20]]); + assertSelectedPoints({0: [0, 1], 1: [0, 1]}); + assertNodeOpacity({0: [1, 1, 0.2], 1: [1, 1, 0.2]}); + assertRanges([[-28.13, 61.88], [28.13, -50.64]]); + }, + null, BOXEVENTS, 'scattergeo select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], + function() { + assertPoints([[-10, 10], [-20, 20], [-30, 30]]); + assertSelectedPoints({0: [], 1: [0, 1, 2]}); + assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]}); + assertLassoPoints([ + [-56.25, 61.88], [-56.24, 5.63], [0, 5.63], [0, 61.88], [-56.25, 61.88] + ]); + }, + null, LASSOEVENTS, 'scattergeo lasso' + ); + }) + .then(function() { + // some projection types can't handle BADNUM during c2p, + // make they are skipped here + return Plotly.relayout(gd, 'geo.projection.type', 'robinson'); + }) + .then(function() { + return _run(hasCssTransform, + [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], + function() { + assertPoints([[-10, 10], [-20, 20], [-30, 30]]); + assertSelectedPoints({0: [], 1: [0, 1, 2]}); + assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]}); + assertLassoPoints([ + [-67.40, 55.07], [-56.33, 4.968], [0, 4.968], [0, 55.07], [-67.40, 55.07] + ]); + }, + null, LASSOEVENTS, 'scattergeo lasso (on robinson projection)' + ); + }) + .then(function() { + // make sure selection handlers don't get called in 'pan' dragmode + return Plotly.relayout(gd, 'dragmode', 'pan'); + }) + .then(function() { + return _run(hasCssTransform, + [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattergeo pan' + ); + }) + .catch(failTest) + .then(done); + }, LONG_TIMEOUT_INTERVAL); + }); - var fig = Lib.extendDeep({}, require('@mocks/polar_subplots')); - fig.layout.width = 800; - fig.layout.dragmode = 'select'; - addInvisible(fig); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on scatterpolar traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['r', 'theta']); + var assertSelectedPoints = makeAssertSelectedPoints(); - Plotly.plot(gd, fig).then(function() { - return _run( - [[150, 150], [350, 250]], - function() { - assertPoints([[1, 0], [2, 45]]); - assertSelectedPoints({0: [0, 1]}); - }, - [200, 200], - BOXEVENTS, 'scatterpolar select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]], - function() { - assertPoints([[1, 0], [2, 45]]); - assertSelectedPoints({0: [0, 1]}); - }, - [200, 200], - LASSOEVENTS, 'scatterpolar lasso' - ); - }) - .catch(failTest) - .then(done); + var fig = Lib.extendDeep({}, require('@mocks/polar_subplots')); + fig.layout.width = 800; + fig.layout.dragmode = 'select'; + addInvisible(fig); + + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig).then(function() { + return _run(hasCssTransform, + [[150, 150], [350, 250]], + function() { + assertPoints([[1, 0], [2, 45]]); + assertSelectedPoints({0: [0, 1]}); + }, + [200, 200], + BOXEVENTS, 'scatterpolar select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]], + function() { + assertPoints([[1, 0], [2, 45]]); + assertSelectedPoints({0: [0, 1]}); + }, + [200, 200], + LASSOEVENTS, 'scatterpolar lasso' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work on barpolar traces', function(done) { - var assertPoints = makeAssertPoints(['r', 'theta']); - var assertSelectedPoints = makeAssertSelectedPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on barpolar traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['r', 'theta']); + var assertSelectedPoints = makeAssertSelectedPoints(); - var fig = Lib.extendDeep({}, require('@mocks/polar_wind-rose.json')); - fig.layout.showlegend = false; - fig.layout.width = 500; - fig.layout.height = 500; - fig.layout.dragmode = 'select'; - addInvisible(fig); + var fig = Lib.extendDeep({}, require('@mocks/polar_wind-rose.json')); + fig.layout.showlegend = false; + fig.layout.width = 500; + fig.layout.height = 500; + fig.layout.dragmode = 'select'; + addInvisible(fig); - Plotly.plot(gd, fig).then(function() { - return _run( - [[150, 150], [250, 250]], - function() { - assertPoints([ - [62.5, 'N-W'], [55, 'N-W'], [40, 'North'], - [40, 'N-W'], [20, 'North'], [22.5, 'N-W'] - ]); - assertSelectedPoints({ - 0: [7], - 1: [7], - 2: [0, 7], - 3: [0, 7] - }); - }, - [200, 200], - BOXEVENTS, 'barpolar select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]], - function() { - assertPoints([ - [62.5, 'N-W'], [50, 'N-E'], [55, 'N-W'], [40, 'North'], - [30, 'N-E'], [40, 'N-W'], [20, 'North'], [7.5, 'N-E'], [22.5, 'N-W'] - ]); - assertSelectedPoints({ - 0: [7], - 1: [1, 7], - 2: [0, 1, 7], - 3: [0, 1, 7] - }); - }, - [200, 200], - LASSOEVENTS, 'barpolar lasso' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig).then(function() { + return _run(hasCssTransform, + [[150, 150], [250, 250]], + function() { + assertPoints([ + [62.5, 'N-W'], [55, 'N-W'], [40, 'North'], + [40, 'N-W'], [20, 'North'], [22.5, 'N-W'] + ]); + assertSelectedPoints({ + 0: [7], + 1: [7], + 2: [0, 7], + 3: [0, 7] + }); + }, + [200, 200], + BOXEVENTS, 'barpolar select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]], + function() { + assertPoints([ + [62.5, 'N-W'], [50, 'N-E'], [55, 'N-W'], [40, 'North'], + [30, 'N-E'], [40, 'N-W'], [20, 'North'], [7.5, 'N-E'], [22.5, 'N-W'] + ]); + assertSelectedPoints({ + 0: [7], + 1: [1, 7], + 2: [0, 1, 7], + 3: [0, 1, 7] + }); + }, + [200, 200], + LASSOEVENTS, 'barpolar lasso' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work on choropleth traces', function(done) { - var assertPoints = makeAssertPoints(['location', 'z']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges('geo', -0.5); - var assertLassoPoints = makeAssertLassoPoints('geo', -0.5); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on choropleth traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['location', 'z']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges('geo', -0.5); + var assertLassoPoints = makeAssertLassoPoints('geo', -0.5); - var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-text')); - fig.layout.width = 870; - fig.layout.height = 450; - fig.layout.dragmode = 'select'; - fig.layout.geo.scope = 'europe'; - addInvisible(fig, false); + var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-text')); + fig.layout.width = 870; + fig.layout.height = 450; + fig.layout.dragmode = 'select'; + fig.layout.geo.scope = 'europe'; + addInvisible(fig, false); - // add a trace with no locations which will then make trace invisible, lacking DOM elements - var emptyChoroplethTrace = Lib.extendDeep({}, fig.data[0]); - emptyChoroplethTrace.text = []; - emptyChoroplethTrace.locations = []; - emptyChoroplethTrace.z = []; - fig.data.push(emptyChoroplethTrace); + // add a trace with no locations which will then make trace invisible, lacking DOM elements + var emptyChoroplethTrace = Lib.extendDeep({}, fig.data[0]); + emptyChoroplethTrace.text = []; + emptyChoroplethTrace.locations = []; + emptyChoroplethTrace.z = []; + fig.data.push(emptyChoroplethTrace); - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[350, 200], [400, 250]], - function() { - assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]); - assertSelectedPoints({0: [43, 54]}); - assertRanges([[-19.11, 63.06], [7.31, 53.72]]); - }, - [280, 190], - BOXEVENTS, 'choropleth select' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'lasso'); - }) - .then(function() { - return _run( - [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]], - function() { - assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]); - assertSelectedPoints({0: [43, 54]}); - assertLassoPoints([ - [-19.11, 63.06], [5.50, 65.25], [7.31, 53.72], [-12.90, 51.70], [-19.11, 63.06] - ]); - }, - [280, 190], - LASSOEVENTS, 'choropleth lasso' - ); - }) - .then(function() { - // make selection handlers don't get called in 'pan' dragmode - return Plotly.relayout(gd, 'dragmode', 'pan'); - }) - .then(function() { - return _run( - [[370, 120], [500, 200]], null, [200, 180], NOEVENTS, 'choropleth pan' - ); - }) - .catch(failTest) - .then(done); - }, LONG_TIMEOUT_INTERVAL); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[350, 200], [400, 250]], + function() { + assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]); + assertSelectedPoints({0: [43, 54]}); + assertRanges([[-19.11, 63.06], [7.31, 53.72]]); + }, + [280, 190], + BOXEVENTS, 'choropleth select' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run(hasCssTransform, + [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]], + function() { + assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]); + assertSelectedPoints({0: [43, 54]}); + assertLassoPoints([ + [-19.11, 63.06], [5.50, 65.25], [7.31, 53.72], [-12.90, 51.70], [-19.11, 63.06] + ]); + }, + [280, 190], + LASSOEVENTS, 'choropleth lasso' + ); + }) + .then(function() { + // make selection handlers don't get called in 'pan' dragmode + return Plotly.relayout(gd, 'dragmode', 'pan'); + }) + .then(function() { + return _run(hasCssTransform, + [[370, 120], [500, 200]], null, [200, 180], NOEVENTS, 'choropleth pan' + ); + }) + .catch(failTest) + .then(done); + }, LONG_TIMEOUT_INTERVAL); + }); - it('@flaky should work for waterfall traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges(); - var assertLassoPoints = makeAssertLassoPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for waterfall traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges(); + var assertLassoPoints = makeAssertLassoPoints(); - var fig = Lib.extendDeep({}, require('@mocks/waterfall_profit-loss_2018_positive-negative')); - fig.layout.dragmode = 'lasso'; - addInvisible(fig); + var fig = Lib.extendDeep({}, require('@mocks/waterfall_profit-loss_2018_positive-negative')); + fig.layout.dragmode = 'lasso'; + addInvisible(fig); - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]], - function() { - assertPoints([ - [0, 281, 'Purchases'], - [0, 269, 'Material expenses'], - [0, 191, 'Personnel expenses'], - [0, 179, 'Other expenses'] - ]); - assertSelectedPoints({ - 0: [5, 6, 7, 8] - }); - assertLassoPoints([ - [288.8086, 57.7617, 288.8086, 519.8555, 404.3321], - [4.33870, 6.7580, 9.1774, 6.75806, 5.54838] - ]); - }, - null, LASSOEVENTS, 'waterfall lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - return _run( - [[300, 300], [400, 400]], - function() { - assertPoints([ - [0, 281, 'Purchases'], - [0, 269, 'Material expenses'] - ]); - assertSelectedPoints({ - 0: [5, 6] - }); - assertRanges([ - [173.28519, 288.8086], - [4.3387, 6.7580] - ]); - }, - null, BOXEVENTS, 'waterfall select' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]], + function() { + assertPoints([ + [0, 281, 'Purchases'], + [0, 269, 'Material expenses'], + [0, 191, 'Personnel expenses'], + [0, 179, 'Other expenses'] + ]); + assertSelectedPoints({ + 0: [5, 6, 7, 8] + }); + assertLassoPoints([ + [288.8086, 57.7617, 288.8086, 519.8555, 404.3321], + [4.33870, 6.7580, 9.1774, 6.75806, 5.54838] + ]); + }, + null, LASSOEVENTS, 'waterfall lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + return _run(hasCssTransform, + [[300, 300], [400, 400]], + function() { + assertPoints([ + [0, 281, 'Purchases'], + [0, 269, 'Material expenses'] + ]); + assertSelectedPoints({ + 0: [5, 6] + }); + assertRanges([ + [173.28519, 288.8086], + [4.3387, 6.7580] + ]); + }, + null, BOXEVENTS, 'waterfall select' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work for funnel traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges(); - var assertLassoPoints = makeAssertLassoPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for funnel traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges(); + var assertLassoPoints = makeAssertLassoPoints(); - var fig = Lib.extendDeep({}, require('@mocks/funnel_horizontal_group_basic')); - fig.layout.dragmode = 'lasso'; - addInvisible(fig); + var fig = Lib.extendDeep({}, require('@mocks/funnel_horizontal_group_basic')); + fig.layout.dragmode = 'lasso'; + addInvisible(fig); - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]], - function() { - assertPoints([ - [0, 331.5, 'Author: etpinard'], - [1, 53.5, 'Pull requests'], - [1, 15.5, 'Author: etpinard'], - ]); - assertSelectedPoints({ - 0: [2], - 1: [1, 2] - }); - assertLassoPoints([ - [-161.6974, -1701.6728, -161.6974, 1378.2779, 608.2902], - [1.1129, 1.9193, 2.7258, 1.9193, 1.5161] - ]); - }, - null, LASSOEVENTS, 'funnel lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - return _run( - [[300, 300], [500, 500]], - function() { - assertPoints([ - [0, 331.5, 'Author: etpinard'], - [1, 53.5, 'Pull requests'], - [1, 15.5, 'Author: etpinard'] - ]); - assertSelectedPoints({ - 0: [2], - 1: [1, 2] - }); - assertRanges([ - [-931.6851, 608.2902], - [1.1129, 2.7258] - ]); - }, - null, BOXEVENTS, 'funnel select' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]], + function() { + assertPoints([ + [0, 331.5, 'Author: etpinard'], + [1, 53.5, 'Pull requests'], + [1, 15.5, 'Author: etpinard'], + ]); + assertSelectedPoints({ + 0: [2], + 1: [1, 2] + }); + assertLassoPoints([ + [-161.6974, -1701.6728, -161.6974, 1378.2779, 608.2902], + [1.1129, 1.9193, 2.7258, 1.9193, 1.5161] + ]); + }, + null, LASSOEVENTS, 'funnel lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + return _run(hasCssTransform, + [[300, 300], [500, 500]], + function() { + assertPoints([ + [0, 331.5, 'Author: etpinard'], + [1, 53.5, 'Pull requests'], + [1, 15.5, 'Author: etpinard'] + ]); + assertSelectedPoints({ + 0: [2], + 1: [1, 2] + }); + assertRanges([ + [-931.6851, 608.2902], + [1.1129, 2.7258] + ]); + }, + null, BOXEVENTS, 'funnel select' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work for bar traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges(); - var assertLassoPoints = makeAssertLassoPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for bar traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges(); + var assertLassoPoints = makeAssertLassoPoints(); - var fig = Lib.extendDeep({}, require('@mocks/0')); - fig.layout.dragmode = 'lasso'; - addInvisible(fig); + var fig = Lib.extendDeep({}, require('@mocks/0')); + fig.layout.dragmode = 'lasso'; + addInvisible(fig); - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]], - function() { - assertPoints([ - [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336], - [0, 5.3, 0.309], [0, 5.4, 0.275], [0, 5.5, 0.235], [0, 5.6, 0.192], - [0, 5.7, 0.145], - [1, 5.1, 0.485], [1, 5.2, 0.409], [1, 5.3, 0.327], - [1, 5.4, 0.24], [1, 5.5, 0.149], [1, 5.6, 0.059], - [2, 4.9, 0.473], [2, 5, 0.368], [2, 5.1, 0.258], - [2, 5.2, 0.146], [2, 5.3, 0.036] - ]); - assertSelectedPoints({ - 0: [49, 50, 51, 52, 53, 54, 55, 56, 57], - 1: [51, 52, 53, 54, 55, 56], - 2: [49, 50, 51, 52, 53] - }); - assertLassoPoints([ - [4.87, 5.74, 5.74, 4.87, 4.87], - [0.53, 0.53, -0.02, -0.02, 0.53] - ]); - }, - null, LASSOEVENTS, 'bar lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(delay(100)) - .then(function() { - return _run( - [[350, 200], [370, 220]], - function() { - assertPoints([ - [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336], - [1, 5.1, 0.485], [1, 5.2, 0.41], - [2, 4.9, 0.473], [2, 5, 0.37] - ]); - assertSelectedPoints({ - 0: [49, 50, 51, 52], - 1: [51, 52], - 2: [49, 50] - }); - assertRanges([[4.87, 5.22], [0.31, 0.53]]); - }, - null, BOXEVENTS, 'bar select' - ); - }) - .then(function() { - // mimic https://github.com/plotly/plotly.js/issues/3795 - return Plotly.relayout(gd, { - 'xaxis.rangeslider.visible': true, - 'xaxis.range': [0, 6] - }); - }) - .then(function() { - return _run( - [[350, 200], [360, 200]], - function() { - assertPoints([ - [0, 2.5, -0.429], [1, 2.5, -1.015], [2, 2.5, -1.172], - ]); - assertSelectedPoints({ - 0: [25], - 1: [25], - 2: [25] - }); - assertRanges([[2.434, 2.521], [-1.4355, 2.0555]]); - }, - null, BOXEVENTS, 'bar select (after xaxis.range relayout)' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]], + function() { + assertPoints([ + [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336], + [0, 5.3, 0.309], [0, 5.4, 0.275], [0, 5.5, 0.235], [0, 5.6, 0.192], + [0, 5.7, 0.145], + [1, 5.1, 0.485], [1, 5.2, 0.409], [1, 5.3, 0.327], + [1, 5.4, 0.24], [1, 5.5, 0.149], [1, 5.6, 0.059], + [2, 4.9, 0.473], [2, 5, 0.368], [2, 5.1, 0.258], + [2, 5.2, 0.146], [2, 5.3, 0.036] + ]); + assertSelectedPoints({ + 0: [49, 50, 51, 52, 53, 54, 55, 56, 57], + 1: [51, 52, 53, 54, 55, 56], + 2: [49, 50, 51, 52, 53] + }); + assertLassoPoints([ + [4.87, 5.74, 5.74, 4.87, 4.87], + [0.53, 0.53, -0.02, -0.02, 0.53] + ]); + }, + null, LASSOEVENTS, 'bar lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(delay(100)) + .then(function() { + return _run(hasCssTransform, + [[350, 200], [370, 220]], + function() { + assertPoints([ + [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336], + [1, 5.1, 0.485], [1, 5.2, 0.41], + [2, 4.9, 0.473], [2, 5, 0.37] + ]); + assertSelectedPoints({ + 0: [49, 50, 51, 52], + 1: [51, 52], + 2: [49, 50] + }); + assertRanges([[4.87, 5.22], [0.31, 0.53]]); + }, + null, BOXEVENTS, 'bar select' + ); + }) + .then(function() { + // mimic https://github.com/plotly/plotly.js/issues/3795 + return Plotly.relayout(gd, { + 'xaxis.rangeslider.visible': true, + 'xaxis.range': [0, 6] + }); + }) + .then(function() { + return _run(hasCssTransform, + [[350, 200], [360, 200]], + function() { + assertPoints([ + [0, 2.5, -0.429], [1, 2.5, -1.015], [2, 2.5, -1.172], + ]); + assertSelectedPoints({ + 0: [25], + 1: [25], + 2: [25] + }); + assertRanges([[2.434, 2.521], [-1.4355, 2.0555]]); + }, + null, BOXEVENTS, 'bar select (after xaxis.range relayout)' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work for date/category traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); - var assertSelectedPoints = makeAssertSelectedPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for date/category traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); + var assertSelectedPoints = makeAssertSelectedPoints(); - var fig = { - data: [{ - x: ['2017-01-01', '2017-02-01', '2017-03-01'], - y: ['a', 'b', 'c'] - }, { - type: 'bar', - x: ['2017-01-01', '2017-02-02', '2017-03-01'], - y: ['a', 'b', 'c'] - }], - layout: { - dragmode: 'lasso', - width: 400, - height: 400 - } - }; - addInvisible(fig); + var fig = { + data: [{ + x: ['2017-01-01', '2017-02-01', '2017-03-01'], + y: ['a', 'b', 'c'] + }, { + type: 'bar', + x: ['2017-01-01', '2017-02-02', '2017-03-01'], + y: ['a', 'b', 'c'] + }], + layout: { + dragmode: 'lasso', + width: 400, + height: 400 + } + }; + addInvisible(fig); - var x0 = 100; - var y0 = 100; - var x1 = 250; - var y1 = 250; + var x0 = 100; + var y0 = 100; + var x1 = 250; + var y1 = 250; - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]], - function() { - assertPoints([ - [0, '2017-02-01', 'b'], - [1, '2017-02-02', 'b'] - ]); - assertSelectedPoints({0: [1], 1: [1]}); - }, - null, LASSOEVENTS, 'date/category lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - return _run( - [[x0, y0], [x1, y1]], - function() { - assertPoints([ - [0, '2017-02-01', 'b'], - [1, '2017-02-02', 'b'] - ]); - assertSelectedPoints({0: [1], 1: [1]}); - }, - null, BOXEVENTS, 'date/category select' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]], + function() { + assertPoints([ + [0, '2017-02-01', 'b'], + [1, '2017-02-02', 'b'] + ]); + assertSelectedPoints({0: [1], 1: [1]}); + }, + null, LASSOEVENTS, 'date/category lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + return _run(hasCssTransform, + [[x0, y0], [x1, y1]], + function() { + assertPoints([ + [0, '2017-02-01', 'b'], + [1, '2017-02-02', 'b'] + ]); + assertSelectedPoints({0: [1], 1: [1]}); + }, + null, BOXEVENTS, 'date/category select' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work for histogram traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y', 'pointIndices']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges(); - var assertLassoPoints = makeAssertLassoPoints(); - - var fig = Lib.extendDeep({}, require('@mocks/hist_grouped')); - fig.layout.dragmode = 'lasso'; - fig.layout.width = 600; - fig.layout.height = 500; - addInvisible(fig); - - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], - function() { - assertPoints([ - [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]] - ]); - assertSelectedPoints({0: [3, 4], 1: [1, 2]}); - assertLassoPoints([ - [1.66, 3.59, 3.59, 1.66, 1.66], - [2.17, 2.17, 0.69, 0.69, 2.17] - ]); - }, - null, LASSOEVENTS, 'histogram lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - return _run( - [[200, 200], [400, 350]], - function() { - assertPoints([ - [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]] - ]); - assertSelectedPoints({0: [3, 4], 1: [1, 2]}); - assertRanges([[1.66, 3.59], [0.69, 2.17]]); - }, - null, BOXEVENTS, 'histogram select' - ); - }) - .catch(failTest) - .then(done); - }); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for histogram traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y', 'pointIndices']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges(); + var assertLassoPoints = makeAssertLassoPoints(); - it('@flaky should work for box traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges(); - var assertLassoPoints = makeAssertLassoPoints(); + var fig = Lib.extendDeep({}, require('@mocks/hist_grouped')); + fig.layout.dragmode = 'lasso'; + fig.layout.width = 600; + fig.layout.height = 500; + addInvisible(fig); - var fig = Lib.extendDeep({}, require('@mocks/box_grouped')); - fig.data.forEach(function(trace) { - trace.boxpoints = 'all'; + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], + function() { + assertPoints([ + [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]] + ]); + assertSelectedPoints({0: [3, 4], 1: [1, 2]}); + assertLassoPoints([ + [1.66, 3.59, 3.59, 1.66, 1.66], + [2.17, 2.17, 0.69, 0.69, 2.17] + ]); + }, + null, LASSOEVENTS, 'histogram lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + return _run(hasCssTransform, + [[200, 200], [400, 350]], + function() { + assertPoints([ + [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]] + ]); + assertSelectedPoints({0: [3, 4], 1: [1, 2]}); + assertRanges([[1.66, 3.59], [0.69, 2.17]]); + }, + null, BOXEVENTS, 'histogram select' + ); + }) + .catch(failTest) + .then(done); }); - fig.layout.dragmode = 'lasso'; - fig.layout.width = 600; - fig.layout.height = 500; - fig.layout.xaxis = {range: [-0.565, 1.5]}; - addInvisible(fig); - - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], - function() { - assertPoints([ - [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], - [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], - [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'] - ]); - assertSelectedPoints({ - 0: [6, 11, 10, 7], - 1: [11, 8, 6, 10], - 2: [1, 4, 5] - }); - assertLassoPoints([ - [0.0423, 1.0546, 1.0546, 0.0423, 0.0423], - [0.71, 0.71, 0.1875, 0.1875, 0.71] - ]); - }, - null, LASSOEVENTS, 'box lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - return _run( - [[200, 200], [400, 350]], - function() { - assertPoints([ - [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], - [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], - [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'] - ]); - assertSelectedPoints({ - 0: [6, 11, 10, 7], - 1: [11, 8, 6, 10], - 2: [1, 4, 5] - }); - assertRanges([[0.04235, 1.0546], [0.1875, 0.71]]); - }, - null, BOXEVENTS, 'box select' - ); - }) - .catch(failTest) - .then(done); }); - it('@flaky should work for box traces (q1/median/q3 case)', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']); - var assertSelectedPoints = makeAssertSelectedPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for box traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges(); + var assertLassoPoints = makeAssertLassoPoints(); - var fig = { - data: [{ - type: 'box', - x0: 'A', - q1: [1], - median: [2], - q3: [3], - y: [[0, 1, 2, 3, 4]], - pointpos: 0, - }], - layout: { - width: 500, - height: 500, - dragmode: 'lasso' - } - }; + var fig = Lib.extendDeep({}, require('@mocks/box_grouped')); + fig.data.forEach(function(trace) { + trace.boxpoints = 'all'; + }); + fig.layout.dragmode = 'lasso'; + fig.layout.width = 600; + fig.layout.height = 500; + fig.layout.xaxis = {range: [-0.565, 1.5]}; + addInvisible(fig); - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], - function() { - assertPoints([ [0, 1, undefined], [0, 2, undefined] ]); - assertSelectedPoints({ 0: [[0, 1], [0, 2]] }); - }, - null, LASSOEVENTS, 'box lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - return _run( - [[200, 200], [400, 300]], - function() { - assertPoints([ [0, 2, undefined] ]); - assertSelectedPoints({ 0: [[0, 2]] }); - }, - null, BOXEVENTS, 'box select' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], + function() { + assertPoints([ + [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], + [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], + [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'] + ]); + assertSelectedPoints({ + 0: [6, 11, 10, 7], + 1: [11, 8, 6, 10], + 2: [1, 4, 5] + }); + assertLassoPoints([ + [0.0423, 1.0546, 1.0546, 0.0423, 0.0423], + [0.71, 0.71, 0.1875, 0.1875, 0.71] + ]); + }, + null, LASSOEVENTS, 'box lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + return _run(hasCssTransform, + [[200, 200], [400, 350]], + function() { + assertPoints([ + [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], + [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], + [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'] + ]); + assertSelectedPoints({ + 0: [6, 11, 10, 7], + 1: [11, 8, 6, 10], + 2: [1, 4, 5] + }); + assertRanges([[0.04235, 1.0546], [0.1875, 0.71]]); + }, + null, BOXEVENTS, 'box select' + ); + }) + .catch(failTest) + .then(done); + }); }); - it('@flaky should work for violin traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']); - var assertSelectedPoints = makeAssertSelectedPoints(); - var assertRanges = makeAssertRanges(); - var assertLassoPoints = makeAssertLassoPoints(); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for box traces (q1/median/q3 case), hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']); + var assertSelectedPoints = makeAssertSelectedPoints(); - var fig = Lib.extendDeep({}, require('@mocks/violin_grouped')); - fig.layout.dragmode = 'lasso'; - fig.layout.width = 600; - fig.layout.height = 500; - addInvisible(fig); + var fig = { + data: [{ + type: 'box', + x0: 'A', + q1: [1], + median: [2], + q3: [3], + y: [[0, 1, 2, 3, 4]], + pointpos: 0, + }], + layout: { + width: 500, + height: 500, + dragmode: 'lasso' + } + }; - Plotly.plot(gd, fig) - .then(function() { - return _run( - [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], - function() { - assertPoints([ - [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'], - [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'], - [1, 0.9, 'day 2'], - [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1'] - ]); - assertSelectedPoints({ - 0: [11, 10, 7, 8], - 1: [8, 6, 10, 9, 7], - 2: [1, 4, 5, 3] - }); - assertLassoPoints([ - [0.07777, 1.0654, 1.0654, 0.07777, 0.07777], - [1.02, 1.02, 0.27, 0.27, 1.02] - ]); - }, - null, LASSOEVENTS, 'violin lasso' - ); - }) - .then(function() { - return Plotly.relayout(gd, 'dragmode', 'select'); - }) - .then(function() { - return _run( - [[200, 200], [400, 350]], - function() { - assertPoints([ - [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'], - [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'], - [1, 0.9, 'day 2'], - [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1'] - ]); - assertSelectedPoints({ - 0: [11, 10, 7, 8], - 1: [8, 6, 10, 9, 7], - 2: [1, 4, 5, 3] - }); - assertRanges([[0.07777, 1.0654], [0.27, 1.02]]); - }, - null, BOXEVENTS, 'violin select' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + return _run(hasCssTransform, + [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], + function() { + assertPoints([ [0, 1, undefined], [0, 2, undefined] ]); + assertSelectedPoints({ 0: [[0, 1], [0, 2]] }); + }, + null, LASSOEVENTS, 'box lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + return _run(hasCssTransform, + [[200, 200], [400, 300]], + function() { + assertPoints([ [0, 2, undefined] ]); + assertSelectedPoints({ 0: [[0, 2]] }); + }, + null, BOXEVENTS, 'box select' + ); + }) + .catch(failTest) + .then(done); + }); }); - ['ohlc', 'candlestick'].forEach(function(type) { - it('@flaky should work for ' + type + ' traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'x', 'open', 'high', 'low', 'close']); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work for violin traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']); var assertSelectedPoints = makeAssertSelectedPoints(); var assertRanges = makeAssertRanges(); var assertLassoPoints = makeAssertLassoPoints(); - var l0 = 275; - var lv0 = '2011-01-03 18:00'; - var r0 = 325; - var rv0 = '2011-01-04 06:00'; - var l1 = 75; - var lv1 = '2011-01-01 18:00'; - var r1 = 125; - var rv1 = '2011-01-02 06:00'; - var t = 75; - var tv = 7.565; - var b = 225; - var bv = -1.048; - - function countUnSelectedPaths(selector) { - var unselected = 0; - d3.select(gd).selectAll(selector).each(function() { - var opacity = this.style.opacity; - if(opacity < 1) unselected++; - }); - return unselected; - } - Plotly.newPlot(gd, [{ - type: type, - x: ['2011-01-02', '2011-01-03', '2011-01-04'], - open: [1, 2, 3], - high: [3, 4, 5], - low: [0, 1, 2], - close: [0, 3, 2] - }], { - width: 400, - height: 400, - margin: {l: 50, r: 50, t: 50, b: 50}, - yaxis: {range: [-3, 9]}, - dragmode: 'lasso' - }) + var fig = Lib.extendDeep({}, require('@mocks/violin_grouped')); + fig.layout.dragmode = 'lasso'; + fig.layout.width = 600; + fig.layout.height = 500; + addInvisible(fig); + + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) .then(function() { - return _run( - [[l0, t], [l0, b], [r0, b], [r0, t], [l0, t]], + return _run(hasCssTransform, + [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], function() { - assertPoints([[0, '2011-01-04', 3, 5, 2, 2]]); - assertSelectedPoints([[2]]); + assertPoints([ + [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'], + [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'], + [1, 0.9, 'day 2'], + [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1'] + ]); + assertSelectedPoints({ + 0: [11, 10, 7, 8], + 1: [8, 6, 10, 9, 7], + 2: [1, 4, 5, 3] + }); assertLassoPoints([ - [lv0, lv0, rv0, rv0, lv0], - [tv, bv, bv, tv, tv] + [0.07777, 1.0654, 1.0654, 0.07777, 0.07777], + [1.02, 1.02, 0.27, 0.27, 1.02] ]); - expect(countUnSelectedPaths('.cartesianlayer .trace path')).toBe(2); - expect(countUnSelectedPaths('.rangeslider-rangeplot .trace path')).toBe(2); }, - null, LASSOEVENTS, type + ' lasso' + null, LASSOEVENTS, 'violin lasso' ); }) .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) .then(function() { - return _run( - [[l1, t], [r1, b]], + return _run(hasCssTransform, + [[200, 200], [400, 350]], function() { - assertPoints([[0, '2011-01-02', 1, 3, 0, 0]]); - assertSelectedPoints([[0]]); - assertRanges([[lv1, rv1], [bv, tv]]); + assertPoints([ + [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'], + [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'], + [1, 0.9, 'day 2'], + [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1'] + ]); + assertSelectedPoints({ + 0: [11, 10, 7, 8], + 1: [8, 6, 10, 9, 7], + 2: [1, 4, 5, 3] + }); + assertRanges([[0.07777, 1.0654], [0.27, 1.02]]); }, - null, BOXEVENTS, type + ' select' + null, BOXEVENTS, 'violin select' ); }) .catch(failTest) @@ -2881,134 +2874,223 @@ describe('Test select box and lasso per trace:', function() { }); }); - it('@flaky should work on traces with enabled transforms', function(done) { - var assertSelectedPoints = makeAssertSelectedPoints(); + [false].forEach(function(hasCssTransform) { + ['ohlc', 'candlestick'].forEach(function(type) { + it('@flaky should work for ' + type + ' traces, hasCssTransform: ' + hasCssTransform, function(done) { + var assertPoints = makeAssertPoints(['curveNumber', 'x', 'open', 'high', 'low', 'close']); + var assertSelectedPoints = makeAssertSelectedPoints(); + var assertRanges = makeAssertRanges(); + var assertLassoPoints = makeAssertLassoPoints(); + var l0 = 275; + var lv0 = '2011-01-03 18:00'; + var r0 = 325; + var rv0 = '2011-01-04 06:00'; + var l1 = 75; + var lv1 = '2011-01-01 18:00'; + var r1 = 125; + var rv1 = '2011-01-02 06:00'; + var t = 75; + var tv = 7.565; + var b = 225; + var bv = -1.048; + + function countUnSelectedPaths(selector) { + var unselected = 0; + d3.select(gd).selectAll(selector).each(function() { + var opacity = this.style.opacity; + if(opacity < 1) unselected++; + }); + return unselected; + } - Plotly.plot(gd, [{ - x: [1, 2, 3, 4, 5], - y: [2, 3, 1, 7, 9], - marker: {size: [10, 20, 20, 20, 10]}, - transforms: [{ - type: 'filter', - operation: '>', - value: 2, - target: 'y' - }, { - type: 'aggregate', - groups: 'marker.size', - aggregations: [ - // 20: 6, 10: 5 - {target: 'x', func: 'sum'}, - // 20: 5, 10: 9 - {target: 'y', func: 'avg'} - ] - }] - }], { - dragmode: 'select', - showlegend: false, - width: 400, - height: 400, - margin: {l: 0, t: 0, r: 0, b: 0} - }) - .then(function() { - return _run( - [[5, 5], [395, 395]], - function() { - assertSelectedPoints({0: [1, 3, 4]}); - }, - [380, 180], - BOXEVENTS, 'transformed trace select (all points selected)' - ); - }) - .catch(failTest) - .then(done); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.newPlot(gd, [{ + type: type, + x: ['2011-01-02', '2011-01-03', '2011-01-04'], + open: [1, 2, 3], + high: [3, 4, 5], + low: [0, 1, 2], + close: [0, 3, 2] + }], { + width: 400, + height: 400, + margin: {l: 50, r: 50, t: 50, b: 50}, + yaxis: {range: [-3, 9]}, + dragmode: 'lasso' + }) + .then(function() { + return _run(hasCssTransform, + [[l0, t], [l0, b], [r0, b], [r0, t], [l0, t]], + function() { + assertPoints([[0, '2011-01-04', 3, 5, 2, 2]]); + assertSelectedPoints([[2]]); + assertLassoPoints([ + [lv0, lv0, rv0, rv0, lv0], + [tv, bv, bv, tv, tv] + ]); + expect(countUnSelectedPaths('.cartesianlayer .trace path')).toBe(2); + expect(countUnSelectedPaths('.rangeslider-rangeplot .trace path')).toBe(2); + }, + null, LASSOEVENTS, type + ' lasso' + ); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + return _run(hasCssTransform, + [[l1, t], [r1, b]], + function() { + assertPoints([[0, '2011-01-02', 1, 3, 0, 0]]); + assertSelectedPoints([[0]]); + assertRanges([[lv1, rv1], [bv, tv]]); + }, + null, BOXEVENTS, type + ' select' + ); + }) + .catch(failTest) + .then(done); + }); + }); }); - it('@flaky should work on scatter/bar traces with text nodes', function(done) { - var assertSelectedPoints = makeAssertSelectedPoints(); - - function assertFillOpacity(exp, msg) { - var txtPts = d3.select(gd).select('g.plot').selectAll('text'); + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on traces with enabled transforms, hasCssTransform: ' + hasCssTransform, function(done) { + var assertSelectedPoints = makeAssertSelectedPoints(); - expect(txtPts.size()).toBe(exp.length, '# of text nodes: ' + msg); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, [{ + x: [1, 2, 3, 4, 5], + y: [2, 3, 1, 7, 9], + marker: {size: [10, 20, 20, 20, 10]}, + transforms: [{ + type: 'filter', + operation: '>', + value: 2, + target: 'y' + }, { + type: 'aggregate', + groups: 'marker.size', + aggregations: [ + // 20: 6, 10: 5 + {target: 'x', func: 'sum'}, + // 20: 5, 10: 9 + {target: 'y', func: 'avg'} + ] + }] + }], { + dragmode: 'select', + showlegend: false, + width: 400, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0} + }) + .then(function() { + return _run(hasCssTransform, + [[5, 5], [395, 395]], + function() { + assertSelectedPoints({0: [1, 3, 4]}); + }, + [380, 180], + BOXEVENTS, 'transformed trace select (all points selected)' + ); + }) + .catch(failTest) + .then(done); + }); + }); - txtPts.each(function(_, i) { - var act = Number(this.style['fill-opacity']); - expect(act).toBe(exp[i], 'node ' + i + ' fill opacity: ' + msg); - }); - } + [false, true].forEach(function(hasCssTransform) { + it('@flaky should work on scatter/bar traces with text nodes, hasCssTransform: ' + hasCssTransform, function(done) { + var assertSelectedPoints = makeAssertSelectedPoints(); - Plotly.plot(gd, [{ - mode: 'markers+text', - x: [1, 2, 3], - y: [1, 2, 1], - text: ['a', 'b', 'c'] - }, { - type: 'bar', - x: [1, 2, 3], - y: [1, 2, 1], - text: ['A', 'B', 'C'], - textposition: 'outside' - }], { - dragmode: 'select', - hovermode: 'closest', - showlegend: false, - width: 400, - height: 400, - margin: {l: 0, t: 0, r: 0, b: 0} - }) - .then(function() { - return _run( - [[10, 10], [100, 300]], - function() { - assertSelectedPoints({0: [0], 1: [0]}); - assertFillOpacity([1, 0.2, 0.2, 1, 0.2, 0.2], '_run'); - }, - [10, 10], BOXEVENTS, 'selecting first scatter/bar text nodes' - ); - }) - .then(function() { - assertFillOpacity([1, 1, 1, 1, 1, 1], 'final'); - }) - .catch(failTest) - .then(done); - }); + function assertFillOpacity(exp, msg) { + var txtPts = d3.select(gd).select('g.plot').selectAll('text'); - describe('should work on sankey traces', function() { - var waitingTime = sankeyConstants.duration * 2; + expect(txtPts.size()).toBe(exp.length, '# of text nodes: ' + msg); - it('@flaky select', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json')); - fig.layout.dragmode = 'select'; - var dblClickPos = [250, 400]; + txtPts.each(function(_, i) { + var act = Number(this.style['fill-opacity']); + expect(act).toBe(exp[i], 'node ' + i + ' fill opacity: ' + msg); + }); + } - Plotly.plot(gd, fig) - .then(function() { - // No groups initially - expect(gd._fullData[0].node.groups).toEqual([]); + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, [{ + mode: 'markers+text', + x: [1, 2, 3], + y: [1, 2, 1], + text: ['a', 'b', 'c'] + }, { + type: 'bar', + x: [1, 2, 3], + y: [1, 2, 1], + text: ['A', 'B', 'C'], + textposition: 'outside' + }], { + dragmode: 'select', + hovermode: 'closest', + showlegend: false, + width: 400, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0} }) .then(function() { - // Grouping the two nodes on the top right - return _run( - [[640, 130], [400, 450]], + return _run(hasCssTransform, + [[10, 10], [100, 300]], function() { - expect(gd._fullData[0].node.groups).toEqual([[2, 3]], 'failed to group #2 + #3'); + assertSelectedPoints({0: [0], 1: [0]}); + assertFillOpacity([1, 0.2, 0.2, 1, 0.2, 0.2], '_run'); }, - dblClickPos, BOXEVENTS, 'for top right nodes #2 and #3' + [10, 10], BOXEVENTS, 'selecting first scatter/bar text nodes' ); }) - .then(delay(waitingTime)) - .then(function() { - // Grouping node #4 and the previous group - drag([[715, 400], [300, 110]]); - }) - .then(delay(waitingTime)) .then(function() { - expect(gd._fullData[0].node.groups).toEqual([[4, 3, 2]], 'failed to group #4 + existing group of #2 and #3'); + assertFillOpacity([1, 1, 1, 1, 1, 1], 'final'); }) .catch(failTest) .then(done); }); + }); + + describe('should work on sankey traces', function() { + var waitingTime = sankeyConstants.duration * 2; + + [false, true].forEach(function(hasCssTransform) { + it('@flaky select, hasCssTransform: ' + hasCssTransform, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json')); + fig.layout.dragmode = 'select'; + var dblClickPos = [250, 400]; + + if(hasCssTransform) transformPlot(gd, cssTransform); + Plotly.plot(gd, fig) + .then(function() { + // No groups initially + expect(gd._fullData[0].node.groups).toEqual([]); + }) + .then(function() { + // Grouping the two nodes on the top right + return _run(hasCssTransform, + [[640, 130], [400, 450]], + function() { + expect(gd._fullData[0].node.groups).toEqual([[2, 3]], 'failed to group #2 + #3'); + }, + dblClickPos, BOXEVENTS, 'for top right nodes #2 and #3' + ); + }) + .then(delay(waitingTime)) + .then(function() { + // Grouping node #4 and the previous group + drag([[715, 400], [300, 110]]); + }) + .then(delay(waitingTime)) + .then(function() { + expect(gd._fullData[0].node.groups).toEqual([[4, 3, 2]], 'failed to group #4 + existing group of #2 and #3'); + }) + .catch(failTest) + .then(done); + }); + }); it('@flaky should not work when dragmode is undefined', function(done) { var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json')); From 29e772d2d18ce158cd4c5ba4171c1df2260b56fa Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 20:49:12 -0500 Subject: [PATCH 56/63] expand ternary hover and drag zoom tests to cover css transform --- test/jasmine/tests/ternary_test.js | 170 +++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index df7d3a1ccaf..cdc3b95c559 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -559,6 +559,176 @@ describe('ternary plots', function() { } }); +describe('ternary plots when css transform is present', function() { + 'use strict'; + + afterEach(destroyGraphDiv); + + var mock = require('@mocks/ternary_simple.json'); + var gd; + + function transformPlot(gd, transformString) { + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + var cssTransform = 'translate(-25%, -25%) scale(0.5)'; + var scale = 0.5; + var pointPos = [scale * 391, scale * 219]; + var blankPos = [scale * 200, scale * 200]; + + beforeEach(function(done) { + gd = createGraphDiv(); + + var mockCopy = Lib.extendDeep({}, mock); + + transformPlot(gd, cssTransform); + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + }); + + it('should respond zoom drag interactions', function(done) { + function assertRange(gd, expected) { + var ternary = gd._fullLayout.ternary; + var actual = [ + ternary.aaxis.min, + ternary.baxis.min, + ternary.caxis.min + ]; + expect(actual).toBeCloseToArray(expected); + } + + assertRange(gd, [0.231, 0.2, 0.11]); + + drag({path: [[scale * 383, scale * 213], [scale * 293, scale * 243]]}) + .then(function() { assertRange(gd, [0.4486, 0.2480, 0.1453]); }) + .then(function() { return doubleClick(pointPos[0], pointPos[1]); }) + .then(function() { assertRange(gd, [0, 0, 0]); }) + .catch(failTest) + .then(done); + }); + + it('should display to hover labels', function(done) { + mouseEvent('mousemove', blankPos[0], blankPos[1]); + assertHoverLabelContent([null, null], 'only on data points'); + + function check(content, style, msg) { + Lib.clearThrottle(); + mouseEvent('mousemove', pointPos[0], pointPos[1]); + + assertHoverLabelContent({nums: content}, msg); + assertHoverLabelStyle(d3.select('g.hovertext'), style, msg); + } + + check([ + 'Component A: 0.5', + 'B: 0.25', + 'Component C: 0.25' + ].join('\n'), { + bgcolor: 'rgb(31, 119, 180)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, 'one label per data pt'); + + Plotly.restyle(gd, { + 'hoverlabel.bordercolor': 'blue', + 'hoverlabel.font.family': [['Gravitas', 'Arial', 'Roboto']] + }) + .then(function() { + check([ + 'Component A: 0.5', + 'B: 0.25', + 'Component C: 0.25' + ].join('\n'), { + bgcolor: 'rgb(31, 119, 180)', + bordercolor: 'rgb(0, 0, 255)', + fontColor: 'rgb(0, 0, 255)', + fontSize: 13, + fontFamily: 'Gravitas' + }, 'after hoverlabel styling restyle call'); + + return Plotly.restyle(gd, 'hoverinfo', [['a', 'b+c', 'b']]); + }) + .then(function() { + check('Component A: 0.5', { + bgcolor: 'rgb(31, 119, 180)', + bordercolor: 'rgb(0, 0, 255)', + fontColor: 'rgb(0, 0, 255)', + fontSize: 13, + fontFamily: 'Gravitas' + }, 'after hoverlabel styling restyle call'); + }) + .catch(failTest) + .then(done); + }); + + it('should respond to hover interactions by', function() { + var hoverCnt = 0; + var unhoverCnt = 0; + + var hoverData, unhoverData; + + gd.on('plotly_hover', function(eventData) { + hoverCnt++; + hoverData = eventData.points[0]; + }); + + gd.on('plotly_unhover', function(eventData) { + unhoverCnt++; + unhoverData = eventData.points[0]; + }); + + mouseEvent('mousemove', blankPos[0], blankPos[1]); + expect(hoverData).toBe(undefined, 'not firing on blank points'); + expect(unhoverData).toBe(undefined, 'not firing on blank points'); + + mouseEvent('mousemove', pointPos[0], pointPos[1]); + expect(hoverData).not.toBe(undefined, 'firing on data points'); + expect(Object.keys(hoverData)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'xaxis', 'yaxis', 'a', 'b', 'c' + ], 'returning the correct event data keys'); + expect(hoverData.curveNumber).toEqual(0, 'returning the correct curve number'); + expect(hoverData.pointNumber).toEqual(0, 'returning the correct point number'); + + mouseEvent('mouseout', pointPos[0], pointPos[1]); + expect(unhoverData).not.toBe(undefined, 'firing on data points'); + expect(Object.keys(unhoverData)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'xaxis', 'yaxis', 'a', 'b', 'c' + ], 'returning the correct event data keys'); + expect(unhoverData.curveNumber).toEqual(0, 'returning the correct curve number'); + expect(unhoverData.pointNumber).toEqual(0, 'returning the correct point number'); + + expect(hoverCnt).toEqual(1); + expect(unhoverCnt).toEqual(1); + }); + + it('should respond to click interactions by', function() { + var ptData; + + gd.on('plotly_click', function(eventData) { + ptData = eventData.points[0]; + }); + + click(blankPos[0], blankPos[1]); + expect(ptData).toBe(undefined, 'not firing on blank points'); + + click(pointPos[0], pointPos[1]); + expect(ptData).not.toBe(undefined, 'firing on data points'); + expect(Object.keys(ptData)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'xaxis', 'yaxis', 'a', 'b', 'c' + ], 'returning the correct event data keys'); + expect(ptData.curveNumber).toEqual(0, 'returning the correct curve number'); + expect(ptData.pointNumber).toEqual(0, 'returning the correct point number'); + }); +}); + describe('ternary defaults', function() { 'use strict'; From 28a917861d17ca923078b521612a08cc248bd23e Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 17 Nov 2020 22:11:25 -0500 Subject: [PATCH 57/63] remove dirty matrix validation - take care of matrix3d form instead --- src/lib/dom.js | 9 +++++++-- src/lib/matrix.js | 10 +--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/lib/dom.js b/src/lib/dom.js index 879d39e1cf3..cd2a0ec6690 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -126,8 +126,13 @@ function getElementTransformMatrix(element) { ); if(transform === 'none') return null; - // the slice is because the transform string returns eg "matrix(0.5, 0, 1, 0, 1, 1)" - return transform.slice(7, -1).split(',').map(function(n) {return +n;}); + // the transform is a string in the form of matrix(a, b, ...) or matrix3d(...) + return transform + .replace('matrix', '') + .replace('3d', '') + .slice(1, -1) + .split(',') + .map(function(n) { return +n; }); } /** * retrieve all DOM elements that are ancestors of the specified one (including itself) diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 4ca500d0b63..a7467856cbc 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -118,15 +118,7 @@ exports.apply2DTransform2 = function(transform) { exports.convertCssMatrix = function(m) { if(m) { var len = m.length; - if(len === 16) { - // validate values - return [ - m[0] || 1, m[1] || 0, m[2] || 0, m[3] || 0, - m[4] || 0, m[5] || 1, m[6] || 0, m[7] || 0, - m[8] || 0, m[9] || 0, m[10] || 1, m[11] || 0, - m[12] || 0, m[13] || 0, m[14] || 0, m[15] || 1 - ]; - } + if(len === 16) return m; if(len === 6) { // converts a 2x3 css transform matrix to a 4x4 matrix see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix return [ From a2f34c939714270610587833771b4066219b44dc Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 18 Nov 2020 10:39:01 -0500 Subject: [PATCH 58/63] adjust hover positions on parcats category bands in respect to css transforms --- src/traces/parcats/parcats.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/traces/parcats/parcats.js b/src/traces/parcats/parcats.js index a5fc1a60659..143199c8ba3 100644 --- a/src/traces/parcats/parcats.js +++ b/src/traces/parcats/parcats.js @@ -765,7 +765,10 @@ function emitPointsEventColorHovermode(bandElement, eventName, event) { * HTML element for band * */ -function createHoverLabelForCategoryHovermode(rootBBox, bandElement) { +function createHoverLabelForCategoryHovermode(gd, rootBBox, bandElement) { + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; + // Selections var rectSelection = d3.select(bandElement.parentNode).select('rect.catrect'); var rectBoundingBox = rectSelection.node().getBoundingClientRect(); @@ -813,8 +816,8 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) { var hovertext = hoverinfoParts.join('
'); return { trace: trace, - x: hoverCenterX - rootBBox.left, - y: hoverCenterY - rootBBox.top, + x: scaleX * (hoverCenterX - rootBBox.left), + y: scaleY * (hoverCenterY - rootBBox.top), text: hovertext, color: 'lightgray', borderColor: 'black', @@ -843,7 +846,7 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) { * HTML element for band * */ -function createHoverLabelForDimensionHovermode(rootBBox, bandElement) { +function createHoverLabelForDimensionHovermode(gd, rootBBox, bandElement) { var allHoverlabels = []; d3.select(bandElement.parentNode.parentNode) @@ -851,7 +854,7 @@ function createHoverLabelForDimensionHovermode(rootBBox, bandElement) { .select('rect.catrect') .each(function() { var bandNode = this; - allHoverlabels.push(createHoverLabelForCategoryHovermode(rootBBox, bandNode)); + allHoverlabels.push(createHoverLabelForCategoryHovermode(gd, rootBBox, bandNode)); }); return allHoverlabels; @@ -866,7 +869,10 @@ function createHoverLabelForDimensionHovermode(rootBBox, bandElement) { * HTML element for band * */ -function createHoverLabelForColorHovermode(rootBBox, bandElement) { +function createHoverLabelForColorHovermode(gd, rootBBox, bandElement) { + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; + var bandBoundingBox = bandElement.getBoundingClientRect(); // Models @@ -944,8 +950,8 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) { return { trace: trace, - x: hoverCenterX - rootBBox.left, - y: hoverCenterY - rootBBox.top, + x: scaleX * (hoverCenterX - rootBBox.left), + y: scaleY * (hoverCenterY - rootBBox.top), // name: 'NAME', text: hovertext, color: bandViewModel.color, @@ -1008,11 +1014,11 @@ function mouseoverCategoryBand(bandViewModel) { if(bandViewModel.parcatsViewModel.hoverinfoItems.indexOf('none') === -1) { var hoverItems; if(hoveron === 'category') { - hoverItems = createHoverLabelForCategoryHovermode(rootBBox, bandElement); + hoverItems = createHoverLabelForCategoryHovermode(gd, rootBBox, bandElement); } else if(hoveron === 'color') { - hoverItems = createHoverLabelForColorHovermode(rootBBox, bandElement); + hoverItems = createHoverLabelForColorHovermode(gd, rootBBox, bandElement); } else if(hoveron === 'dimension') { - hoverItems = createHoverLabelForDimensionHovermode(rootBBox, bandElement); + hoverItems = createHoverLabelForDimensionHovermode(gd, rootBBox, bandElement); } if(hoverItems) { From 9fe42b6a6dee721d73acfbbce541ab0ac1255e62 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 18 Nov 2020 10:53:58 -0500 Subject: [PATCH 59/63] adjust hover positions on sankey nodes in respect to css transforms --- src/traces/sankey/plot.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js index d85a2da7b7b..6356ff3fa4c 100644 --- a/src/traces/sankey/plot.js +++ b/src/traces/sankey/plot.js @@ -292,10 +292,13 @@ module.exports = function plot(gd, calcData) { var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.node.value) + d.valueSuffix}; d.node.fullData = d.node.trace; + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; + var tooltip = Fx.loneHover({ - x0: hoverCenterX0, - x1: hoverCenterX1, - y: hoverCenterY, + x0: scaleX * hoverCenterX0, + x1: scaleX * hoverCenterX1, + y: scaleY * hoverCenterY, name: d3.format(d.valueFormat)(d.node.value) + d.valueSuffix, text: [ d.node.label, From 5681a3434cc270baed5245581beda0680a987edd Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 18 Nov 2020 13:01:13 -0500 Subject: [PATCH 60/63] bypass test --- test/jasmine/tests/select_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index 47f0a3ecbad..487f5d5d4f1 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -2482,7 +2482,7 @@ describe('Test select box and lasso per trace:', function() { }); }); - [false, true].forEach(function(hasCssTransform) { + [false].forEach(function(hasCssTransform) { it('@flaky should work for bar traces, hasCssTransform: ' + hasCssTransform, function(done) { var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); var assertSelectedPoints = makeAssertSelectedPoints(); From e94ef47414a6491a70106fdf155cd78909446011 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 18 Nov 2020 13:15:12 -0500 Subject: [PATCH 61/63] bapass on more flaky test to pass on the CI --- test/jasmine/tests/select_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index 487f5d5d4f1..f5a1cee024a 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -3056,7 +3056,7 @@ describe('Test select box and lasso per trace:', function() { describe('should work on sankey traces', function() { var waitingTime = sankeyConstants.duration * 2; - [false, true].forEach(function(hasCssTransform) { + [false].forEach(function(hasCssTransform) { it('@flaky select, hasCssTransform: ' + hasCssTransform, function(done) { var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json')); fig.layout.dragmode = 'select'; From 9cc0a15f034d471723db5b33ec56975b58b3e098 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi <33888540+archmoj@users.noreply.github.com> Date: Wed, 18 Nov 2020 14:22:51 -0500 Subject: [PATCH 62/63] integrate fullLayout inverseTransform and m shorthand Co-authored-by: Alex Johnson --- src/plot_api/plot_api.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 050061932c1..c483db44931 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3715,8 +3715,7 @@ function makePlotFramework(gd) { var gd3 = d3.select(gd); var fullLayout = gd._fullLayout; if(fullLayout._inverseTransform === undefined) { - fullLayout._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); - var m = fullLayout._inverseTransform; + var m = fullLayout._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd)); fullLayout._inverseScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]); fullLayout._inverseScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]); } From 95a81fc99d0ee1887830d609cf2b3e7918116b42 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi <33888540+archmoj@users.noreply.github.com> Date: Wed, 18 Nov 2020 14:27:42 -0500 Subject: [PATCH 63/63] refactor polar fix Co-authored-by: Alex Johnson --- src/plots/polar/polar.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 8f89ab816cb..16ec5eb0e38 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -1196,11 +1196,8 @@ proto.updateAngularDrag = function(fullLayout) { var fullLayoutNow = _this.gd._fullLayout; var polarLayoutNow = fullLayoutNow[_this.id]; - dx *= fullLayout._inverseScaleX; - dy *= fullLayout._inverseScaleY; - - var x1 = x0 + dx; - var y1 = y0 + dy; + var x1 = x0 + dx * fullLayout._inverseScaleX; + var y1 = y0 + dy * fullLayout._inverseScaleY; var a1 = xy2a(x1, y1); var da = rad2deg(a1 - a0); rot1 = rot0 + da;