diff --git a/package-lock.json b/package-lock.json index 1ac9254245b..a2b6a492c06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4839,11 +4839,6 @@ "ndarray": "^1.0.18" } }, - "gl-mat3": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gl-mat3/-/gl-mat3-1.0.0.tgz", - "integrity": "sha1-iWMyGcpCk3mha5GF2V1BcTRTuRI=" - }, "gl-mat4": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.2.0.tgz", @@ -4932,6 +4927,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/src/components/fx/hover.js b/src/components/fx/hover.js index 3b43a069bc3..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); + var scaleX = opts.gd._fullLayout._inverseScaleX; + var scaleY = opts.gd._fullLayout._inverseScaleY; + alignHoverText(hoverLabel, fullOpts.rotateLabels, scaleX, scaleY); return multiHover ? hoverLabel : hoverLabel.node(); }; @@ -336,6 +338,11 @@ function _hover(gd, evt, subplot, noHoverEvent) { xpx = evt.clientX - dbb.left; ypx = evt.clientY - dbb.top; + var transformedCoords = Lib.apply3DTransform(fullLayout._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) { @@ -716,10 +723,8 @@ function _hover(gd, evt, subplot, noHoverEvent) { if(!helpers.isUnifiedHover(hovermode)) { hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); - alignHoverText(hoverLabels, rotateLabels); - } - - // 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); @@ -1477,7 +1482,10 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) { } } -function alignHoverText(hoverLabels, rotateLabels) { +function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) { + 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) { @@ -1493,7 +1501,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; } @@ -1502,49 +1511,50 @@ 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') : + ('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 posX = offsetX + txx; var posY = offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD; var textAlign = d.textAlign || 'auto'; 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; } } - tx.call(svgTextUtils.positionText, posX, posY); + tx.call(svgTextUtils.positionText, pX(posX), pY(posY)); 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)); } }); } diff --git a/src/lib/dom.js b/src/lib/dom.js index 6bc7760d253..cd2a0ec6690 100644 --- a/src/lib/dom.js +++ b/src/lib/dom.js @@ -10,6 +10,8 @@ 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 @@ -91,11 +93,71 @@ function deleteRelatedStyleRule(uid) { if(style) removeElement(style); } +function getFullTransformMatrix(element) { + var allElements = getElementAndAncestors(element); + // the identity matrix + 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) { + var m = matrix.convertCssMatrix(t); + out = mat4X4.multiply(out, out, m); + } + }); + return out; +} + +/** + * extracts and parses the 2d css style transform matrix from some element + */ +function getElementTransformMatrix(element) { + 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 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) + */ +function getElementAndAncestors(element) { + var allElements = []; + while(isTransformableElement(element)) { + allElements.push(element); + element = element.parentNode; + } + return allElements; +} + +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, + getElementAndAncestors: getElementAndAncestors, }; diff --git a/src/lib/index.js b/src/lib/index.js index 157dd15542b..f73666037c2 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -88,8 +88,11 @@ 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; +lib.inverseTransformMatrix = matrixModule.inverseTransformMatrix; var anglesModule = require('./angles'); lib.deg2rad = anglesModule.deg2rad; @@ -145,6 +148,9 @@ 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.getElementAndAncestors = domModule.getElementAndAncestors; lib.clearResponsive = require('./clear_responsive'); diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 80f5a87e508..a7467856cbc 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -9,6 +9,7 @@ 'use strict'; +var mat4X4 = require('gl-mat4'); exports.init2dArray = function(rowLength, colLength) { var array = new Array(rowLength); @@ -84,13 +85,23 @@ 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() { 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); }; @@ -103,3 +114,37 @@ exports.apply2DTransform2 = function(transform) { return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4))); }; }; + +exports.convertCssMatrix = function(m) { + if(m) { + var len = m.length; + 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 [ + m[0], m[1], 0, 0, + m[2], m[3], 0, 0, + 0, 0, 1, 0, + m[4], m[5], 0, 1 + ]; + } + } + return [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]; +}; + +// find the inverse for a 4x4 affine transform matrix +exports.inverseTransformMatrix = function(m) { + var 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[9], out[10], out[11]], + [out[12], out[13], out[14], out[15]] + ]; +}; 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; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 893e8b9a68e..c483db44931 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3714,6 +3714,11 @@ function purge(gd) { function makePlotFramework(gd) { var gd3 = d3.select(gd); var fullLayout = gd._fullLayout; + if(fullLayout._inverseTransform === undefined) { + 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]); + } // 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 573362f0047..9190aa709c5 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,9 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { recomputeAxisLists(); + scaleX = gd._fullLayout._inverseScaleX; + scaleY = gd._fullLayout._inverseScaleY; + if(!allFixedRanges) { if(isMainDrag) { // main dragger handles all drag modes, and changes @@ -326,6 +332,11 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var dragBBox = dragger.getBoundingClientRect(); x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; + + var transformedCoords = Lib.apply3DTransform(gd._fullLayout._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) : @@ -343,8 +354,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, 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); @@ -544,6 +555,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; diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index a61bbbcb23c..029f32815e6 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -67,6 +67,13 @@ function prepSelect(e, startX, startY, dragOptions, mode) { var transform = getTransform(plotinfo); var x0 = startX - dragBBox.left; var y0 = startY - dragBBox.top; + + var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(x0, y0); + x0 = transformedCoords[0]; + y0 = transformedCoords[1]; + var scaleX = fullLayout._inverseScaleX; + var scaleY = fullLayout._inverseScaleY; + var x1 = x0; var y1 = y0; var path0 = 'M' + x0 + ',' + y0; @@ -156,8 +163,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); diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index e45fb6e932a..886fb14c7f1 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -296,8 +296,10 @@ 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 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); svgContainer.setAttributeNS(null, 'width', width); svgContainer.setAttributeNS(null, 'height', height); diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index b3268b9a5cb..71d48193187 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -450,15 +450,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; + var xy = [ + evt.originalEvent.offsetX, + evt.originalEvent.offsetY + ]; evt.target.getBoundingClientRect = function() { return bb; }; - self.xaxis.p2c = function() { return evt.lngLat.lng; }; - self.yaxis.p2c = function() { return evt.lngLat.lat; }; + 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/plots/polar/polar.js b/src/plots/polar/polar.js index ebeed62ab25..16ec5eb0e38 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -678,6 +678,9 @@ proto.updateMainDrag = function(fullLayout) { var chw = constants.cornerHalfWidth; var chl = constants.cornerLen / 2; + var scaleX = gd._fullLayout._inverseScaleX; + var scaleY = gd._fullLayout._inverseScaleY; + var mainDrag = dragBox.makeDragger(layers, 'path', 'maindrag', 'crosshair'); d3.select(mainDrag) @@ -837,8 +840,12 @@ proto.updateMainDrag = function(fullLayout) { } function zoomMove(dx, dy) { + dx = dx * scaleX; + dy = dy * scaleY; + 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 @@ -1187,8 +1196,8 @@ proto.updateAngularDrag = function(fullLayout) { var fullLayoutNow = _this.gd._fullLayout; var polarLayoutNow = fullLayoutNow[_this.id]; - 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; @@ -1281,6 +1290,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; 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))); diff --git a/src/traces/choropleth/hover.js b/src/traces/choropleth/hover.js index 3d1609ef627..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; } } @@ -49,7 +52,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]; }; 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) { 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, diff --git a/tasks/test_syntax.js b/tasks/test_syntax.js index 14c20675120..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') { @@ -213,7 +208,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 + diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 2fdd891f83b..819708b2de3 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -2244,3 +2244,196 @@ 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 transformPlot(gd, transformString) { + gd.style.webkitTransform = transformString; + gd.style.MozTransform = transformString; + gd.style.msTransform = transformString; + gd.style.OTransform = transformString; + gd.style.transform = transformString; + } + + 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 mock = { + data: [{ + x: xLabels, + y: [1, 2, 3], + type: 'bar' + }], + layout: { + width: 600, + height: 400, + margin: {l: 0, t: 0, r: 0, b: 0} + } + }; + + [{ + 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(t.hovered); + }) + .then(function() { + _unhover(point); + }); + } + + 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() {_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: ' + 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], -1); + 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); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) + .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]; + + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'select'); + }) + .then(function() { + _dragRelease(start, end); + }) + .then(function() { + _assertSelected(t.selected); + }) + .catch(failTest) + .then(done); + }); + }); +}); 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); + }); }); }); 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); + }); }); }); }); diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index e087f2495e4..483e62d2591 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1595,3 +1595,160 @@ describe('Test polar *gridshape linear* interactions', function() { .then(done); }); }); + + +describe('Polar plots with css transforms', function() { + var gd; + + beforeEach(function() { + 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 _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) { + 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 _getVisiblePointsData() { + return Array.from( + document.querySelectorAll('.point').entries(), + function(e) { return e[1]; } + ) + .filter(function(e) { return window.getComputedStyle(e).display !== 'none'; }) + .map(function(e) { return e.__data__; }); + } + + 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' + } + }; + + [{ + 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 = {}; + + 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() { _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(t.hovered); + }) + .catch(failTest) + .then(done); + }); + + it('drag-zoom behaves correctly after css transform: ' + transform, function(done) { + transformPlot(gd, transform); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) + + .then(function() { + return _drag([10, 10], [50, 50]); + }) + .then(function() { + var points = _getVisiblePointsData(); + expect(points.map(function(e) { return e.i; })).toEqual(t.zoomed); + }) + .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); + } + } + + 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(t.selected); + }) + .catch(failTest) + .then(done); + }); + }); +}); diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 298d64f7b98..63d3df59230 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,129 @@ 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'); + }).then(done); + }); + }); +}); + +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); - expect(evt.clientX).toEqual(nearPos[0], 'event.clientX'); - expect(evt.clientY).toEqual(nearPos[1], 'event.clientY'); + 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); }); }); diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index fbcc5261fa3..f5a1cee024a 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].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].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')); 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';