Skip to content

Commit cdd836c

Browse files
authored
Merge pull request #5302 from Displayr/5291-dynamic-transforms
5291: recalculation of dynamic CSS transforms on hover
2 parents f7baf5d + b97895f commit cdd836c

19 files changed

+161
-80
lines changed

src/components/fx/hover.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ exports.loneHover = function loneHover(hoverItems, opts) {
194194
d.offset -= anchor;
195195
});
196196

197-
var scaleX = opts.gd._fullLayout._inverseScaleX;
198-
var scaleY = opts.gd._fullLayout._inverseScaleY;
197+
var scaleX = opts.gd._fullLayout._invScaleX;
198+
var scaleY = opts.gd._fullLayout._invScaleY;
199199
alignHoverText(hoverLabel, fullOpts.rotateLabels, scaleX, scaleY);
200200

201201
return multiHover ? hoverLabel : hoverLabel.node();
@@ -340,7 +340,8 @@ function _hover(gd, evt, subplot, noHoverEvent) {
340340
xpx = evt.clientX - dbb.left;
341341
ypx = evt.clientY - dbb.top;
342342

343-
var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(xpx, ypx);
343+
fullLayout._calcInverseTransform(gd);
344+
var transformedCoords = Lib.apply3DTransform(fullLayout._invTransform)(xpx, ypx);
344345

345346
xpx = transformedCoords[0];
346347
ypx = transformedCoords[1];
@@ -725,7 +726,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
725726

726727
if(!helpers.isUnifiedHover(hovermode)) {
727728
hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
728-
alignHoverText(hoverLabels, rotateLabels, fullLayout._inverseScaleX, fullLayout._inverseScaleY);
729+
alignHoverText(hoverLabels, rotateLabels, fullLayout._invScaleX, fullLayout._invScaleY);
729730
} // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true
730731
// we should improve the "fx" API so other plots can use it without these hack.
731732
if(evt.target && evt.target.tagName) {

src/lib/dom.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ function isTransformableElement(element) {
150150
return element && (element instanceof Element || element instanceof HTMLElement);
151151
}
152152

153+
function equalDomRects(a, b) {
154+
return (
155+
a && b &&
156+
a.x === b.x &&
157+
a.y === b.y &&
158+
a.top === b.top &&
159+
a.left === b.left &&
160+
a.right === b.right &&
161+
a.bottom === b.bottom
162+
);
163+
}
164+
153165
module.exports = {
154166
getGraphDiv: getGraphDiv,
155167
isPlotDiv: isPlotDiv,
@@ -160,4 +172,5 @@ module.exports = {
160172
getFullTransformMatrix: getFullTransformMatrix,
161173
getElementTransformMatrix: getElementTransformMatrix,
162174
getElementAndAncestors: getElementAndAncestors,
175+
equalDomRects: equalDomRects
163176
};

src/lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule;
151151
lib.getFullTransformMatrix = domModule.getFullTransformMatrix;
152152
lib.getElementTransformMatrix = domModule.getElementTransformMatrix;
153153
lib.getElementAndAncestors = domModule.getElementAndAncestors;
154+
lib.equalDomRects = domModule.equalDomRects;
154155

155156
lib.clearResponsive = require('./clear_responsive');
156157

src/lib/svg_text_utils.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -748,9 +748,12 @@ function alignHTMLWith(_base, container, options) {
748748
var x0 = getLeft() - cRect.left;
749749
var y0 = getTop() - cRect.top;
750750
var gd = options.gd || {};
751-
var transformedCoords = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(x0, y0);
752-
x0 = transformedCoords[0];
753-
y0 = transformedCoords[1];
751+
if(options.gd) {
752+
gd._fullLayout._calcInverseTransform(gd);
753+
var transformedCoords = Lib.apply3DTransform(gd._fullLayout._invTransform)(x0, y0);
754+
x0 = transformedCoords[0];
755+
y0 = transformedCoords[1];
756+
}
754757

755758
this.style({
756759
top: y0 + 'px',

src/plot_api/plot_api.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3706,17 +3706,28 @@ function purge(gd) {
37063706
return gd;
37073707
}
37083708

3709+
// determines if the graph div requires a recalculation of its inverse matrix transforms by comparing old + new bounding boxes.
3710+
function calcInverseTransform(gd) {
3711+
var fullLayout = gd._fullLayout;
3712+
3713+
var newBBox = gd.getBoundingClientRect();
3714+
if(Lib.equalDomRects(newBBox, fullLayout._lastBBox)) return;
3715+
3716+
var m = fullLayout._invTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd));
3717+
fullLayout._invScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]);
3718+
fullLayout._invScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]);
3719+
fullLayout._lastBBox = newBBox;
3720+
}
3721+
37093722
// -------------------------------------------------------
37103723
// makePlotFramework: Create the plot container and axes
37113724
// -------------------------------------------------------
37123725
function makePlotFramework(gd) {
37133726
var gd3 = d3.select(gd);
37143727
var fullLayout = gd._fullLayout;
3715-
if(fullLayout._inverseTransform === undefined) {
3716-
var m = fullLayout._inverseTransform = Lib.inverseTransformMatrix(Lib.getFullTransformMatrix(gd));
3717-
fullLayout._inverseScaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]);
3718-
fullLayout._inverseScaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]);
3719-
}
3728+
3729+
fullLayout._calcInverseTransform = calcInverseTransform;
3730+
fullLayout._calcInverseTransform(gd);
37203731

37213732
// Plot container
37223733
fullLayout._container = gd3.selectAll('.plot-container').data([0]);

src/plots/cartesian/dragbox.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
166166

167167
recomputeAxisLists();
168168

169-
scaleX = gd._fullLayout._inverseScaleX;
170-
scaleY = gd._fullLayout._inverseScaleY;
169+
scaleX = gd._fullLayout._invScaleX;
170+
scaleY = gd._fullLayout._invScaleY;
171171

172172
if(!allFixedRanges) {
173173
if(isMainDrag) {
@@ -335,7 +335,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
335335
x0 = startX - dragBBox.left;
336336
y0 = startY - dragBBox.top;
337337

338-
var transformedCoords = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(x0, y0);
338+
gd._fullLayout._calcInverseTransform(gd);
339+
var transformedCoords = Lib.apply3DTransform(gd._fullLayout._invTransform)(x0, y0);
339340
x0 = transformedCoords[0];
340341
y0 = transformedCoords[1];
341342

src/plots/cartesian/select.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,12 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
6868
var x0 = startX - dragBBox.left;
6969
var y0 = startY - dragBBox.top;
7070

71-
var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(x0, y0);
71+
fullLayout._calcInverseTransform(gd);
72+
var transformedCoords = Lib.apply3DTransform(fullLayout._invTransform)(x0, y0);
7273
x0 = transformedCoords[0];
7374
y0 = transformedCoords[1];
74-
var scaleX = fullLayout._inverseScaleX;
75-
var scaleY = fullLayout._inverseScaleY;
75+
var scaleX = fullLayout._invScaleX;
76+
var scaleY = fullLayout._invScaleY;
7677

7778
var x1 = x0;
7879
var y1 = y0;

src/plots/gl3d/scene.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,10 @@ proto.render = function() {
307307
// update size of svg container
308308
var svgContainer = scene.svgContainer;
309309
var clientRect = scene.container.getBoundingClientRect();
310-
var scaleX = gd._fullLayout._inverseScaleX;
311-
var scaleY = gd._fullLayout._inverseScaleY;
310+
311+
gd._fullLayout._calcInverseTransform(gd);
312+
var scaleX = gd._fullLayout._invScaleX;
313+
var scaleY = gd._fullLayout._invScaleY;
312314
var width = clientRect.width * scaleX;
313315
var height = clientRect.height * scaleY;
314316
svgContainer.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height);

src/plots/polar/polar.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,8 @@ proto.updateMainDrag = function(fullLayout) {
680680
var chw = constants.cornerHalfWidth;
681681
var chl = constants.cornerLen / 2;
682682

683-
var scaleX = gd._fullLayout._inverseScaleX;
684-
var scaleY = gd._fullLayout._inverseScaleY;
683+
var scaleX;
684+
var scaleY;
685685

686686
var mainDrag = dragBox.makeDragger(layers, 'path', 'maindrag', 'crosshair');
687687

@@ -943,7 +943,10 @@ proto.updateMainDrag = function(fullLayout) {
943943
var dragModeNow = gd._fullLayout.dragmode;
944944

945945
var bbox = mainDrag.getBoundingClientRect();
946-
var inverse = gd._fullLayout._inverseTransform;
946+
gd._fullLayout._calcInverseTransform(gd);
947+
var inverse = gd._fullLayout._invTransform;
948+
scaleX = gd._fullLayout._invScaleX;
949+
scaleY = gd._fullLayout._invScaleY;
947950
var transformedCoords = Lib.apply3DTransform(inverse)(startX - bbox.left, startY - bbox.top);
948951
x0 = transformedCoords[0];
949952
y0 = transformedCoords[1];
@@ -1198,8 +1201,8 @@ proto.updateAngularDrag = function(fullLayout) {
11981201
var fullLayoutNow = _this.gd._fullLayout;
11991202
var polarLayoutNow = fullLayoutNow[_this.id];
12001203

1201-
var x1 = x0 + dx * fullLayout._inverseScaleX;
1202-
var y1 = y0 + dy * fullLayout._inverseScaleY;
1204+
var x1 = x0 + dx * fullLayout._invScaleX;
1205+
var y1 = y0 + dy * fullLayout._invScaleY;
12031206
var a1 = xy2a(x1, y1);
12041207
var da = rad2deg(a1 - a0);
12051208
rot1 = rot0 + da;
@@ -1293,7 +1296,8 @@ proto.updateAngularDrag = function(fullLayout) {
12931296
x0 = startX - bbox.left;
12941297
y0 = startY - bbox.top;
12951298

1296-
var transformedCoords = Lib.apply3DTransform(fullLayout._inverseTransform)(x0, y0);
1299+
gd._fullLayout._calcInverseTransform(gd);
1300+
var transformedCoords = Lib.apply3DTransform(fullLayout._invTransform)(x0, y0);
12971301
x0 = transformedCoords[0];
12981302
y0 = transformedCoords[1];
12991303

src/plots/ternary/ternary.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,8 @@ proto.initInteractions = function() {
523523
_this.dragOptions.xaxes = [_this.xaxis];
524524
_this.dragOptions.yaxes = [_this.yaxis];
525525

526-
scaleX = gd._fullLayout._inverseScaleX;
527-
scaleY = gd._fullLayout._inverseScaleY;
526+
scaleX = gd._fullLayout._invScaleX;
527+
scaleY = gd._fullLayout._invScaleY;
528528

529529
var dragModeNow = _this.dragOptions.dragmode = gd._fullLayout.dragmode;
530530

@@ -579,9 +579,11 @@ proto.initInteractions = function() {
579579

580580
function zoomPrep(e, startX, startY) {
581581
var dragBBox = dragger.getBoundingClientRect();
582-
var inverse = gd._fullLayout._inverseTransform;
583582
x0 = startX - dragBBox.left;
584583
y0 = startY - dragBBox.top;
584+
585+
gd._fullLayout._calcInverseTransform(gd);
586+
var inverse = gd._fullLayout._invTransform;
585587
var transformedCoords = Lib.apply3DTransform(inverse)(x0, y0);
586588
x0 = transformedCoords[0];
587589
y0 = transformedCoords[1];

src/traces/parcats/parcats.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -767,8 +767,9 @@ function emitPointsEventColorHovermode(bandElement, eventName, event) {
767767
*
768768
*/
769769
function createHoverLabelForCategoryHovermode(gd, rootBBox, bandElement) {
770-
var scaleX = gd._fullLayout._inverseScaleX;
771-
var scaleY = gd._fullLayout._inverseScaleY;
770+
gd._fullLayout._calcInverseTransform(gd);
771+
var scaleX = gd._fullLayout._invScaleX;
772+
var scaleY = gd._fullLayout._invScaleY;
772773

773774
// Selections
774775
var rectSelection = d3.select(bandElement.parentNode).select('rect.catrect');
@@ -871,8 +872,9 @@ function createHoverLabelForDimensionHovermode(gd, rootBBox, bandElement) {
871872
*
872873
*/
873874
function createHoverLabelForColorHovermode(gd, rootBBox, bandElement) {
874-
var scaleX = gd._fullLayout._inverseScaleX;
875-
var scaleY = gd._fullLayout._inverseScaleY;
875+
gd._fullLayout._calcInverseTransform(gd);
876+
var scaleX = gd._fullLayout._invScaleX;
877+
var scaleY = gd._fullLayout._invScaleY;
876878

877879
var bandBoundingBox = bandElement.getBoundingClientRect();
878880

src/traces/sankey/plot.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,9 @@ module.exports = function plot(gd, calcData) {
292292
var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.node.value) + d.valueSuffix};
293293
d.node.fullData = d.node.trace;
294294

295-
var scaleX = gd._fullLayout._inverseScaleX;
296-
var scaleY = gd._fullLayout._inverseScaleY;
295+
gd._fullLayout._calcInverseTransform(gd);
296+
var scaleX = gd._fullLayout._invScaleX;
297+
var scaleY = gd._fullLayout._invScaleY;
297298

298299
var tooltip = Fx.loneHover({
299300
x0: scaleX * hoverCenterX0,

test/jasmine/tests/cartesian_interact_test.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,9 +2475,10 @@ describe('Cartesian plots with css transforms', function() {
24752475
});
24762476
}
24772477

2478-
transformPlot(gd, transform);
24792478
Plotly.newPlot(gd, Lib.extendDeep({}, mock))
24802479
.then(function() {
2480+
transformPlot(gd, transform);
2481+
24812482
gd.on('plotly_hover', function(d) {
24822483
eventRecordings[d.points[0].x] = 1;
24832484
});
@@ -2505,8 +2506,8 @@ describe('Cartesian plots with css transforms', function() {
25052506
// asserts that the zoombox path must go from the start to end positions,
25062507
// in css-transformed coordinates.
25072508
function _assertTransformedZoombox(startPos, endPos) {
2508-
startPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(startPos[0], startPos[1]);
2509-
endPos = Lib.apply3DTransform(gd._fullLayout._inverseTransform)(endPos[0], endPos[1]);
2509+
startPos = Lib.apply3DTransform(gd._fullLayout._invTransform)(startPos[0], startPos[1]);
2510+
endPos = Lib.apply3DTransform(gd._fullLayout._invTransform)(endPos[0], endPos[1]);
25102511
var size = [endPos[0] - startPos[0], endPos[1] - startPos[1]];
25112512
var zb = d3.select(gd).select('g.zoomlayer > path.zoombox');
25122513
var zoomboxRect = _getZoomlayerPathRect(zb.attr('d'));
@@ -2519,9 +2520,12 @@ describe('Cartesian plots with css transforms', function() {
25192520
var start = [50, 50];
25202521
var end = [150, 150];
25212522

2522-
transformPlot(gd, transform);
25232523
Plotly.newPlot(gd, Lib.extendDeep({}, mock))
2524-
.then(function() {_drag(start, end); })
2524+
.then(function() {
2525+
transformPlot(gd, transform);
2526+
2527+
_drag(start, end);
2528+
})
25252529
.then(function() {
25262530
_assertTransformedZoombox(start, end);
25272531
})
@@ -2547,9 +2551,10 @@ describe('Cartesian plots with css transforms', function() {
25472551
var start = [10, 10];
25482552
var end = [200, 200];
25492553

2550-
transformPlot(gd, transform);
25512554
Plotly.newPlot(gd, Lib.extendDeep({}, mock))
25522555
.then(function() {
2556+
transformPlot(gd, transform);
2557+
25532558
return Plotly.relayout(gd, 'dragmode', 'select');
25542559
})
25552560
.then(function() {

test/jasmine/tests/choropleth_test.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,6 @@ describe('Test choropleth hover:', function() {
167167
function run(hasCssTransform, pos, fig, content, style) {
168168
gd = createGraphDiv();
169169
var scale = 1;
170-
if(hasCssTransform) {
171-
scale = 0.5;
172-
transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');
173-
}
174170

175171
style = style || {
176172
bgcolor: 'rgb(68, 68, 68)',
@@ -180,7 +176,13 @@ describe('Test choropleth hover:', function() {
180176
fontFamily: 'Arial'
181177
};
182178

183-
return Plotly.plot(gd, fig).then(function() {
179+
return Plotly.plot(gd, fig)
180+
.then(function() {
181+
if(hasCssTransform) {
182+
scale = 0.5;
183+
transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');
184+
}
185+
184186
mouseEvent('mousemove', scale * pos[0], scale * pos[1]);
185187
assertHoverLabelContent({
186188
nums: content[0],

test/jasmine/tests/choroplethmapbox_test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,6 @@ describe('@noCI Test choroplethmapbox hover:', function() {
538538
var scale = 1;
539539
if(hasCssTransform) {
540540
scale = 0.5;
541-
transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');
542541
}
543542

544543
var fig = Lib.extendDeep({},
@@ -556,6 +555,8 @@ describe('@noCI Test choroplethmapbox hover:', function() {
556555
var pos = s.pos || [270, 220];
557556

558557
return Plotly.plot(gd, fig).then(function() {
558+
if(hasCssTransform) transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');
559+
559560
var to = setTimeout(function() {
560561
failTest('no event data received');
561562
done();

test/jasmine/tests/polar_test.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,9 +1746,10 @@ describe('Polar plots with css transforms', function() {
17461746
it('hover behaves correctly after css transform: ' + transform, function(done) {
17471747
var hoverEvents = {};
17481748

1749-
transformPlot(gd, transform);
17501749
Plotly.newPlot(gd, Lib.extendDeep({}, mock))
17511750
.then(function() {
1751+
transformPlot(gd, transform);
1752+
17521753
gd.on('plotly_hover', function(d) {
17531754
hoverEvents[d.points[0].pointIndex] = true;
17541755
});
@@ -1765,10 +1766,11 @@ describe('Polar plots with css transforms', function() {
17651766
});
17661767

17671768
it('drag-zoom behaves correctly after css transform: ' + transform, function(done) {
1768-
transformPlot(gd, transform);
17691769
Plotly.newPlot(gd, Lib.extendDeep({}, mock))
17701770

17711771
.then(function() {
1772+
transformPlot(gd, transform);
1773+
17721774
return _drag([10, 10], [50, 50]);
17731775
})
17741776
.then(function() {
@@ -1789,9 +1791,10 @@ describe('Polar plots with css transforms', function() {
17891791
}
17901792
}
17911793

1792-
transformPlot(gd, transform);
17931794
Plotly.newPlot(gd, Lib.extendDeep({}, mock))
17941795
.then(function() {
1796+
transformPlot(gd, transform);
1797+
17951798
return Plotly.relayout(gd, 'dragmode', 'select');
17961799
})
17971800
.then(function() { return _drag([30, 30], [130, 130]); })

0 commit comments

Comments
 (0)