From 02ed2eb3ae352c223aa066d8f0b62e183dbb2235 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Apr 2018 14:25:42 -0400 Subject: [PATCH 01/11] move doAutoRangeAndConstraints, drawData and finalDraw to subroutines ... (i.e. out of plot_api.js), and move Plots.addLinks out of drawData into its own step in Plotly.plot. --- src/plot_api/plot_api.js | 97 ++----------------------------------- src/plot_api/subroutines.js | 92 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 93 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 89f2a7b7d2d..5747a2e9acc 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -29,7 +29,6 @@ var Drawing = require('../components/drawing'); var Color = require('../components/color'); var xmlnsNamespaces = require('../constants/xmlns_namespaces'); var svgTextUtils = require('../lib/svg_text_utils'); -var clearGlCanvases = require('../lib/clear_gl_canvases'); var defaultConfig = require('./plot_config'); var manageArrays = require('./manage_arrays'); @@ -38,10 +37,6 @@ var subroutines = require('./subroutines'); var editTypes = require('./edit_types'); var cartesianConstants = require('../plots/cartesian/constants'); -var axisConstraints = require('../plots/cartesian/constraints'); -var enforceAxisConstraints = axisConstraints.enforce; -var cleanAxisConstraints = axisConstraints.clean; -var doAutoRange = require('../plots/cartesian/autorange').doAutoRange; var numericNameWarningCount = 0; var numericNameWarningCountLimit = 5; @@ -331,15 +326,7 @@ exports.plot = function(gd, data, layout, config) { function doAutoRangeAndConstraints() { if(gd._transitioning) return; - var axList = Axes.list(gd, '', true); - for(var i = 0; i < axList.length; i++) { - var ax = axList[i]; - cleanAxisConstraints(gd, ax); - - doAutoRange(ax); - } - - enforceAxisConstraints(gd); + subroutines.doAutoRangeAndConstraints(gd); // store initial ranges *after* enforcing constraints, otherwise // we will never look like we're at the initial ranges @@ -351,83 +338,6 @@ exports.plot = function(gd, data, layout, config) { return Axes.doTicks(gd, graphWasEmpty ? '' : 'redraw'); } - // Now plot the data - function drawData() { - var calcdata = gd.calcdata, - i, - rangesliderContainers = fullLayout._infolayer.selectAll('g.rangeslider-container'); - - // in case of traces that were heatmaps or contour maps - // previously, remove them and their colorbars explicitly - for(i = 0; i < calcdata.length; i++) { - var trace = calcdata[i][0].trace, - isVisible = (trace.visible === true), - uid = trace.uid; - - if(!isVisible || !Registry.traceIs(trace, '2dMap')) { - var query = ( - '.hm' + uid + - ',.contour' + uid + - ',#clip' + uid - ); - - fullLayout._paper - .selectAll(query) - .remove(); - - rangesliderContainers - .selectAll(query) - .remove(); - } - - if(!isVisible || !trace._module.colorbar) { - fullLayout._infolayer.selectAll('.cb' + uid).remove(); - } - } - - // TODO does this break or slow down parcoords?? - clearGlCanvases(gd); - - // loop over the base plot modules present on graph - var basePlotModules = fullLayout._basePlotModules; - for(i = 0; i < basePlotModules.length; i++) { - basePlotModules[i].plot(gd); - } - - // keep reference to shape layers in subplots - var layerSubplot = fullLayout._paper.selectAll('.layer-subplot'); - fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer'); - - // styling separate from drawing - Plots.style(gd); - - // show annotations and shapes - Registry.getComponentMethod('shapes', 'draw')(gd); - Registry.getComponentMethod('annotations', 'draw')(gd); - - // source links - Plots.addLinks(gd); - - // Mark the first render as complete - fullLayout._replotting = false; - - return Plots.previousPromises(gd); - } - - // An initial paint must be completed before these components can be - // correctly sized and the whole plot re-margined. fullLayout._replotting must - // be set to false before these will work properly. - function finalDraw() { - Registry.getComponentMethod('shapes', 'draw')(gd); - Registry.getComponentMethod('images', 'draw')(gd); - Registry.getComponentMethod('annotations', 'draw')(gd); - Registry.getComponentMethod('legend', 'draw')(gd); - Registry.getComponentMethod('rangeslider', 'draw')(gd); - Registry.getComponentMethod('rangeselector', 'draw')(gd); - Registry.getComponentMethod('sliders', 'draw')(gd); - Registry.getComponentMethod('updatemenus', 'draw')(gd); - } - var seq = [ Plots.previousPromises, addFrames, @@ -439,9 +349,10 @@ exports.plot = function(gd, data, layout, config) { seq.push(subroutines.layoutStyles); if(hasCartesian) seq.push(drawAxes); seq.push( - drawData, - finalDraw, + subroutines.drawData, + subroutines.finalDraw, initInteractions, + Plots.addLinks, Plots.rehover, Plots.previousPromises ); diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index fa74da9e967..2e043ca4fbe 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -11,7 +11,9 @@ var d3 = require('d3'); var Registry = require('../registry'); var Plots = require('../plots/plots'); + var Lib = require('../lib'); +var clearGlCanvases = require('../lib/clear_gl_canvases'); var Color = require('../components/color'); var Drawing = require('../components/drawing'); @@ -21,6 +23,10 @@ var Axes = require('../plots/cartesian/axes'); var initInteractions = require('../plots/cartesian/graph_interact'); var cartesianConstants = require('../plots/cartesian/constants'); var alignmentConstants = require('../constants/alignment'); +var axisConstraints = require('../plots/cartesian/constraints'); +var enforceAxisConstraints = axisConstraints.enforce; +var cleanAxisConstraints = axisConstraints.clean; +var doAutoRange = require('../plots/cartesian/autorange').doAutoRange; exports.layoutStyles = function(gd) { return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd); @@ -480,3 +486,89 @@ exports.doCamera = function(gd) { scene.setCamera(sceneLayout.camera); } }; + +exports.drawData = function(gd) { + var fullLayout = gd._fullLayout; + var calcdata = gd.calcdata; + var rangesliderContainers = fullLayout._infolayer.selectAll('g.rangeslider-container'); + var i; + + // in case of traces that were heatmaps or contour maps + // previously, remove them and their colorbars explicitly + for(i = 0; i < calcdata.length; i++) { + var trace = calcdata[i][0].trace; + var isVisible = (trace.visible === true); + var uid = trace.uid; + + if(!isVisible || !Registry.traceIs(trace, '2dMap')) { + var query = ( + '.hm' + uid + + ',.contour' + uid + + ',#clip' + uid + ); + + fullLayout._paper + .selectAll(query) + .remove(); + + rangesliderContainers + .selectAll(query) + .remove(); + } + + if(!isVisible || !trace._module.colorbar) { + fullLayout._infolayer.selectAll('.cb' + uid).remove(); + } + } + + // TODO does this break or slow down parcoords?? + clearGlCanvases(gd); + + // loop over the base plot modules present on graph + var basePlotModules = fullLayout._basePlotModules; + for(i = 0; i < basePlotModules.length; i++) { + basePlotModules[i].plot(gd); + } + + // keep reference to shape layers in subplots + var layerSubplot = fullLayout._paper.selectAll('.layer-subplot'); + fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer'); + + // styling separate from drawing + Plots.style(gd); + + // show annotations and shapes + Registry.getComponentMethod('shapes', 'draw')(gd); + Registry.getComponentMethod('annotations', 'draw')(gd); + + // Mark the first render as complete + fullLayout._replotting = false; + + return Plots.previousPromises(gd); +}; + +exports.doAutoRangeAndConstraints = function(gd) { + var axList = Axes.list(gd, '', true); + + for(var i = 0; i < axList.length; i++) { + var ax = axList[i]; + cleanAxisConstraints(gd, ax); + doAutoRange(ax); + } + + enforceAxisConstraints(gd); +}; + +// An initial paint must be completed before these components can be +// correctly sized and the whole plot re-margined. fullLayout._replotting must +// be set to false before these will work properly. +exports.finalDraw = function(gd) { + Registry.getComponentMethod('shapes', 'draw')(gd); + Registry.getComponentMethod('images', 'draw')(gd); + Registry.getComponentMethod('annotations', 'draw')(gd); + Registry.getComponentMethod('legend', 'draw')(gd); + Registry.getComponentMethod('rangeslider', 'draw')(gd); + Registry.getComponentMethod('rangeselector', 'draw')(gd); + Registry.getComponentMethod('sliders', 'draw')(gd); + Registry.getComponentMethod('updatemenus', 'draw')(gd); +}; From 3ad1eaad7073011b769fdfb477a1fed597444252 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Apr 2018 15:41:18 -0400 Subject: [PATCH 02/11] add 'axrange' editType - this represents the minimal sequence for '(x|y)axis.range' relayout calls which are pretty common (e.g. on zoom/pan mouseup). - by bypassing drawFramework, lsInner and initInteraction, this can save ~1000ms on 50x50 subplot grids. --- src/plot_api/edit_types.js | 3 +- src/plot_api/plot_api.js | 36 +++++++++++++++++++ src/plots/cartesian/axes.js | 2 +- src/plots/cartesian/layout_attributes.js | 6 ++-- test/jasmine/tests/plot_api_test.js | 45 +++++++++++++++++++++++- 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/plot_api/edit_types.js b/src/plot_api/edit_types.js index 24701a9a443..ea6defdc2c5 100644 --- a/src/plot_api/edit_types.js +++ b/src/plot_api/edit_types.js @@ -35,7 +35,7 @@ var layoutOpts = { valType: 'flaglist', extras: ['none'], flags: [ - 'calc', 'calcIfAutorange', 'plot', 'legend', 'ticks', 'margins', + 'calc', 'calcIfAutorange', 'plot', 'legend', 'ticks', 'axrange', 'margins', 'layoutstyle', 'modebar', 'camera', 'arraydraw' ], description: [ @@ -48,6 +48,7 @@ var layoutOpts = { '*legend* only redraws the legend.', '*ticks* only redraws axis ticks, labels, and gridlines.', '*margins* recomputes ticklabel automargins.', + '*axrange* minimal sequence when updating axis ranges.', '*layoutstyle* reapplies global and SVG cartesian axis styles.', '*modebar* just updates the modebar.', '*camera* just updates the camera settings for gl3d scenes.', diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 5747a2e9acc..494bf32df45 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1683,6 +1683,26 @@ exports.relayout = function relayout(gd, astr, val) { if(flags.legend) seq.push(subroutines.doLegend); if(flags.layoutstyle) seq.push(subroutines.layoutStyles); + + if(flags.axrange) { + // N.B. leave as sequence of subroutines (for now) instead of + // subroutine of its own so that finalDraw always gets + // executed after drawData + seq.push( + // TODO + // no test fail when commenting out doAutoRangeAndConstraints, + // but I think we do need this (maybe just the enforce part?) + // Am I right? + subroutines.doAutoRangeAndConstraints, + // TODO + // can target specific axes, + // do not have to redraw all axes here + subroutines.doTicksRelayout, + subroutines.drawData, + subroutines.finalDraw + ); + } + if(flags.ticks) seq.push(subroutines.doTicksRelayout); if(flags.modebar) seq.push(subroutines.doModeBar); if(flags.camera) seq.push(subroutines.doCamera); @@ -2147,6 +2167,14 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) { if(restyleFlags.colorbars) seq.push(subroutines.doColorBars); if(relayoutFlags.legend) seq.push(subroutines.doLegend); if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles); + if(relayoutFlags.axrange) { + seq.push( + subroutines.doAutoRangeAndConstraints, + subroutines.doTicksRelayout, + subroutines.drawData, + subroutines.finalDraw + ); + } if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout); if(relayoutFlags.modebar) seq.push(subroutines.doModeBar); if(relayoutFlags.camera) seq.push(subroutines.doCamera); @@ -2299,6 +2327,14 @@ exports.react = function(gd, data, layout, config) { if(restyleFlags.colorbars) seq.push(subroutines.doColorBars); if(relayoutFlags.legend) seq.push(subroutines.doLegend); if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles); + if(relayoutFlags.axrange) { + seq.push( + subroutines.doAutoRangeAndConstraints, + subroutines.doTicksRelayout, + subroutines.drawData, + subroutines.finalDraw + ); + } if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout); if(relayoutFlags.modebar) seq.push(subroutines.doModeBar); if(relayoutFlags.camera) seq.push(subroutines.doCamera); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 29414f278dd..c44dd3db8f9 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -2188,7 +2188,7 @@ axes.doTicks = function(gd, axid, skipTitle) { } drawTicks(mainPlotinfo[axLetter + 'axislayer'], tickpath); - tickSubplots = Object.keys(ax._linepositions); + tickSubplots = Object.keys(ax._linepositions || {}); } tickSubplots.map(function(subplot) { diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 8334ab5d0ec..25bfda44393 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -100,10 +100,10 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'any', editType: 'plot+margins', impliedEdits: {'^autorange': false}}, - {valType: 'any', editType: 'plot+margins', impliedEdits: {'^autorange': false}} + {valType: 'any', editType: 'axrange+margins', impliedEdits: {'^autorange': false}}, + {valType: 'any', editType: 'axrange+margins', impliedEdits: {'^autorange': false}} ], - editType: 'plot+margins', + editType: 'axrange+margins', impliedEdits: {'autorange': false}, description: [ 'Sets the range of this axis.', diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 1924111d1a0..6f38e70db41 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -504,7 +504,10 @@ describe('Test plot api', function() { 'layoutStyles', 'doTicksRelayout', 'doModeBar', - 'doCamera' + 'doCamera', + 'doAutoRangeAndConstraints', + 'drawData', + 'finalDraw' ]; var gd; @@ -613,6 +616,46 @@ describe('Test plot api', function() { expectReplot(attr); } }); + + it('should trigger minimal sequence for cartesian axis range updates', function() { + var seq = ['doAutoRangeAndConstraints', 'doTicksRelayout', 'drawData', 'finalDraw']; + + function _assert(msg) { + expect(gd.calcdata).toBeDefined(); + mockedMethods.forEach(function(m) { + expect(subroutines[m].calls.count()).toBe( + seq.indexOf(m) === -1 ? 0 : 1, + '# of ' + m + ' calls - ' + msg + ); + }); + } + + var trace = {y: [1, 2, 1]}; + + var specs = [ + ['relayout', ['xaxis.range[0]', 0]], + ['relayout', ['xaxis.range[1]', 3]], + ['relayout', ['xaxis.range', [-1, 5]]], + ['update', [{}, {'xaxis.range': [-1, 10]}]], + ['react', [[trace], {xaxis: {range: [0, 1]}}]] + ]; + + specs.forEach(function(s) { + // create 'real' div for Plotly.react to work + gd = createGraphDiv(); + Plotly.plot(gd, [trace], {xaxis: {range: [1, 2]}}); + mock(gd); + + Plotly[s[0]](gd, s[1][0], s[1][1]); + + _assert([ + 'Plotly.', s[0], + '(gd, ', JSON.stringify(s[1][0]), ', ', JSON.stringify(s[1][1]), ')' + ].join('')); + + destroyGraphDiv(); + }); + }); }); describe('Plotly.restyle subroutines switchboard', function() { From bb022818e7fe6c2c808ad440a633558398853325 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Apr 2018 15:48:48 -0400 Subject: [PATCH 03/11] speed up doModeBar subroutines for cartesian subplots - split minimal updateFx part out of initInteractions - set maindrag cursor class (which depends only on layout.dragmode) on instead of inner to update it for all subplots in < 1ms (that's a > 600ms improvement on 50x50 grids) - use gd._fullLayout instead of scoped fullLayout in initInteractions and makeDragBox to ensure correct reference after doModeBar() --- src/plot_api/plot_api.js | 2 +- src/plot_api/subroutines.js | 3 +-- src/plots/cartesian/dragbox.js | 21 ++++++++++++--------- src/plots/cartesian/graph_interact.js | 26 ++++++++++++++++++-------- src/plots/cartesian/index.js | 2 ++ test/jasmine/tests/fx_test.js | 7 +++---- 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 494bf32df45..a2906090d84 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -22,11 +22,11 @@ var Registry = require('../registry'); var PlotSchema = require('./plot_schema'); var Plots = require('../plots/plots'); var Polar = require('../plots/polar/legacy'); -var initInteractions = require('../plots/cartesian/graph_interact'); var Axes = require('../plots/cartesian/axes'); var Drawing = require('../components/drawing'); var Color = require('../components/color'); +var initInteractions = require('../plots/cartesian/graph_interact').initInteractions; var xmlnsNamespaces = require('../constants/xmlns_namespaces'); var svgTextUtils = require('../lib/svg_text_utils'); diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 2e043ca4fbe..135dacae301 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -19,8 +19,8 @@ var Color = require('../components/color'); var Drawing = require('../components/drawing'); var Titles = require('../components/titles'); var ModeBar = require('../components/modebar'); + var Axes = require('../plots/cartesian/axes'); -var initInteractions = require('../plots/cartesian/graph_interact'); var cartesianConstants = require('../plots/cartesian/constants'); var alignmentConstants = require('../constants/alignment'); var axisConstraints = require('../plots/cartesian/constraints'); @@ -465,7 +465,6 @@ exports.doModeBar = function(gd) { var fullLayout = gd._fullLayout; ModeBar.manage(gd); - initInteractions(gd); for(var i = 0; i < fullLayout._basePlotModules.length; i++) { var updateFx = fullLayout._basePlotModules[i].updateFx; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index f89468b128a..48412502c5c 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -55,7 +55,6 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // within DBLCLICKDELAY so we can check for click or doubleclick events // dragged stores whether a drag has occurred, so we don't have to // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px - var fullLayout = gd._fullLayout; var zoomlayer = gd._fullLayout._zoomlayer; var isMainDrag = (ns + ew === 'nsew'); var singleEnd = (ns + ew).length === 1; @@ -111,6 +110,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { recomputeAxisLists(); + var cursor = getDragCursor(yActive + xActive, gd._fullLayout.dragmode, isMainDrag); var dragger = makeRectDragger(plotinfo, ns + ew + 'drag', cursor, x, y, w, h); var allFixedRanges = !yActive && !xActive; @@ -131,6 +131,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { prepFn: function(e, startX, startY) { var dragModeNow = gd._fullLayout.dragmode; + recomputeAxisLists(); + if(!allFixedRanges) { if(isMainDrag) { // main dragger handles all drag modes, and changes @@ -204,7 +206,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { .call(svgTextUtils.makeEditable, { gd: gd, immediate: true, - background: fullLayout.paper_bgcolor, + background: gd._fullLayout.paper_bgcolor, text: String(initialText), fill: ax.tickfont ? ax.tickfont.color : '#444', horizontalAlign: hAlign, @@ -354,7 +356,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // deactivate mousewheel scrolling on embedded graphs // devs can override this with layout._enablescrollzoom, // but _ ensures this setting won't leave their page - if(!gd._context.scrollZoom && !fullLayout._enablescrollzoom) { + if(!gd._context.scrollZoom && !gd._fullLayout._enablescrollzoom) { return; } @@ -456,8 +458,6 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { return; } - recomputeAxisLists(); - if(xActive === 'ew' || yActive === 'ns') { if(xActive) dragAxList(xa, dx); if(yActive) dragAxList(ya, dy); @@ -584,9 +584,9 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // annotations and shapes 'draw' method is slow, // use the finer-grained 'drawOne' method instead - redrawObjs(fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne')); - redrawObjs(fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne')); - redrawObjs(fullLayout.images || [], Registry.getComponentMethod('images', 'draw'), true); + redrawObjs(gd._fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne')); + redrawObjs(gd._fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne')); + redrawObjs(gd._fullLayout.images || [], Registry.getComponentMethod('images', 'draw'), true); } function doubleClick() { @@ -892,9 +892,12 @@ function dZoom(d) { 1 / (1 / Math.max(d, -0.3) + 3.222)); } -function getDragCursor(nsew, dragmode) { +function getDragCursor(nsew, dragmode, isMainDrag) { if(!nsew) return 'pointer'; if(nsew === 'nsew') { + // in this case here, clear cursor and + // use the cursor style set on + if(isMainDrag) return ''; if(dragmode === 'pan') return 'move'; return 'crosshair'; } diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 7508b288ae7..f872ddef16e 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -13,11 +13,12 @@ var d3 = require('d3'); var Fx = require('../../components/fx'); var dragElement = require('../../components/dragelement'); +var setCursor = require('../../lib/setcursor'); -var constants = require('./constants'); var makeDragBox = require('./dragbox').makeDragBox; +var DRAGGERSIZE = require('./constants').DRAGGERSIZE; -module.exports = function initInteractions(gd) { +exports.initInteractions = function initInteractions(gd) { var fullLayout = gd._fullLayout; if(gd._context.staticPlot) { @@ -43,12 +44,9 @@ module.exports = function initInteractions(gd) { subplots.forEach(function(subplot) { var plotinfo = fullLayout._plots[subplot]; - var xa = plotinfo.xaxis; var ya = plotinfo.yaxis; - var DRAGGERSIZE = constants.DRAGGERSIZE; - // main and corner draggers need not be repeated for // overlaid subplots - these draggers drag them all if(!plotinfo.mainplot) { @@ -139,17 +137,29 @@ module.exports = function initInteractions(gd) { var hoverLayer = fullLayout._hoverlayer.node(); hoverLayer.onmousemove = function(evt) { - evt.target = fullLayout._lasthover; + evt.target = gd._fullLayout._lasthover; Fx.hover(gd, evt, fullLayout._hoversubplot); }; hoverLayer.onclick = function(evt) { - evt.target = fullLayout._lasthover; + evt.target = gd._fullLayout._lasthover; Fx.click(gd, evt); }; // also delegate mousedowns... TODO: does this actually work? hoverLayer.onmousedown = function(evt) { - fullLayout._lasthover.onmousedown(evt); + gd._fullLayout._lasthover.onmousedown(evt); }; + + exports.updateFx(fullLayout); +}; + +// Minimal set of update needed on 'modebar' edits. +// We only need to update the cursor style. +// +// Note that changing the axis configuration and/or the fixedrange attribute +// should trigger a full initInteractions. +exports.updateFx = function(fullLayout) { + var cursor = fullLayout.dragmode === 'pan' ? 'move' : 'crosshair'; + setCursor(fullLayout._draggers, cursor); }; diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 16fed7c3f88..eca498b809b 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -548,3 +548,5 @@ exports.toSVG = function(gd) { canvases.each(canvasToImage); }; + +exports.updateFx = require('./graph_interact').updateFx; diff --git a/test/jasmine/tests/fx_test.js b/test/jasmine/tests/fx_test.js index 293d482e63d..50b2172c9e9 100644 --- a/test/jasmine/tests/fx_test.js +++ b/test/jasmine/tests/fx_test.js @@ -201,13 +201,12 @@ describe('relayout', function() { afterEach(destroyGraphDiv); it('should update main drag with correct', function(done) { - function assertMainDrag(cursor, isActive) { expect(d3.selectAll('rect.nsewdrag').size()).toEqual(1, 'number of nodes'); - var mainDrag = d3.select('rect.nsewdrag'), - node = mainDrag.node(); + var mainDrag = d3.select('rect.nsewdrag'); + var node = mainDrag.node(); - expect(mainDrag.classed('cursor-' + cursor)).toBe(true, 'cursor ' + cursor); + expect(window.getComputedStyle(node).cursor).toBe(cursor, 'cursor ' + cursor); expect(node.style.pointerEvents).toEqual('all', 'pointer event'); expect(!!node.onmousedown).toBe(isActive, 'mousedown handler'); } From d07ae70e8ca09103a14fade802b4e1b8c1975754 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Apr 2018 15:51:24 -0400 Subject: [PATCH 04/11] improve fullLayout._has() - allow fullLayout._has(/*trace type*/) to work - use registry category hash object (instead of categories.indexOf) to find categories in fullLayout._modules --- src/plots/plots.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 3e193ed6d98..1a0bd47886a 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -627,24 +627,22 @@ plots.createTransitionData = function(gd) { // whether a certain plot type is present on plot // or trace has a category plots._hasPlotType = function(category) { - // check plot - var basePlotModules = this._basePlotModules || []; var i; + // check base plot modules + var basePlotModules = this._basePlotModules || []; for(i = 0; i < basePlotModules.length; i++) { - var _module = basePlotModules[i]; - - if(_module.name === category) return true; + if(basePlotModules[i].name === category) return true; } - // check trace + // check trace modules var modules = this._modules || []; - for(i = 0; i < modules.length; i++) { - var modulei = modules[i]; - if(modulei.categories && modulei.categories.indexOf(category) >= 0) { - return true; - } + var name = modules[i].name; + if(name === category) return true; + // N.B. this is modules[i] along with 'categories' as a hash object + var _module = Registry.modules[name]; + if(_module && _module.categories[category]) return true; } return false; From 468119eb9d2d4a82c3a0a5489d4521e208d2b206 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Apr 2018 15:59:28 -0400 Subject: [PATCH 05/11] speed up dragbox - replace indexOf with hash objects lookups - add 'svg' and 'draggedPts' trace module categories - speed up updateSubplots (called on pan and scroll) by splitting it into splom, scattergl, svg and draggedPts blocks, (draggedPts is very slow, svg can be slow at 50x50) - ... some scope variable clean up --- src/plot_api/subroutines.js | 2 +- src/plots/cartesian/dragbox.js | 438 ++++++++++++++----------- src/plots/cartesian/transition_axes.js | 2 +- src/traces/bar/index.js | 2 +- src/traces/box/index.js | 2 +- src/traces/candlestick/index.js | 2 +- src/traces/carpet/index.js | 2 +- src/traces/contour/index.js | 2 +- src/traces/contourcarpet/index.js | 2 +- src/traces/heatmap/index.js | 2 +- src/traces/histogram/index.js | 2 +- src/traces/histogram2d/index.js | 2 +- src/traces/histogram2dcontour/index.js | 2 +- src/traces/ohlc/index.js | 2 +- src/traces/scatter/index.js | 2 +- src/traces/scattercarpet/index.js | 2 +- src/traces/violin/index.js | 2 +- 17 files changed, 270 insertions(+), 200 deletions(-) diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 135dacae301..57fad95e180 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -179,7 +179,7 @@ exports.lsInner = function(gd) { .append('rect'); }); - plotClip.select('rect').attr({ + plotinfo.clipRect = plotClip.select('rect').attr({ width: xa._length, height: ya._length }); diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 48412502c5c..15f13e11426 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -59,53 +59,63 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var isMainDrag = (ns + ew === 'nsew'); var singleEnd = (ns + ew).length === 1; - var subplots, xa, ya, xs, ys, pw, ph, xActive, yActive, cursor, - isSubplotConstrained, xaLinked, yaLinked; + // main subplot x and y (i.e. found in plotinfo - the main ones) + var xa0, ya0; + // {ax._id: ax} hash objects + var xaHash, yaHash; + // xaHash/yaHash values (arrays) + var xaxes, yaxes; + // main axis offsets + var xs, ys; + // main axis lengths + var pw, ph; + // contains keys 'xaHash', 'yaHash', 'xaxes', and 'yaxes' + // which are the x/y {ax._id: ax} hash objects and their values + // for linked axis relative to this subplot + var links; + // set to ew/ns val when active, set to '' when inactive + var xActive, yActive; + // are all axes in this subplot are fixed? + var allFixedRanges; + // is subplot constrained? + var isSubplotConstrained; + // do we need to edit x/y ranges? + var editX, editY; function recomputeAxisLists() { - xa = [plotinfo.xaxis]; - ya = [plotinfo.yaxis]; - var xa0 = xa[0]; - var ya0 = ya[0]; + xa0 = plotinfo.xaxis; + ya0 = plotinfo.yaxis; pw = xa0._length; ph = ya0._length; + xs = xa0._offset; + ys = ya0._offset; - var constraintGroups = fullLayout._axisConstraintGroups; - var xIDs = [xa0._id]; - var yIDs = [ya0._id]; + xaHash = {}; + xaHash[xa0._id] = xa0; + yaHash = {}; + yaHash[ya0._id] = ya0; // if we're dragging two axes at once, also drag overlays - subplots = [plotinfo].concat((ns && ew) ? plotinfo.overlays : []); - - for(var i = 1; i < subplots.length; i++) { - var subplotXa = subplots[i].xaxis, - subplotYa = subplots[i].yaxis; - - if(xa.indexOf(subplotXa) === -1) { - xa.push(subplotXa); - xIDs.push(subplotXa._id); - } - - if(ya.indexOf(subplotYa) === -1) { - ya.push(subplotYa); - yIDs.push(subplotYa._id); + if(ns && ew) { + var overlays = plotinfo.overlays; + for(var i = 0; i < overlays.length; i++) { + var xa = overlays[i].xaxis; + xaHash[xa._id] = xa; + var ya = overlays[i].yaxis; + yaHash[ya._id] = ya; } } - xActive = isDirectionActive(xa, ew); - yActive = isDirectionActive(ya, ns); - cursor = getDragCursor(yActive + xActive, fullLayout.dragmode); - xs = xa0._offset; - ys = ya0._offset; - - var links = calcLinks(constraintGroups, xIDs, yIDs); - isSubplotConstrained = links.xy; + xaxes = hashValues(xaHash); + yaxes = hashValues(yaHash); + xActive = isDirectionActive(xaxes, ew); + yActive = isDirectionActive(yaxes, ns); + allFixedRanges = !yActive && !xActive; - // finally make the list of axis objects to link - xaLinked = []; - for(var xLinkID in links.x) { xaLinked.push(getFromId(gd, xLinkID)); } - yaLinked = []; - for(var yLinkID in links.y) { yaLinked.push(getFromId(gd, yLinkID)); } + links = calcLinks(gd, xaHash, yaHash); + isSubplotConstrained = links.isSubplotConstrained; + editX = ew || isSubplotConstrained; + editY = ns || isSubplotConstrained; } recomputeAxisLists(); @@ -113,8 +123,6 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { var cursor = getDragCursor(yActive + xActive, gd._fullLayout.dragmode, isMainDrag); var dragger = makeRectDragger(plotinfo, ns + ew + 'drag', cursor, x, y, w, h); - var allFixedRanges = !yActive && !xActive; - // still need to make the element if the axes are disabled // but nuke its events (except for maindrag which needs them for hover) // and stop there @@ -153,8 +161,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { else dragOptions.minDrag = undefined; if(isSelectOrLasso(dragModeNow)) { - dragOptions.xaxes = xa; - dragOptions.yaxes = ya; + dragOptions.xaxes = xaxes; + dragOptions.yaxes = yaxes; prepSelect(e, startX, startY, dragOptions, dragModeNow); } else if(allFixedRanges) { @@ -186,7 +194,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { Fx.click(gd, evt, plotinfo.id); } else if(numClicks === 1 && singleEnd) { - var ax = ns ? ya[0] : xa[0], + var ax = ns ? ya0 : xa0, end = (ns === 's' || ew === 'w') ? 0 : 1, attrStr = ax._name + '.range[' + end + ']', initialText = getEndText(ax, end), @@ -336,8 +344,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { } // TODO: edit linked axes in zoomAxRanges and in dragTail - if(zoomMode === 'xy' || zoomMode === 'x') zoomAxRanges(xa, box.l / pw, box.r / pw, updates, xaLinked); - if(zoomMode === 'xy' || zoomMode === 'y') zoomAxRanges(ya, (ph - box.b) / ph, (ph - box.t) / ph, updates, yaLinked); + if(zoomMode === 'xy' || zoomMode === 'x') { + zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes); + } + if(zoomMode === 'xy' || zoomMode === 'y') { + zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes); + } removeZoombox(gd); dragTail(); @@ -349,8 +361,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // wait a little after scrolling before redrawing var redrawTimer = null; var REDRAWDELAY = constants.REDRAWDELAY; - var mainplot = plotinfo.mainplot ? - fullLayout._plots[plotinfo.mainplot] : plotinfo; + var mainplot = plotinfo.mainplot ? gd._fullLayout._plots[plotinfo.mainplot] : plotinfo; function zoomWheel(e) { // deactivate mousewheel scrolling on embedded graphs @@ -407,20 +418,24 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { ax.range = axRange.map(doZoom); } - if(ew || isSubplotConstrained) { + if(editX) { // if we're only zooming this axis because of constraints, // zoom it about the center if(!ew) xfrac = 0.5; - for(i = 0; i < xa.length; i++) zoomWheelOneAxis(xa[i], xfrac, zoom); + for(i = 0; i < xaxes.length; i++) { + zoomWheelOneAxis(xaxes[i], xfrac, zoom); + } scrollViewBox[2] *= zoom; scrollViewBox[0] += scrollViewBox[2] * xfrac * (1 / zoom - 1); } - if(ns || isSubplotConstrained) { + if(editY) { if(!ns) yfrac = 0.5; - for(i = 0; i < ya.length; i++) zoomWheelOneAxis(ya[i], yfrac, zoom); + for(i = 0; i < yaxes.length; i++) { + zoomWheelOneAxis(yaxes[i], yfrac, zoom); + } scrollViewBox[3] *= zoom; scrollViewBox[1] += scrollViewBox[3] * (1 - yfrac) * (1 / zoom - 1); @@ -459,8 +474,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { } if(xActive === 'ew' || yActive === 'ns') { - if(xActive) dragAxList(xa, dx); - if(yActive) dragAxList(ya, dy); + if(xActive) dragAxList(xaxes, dx); + if(yActive) dragAxList(yaxes, dy); updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]); ticksAndAnnotations(yActive, xActive); return; @@ -500,12 +515,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { dy = dxySign * dxyFraction * ph; } - if(xActive === 'w') dx = dz(xa, 0, dx); - else if(xActive === 'e') dx = dz(xa, 1, -dx); + if(xActive === 'w') dx = dz(xaxes, 0, dx); + else if(xActive === 'e') dx = dz(xaxes, 1, -dx); else if(!xActive) dx = 0; - if(yActive === 'n') dy = dz(ya, 1, dy); - else if(yActive === 's') dy = dz(ya, 0, -dy); + if(yActive === 'n') dy = dz(yaxes, 1, dy); + else if(yActive === 's') dy = dz(yaxes, 0, -dy); else if(!yActive) dy = 0; var x0 = (xActive === 'w') ? dx : 0; @@ -516,17 +531,17 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(!xActive && yActive.length === 1) { // dragging one end of the y axis of a constrained subplot // scale the other axis the same about its middle - for(i = 0; i < xa.length; i++) { - xa[i].range = xa[i]._r.slice(); - scaleZoom(xa[i], 1 - dy / ph); + for(i = 0; i < xaxes.length; i++) { + xaxes[i].range = xaxes[i]._r.slice(); + scaleZoom(xaxes[i], 1 - dy / ph); } dx = dy * pw / ph; x0 = dx / 2; } if(!yActive && xActive.length === 1) { - for(i = 0; i < ya.length; i++) { - ya[i].range = ya[i]._r.slice(); - scaleZoom(ya[i], 1 - dx / pw); + for(i = 0; i < yaxes.length; i++) { + yaxes[i].range = yaxes[i]._r.slice(); + scaleZoom(yaxes[i], 1 - dx / pw); } dy = dx * ph / pw; y0 = dy / 2; @@ -534,7 +549,6 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { } updateSubplots([x0, y0, pw - dx, ph - dy]); - ticksAndAnnotations(yActive, xActive); } @@ -550,13 +564,13 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { } } - if(ew || isSubplotConstrained) { - pushActiveAxIds(xa); - pushActiveAxIds(xaLinked); + if(editX) { + pushActiveAxIds(xaxes); + pushActiveAxIds(links.xaxes); } - if(ns || isSubplotConstrained) { - pushActiveAxIds(ya); - pushActiveAxIds(yaLinked); + if(editY) { + pushActiveAxIds(yaxes); + pushActiveAxIds(links.yaxes); } updates = {}; @@ -593,7 +607,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(gd._transitioningWithDuration) return; var doubleClickConfig = gd._context.doubleClick, - axList = (xActive ? xa : []).concat(yActive ? ya : []), + axList = (xActive ? xaxes : []).concat(yActive ? yaxes : []), attrs = {}; var ax, i, rangeInitial; @@ -632,12 +646,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { else if(doubleClickConfig === 'reset') { // when we're resetting, reset all linked axes too, so we get back // to the fully-auto-with-constraints situation - if(xActive || isSubplotConstrained) axList = axList.concat(xaLinked); - if(yActive && !isSubplotConstrained) axList = axList.concat(yaLinked); + if(xActive || isSubplotConstrained) axList = axList.concat(links.xaxes); + if(yActive && !isSubplotConstrained) axList = axList.concat(links.yaxes); if(isSubplotConstrained) { - if(!xActive) axList = axList.concat(xa); - else if(!yActive) axList = axList.concat(ya); + if(!xActive) axList = axList.concat(xaxes); + else if(!yActive) axList = axList.concat(yaxes); } for(i = 0; i < axList.length; i++) { @@ -675,122 +689,153 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // updateSubplots - find all plot viewboxes that should be // affected by this drag, and update them. look for all plots - // sharing an affected axis (including the one being dragged) + // sharing an affected axis (including the one being dragged), + // includes also scattergl and splom logic. function updateSubplots(viewBox) { + var fullLayout = gd._fullLayout; var plotinfos = fullLayout._plots; - var subplots = Object.keys(plotinfos); - var xScaleFactor = viewBox[2] / xa[0]._length; - var yScaleFactor = viewBox[3] / ya[0]._length; - var editX = ew || isSubplotConstrained; - var editY = ns || isSubplotConstrained; - - var i, xScaleFactor2, yScaleFactor2, clipDx, clipDy; - - // Find the appropriate scaling for this axis, if it's linked to the - // dragged axes by constraints. 0 is special, it means this axis shouldn't - // ever be scaled (will be converted to 1 if the other axis is scaled) - function getLinkedScaleFactor(ax) { - if(ax.fixedrange) return 0; - - if(editX && xaLinked.indexOf(ax) !== -1) { - return xScaleFactor; - } - if(editY && (isSubplotConstrained ? xaLinked : yaLinked).indexOf(ax) !== -1) { - return yScaleFactor; - } - return 0; - } + var subplots = fullLayout._subplots.cartesian; - function scaleAndGetShift(ax, scaleFactor) { - if(scaleFactor) { - ax.range = ax._r.slice(); - scaleZoom(ax, scaleFactor); - return getShift(ax, scaleFactor); - } - return 0; + // TODO can we move these to outer scope? + var hasScatterGl = fullLayout._has('scattergl'); + var hasOnlyLargeSploms = fullLayout._hasOnlyLargeSploms; + var hasSplom = hasOnlyLargeSploms || fullLayout._has('splom'); + var hasSVG = fullLayout._has('svg'); + var hasDraggedPts = fullLayout._has('draggedPts'); + + var i, sp, xa, ya; + + if(hasSplom || hasScatterGl) { + clearGlCanvases(gd); } - function getShift(ax, scaleFactor) { - return ax._length * (1 - scaleFactor) * FROM_TL[ax.constraintoward || 'middle']; + if(hasSplom) { + Registry.subplotsRegistry.splom.drag(gd); + if(hasOnlyLargeSploms) return; } - clearGlCanvases(gd); - - for(i = 0; i < subplots.length; i++) { - var subplot = plotinfos[subplots[i]], - xa2 = subplot.xaxis, - ya2 = subplot.yaxis, - editX2 = editX && !xa2.fixedrange && (xa.indexOf(xa2) !== -1), - editY2 = editY && !ya2.fixedrange && (ya.indexOf(ya2) !== -1); - - // scattergl translate - if(subplot._scene && subplot._scene.update) { - // FIXME: possibly we could update axis internal _r and _rl here - var xaRange = Lib.simpleMap(xa2.range, xa2.r2l); - var yaRange = Lib.simpleMap(ya2.range, ya2.r2l); - subplot._scene.update( - {range: [xaRange[0], yaRange[0], xaRange[1], yaRange[1]]} - ); + if(hasScatterGl) { + // loop over all subplots (w/o exceptions) here, + // as we cleared the gl canvases above + for(i = 0; i < subplots.length; i++) { + sp = plotinfos[subplots[i]]; + xa = sp.xaxis; + ya = sp.yaxis; + + var scene = sp._scene; + if(scene) { + // FIXME: possibly we could update axis internal _r and _rl here + var xrng = Lib.simpleMap(xa.range, xa.r2l); + var yrng = Lib.simpleMap(ya.range, ya.r2l); + scene.update({range: [xrng[0], yrng[0], xrng[1], yrng[1]]}); + } } + } - if(editX2) { - xScaleFactor2 = xScaleFactor; - clipDx = ew ? viewBox[0] : getShift(xa2, xScaleFactor2); - } - else { - xScaleFactor2 = getLinkedScaleFactor(xa2); - clipDx = scaleAndGetShift(xa2, xScaleFactor2); - } + if(hasSVG) { + var xScaleFactor = viewBox[2] / xa0._length; + var yScaleFactor = viewBox[3] / ya0._length; - if(editY2) { - yScaleFactor2 = yScaleFactor; - clipDy = ns ? viewBox[1] : getShift(ya2, yScaleFactor2); - } - else { - yScaleFactor2 = getLinkedScaleFactor(ya2); - clipDy = scaleAndGetShift(ya2, yScaleFactor2); - } + for(i = 0; i < subplots.length; i++) { + sp = plotinfos[subplots[i]]; + xa = sp.xaxis; + ya = sp.yaxis; - // don't scale at all if neither axis is scalable here - if(!xScaleFactor2 && !yScaleFactor2) { - continue; - } + var editX2 = editX && !xa.fixedrange && xaHash[xa._id]; + var editY2 = editY && !ya.fixedrange && yaHash[ya._id]; - // but if only one is, reset the other axis scaling - if(!xScaleFactor2) xScaleFactor2 = 1; - if(!yScaleFactor2) yScaleFactor2 = 1; + var xScaleFactor2, yScaleFactor2; + var clipDx, clipDy; - var plotDx = xa2._offset - clipDx / xScaleFactor2, - plotDy = ya2._offset - clipDy / yScaleFactor2; + if(editX2) { + xScaleFactor2 = xScaleFactor; + clipDx = ew ? viewBox[0] : getShift(xa, xScaleFactor2); + } else { + xScaleFactor2 = getLinkedScaleFactor(xa, xScaleFactor, yScaleFactor); + clipDx = scaleAndGetShift(xa, xScaleFactor2); + } - fullLayout._defs.select('#' + subplot.clipId + '> rect') - .call(Drawing.setTranslate, clipDx, clipDy) - .call(Drawing.setScale, xScaleFactor2, yScaleFactor2); + if(editY2) { + yScaleFactor2 = yScaleFactor; + clipDy = ns ? viewBox[1] : getShift(ya, yScaleFactor2); + } else { + yScaleFactor2 = getLinkedScaleFactor(ya, xScaleFactor, yScaleFactor); + clipDy = scaleAndGetShift(ya, yScaleFactor2); + } - var traceGroups = subplot.plot - .selectAll('.scatterlayer .trace, .boxlayer .trace, .violinlayer .trace'); + // don't scale at all if neither axis is scalable here + if(!xScaleFactor2 && !yScaleFactor2) { + continue; + } - subplot.plot - .call(Drawing.setTranslate, plotDx, plotDy) - .call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2); + // but if only one is, reset the other axis scaling + if(!xScaleFactor2) xScaleFactor2 = 1; + if(!yScaleFactor2) yScaleFactor2 = 1; + + var plotDx = xa._offset - clipDx / xScaleFactor2; + var plotDy = ya._offset - clipDy / yScaleFactor2; + + // TODO could be more efficient here: + // setTranslate and setScale do a lot of extra work + // when working independently, should perhaps combine + // them into a single routine. + sp.clipRect + .call(Drawing.setTranslate, clipDx, clipDy) + .call(Drawing.setScale, xScaleFactor2, yScaleFactor2); + + sp.plot + .call(Drawing.setTranslate, plotDx, plotDy) + .call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2); + + // TODO move these selectAll calls out of here + // and stash them somewhere nice. + if(hasDraggedPts) { + var traceGroups = sp.plot + .selectAll('.scatterlayer .trace, .boxlayer .trace, .violinlayer .trace'); + + // This is specifically directed at marker points in scatter, box and violin traces, + // applying an inverse scale to individual points to counteract + // the scale of the trace as a whole: + traceGroups.selectAll('.point') + .call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2); + traceGroups.selectAll('.textpoint') + .call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2); + traceGroups + .call(Drawing.hideOutsideRangePoints, sp); + + sp.plot.selectAll('.barlayer .trace') + .call(Drawing.hideOutsideRangePoints, sp, '.bartext'); + } + } + } + } - // This is specifically directed at marker points in scatter, box and violin traces, - // applying an inverse scale to individual points to counteract - // the scale of the trace as a whole: - traceGroups.selectAll('.point') - .call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2); - traceGroups.selectAll('.textpoint') - .call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2); - traceGroups - .call(Drawing.hideOutsideRangePoints, subplot); + // Find the appropriate scaling for this axis, if it's linked to the + // dragged axes by constraints. 0 is special, it means this axis shouldn't + // ever be scaled (will be converted to 1 if the other axis is scaled) + function getLinkedScaleFactor(ax, xScaleFactor, yScaleFactor) { + if(ax.fixedrange) return 0; - subplot.plot.selectAll('.barlayer .trace') - .call(Drawing.hideOutsideRangePoints, subplot, '.bartext'); + if(editX && links.xaHash[ax._id]) { + return xScaleFactor; } + if(editY && (isSubplotConstrained ? links.xaHash : links.yaHash)[ax._id]) { + return yScaleFactor; + } + return 0; + } - if(Registry.subplotsRegistry.splom) { - Registry.subplotsRegistry.splom.drag(gd); + function scaleAndGetShift(ax, scaleFactor) { + if(scaleFactor) { + ax.range = ax._r.slice(); + scaleZoom(ax, scaleFactor); + return getShift(ax, scaleFactor); } + return 0; + } + + function getShift(ax, scaleFactor) { + return ax._length * (1 - scaleFactor) * FROM_TL[ax.constraintoward || 'middle']; } return dragger; @@ -1000,40 +1045,40 @@ function xyCorners(box) { 'h' + clen + 'v3h-' + (clen + 3) + 'Z'; } -function calcLinks(constraintGroups, xIDs, yIDs) { +function calcLinks(gd, xaHash, yaHash) { + var constraintGroups = gd._fullLayout._axisConstraintGroups; var isSubplotConstrained = false; var xLinks = {}; var yLinks = {}; - var i, j, k; + var xID, yID, xLinkID, yLinkID; - var group, xLinkID, yLinkID; - for(i = 0; i < constraintGroups.length; i++) { - group = constraintGroups[i]; + for(var i = 0; i < constraintGroups.length; i++) { + var group = constraintGroups[i]; // check if any of the x axes we're dragging is in this constraint group - for(j = 0; j < xIDs.length; j++) { - if(group[xIDs[j]]) { + for(xID in xaHash) { + if(group[xID]) { // put the rest of these axes into xLinks, if we're not already // dragging them, so we know to scale these axes automatically too // to match the changes in the dragged x axes for(xLinkID in group) { - if((xLinkID.charAt(0) === 'x' ? xIDs : yIDs).indexOf(xLinkID) === -1) { + if(!(xLinkID.charAt(0) === 'x' ? xaHash : yaHash)[xLinkID]) { xLinks[xLinkID] = 1; } } // check if the x and y axes of THIS drag are linked - for(k = 0; k < yIDs.length; k++) { - if(group[yIDs[k]]) isSubplotConstrained = true; + for(yID in yaHash) { + if(group[yID]) isSubplotConstrained = true; } } } // now check if any of the y axes we're dragging is in this constraint group // only look for outside links, as we've already checked for links within the dragger - for(j = 0; j < yIDs.length; j++) { - if(group[yIDs[j]]) { + for(yID in yaHash) { + if(group[yID]) { for(yLinkID in group) { - if((yLinkID.charAt(0) === 'x' ? xIDs : yIDs).indexOf(yLinkID) === -1) { + if(!(yLinkID.charAt(0) === 'x' ? xaHash : yaHash)[yLinkID]) { yLinks[yLinkID] = 1; } } @@ -1048,10 +1093,29 @@ function calcLinks(constraintGroups, xIDs, yIDs) { Lib.extendFlat(xLinks, yLinks); yLinks = {}; } + + var xaHashLinked = {}; + var xaxesLinked = []; + for(xLinkID in xLinks) { + var xa = getFromId(gd, xLinkID); + xaxesLinked.push(xa); + xaHashLinked[xa._id] = xa; + } + + var yaHashLinked = {}; + var yaxesLinked = []; + for(yLinkID in yLinks) { + var ya = getFromId(gd, xLinkID); + yaxesLinked.push(ya); + yaHashLinked[ya._id] = ya; + } + return { - x: xLinks, - y: yLinks, - xy: isSubplotConstrained + xaHash: xaHashLinked, + yaHash: yaHashLinked, + xaxes: xaxesLinked, + yaxes: yaxesLinked, + isSubplotConstrained: isSubplotConstrained }; } @@ -1073,6 +1137,12 @@ function attachWheelEventHandler(element, handler) { } } +function hashValues(hash) { + var out = []; + for(var k in hash) out.push(hash[k]); + return out; +} + module.exports = { makeDragBox: makeDragBox, diff --git a/src/plots/cartesian/transition_axes.js b/src/plots/cartesian/transition_axes.js index 281b9964b9b..c8d306d5e0e 100644 --- a/src/plots/cartesian/transition_axes.js +++ b/src/plots/cartesian/transition_axes.js @@ -233,7 +233,7 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo var plotDx = xa2._offset - fracDx, plotDy = ya2._offset - fracDy; - fullLayout._defs.select('#' + subplot.clipId + '> rect') + subplot.clipRect .call(Drawing.setTranslate, clipDx, clipDy) .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor); diff --git a/src/traces/bar/index.js b/src/traces/bar/index.js index 6ff430354b8..1798b2a7c88 100644 --- a/src/traces/bar/index.js +++ b/src/traces/bar/index.js @@ -27,7 +27,7 @@ Bar.selectPoints = require('./select'); Bar.moduleType = 'trace'; Bar.name = 'bar'; Bar.basePlotModule = require('../../plots/cartesian'); -Bar.categories = ['cartesian', 'bar', 'oriented', 'markerColorscale', 'errorBarsOK', 'showLegend']; +Bar.categories = ['cartesian', 'svg', 'bar', 'oriented', 'markerColorscale', 'errorBarsOK', 'showLegend', 'draggedPts']; Bar.meta = { description: [ 'The data visualized by the span of the bars is set in `y`', diff --git a/src/traces/box/index.js b/src/traces/box/index.js index 5395dd0af66..ad32d7000aa 100644 --- a/src/traces/box/index.js +++ b/src/traces/box/index.js @@ -24,7 +24,7 @@ Box.selectPoints = require('./select'); Box.moduleType = 'trace'; Box.name = 'box'; Box.basePlotModule = require('../../plots/cartesian'); -Box.categories = ['cartesian', 'symbols', 'oriented', 'box-violin', 'showLegend']; +Box.categories = ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'draggedPts']; Box.meta = { description: [ 'In vertical (horizontal) box plots,', diff --git a/src/traces/candlestick/index.js b/src/traces/candlestick/index.js index dff5c003935..ef94d47bc17 100644 --- a/src/traces/candlestick/index.js +++ b/src/traces/candlestick/index.js @@ -14,7 +14,7 @@ module.exports = { moduleType: 'trace', name: 'candlestick', basePlotModule: require('../../plots/cartesian'), - categories: ['cartesian', 'showLegend', 'candlestick'], + categories: ['cartesian', 'svg', 'showLegend', 'candlestick'], meta: { description: [ 'The candlestick is a style of financial chart describing', diff --git a/src/traces/carpet/index.js b/src/traces/carpet/index.js index e97fc2c1789..0a4c6a36b3f 100644 --- a/src/traces/carpet/index.js +++ b/src/traces/carpet/index.js @@ -20,7 +20,7 @@ Carpet.animatable = true; Carpet.moduleType = 'trace'; Carpet.name = 'carpet'; Carpet.basePlotModule = require('../../plots/cartesian'); -Carpet.categories = ['cartesian', 'carpet', 'carpetAxis', 'notLegendIsolatable']; +Carpet.categories = ['cartesian', 'svg', 'carpet', 'carpetAxis', 'notLegendIsolatable']; Carpet.meta = { description: [ 'The data describing carpet axis layout is set in `y` and (optionally)', diff --git a/src/traces/contour/index.js b/src/traces/contour/index.js index f56f61cd7ec..f498cf78d98 100644 --- a/src/traces/contour/index.js +++ b/src/traces/contour/index.js @@ -22,7 +22,7 @@ Contour.hoverPoints = require('./hover'); Contour.moduleType = 'trace'; Contour.name = 'contour'; Contour.basePlotModule = require('../../plots/cartesian'); -Contour.categories = ['cartesian', '2dMap', 'contour', 'showLegend']; +Contour.categories = ['cartesian', 'svg', '2dMap', 'contour', 'showLegend']; Contour.meta = { description: [ 'The data from which contour lines are computed is set in `z`.', diff --git a/src/traces/contourcarpet/index.js b/src/traces/contourcarpet/index.js index 7853dae6fc1..1529594f1bc 100644 --- a/src/traces/contourcarpet/index.js +++ b/src/traces/contourcarpet/index.js @@ -20,7 +20,7 @@ ContourCarpet.style = require('../contour/style'); ContourCarpet.moduleType = 'trace'; ContourCarpet.name = 'contourcarpet'; ContourCarpet.basePlotModule = require('../../plots/cartesian'); -ContourCarpet.categories = ['cartesian', 'carpet', 'contour', 'symbols', 'showLegend', 'hasLines', 'carpetDependent']; +ContourCarpet.categories = ['cartesian', 'svg', 'carpet', 'contour', 'symbols', 'showLegend', 'hasLines', 'carpetDependent']; ContourCarpet.meta = { hrName: 'contour_carpet', description: [ diff --git a/src/traces/heatmap/index.js b/src/traces/heatmap/index.js index d50b941e377..12ccc878755 100644 --- a/src/traces/heatmap/index.js +++ b/src/traces/heatmap/index.js @@ -22,7 +22,7 @@ Heatmap.hoverPoints = require('./hover'); Heatmap.moduleType = 'trace'; Heatmap.name = 'heatmap'; Heatmap.basePlotModule = require('../../plots/cartesian'); -Heatmap.categories = ['cartesian', '2dMap']; +Heatmap.categories = ['cartesian', 'svg', '2dMap']; Heatmap.meta = { description: [ 'The data that describes the heatmap value-to-color mapping', diff --git a/src/traces/histogram/index.js b/src/traces/histogram/index.js index f1c107e7555..c0949a06447 100644 --- a/src/traces/histogram/index.js +++ b/src/traces/histogram/index.js @@ -41,7 +41,7 @@ Histogram.eventData = require('./event_data'); Histogram.moduleType = 'trace'; Histogram.name = 'histogram'; Histogram.basePlotModule = require('../../plots/cartesian'); -Histogram.categories = ['cartesian', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend']; +Histogram.categories = ['cartesian', 'svg', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend']; Histogram.meta = { description: [ 'The sample data from which statistics are computed is set in `x`', diff --git a/src/traces/histogram2d/index.js b/src/traces/histogram2d/index.js index 324a952598e..c8e9695b8dc 100644 --- a/src/traces/histogram2d/index.js +++ b/src/traces/histogram2d/index.js @@ -23,7 +23,7 @@ Histogram2D.eventData = require('../histogram/event_data'); Histogram2D.moduleType = 'trace'; Histogram2D.name = 'histogram2d'; Histogram2D.basePlotModule = require('../../plots/cartesian'); -Histogram2D.categories = ['cartesian', '2dMap', 'histogram']; +Histogram2D.categories = ['cartesian', 'svg', '2dMap', 'histogram']; Histogram2D.meta = { hrName: 'histogram_2d', description: [ diff --git a/src/traces/histogram2dcontour/index.js b/src/traces/histogram2dcontour/index.js index 9c3d5f2e2da..7953ff48354 100644 --- a/src/traces/histogram2dcontour/index.js +++ b/src/traces/histogram2dcontour/index.js @@ -22,7 +22,7 @@ Histogram2dContour.hoverPoints = require('../contour/hover'); Histogram2dContour.moduleType = 'trace'; Histogram2dContour.name = 'histogram2dcontour'; Histogram2dContour.basePlotModule = require('../../plots/cartesian'); -Histogram2dContour.categories = ['cartesian', '2dMap', 'contour', 'histogram']; +Histogram2dContour.categories = ['cartesian', 'svg', '2dMap', 'contour', 'histogram']; Histogram2dContour.meta = { hrName: 'histogram_2d_contour', description: [ diff --git a/src/traces/ohlc/index.js b/src/traces/ohlc/index.js index d8d2f12f650..cabac0d8568 100644 --- a/src/traces/ohlc/index.js +++ b/src/traces/ohlc/index.js @@ -14,7 +14,7 @@ module.exports = { moduleType: 'trace', name: 'ohlc', basePlotModule: require('../../plots/cartesian'), - categories: ['cartesian', 'showLegend'], + categories: ['cartesian', 'svg', 'showLegend'], meta: { description: [ 'The ohlc (short for Open-High-Low-Close) is a style of financial chart describing', diff --git a/src/traces/scatter/index.js b/src/traces/scatter/index.js index 8bca686de6a..3808dcf7628 100644 --- a/src/traces/scatter/index.js +++ b/src/traces/scatter/index.js @@ -34,7 +34,7 @@ Scatter.animatable = true; Scatter.moduleType = 'trace'; Scatter.name = 'scatter'; Scatter.basePlotModule = require('../../plots/cartesian'); -Scatter.categories = ['cartesian', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend', 'scatter-like']; +Scatter.categories = ['cartesian', 'svg', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend', 'scatter-like', 'draggedPts']; Scatter.meta = { description: [ 'The scatter trace type encompasses line charts, scatter charts, text charts, and bubble charts.', diff --git a/src/traces/scattercarpet/index.js b/src/traces/scattercarpet/index.js index 689f4cedf5f..6427a4e75a0 100644 --- a/src/traces/scattercarpet/index.js +++ b/src/traces/scattercarpet/index.js @@ -23,7 +23,7 @@ ScatterCarpet.eventData = require('./event_data'); ScatterCarpet.moduleType = 'trace'; ScatterCarpet.name = 'scattercarpet'; ScatterCarpet.basePlotModule = require('../../plots/cartesian'); -ScatterCarpet.categories = ['carpet', 'symbols', 'markerColorscale', 'showLegend', 'carpetDependent']; +ScatterCarpet.categories = ['svg', 'carpet', 'symbols', 'markerColorscale', 'showLegend', 'carpetDependent']; ScatterCarpet.meta = { hrName: 'scatter_carpet', description: [ diff --git a/src/traces/violin/index.js b/src/traces/violin/index.js index c97355078d2..26e39f644f6 100644 --- a/src/traces/violin/index.js +++ b/src/traces/violin/index.js @@ -23,7 +23,7 @@ module.exports = { moduleType: 'trace', name: 'violin', basePlotModule: require('../../plots/cartesian'), - categories: ['cartesian', 'symbols', 'oriented', 'box-violin', 'showLegend'], + categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'draggedPts'], meta: { description: [ 'In vertical (horizontal) violin plots,', From 0979272d0c671efee26fae151865bbc3c5487629 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Apr 2018 16:00:45 -0400 Subject: [PATCH 06/11] clean splom drag logic --- src/traces/splom/base_plot.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/traces/splom/base_plot.js b/src/traces/splom/base_plot.js index 1fb81e8fef3..9e051212bf1 100644 --- a/src/traces/splom/base_plot.js +++ b/src/traces/splom/base_plot.js @@ -42,14 +42,9 @@ function drag(gd) { var trace = cd0.trace; var scene = cd0.t._scene; - // FIXME: this probably should not be called for non-splom traces - if(!scene || !scene.matrixOptions) continue; - - var opts = scene.matrixOptions; - if(trace.type === 'splom' && scene && scene.matrix) { var activeLength = trace._activeLength; - var visibleLength = opts.data.length; + var visibleLength = scene.matrixOptions.data.length; var ranges = new Array(visibleLength); var k = 0; @@ -67,7 +62,7 @@ function drag(gd) { } if(fullLayout._hasOnlyLargeSploms) { - fullLayout._modules[0].basePlotModule.drawGrid(gd); + drawGrid(gd); } } @@ -228,7 +223,7 @@ module.exports = { drawFramework: Cartesian.drawFramework, plot: plot, drag: drag, - drawGrid: drawGrid, clean: clean, + updateFx: Cartesian.updateFx, toSVG: Cartesian.toSVG }; From ebb35ce657b484af2dfaddc92018bb1029d8fb37 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Apr 2018 16:00:57 -0400 Subject: [PATCH 07/11] lint in plot_api --- src/plot_api/plot_api.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index a2906090d84..624870d7a43 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -36,7 +36,7 @@ var helpers = require('./helpers'); var subroutines = require('./subroutines'); var editTypes = require('./edit_types'); -var cartesianConstants = require('../plots/cartesian/constants'); +var AX_NAME_PATTERN = require('../plots/cartesian/constants').AX_NAME_PATTERN; var numericNameWarningCount = 0; var numericNameWarningCountLimit = 5; @@ -1296,8 +1296,8 @@ exports.restyle = function restyle(gd, astr, val, _traces) { var traces = helpers.coerceTraceIndices(gd, _traces); - var specs = _restyle(gd, aobj, traces), - flags = specs.flags; + var specs = _restyle(gd, aobj, traces); + var flags = specs.flags; // clear calcdata and/or axis types if required so they get regenerated if(flags.clearCalc) gd.calcdata = undefined; @@ -1661,8 +1661,8 @@ exports.relayout = function relayout(gd, astr, val) { if(Object.keys(aobj).length) gd.changed = true; - var specs = _relayout(gd, aobj), - flags = specs.flags; + var specs = _relayout(gd, aobj); + var flags = specs.flags; // clear calcdata if required if(flags.calc) gd.calcdata = undefined; @@ -1923,7 +1923,7 @@ function _relayout(gd, aobj) { } Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); } - else if(pleaf.match(cartesianConstants.AX_NAME_PATTERN)) { + else if(pleaf.match(AX_NAME_PATTERN)) { var fullProp = Lib.nestedProperty(fullLayout, ai).get(), newType = (vi || {}).type; @@ -1976,8 +1976,9 @@ function _relayout(gd, aobj) { if(checkForAutorange && (refAutorange(gd, objToAutorange, 'x') || refAutorange(gd, objToAutorange, 'y'))) { flags.calc = true; } - else editTypes.update(flags, updateValObject); - + else { + editTypes.update(flags, updateValObject); + } // prepare the edits object we'll send to applyContainerArrayChanges if(!arrayEdits[arrayStr]) arrayEdits[arrayStr] = {}; @@ -2128,11 +2129,11 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) { var traces = helpers.coerceTraceIndices(gd, _traces); - var restyleSpecs = _restyle(gd, Lib.extendFlat({}, traceUpdate), traces), - restyleFlags = restyleSpecs.flags; + var restyleSpecs = _restyle(gd, Lib.extendFlat({}, traceUpdate), traces); + var restyleFlags = restyleSpecs.flags; - var relayoutSpecs = _relayout(gd, Lib.extendFlat({}, layoutUpdate)), - relayoutFlags = relayoutSpecs.flags; + var relayoutSpecs = _relayout(gd, Lib.extendFlat({}, layoutUpdate)); + var relayoutFlags = relayoutSpecs.flags; // clear calcdata and/or axis types if required if(restyleFlags.clearCalc || relayoutFlags.calc) gd.calcdata = undefined; From f9090b7ab6ea4d07cba73be2468179fd7428ac26 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 6 Apr 2018 11:13:06 -0400 Subject: [PATCH 08/11] (fixup) add draggedPts to scattercarpet categories --- src/traces/scattercarpet/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scattercarpet/index.js b/src/traces/scattercarpet/index.js index 6427a4e75a0..c8864a6c45d 100644 --- a/src/traces/scattercarpet/index.js +++ b/src/traces/scattercarpet/index.js @@ -23,7 +23,7 @@ ScatterCarpet.eventData = require('./event_data'); ScatterCarpet.moduleType = 'trace'; ScatterCarpet.name = 'scattercarpet'; ScatterCarpet.basePlotModule = require('../../plots/cartesian'); -ScatterCarpet.categories = ['svg', 'carpet', 'symbols', 'markerColorscale', 'showLegend', 'carpetDependent']; +ScatterCarpet.categories = ['svg', 'carpet', 'symbols', 'markerColorscale', 'showLegend', 'carpetDependent', 'draggedPts']; ScatterCarpet.meta = { hrName: 'scatter_carpet', description: [ From d3fe40dab5708bf94f83b472986726e8f8ff65a9 Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 10 Apr 2018 14:14:05 -0400 Subject: [PATCH 09/11] replace selectAll with for(k in _plots) loop --- src/components/shapes/draw.js | 6 +++++- src/plot_api/subroutines.js | 4 ---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 271e37c4861..7e1bb7e1305 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -42,7 +42,11 @@ function draw(gd) { // Remove previous shapes before drawing new in shapes in fullLayout.shapes fullLayout._shapeUpperLayer.selectAll('path').remove(); fullLayout._shapeLowerLayer.selectAll('path').remove(); - fullLayout._shapeSubplotLayers.selectAll('path').remove(); + + for(var k in fullLayout._plots) { + var shapelayer = fullLayout._plots[k].shapelayer; + if(shapelayer) shapelayer.selectAll('path').remove(); + } for(var i = 0; i < fullLayout.shapes.length; i++) { if(fullLayout.shapes[i].visible) { diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 57fad95e180..1be49a86e2a 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -529,10 +529,6 @@ exports.drawData = function(gd) { basePlotModules[i].plot(gd); } - // keep reference to shape layers in subplots - var layerSubplot = fullLayout._paper.selectAll('.layer-subplot'); - fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer'); - // styling separate from drawing Plots.style(gd); From cc1b3dec723cd0ac7401fb7deca681838dc2964e Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 10 Apr 2018 15:51:55 -0400 Subject: [PATCH 10/11] fixup and :lock: dragbox with linked y axes --- src/plots/cartesian/dragbox.js | 2 +- test/jasmine/tests/cartesian_interact_test.js | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 15f13e11426..b7299c93b4c 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -1105,7 +1105,7 @@ function calcLinks(gd, xaHash, yaHash) { var yaHashLinked = {}; var yaxesLinked = []; for(yLinkID in yLinks) { - var ya = getFromId(gd, xLinkID); + var ya = getFromId(gd, yLinkID); yaxesLinked.push(ya); yaHashLinked[ya._id] = ya; } diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index bb9d6c3073b..d105bb5b4a3 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -530,6 +530,30 @@ describe('axis zoom/pan and main plot zoom', function() { .catch(failTest) .then(done); }); + + it('updates linked axes when there are constraints (axes_scaleanchor mock)', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/axes_scaleanchor.json')); + + function _assert(y3rng, y4rng) { + expect(gd._fullLayout.yaxis3.range).toBeCloseToArray(y3rng, 2, 'y3 rng'); + expect(gd._fullLayout.yaxis4.range).toBeCloseToArray(y4rng, 2, 'y3 rng'); + } + + Plotly.plot(gd, fig) + .then(function() { + _assert([-0.36, 4.36], [-0.36, 4.36]); + }) + .then(doDrag('x2y3', 'nsew', 0, 100)) + .then(function() { + _assert([-0.36, 2], [0.82, 3.18]); + }) + .then(doDrag('x2y4', 'nsew', 0, 50)) + .then(function() { + _assert([0.41, 1.23], [1.18, 2]); + }) + .catch(failTest) + .then(done); + }); }); describe('Event data:', function() { From f7d637d6698e8e040369e89ae9d23841fbcca289 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 11 Apr 2018 10:55:13 -0400 Subject: [PATCH 11/11] :books: link TODOs to gh issues --- src/plot_api/plot_api.js | 4 ++++ src/plot_api/subroutines.js | 1 - src/plots/cartesian/dragbox.js | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 624870d7a43..21ae2ea2cb5 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1693,10 +1693,14 @@ exports.relayout = function relayout(gd, astr, val) { // no test fail when commenting out doAutoRangeAndConstraints, // but I think we do need this (maybe just the enforce part?) // Am I right? + // More info in: + // https://github.com/plotly/plotly.js/issues/2540 subroutines.doAutoRangeAndConstraints, // TODO // can target specific axes, // do not have to redraw all axes here + // See: + // https://github.com/plotly/plotly.js/issues/2547 subroutines.doTicksRelayout, subroutines.drawData, subroutines.finalDraw diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 3cdabcb9b64..7256c48ca2f 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -525,7 +525,6 @@ exports.drawData = function(gd) { } } - // TODO does this break or slow down parcoords?? clearGlCanvases(gd); // loop over the base plot modules present on graph diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index b7299c93b4c..1f2bbd9a5cd 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -788,7 +788,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { .call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2); // TODO move these selectAll calls out of here - // and stash them somewhere nice. + // and stash them somewhere nice, see: + // https://github.com/plotly/plotly.js/issues/2548 if(hasDraggedPts) { var traceGroups = sp.plot .selectAll('.scatterlayer .trace, .boxlayer .trace, .violinlayer .trace');