From 79bb6efe499f5d9690980a57a68f70fd01d0bc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Mon, 23 May 2016 17:24:00 -0400 Subject: [PATCH 1/9] Lib: Add setScale function --- src/lib/index.js | 36 ++++++++++++++++++++++++++++ test/jasmine/tests/lib_test.js | 43 +++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 2d742a12e94..864dcca0d55 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -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]*)(.*)/, + getter = element.attr ? 'attr' : 'getAttribute', + transform = element[getter]('transform') || ''; + + var translate = transform.replace(re, function(match, p1, p2, p3, p4) { + return [p2, p4].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'; }; diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 91a632d9e47..06883a9113c 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -858,9 +858,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 +872,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 +884,43 @@ describe('Test lib.js:', function() { }); }); + 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)'); + }); + }); + describe('pushUnique', function() { beforeEach(function() { From 69ea5f1dc47bf1a1421f60e52af4152ccdb45d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Tue, 24 May 2016 15:49:47 -0400 Subject: [PATCH 2/9] Lib: Robustify getTranslate/getScale methods --- src/lib/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 864dcca0d55..6c135a8d188 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -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(' '); @@ -485,15 +485,17 @@ lib.setTranslate = function(element, x, y) { lib.getScale = function(element) { - var re = /(\bscale\()(\d*\.?\d*)([^\d]*)(\d*\.?\d*)([^\d]*)(.*)/, + var re = /.*\bscale\((\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(' '); + console.log(translate); + return { x: +translate[0] || 1, y: +translate[1] || 1 From 2e141a37907621302d4ca792eb2781a8ba886411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Mon, 23 May 2016 17:24:38 -0400 Subject: [PATCH 3/9] Zoom: Fix scrollZoom functionailty --- src/plots/cartesian/dragbox.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index cccf64d56ba..f0751767a87 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -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 = 50, mainplot = plotinfo.mainplot ? fullLayout._plots[plotinfo.mainplot] : plotinfo; @@ -608,19 +608,24 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { 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, + + var xScaleFactor = xa2._length / viewBox[2], + yScaleFactor = ya2._length / viewBox[3]; + + var clipDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, + clipDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0; + + var plotDx = xa2._offset - clipDx, plotDy = ya2._offset - clipDy; var clipId = 'clip' + fullLayout._uid + subplots[i] + 'plot'; fullLayout._defs.selectAll('#' + clipId) - .attr('transform', 'translate(' + clipDx + ', ' + clipDy + ')'); + .call(Lib.setTranslate, viewBox[0], viewBox[1]) + .call(Lib.setScale, 1 / xScaleFactor, 1 / yScaleFactor); subplot.plot - .attr('transform', 'translate(' + plotDx + ', ' + plotDy + ')'); + .call(Lib.setTranslate, plotDx, plotDy) + .call(Lib.setScale, xScaleFactor, yScaleFactor); } } } From e40c3266aa9500ea027c4fcc2f1fe02f6225a852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Tue, 24 May 2016 10:09:21 -0400 Subject: [PATCH 4/9] Zoom: Fix for when `fixedRange = true` --- src/plots/cartesian/dragbox.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index f0751767a87..bf80a0b4bc8 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -601,32 +601,35 @@ 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]], + clipId = 'clip' + fullLayout._uid + subplots[i] + 'plot', 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) { - var xScaleFactor = xa2._length / viewBox[2], - yScaleFactor = ya2._length / viewBox[3]; + var xScaleFactor = editX ? xa2._length / viewBox[2] : 1, + yScaleFactor = editY ? ya2._length / viewBox[3] : 1; - var clipDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, - clipDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0; + var clipDx = editX ? viewBox[0] : 0, + clipDy = editY ? viewBox[1] : 0; - var plotDx = xa2._offset - clipDx, - plotDy = ya2._offset - clipDy; + var fracDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, + fracDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0; - var clipId = 'clip' + fullLayout._uid + subplots[i] + 'plot'; + var plotDx = xa2._offset - fracDx, + plotDy = ya2._offset - fracDy; - fullLayout._defs.selectAll('#' + clipId) - .call(Lib.setTranslate, viewBox[0], viewBox[1]) - .call(Lib.setScale, 1 / xScaleFactor, 1 / yScaleFactor); - subplot.plot - .call(Lib.setTranslate, plotDx, plotDy) - .call(Lib.setScale, xScaleFactor, yScaleFactor); - } + + fullLayout._defs.selectAll('#' + clipId) + .call(Lib.setTranslate, clipDx, clipDy) + .call(Lib.setScale, 1 / xScaleFactor, 1 / yScaleFactor); + + subplot.plot + .call(Lib.setTranslate, plotDx, plotDy) + .call(Lib.setScale, xScaleFactor, yScaleFactor); } } From 6f06385c657973a8ec318d7ceb5833344deee295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Tue, 24 May 2016 13:22:50 -0400 Subject: [PATCH 5/9] Zoom: Add test while scrolling --- test/jasmine/assets/mouse_event.js | 11 ++++++++-- test/jasmine/tests/click_test.js | 33 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/test/jasmine/assets/mouse_event.js b/test/jasmine/assets/mouse_event.js index 9800215bbfa..39101d76d35 100644 --- a/test/jasmine/assets/mouse_event.js +++ b/test/jasmine/assets/mouse_event.js @@ -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); + } else { + ev = new window.MouseEvent(type, fullOpts); + } + el.dispatchEvent(ev); }; diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index 4e0d5795d8f..062d9dd5bea 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -711,6 +711,37 @@ 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'); + + console.log(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]); + }); + }); + describe('pan interactions', function() { beforeEach(function(done) { mockCopy.layout.dragmode = 'pan'; @@ -745,7 +776,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); }); From 85fd4a84b90223369ef7beba065bac2e8d03f962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Wed, 25 May 2016 12:14:42 -0400 Subject: [PATCH 6/9] Fixup: Remove debug logging --- src/lib/index.js | 2 -- test/jasmine/tests/click_test.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 6c135a8d188..13a053004c4 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -494,8 +494,6 @@ lib.getScale = function(element) { }) .split(' '); - console.log(translate); - return { x: +translate[0] || 1, y: +translate[1] || 1 diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index 062d9dd5bea..c9f70b4dcd9 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -726,8 +726,6 @@ describe('Test click interactions:', function() { var transform = plot.attr('transform'); - console.log(transform); - var mockEl = { attr: function() { return transform; From b2eaa8dac8de30af957536438f352c7aaadc9456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Wed, 25 May 2016 12:16:09 -0400 Subject: [PATCH 7/9] Zoom: Move `REDRAWDELAY` to constants file --- src/plots/cartesian/constants.js | 5 ++++- src/plots/cartesian/dragbox.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index 995bc8fc665..81467cee968 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -69,5 +69,8 @@ module.exports = { HOVERMINTIME: 50, // max pixels off straight before a lasso select line counts as bent - BENDPX: 1.5 + BENDPX: 1.5, + + // delay before a redraw (relayout) after smooth panning and zooming + REDRAWDELAY: 50 }; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index bf80a0b4bc8..c6c6d85b7d2 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -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 = 50, + REDRAWDELAY = constants.REDRAWDELAY, mainplot = plotinfo.mainplot ? fullLayout._plots[plotinfo.mainplot] : plotinfo; From 336c12fa490c08f73a8302e521a0dd4e424bda13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Wed, 25 May 2016 12:17:25 -0400 Subject: [PATCH 8/9] Lib: Add tests for `getScale` --- test/jasmine/tests/lib_test.js | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 06883a9113c..b736d31a73e 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -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 }); }); @@ -884,6 +887,52 @@ 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() { From cf628496ce58f7002882001d71c88cd395f90445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tusz?= Date: Wed, 25 May 2016 12:17:57 -0400 Subject: [PATCH 9/9] Zoom: Add clipId property to subplot objects --- src/plot_api/plot_api.js | 10 +++++----- src/plots/cartesian/dragbox.js | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index d6ed0a60d43..f567d260680 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2913,16 +2913,16 @@ function lsInner(gd) { // Clip so that data only shows up on the plot area. - var clips = fullLayout._defs.selectAll('g.clips'), - clipId = 'clip' + fullLayout._uid + subplot + 'plot'; + plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot'; - var plotClip = clips.selectAll('#' + clipId) + var plotClip = fullLayout._defs.selectAll('g.clips') + .selectAll('#' + plotinfo.clipId) .data([0]); plotClip.enter().append('clipPath') .attr({ 'class': 'plotclip', - 'id': clipId + 'id': plotinfo.clipId }) .append('rect'); @@ -2934,7 +2934,7 @@ function lsInner(gd) { plotinfo.plot.call(Lib.setTranslate, xa._offset, ya._offset); - plotinfo.plot.call(Drawing.setClipUrl, clipId); + plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId); var xlw = Drawing.crispRound(gd, xa.linewidth, 1), ylw = Drawing.crispRound(gd, ya.linewidth, 1), diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index c6c6d85b7d2..b9f94001823 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -603,7 +603,6 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { for(var i = 0; i < subplots.length; i++) { var subplot = plotinfos[subplots[i]], - clipId = 'clip' + fullLayout._uid + subplots[i] + 'plot', xa2 = subplot.x(), ya2 = subplot.y(), editX = ew && xa.indexOf(xa2) !== -1 && !xa2.fixedrange, @@ -623,7 +622,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { plotDy = ya2._offset - fracDy; - fullLayout._defs.selectAll('#' + clipId) + fullLayout._defs.selectAll('#' + subplot.clipId) .call(Lib.setTranslate, clipDx, clipDy) .call(Lib.setScale, 1 / xScaleFactor, 1 / yScaleFactor);