-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Zoomscroll fix #564
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
Zoomscroll fix #564
Changes from all commits
79bb6ef
69ea5f1
2e141a3
e40c326
6f06385
85fd4a8
b2eaa8d
336c12f
cf62849
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -449,12 +449,12 @@ lib.addStyleRule = function(selector, styleString) { | |
|
||
lib.getTranslate = function(element) { | ||
|
||
var re = /(\btranslate\()(\d*\.?\d*)([^\d]*)(\d*\.?\d*)([^\d]*)(.*)/, | ||
var re = /.*\btranslate\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, | ||
getter = element.attr ? 'attr' : 'getAttribute', | ||
transform = element[getter]('transform') || ''; | ||
|
||
var translate = transform.replace(re, function(match, p1, p2, p3, p4) { | ||
return [p2, p4].join(' '); | ||
var translate = transform.replace(re, function(match, p1, p2) { | ||
return [p1, p2].join(' '); | ||
}) | ||
.split(' '); | ||
|
||
|
@@ -483,6 +483,42 @@ lib.setTranslate = function(element, x, y) { | |
return transform; | ||
}; | ||
|
||
lib.getScale = function(element) { | ||
|
||
var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
getter = element.attr ? 'attr' : 'getAttribute', | ||
transform = element[getter]('transform') || ''; | ||
|
||
var translate = transform.replace(re, function(match, p1, p2) { | ||
return [p1, p2].join(' '); | ||
}) | ||
.split(' '); | ||
|
||
return { | ||
x: +translate[0] || 1, | ||
y: +translate[1] || 1 | ||
}; | ||
}; | ||
|
||
lib.setScale = function(element, x, y) { | ||
|
||
var re = /(\bscale\(.*?\);?)/, | ||
getter = element.attr ? 'attr' : 'getAttribute', | ||
setter = element.attr ? 'attr' : 'setAttribute', | ||
transform = element[getter]('transform') || ''; | ||
|
||
x = x || 1; | ||
y = y || 1; | ||
|
||
transform = transform.replace(re, '').trim(); | ||
transform += ' scale(' + x + ', ' + y + ')'; | ||
transform = transform.trim(); | ||
|
||
element[setter]('transform', transform); | ||
|
||
return transform; | ||
}; | ||
|
||
lib.isIE = function() { | ||
return typeof window.navigator.msSaveBlob !== 'undefined'; | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -347,7 +347,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { | |
var scrollViewBox = [0, 0, pw, ph], | ||
// wait a little after scrolling before redrawing | ||
redrawTimer = null, | ||
REDRAWDELAY = 300, | ||
REDRAWDELAY = constants.REDRAWDELAY, | ||
mainplot = plotinfo.mainplot ? | ||
fullLayout._plots[plotinfo.mainplot] : plotinfo; | ||
|
||
|
@@ -601,27 +601,34 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { | |
subplots = Object.keys(plotinfos); | ||
|
||
for(var i = 0; i < subplots.length; i++) { | ||
|
||
var subplot = plotinfos[subplots[i]], | ||
xa2 = subplot.x(), | ||
ya2 = subplot.y(), | ||
editX = ew && xa.indexOf(xa2) !== -1 && !xa2.fixedrange, | ||
editY = ns && ya.indexOf(ya2) !== -1 && !ya2.fixedrange; | ||
|
||
if(editX || editY) { | ||
// plot requires offset position and | ||
// clip moves with opposite sign | ||
var clipDx = editX ? viewBox[0] : 0, | ||
clipDy = editY ? viewBox[1] : 0, | ||
plotDx = xa2._offset - clipDx, | ||
plotDy = ya2._offset - clipDy; | ||
|
||
var clipId = 'clip' + fullLayout._uid + subplots[i] + 'plot'; | ||
var xScaleFactor = editX ? xa2._length / viewBox[2] : 1, | ||
yScaleFactor = editY ? ya2._length / viewBox[3] : 1; | ||
|
||
fullLayout._defs.selectAll('#' + clipId) | ||
.attr('transform', 'translate(' + clipDx + ', ' + clipDy + ')'); | ||
subplot.plot | ||
.attr('transform', 'translate(' + plotDx + ', ' + plotDy + ')'); | ||
} | ||
var clipDx = editX ? viewBox[0] : 0, | ||
clipDy = editY ? viewBox[1] : 0; | ||
|
||
var fracDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, | ||
fracDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0; | ||
|
||
var plotDx = xa2._offset - fracDx, | ||
plotDy = ya2._offset - fracDy; | ||
|
||
|
||
fullLayout._defs.selectAll('#' + subplot.clipId) | ||
.call(Lib.setTranslate, clipDx, clipDy) | ||
.call(Lib.setScale, 1 / xScaleFactor, 1 / yScaleFactor); | ||
|
||
subplot.plot | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great. Thanks for checking the fixed range case 🍻 |
||
.call(Lib.setTranslate, plotDx, plotDy) | ||
.call(Lib.setScale, xScaleFactor, yScaleFactor); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,14 @@ module.exports = function(type, x, y, opts) { | |
fullOpts.buttons = opts.buttons; | ||
} | ||
|
||
var el = document.elementFromPoint(x, y); | ||
var ev = new window.MouseEvent(type, fullOpts); | ||
var el = document.elementFromPoint(x, y), | ||
ev; | ||
|
||
if(type === 'scroll') { | ||
ev = new window.WheelEvent('wheel', opts); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎉 |
||
} else { | ||
ev = new window.MouseEvent(type, fullOpts); | ||
} | ||
|
||
el.dispatchEvent(ev); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -711,6 +711,35 @@ describe('Test click interactions:', function() { | |
}); | ||
}); | ||
|
||
describe('scroll zoom interactions', function() { | ||
|
||
beforeEach(function(done) { | ||
Plotly.plot(gd, mockCopy.data, mockCopy.layout, { scrollZoom: true }).then(done); | ||
}); | ||
|
||
it('zooms in on scroll up', function() { | ||
|
||
var plot = gd._fullLayout._plots.xy.plot; | ||
|
||
mouseEvent('mousemove', 400, 250); | ||
mouseEvent('scroll', 400, 250, { deltaX: 0, deltaY: -1000 }); | ||
|
||
var transform = plot.attr('transform'); | ||
|
||
var mockEl = { | ||
attr: function() { | ||
return transform; | ||
} | ||
}; | ||
|
||
var translate = Lib.getTranslate(mockEl), | ||
scale = Lib.getScale(mockEl); | ||
|
||
expect([translate.x, translate.y]).toBeCloseToArray([62.841, 99.483]); | ||
expect([scale.x, scale.y]).toBeCloseToArray([1.221, 1.221]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
}); | ||
}); | ||
|
||
describe('pan interactions', function() { | ||
beforeEach(function(done) { | ||
mockCopy.layout.dragmode = 'pan'; | ||
|
@@ -745,7 +774,7 @@ describe('Test click interactions:', function() { | |
mouseEvent('mousedown', start, start); | ||
mouseEvent('mousemove', end, end); | ||
|
||
expect(plot.attr('transform')).toBe('translate(250, 280)'); | ||
expect(plot.attr('transform')).toBe('translate(250, 280) scale(1, 1)'); | ||
|
||
mouseEvent('mouseup', end, end); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -823,6 +823,9 @@ describe('Test lib.js:', function() { | |
el.setAttribute('transform', 'translate(1 2); rotate(20deg)'); | ||
expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); | ||
|
||
el.setAttribute('transform', 'rotate(20deg) translate(1 2);'); | ||
expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); | ||
|
||
el.setAttribute('transform', 'rotate(20deg)'); | ||
expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 }); | ||
}); | ||
|
@@ -858,9 +861,6 @@ describe('Test lib.js:', function() { | |
Lib.setTranslate(el, 10, 20); | ||
expect(el.getAttribute('transform')).toBe('translate(10, 20)'); | ||
|
||
Lib.setTranslate(el, 30, 40); | ||
expect(el.getAttribute('transform')).toBe('translate(30, 40)'); | ||
|
||
Lib.setTranslate(el); | ||
expect(el.getAttribute('transform')).toBe('translate(0, 0)'); | ||
|
||
|
@@ -875,9 +875,6 @@ describe('Test lib.js:', function() { | |
Lib.setTranslate(el, 5); | ||
expect(el.attr('transform')).toBe('translate(5, 0)'); | ||
|
||
Lib.setTranslate(el, 10, 20); | ||
expect(el.attr('transform')).toBe('translate(10, 20)'); | ||
|
||
Lib.setTranslate(el, 30, 40); | ||
expect(el.attr('transform')).toBe('translate(30, 40)'); | ||
|
||
|
@@ -890,6 +887,89 @@ describe('Test lib.js:', function() { | |
}); | ||
}); | ||
|
||
describe('getScale', function() { | ||
|
||
it('should work with regular DOM elements', function() { | ||
var el = document.createElement('div'); | ||
|
||
expect(Lib.getScale(el)).toEqual({ x: 1, y: 1 }); | ||
|
||
el.setAttribute('transform', 'scale(1.23, 45)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 1.23, y: 45 }); | ||
|
||
el.setAttribute('transform', 'scale(123.45)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 123.45, y: 1 }); | ||
|
||
el.setAttribute('transform', 'scale(0.1 2)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); | ||
|
||
el.setAttribute('transform', 'scale(0.1 2); rotate(20deg)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); | ||
|
||
el.setAttribute('transform', 'rotate(20deg) scale(0.1 2);'); | ||
expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); | ||
|
||
el.setAttribute('transform', 'rotate(20deg)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 1, y: 1 }); | ||
}); | ||
|
||
it('should work with d3 elements', function() { | ||
var el = d3.select(document.createElement('div')); | ||
|
||
el.attr('transform', 'scale(1.23, 45)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 1.23, y: 45 }); | ||
|
||
el.attr('transform', 'scale(123.45)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 123.45, y: 1 }); | ||
|
||
el.attr('transform', 'scale(0.1 2)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); | ||
|
||
el.attr('transform', 'scale(0.1 2); rotate(20)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); | ||
|
||
el.attr('transform', 'rotate(20)'); | ||
expect(Lib.getScale(el)).toEqual({ x: 1, y: 1 }); | ||
}); | ||
}); | ||
|
||
describe('setScale', function() { | ||
|
||
it('should work with regular DOM elements', function() { | ||
var el = document.createElement('div'); | ||
|
||
Lib.setScale(el, 5); | ||
expect(el.getAttribute('transform')).toBe('scale(5, 1)'); | ||
|
||
Lib.setScale(el, 30, 40); | ||
expect(el.getAttribute('transform')).toBe('scale(30, 40)'); | ||
|
||
Lib.setScale(el); | ||
expect(el.getAttribute('transform')).toBe('scale(1, 1)'); | ||
|
||
el.setAttribute('transform', 'scale(1, 1); rotate(30)'); | ||
Lib.setScale(el, 30, 40); | ||
expect(el.getAttribute('transform')).toBe('rotate(30) scale(30, 40)'); | ||
}); | ||
|
||
it('should work with d3 elements', function() { | ||
var el = d3.select(document.createElement('div')); | ||
|
||
Lib.setScale(el, 5); | ||
expect(el.attr('transform')).toBe('scale(5, 1)'); | ||
|
||
Lib.setScale(el, 30, 40); | ||
expect(el.attr('transform')).toBe('scale(30, 40)'); | ||
|
||
Lib.setScale(el); | ||
expect(el.attr('transform')).toBe('scale(1, 1)'); | ||
|
||
el.attr('transform', 'scale(0, 0); rotate(30)'); | ||
Lib.setScale(el, 30, 40); | ||
expect(el.attr('transform')).toBe('rotate(30) scale(30, 40)'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nicely done. |
||
}); | ||
}); | ||
|
||
describe('pushUnique', function() { | ||
|
||
beforeEach(function() { | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you elaborate on this change (disclaimer I'm not a regex expert)?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex is being used in
String.replace
which only substitutes the matched pattern - this just captures everything in the string and allows replacement with only the two sections (the number values) it needs. I forgot to add a test for the case(s) that it missed, but I'll add it now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!