Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align interactions when plot created inside scaled and/or translated elements using CSS transform #5193

Merged
merged 63 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
b9a5d35
888: transform hover event coords to fit css scaling transform
alexhartstone Sep 29, 2020
5e6eee9
888: fix cartesian dragbox
alexhartstone Oct 12, 2020
1741a4a
fix syntax in new files
archmoj Oct 21, 2020
f0232c4
some minor changes
archmoj Oct 21, 2020
3b63fe6
renaming variables and method since list of all elements includes the…
archmoj Oct 21, 2020
5c60d5e
lets not touch dragbox keys
archmoj Oct 21, 2020
2529474
drop unused functions
archmoj Oct 21, 2020
b8e7c6a
declare isMiddle variable in hover
archmoj Oct 22, 2020
0b4f74a
correct size of hover box and position of text
archmoj Oct 22, 2020
a6aeafa
decompose scales when there is also rotation
archmoj Oct 22, 2020
7e8a0e8
drop unused function
archmoj Oct 22, 2020
41820ce
refactor matrix
archmoj Oct 22, 2020
d7665f3
fixup translate within inverseTransformMatrix function
archmoj Oct 22, 2020
c0868c2
replace invert function with gl-mat3 to compute determinant etc. corr…
archmoj Oct 22, 2020
7625a67
use parentNode instead of parentElement for IE support
archmoj Oct 22, 2020
46ed093
increase computed style calls - new one added in hover
archmoj Oct 22, 2020
44769fa
bypass and return default matrix if encountered a problem instead of …
archmoj Oct 22, 2020
463a0ab
transform drag start position
archmoj Oct 22, 2020
e7d9a53
adjust cartesian drag box endpoint in respect to scales
archmoj Oct 22, 2020
080034b
adjust cartesian select start and end point according to parent trans…
archmoj Oct 22, 2020
2bfc471
adjust trace name hover text and box
archmoj Oct 23, 2020
1b78028
adjust left and right alignments
archmoj Oct 23, 2020
f412175
adjust vertical offset between multiple hover boxes
archmoj Oct 23, 2020
825d962
compute inverse transform at graph main div level not the event target
archmoj Oct 23, 2020
7e26e91
compute inverse transform for gd at makePlotFramwork and stash it on …
archmoj Oct 23, 2020
c137f14
no need for extra adjustment for y offset
archmoj Oct 23, 2020
bedbee7
adjust scale of svg hover container in respect to transform for gl3d …
archmoj Oct 23, 2020
2c810fc
handle cases where css transform has a length of 16 using 4x4 matrices
archmoj Oct 26, 2020
c409a28
fixup matrix for translate
archmoj Oct 29, 2020
68f5add
888: cartesian hover test
alexhartstone Nov 2, 2020
8e64281
888: changed transformPlot fn, added dragzoom test
alexhartstone Nov 3, 2020
dbc5232
888: select test
alexhartstone Nov 4, 2020
07b934b
888: more flexible transform testing
alexhartstone Nov 4, 2020
d75c018
888: polar hover test
alexhartstone Nov 5, 2020
d31244a
888: fix polar dragzoom
alexhartstone Nov 9, 2020
94f685f
888: fixed cartesian pan
alexhartstone Nov 10, 2020
435945b
888: use scale factors in polar too for performance
alexhartstone Nov 10, 2020
f3c03cd
888: assign scales at makePlotFramework
alexhartstone Nov 16, 2020
23b4d3c
888: fix polar plot rotation
alexhartstone Nov 16, 2020
84ede31
888: fix alignHTMLWith
archmoj Nov 16, 2020
4b1bf62
888: various syntax fixes
archmoj Nov 16, 2020
d3b548f
888: adjust scatter mapbox hover when css transition is present
archmoj Nov 16, 2020
4e1fd5f
drop unused argument
archmoj Nov 16, 2020
76afc6c
a bit of optimization in choropleth/hover - avoid creating the same a…
archmoj Nov 16, 2020
0117b41
888: fixup scattermapbox both select and hover
archmoj Nov 16, 2020
4980e24
return correct latitude and longitude from mapbox p2c
archmoj Nov 16, 2020
88b24f6
offsetX and offsetY are now implemented in FireFox - remove from blac…
archmoj Nov 17, 2020
b2eed52
888: added polar drag and select tests
alexhartstone Nov 17, 2020
8cc496c
fixup polar test
archmoj Nov 17, 2020
1dee019
expand and fix tests
archmoj Nov 17, 2020
02b6f79
removed hacky clientX and clientY from scattermaobox events
archmoj Nov 17, 2020
805d97b
add scattermapbox tests when css transform is present
archmoj Nov 17, 2020
127f171
expand choropleth hover tests to cover css transform
archmoj Nov 17, 2020
12a57c9
expand choroplethmapbox hover tests to cover css transform
archmoj Nov 17, 2020
34845ec
expand select tests for various traces to cover css transform
archmoj Nov 18, 2020
29e772d
expand ternary hover and drag zoom tests to cover css transform
archmoj Nov 18, 2020
28a9178
remove dirty matrix validation - take care of matrix3d form instead
archmoj Nov 18, 2020
a2f34c9
adjust hover positions on parcats category bands in respect to css tr…
archmoj Nov 18, 2020
9fe42b6
adjust hover positions on sankey nodes in respect to css transforms
archmoj Nov 18, 2020
5681a34
bypass test
archmoj Nov 18, 2020
e94ef47
bapass on more flaky test to pass on the CI
archmoj Nov 18, 2020
9cc0a15
integrate fullLayout inverseTransform and m shorthand
archmoj Nov 18, 2020
95a81fc
refactor polar fix
archmoj Nov 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 7 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 35 additions & 25 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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));
}
});
}
Expand Down
64 changes: 63 additions & 1 deletion src/lib/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
};
6 changes: 6 additions & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');

Expand Down
47 changes: 46 additions & 1 deletion src/lib/matrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

'use strict';

var mat4X4 = require('gl-mat4');

exports.init2dArray = function(rowLength, colLength) {
var array = new Array(rowLength);
Expand Down Expand Up @@ -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);
};
Expand All @@ -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]]
];
};
12 changes: 10 additions & 2 deletions src/lib/svg_text_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down