From 858f7c2aea73f03f06215c280dd11c3429d11584 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 19 Nov 2018 21:49:30 -0500 Subject: [PATCH 1/7] fix #3255 - get automargin calls out of supplyDefaults --- src/components/rangeslider/draw.js | 5 +++ src/plots/plots.js | 45 +++++++++++++---------- test/jasmine/tests/heatmap_test.js | 5 +++ test/jasmine/tests/plot_api_react_test.js | 45 +++++++++++++++++++++++ test/jasmine/tests/plots_test.js | 4 +- test/jasmine/tests/splom_test.js | 14 +++---- 6 files changed, 89 insertions(+), 29 deletions(-) diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js index 76ef7a04a78..8bc9eef7673 100644 --- a/src/components/rangeslider/draw.js +++ b/src/components/rangeslider/draw.js @@ -445,6 +445,11 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) { var xa = mockFigure._fullLayout.xaxis; var ya = mockFigure._fullLayout[oppAxisName]; + xa.clearCalc(); + xa.setScale(); + ya.clearCalc(); + ya.setScale(); + var plotinfo = { id: id, plotgroup: plotgroup, diff --git a/src/plots/plots.js b/src/plots/plots.js index e2a28e87a39..b8ce8d5fe6c 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -497,15 +497,8 @@ plots.supplyDefaults = function(gd, opts) { if(uids[uid] === 'old') delete tracePreGUI[uid]; } - // TODO may return a promise - plots.doAutoMargin(gd); - - // set scale after auto margin routine - var axList = axisIDs.list(gd); - for(i = 0; i < axList.length; i++) { - var ax = axList[i]; - ax.setScale(); - } + // set up containers for margin calculations + initMargins(newFullLayout); // update object references in calcdata if(!skipUpdateCalc && oldCalcdata.length === newFullData.length) { @@ -1686,7 +1679,20 @@ plots.allowAutoMargin = function(gd, id) { gd._fullLayout._pushmarginIds[id] = 1; }; -function setupAutoMargin(fullLayout) { +function initMargins(fullLayout) { + var margin = fullLayout.margin; + + if(!fullLayout._size) { + var gs = fullLayout._size = { + l: Math.round(margin.l), + r: Math.round(margin.r), + t: Math.round(margin.t), + b: Math.round(margin.b), + p: Math.round(margin.pad) + }; + gs.w = Math.round(fullLayout.width) - gs.l - gs.r; + gs.h = Math.round(fullLayout.height) - gs.t - gs.b; + } if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; if(!fullLayout._pushmarginIds) fullLayout._pushmarginIds = {}; } @@ -1709,8 +1715,6 @@ function setupAutoMargin(fullLayout) { plots.autoMargin = function(gd, id, o) { var fullLayout = gd._fullLayout; - setupAutoMargin(fullLayout); - var pushMargin = fullLayout._pushmargin; var pushMarginIds = fullLayout._pushmarginIds; @@ -1754,18 +1758,19 @@ plots.autoMargin = function(gd, id, o) { plots.doAutoMargin = function(gd) { var fullLayout = gd._fullLayout; if(!fullLayout._size) fullLayout._size = {}; - setupAutoMargin(fullLayout); + initMargins(fullLayout); - var gs = fullLayout._size, - oldmargins = JSON.stringify(gs); + var gs = fullLayout._size; + var oldmargins = JSON.stringify(gs); + var margin = fullLayout.margin; // adjust margins for outside components // fullLayout.margin is the requested margin, // fullLayout._size has margins and plotsize after adjustment - var ml = Math.max(fullLayout.margin.l || 0, 0); - var mr = Math.max(fullLayout.margin.r || 0, 0); - var mt = Math.max(fullLayout.margin.t || 0, 0); - var mb = Math.max(fullLayout.margin.b || 0, 0); + var ml = margin.l; + var mr = margin.r; + var mt = margin.t; + var mb = margin.b; var pushMargin = fullLayout._pushmargin; var pushMarginIds = fullLayout._pushmarginIds; @@ -1835,7 +1840,7 @@ plots.doAutoMargin = function(gd) { gs.r = Math.round(mr); gs.t = Math.round(mt); gs.b = Math.round(mb); - gs.p = Math.round(fullLayout.margin.pad); + gs.p = Math.round(margin.pad); gs.w = Math.round(fullLayout.width) - gs.l - gs.r; gs.h = Math.round(fullLayout.height) - gs.t - gs.b; diff --git a/test/jasmine/tests/heatmap_test.js b/test/jasmine/tests/heatmap_test.js index 277c0b33abf..5c4f7f9fc4e 100644 --- a/test/jasmine/tests/heatmap_test.js +++ b/test/jasmine/tests/heatmap_test.js @@ -298,6 +298,11 @@ describe('heatmap calc', function() { fullTrace._extremes = {}; + // clearCalc used to be (oddly enough) part of supplyDefaults. + // Now it's in doCalcData, which we don't include in this partial pathway. + fullLayout.xaxis.clearCalc(); + fullLayout.yaxis.clearCalc(); + var out = Heatmap.calc(gd, fullTrace)[0]; out._xcategories = fullLayout.xaxis._categories; out._ycategories = fullLayout.yaxis._categories; diff --git a/test/jasmine/tests/plot_api_react_test.js b/test/jasmine/tests/plot_api_react_test.js index 812eb6f63e5..4dbf48ddc8a 100644 --- a/test/jasmine/tests/plot_api_react_test.js +++ b/test/jasmine/tests/plot_api_react_test.js @@ -468,6 +468,51 @@ describe('@noCIdep Plotly.react', function() { .then(done); }); + it('can change from scatter to category scatterpolar and back', function(done) { + function scatter() { + return { + data: [{x: ['a', 'b'], y: [1, 2]}], + layout: {width: 400, height: 400, margin: {r: 80, t: 20}} + }; + } + + function scatterpolar() { + return { + // the bug https://github.com/plotly/plotly.js/issues/3255 + // required all of this to change: + // - type -> scatterpolar + // - category theta + // - margins changed + data: [{type: 'scatterpolar', r: [1, 2, 3], theta: ['a', 'b', 'c']}], + layout: {width: 400, height: 400, margin: {r: 80, t: 50}} + }; + } + + function countTraces(scatterTraces, polarTraces) { + expect(document.querySelectorAll('.scatter').length) + .toBe(scatterTraces + polarTraces); + expect(document.querySelectorAll('.xy .scatter').length) + .toBe(scatterTraces); + expect(document.querySelectorAll('.polar .scatter').length) + .toBe(polarTraces); + } + + Plotly.newPlot(gd, scatter()) + .then(function() { + countTraces(1, 0); + return Plotly.react(gd, scatterpolar()); + }) + .then(function() { + countTraces(0, 1); + return Plotly.react(gd, scatter()); + }) + .then(function() { + countTraces(1, 0); + }) + .catch(failTest) + .then(done); + }); + it('can change data in candlesticks multiple times', function(done) { // test that we've fixed the original issue in // https://github.com/plotly/plotly.js/issues/2510 diff --git a/test/jasmine/tests/plots_test.js b/test/jasmine/tests/plots_test.js index 49c7c33ed54..11db2d50a96 100644 --- a/test/jasmine/tests/plots_test.js +++ b/test/jasmine/tests/plots_test.js @@ -83,9 +83,9 @@ describe('Test Plots', function() { expect(gd._fullLayout.someFunc).toBe(oldFullLayout.someFunc); expect(gd._fullLayout.xaxis.c2p) - .not.toBe(oldFullLayout.xaxis.c2p, '(set during ax.setScale'); + .not.toBe(oldFullLayout.xaxis.c2p, '(set during setConvert)'); expect(gd._fullLayout.yaxis._m) - .not.toBe(oldFullLayout.yaxis._m, '(set during ax.setScale'); + .toBe(oldFullLayout.yaxis._m, '(we don\'t run ax.setScale here)'); }); it('should include the correct reference to user data', function() { diff --git a/test/jasmine/tests/splom_test.js b/test/jasmine/tests/splom_test.js index 3c4d81911b3..3e09444efd9 100644 --- a/test/jasmine/tests/splom_test.js +++ b/test/jasmine/tests/splom_test.js @@ -820,11 +820,11 @@ describe('Test splom interactions:', function() { function _assert(msg, exp) { var splomScenes = gd._fullLayout._splomScenes; - var ids = Object.keys(splomScenes); + var ids = gd._fullData.map(function(trace) { return trace.uid; }); for(var i = 0; i < 3; i++) { var drawFn = splomScenes[ids[i]].draw; - expect(drawFn).toHaveBeenCalledTimes(exp[i], msg + ' - trace ' + i); + expect(drawFn.calls.count()).toBe(exp[i], msg + ' - trace ' + i); drawFn.calls.reset(); } } @@ -869,7 +869,7 @@ describe('Test splom interactions:', function() { methods.forEach(function(m) { spyOn(Plots, m).and.callThrough(); }); - function assetsFnCall(msg, exp) { + function assertFnCall(msg, exp) { methods.forEach(function(m) { expect(Plots[m]).toHaveBeenCalledTimes(exp[m], msg); Plots[m].calls.reset(); @@ -879,7 +879,7 @@ describe('Test splom interactions:', function() { spyOn(Lib, 'log'); Plotly.plot(gd, fig).then(function() { - assetsFnCall('base', { + assertFnCall('base', { cleanPlot: 1, // called once from inside Plots.supplyDefaults supplyDefaults: 1, doCalcdata: 1 @@ -892,9 +892,9 @@ describe('Test splom interactions:', function() { return Plotly.relayout(gd, {width: 4810, height: 3656}); }) .then(function() { - assetsFnCall('after', { - cleanPlot: 4, // 3 three from supplyDefaults, once in drawFramework - supplyDefaults: 3, // 1 from relayout, 1 from automargin, 1 in drawFramework + assertFnCall('after', { + cleanPlot: 3, // 2 from supplyDefaults, once in drawFramework + supplyDefaults: 2, // 1 from relayout, 1 in drawFramework doCalcdata: 1 // once in drawFramework }); assertDims('after', 4810, 3656); From bdabf48c298001f490368618db0bd4281ae22a89 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 20 Nov 2018 11:31:46 -0500 Subject: [PATCH 2/7] refactor rangeslider pipeline --- src/components/legend/defaults.js | 2 +- src/components/rangeslider/draw.js | 57 ++++--------- src/components/rangeslider/helpers.js | 66 ++++++++++++++ src/components/rangeslider/index.js | 6 +- src/plot_api/plot_api.js | 8 +- src/plot_api/subroutines.js | 56 +----------- src/plots/cartesian/autorange.js | 15 ++-- src/plots/cartesian/axes.js | 109 +++++++++++++++--------- src/plots/cartesian/constraints.js | 2 +- src/plots/cartesian/layout_defaults.js | 6 +- src/plots/plots.js | 55 +++++++++++- test/jasmine/tests/range_slider_test.js | 4 +- 12 files changed, 229 insertions(+), 157 deletions(-) create mode 100644 src/components/rangeslider/helpers.js diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index ada758b9955..5b653c02884 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -86,7 +86,7 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { coerce('orientation'); if(containerOut.orientation === 'h') { var xaxis = layoutIn.xaxis; - if(xaxis && xaxis.rangeslider && xaxis.rangeslider.visible) { + if(Registry.getComponentMethod('rangeslider', 'isVisible')(xaxis)) { defaultX = 0; defaultXAnchor = 'left'; defaultY = 1.1; diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js index 8bc9eef7673..960d3488a57 100644 --- a/src/components/rangeslider/draw.js +++ b/src/components/rangeslider/draw.js @@ -27,8 +27,13 @@ var setCursor = require('../../lib/setcursor'); var constants = require('./constants'); module.exports = function(gd) { - var fullLayout = gd._fullLayout, - rangeSliderData = makeRangeSliderData(fullLayout); + var fullLayout = gd._fullLayout; + var rangeSliderData = fullLayout._rangeSliderData; + for(var i = 0; i < rangeSliderData.length; i++) { + var opts = rangeSliderData[i][constants.name]; + // fullLayout._uid may not exist when we call makeData + opts._clipId = opts._id + '-' + fullLayout._uid; + } /* * @@ -55,10 +60,6 @@ module.exports = function(gd) { .selectAll('g.' + constants.containerClassName) .data(rangeSliderData, keyFunction); - rangeSliders.enter().append('g') - .classed(constants.containerClassName, true) - .attr('pointer-events', 'all'); - // remove exiting sliders and their corresponding clip paths rangeSliders.exit().each(function(axisOpts) { var opts = axisOpts[constants.name]; @@ -68,12 +69,16 @@ module.exports = function(gd) { // return early if no range slider is visible if(rangeSliderData.length === 0) return; + rangeSliders.enter().append('g') + .classed(constants.containerClassName, true) + .attr('pointer-events', 'all'); + // for all present range sliders rangeSliders.each(function(axisOpts) { - var rangeSlider = d3.select(this), - opts = axisOpts[constants.name], - oppAxisOpts = fullLayout[Axes.id2name(axisOpts.anchor)], - oppAxisRangeOpts = opts[Axes.id2name(axisOpts.anchor)]; + var rangeSlider = d3.select(this); + var opts = axisOpts[constants.name]; + var oppAxisOpts = fullLayout[Axes.id2name(axisOpts.anchor)]; + var oppAxisRangeOpts = opts[Axes.id2name(axisOpts.anchor)]; // update range // Expand slider range to the axis range @@ -104,12 +109,7 @@ module.exports = function(gd) { oppBottom = Math.min(oppBottom, oppAxis.domain[0]); } - opts._id = constants.name + axisOpts._id; - opts._clipId = opts._id + '-' + fullLayout._uid; - opts._width = graphSize.w * (domain[1] - domain[0]); - opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness; - opts._offsetShift = Math.floor(opts.borderwidth / 2); var x = Math.round(margin.l + (graphSize.w * domain[0])); @@ -177,36 +177,9 @@ module.exports = function(gd) { } }); } - - // update margins - Plots.autoMargin(gd, opts._id, { - x: domain[0], - y: oppBottom, - l: 0, - r: 0, - t: 0, - b: opts._height + margin.b + tickHeight, - pad: constants.extraPad + opts._offsetShift * 2 - }); }); }; -function makeRangeSliderData(fullLayout) { - var axes = Axes.list({ _fullLayout: fullLayout }, 'x', true), - name = constants.name, - out = []; - - if(fullLayout._has('gl2d')) return out; - - for(var i = 0; i < axes.length; i++) { - var ax = axes[i]; - - if(ax[name] && ax[name].visible) out.push(ax); - } - - return out; -} - function setupDragElement(rangeSlider, gd, axisOpts, opts) { var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(), grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(), diff --git a/src/components/rangeslider/helpers.js b/src/components/rangeslider/helpers.js new file mode 100644 index 00000000000..b4188f5239c --- /dev/null +++ b/src/components/rangeslider/helpers.js @@ -0,0 +1,66 @@ +/** +* Copyright 2012-2018, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Axes = require('../../plots/cartesian/axes'); +var constants = require('./constants'); +var name = constants.name; + +function isVisible(ax) { + var rangeSlider = ax && ax[name]; + return rangeSlider && rangeSlider.visible; +} +exports.isVisible = isVisible; + +exports.makeData = function(fullLayout) { + var axes = Axes.list({ _fullLayout: fullLayout }, 'x', true); + var margin = fullLayout.margin; + var rangeSliderData = []; + + if(!fullLayout._has('gl2d')) { + for(var i = 0; i < axes.length; i++) { + var ax = axes[i]; + + if(isVisible(ax)) { + rangeSliderData.push(ax); + + var opts = ax[name]; + opts._id = name + ax._id; + opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness; + opts._offsetShift = Math.floor(opts.borderwidth / 2); + } + } + } + + fullLayout._rangeSliderData = rangeSliderData; +}; + +exports.autoMarginOpts = function(gd, ax) { + var opts = ax[name]; + + var oppBottom = Infinity; + var subplotData = Axes.getSubplots(gd, ax); + for(var j = 0; j < subplotData.length; j++) { + var subplotj = subplotData[j]; + var oppAxis = Axes.getFromId(gd, subplotj.substr(subplotj.indexOf('y'))); + oppBottom = Math.min(oppBottom, oppAxis.domain[0]); + } + + var tickHeight = (ax.side === 'bottom' && ax._boundingBox.height) || 0; + + return { + x: 0, + y: oppBottom, + l: 0, + r: 0, + t: 0, + b: opts._height + gd._fullLayout.margin.b + tickHeight, + pad: constants.extraPad + opts._offsetShift * 2 + }; +}; diff --git a/src/components/rangeslider/index.js b/src/components/rangeslider/index.js index 2983d72c58e..fd3395ff114 100644 --- a/src/components/rangeslider/index.js +++ b/src/components/rangeslider/index.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var attrs = require('./attributes'); var oppAxisAttrs = require('./oppaxis_attributes'); +var helpers = require('./helpers'); module.exports = { moduleType: 'component', @@ -29,5 +30,8 @@ module.exports = { layoutAttributes: require('./attributes'), handleDefaults: require('./defaults'), calcAutorange: require('./calc_autorange'), - draw: require('./draw') + draw: require('./draw'), + isVisible: helpers.isVisible, + makeData: helpers.makeData, + autoMarginOpts: helpers.autoMarginOpts }; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 828797411f2..d95a31ca72e 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -332,8 +332,7 @@ exports.plot = function(gd, data, layout, config) { return Lib.syncOrAsync([ Registry.getComponentMethod('shapes', 'calcAutorange'), Registry.getComponentMethod('annotations', 'calcAutorange'), - doAutoRangeAndConstraints, - Registry.getComponentMethod('rangeslider', 'calcAutorange') + doAutoRangeAndConstraints ], gd); } @@ -345,6 +344,11 @@ exports.plot = function(gd, data, layout, config) { // store initial ranges *after* enforcing constraints, otherwise // we will never look like we're at the initial ranges if(graphWasEmpty) Axes.saveRangeInitial(gd); + + // this one is different from shapes/annotations calcAutorange + // the others incorporate those components into ax._extremes, + // this one actually sets the ranges in rangesliders. + Registry.getComponentMethod('rangeslider', 'calcAutorange')(gd); } // draw ticks, titles, and calculate axis scaling (._b, ._m) diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 68874a4eae7..3033462e785 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -55,7 +55,7 @@ function lsInner(gd) { var gs = fullLayout._size; var pad = gs.p; var axList = Axes.list(gd, '', true); - var i, subplot, plotinfo, xa, ya; + var i, subplot, plotinfo, ax, xa, ya; fullLayout._paperdiv.style({ width: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroWidth && !gd.layout.width) ? '100%' : fullLayout.width + 'px', @@ -91,10 +91,7 @@ function lsInner(gd) { // some preparation of axis position info for(i = 0; i < axList.length; i++) { - var ax = axList[i]; - - // reset scale in case the margins have changed - ax.setScale(); + ax = axList[i]; var counterAx = ax._anchorAxis; @@ -113,11 +110,6 @@ function lsInner(gd) { ax._mainMirrorPosition = (ax.mirror && counterAx) ? getLinePosition(ax, counterAx, alignmentConstants.OPPOSITE_SIDE[ax.side]) : null; - - // Figure out which subplot to draw ticks, labels, & axis lines on - // do this as a separate loop so we already have all the - // _mainAxis and _anchorAxis links set - ax._mainSubplot = findMainSubplot(ax, fullLayout); } // figure out which backgrounds we need to draw, @@ -358,48 +350,6 @@ function lsInner(gd) { return gd._promises.length && Promise.all(gd._promises); } -function findMainSubplot(ax, fullLayout) { - var subplotList = fullLayout._subplots; - var ids = subplotList.cartesian.concat(subplotList.gl2d || []); - var mockGd = {_fullLayout: fullLayout}; - - var isX = ax._id.charAt(0) === 'x'; - var anchorAx = ax._mainAxis._anchorAxis; - var mainSubplotID = ''; - var nextBestMainSubplotID = ''; - var anchorID = ''; - - // First try the main ID with the anchor - if(anchorAx) { - anchorID = anchorAx._mainAxis._id; - mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id); - } - - // Then look for a subplot with the counteraxis overlaying the anchor - // If that fails just use the first subplot including this axis - if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) { - mainSubplotID = ''; - - for(var j = 0; j < ids.length; j++) { - var id = ids[j]; - var yIndex = id.indexOf('y'); - var idPart = isX ? id.substr(0, yIndex) : id.substr(yIndex); - var counterPart = isX ? id.substr(yIndex) : id.substr(0, yIndex); - - if(idPart === ax._id) { - if(!nextBestMainSubplotID) nextBestMainSubplotID = id; - var counterAx = Axes.getFromId(mockGd, counterPart); - if(anchorID && counterAx.overlaying === anchorID) { - mainSubplotID = id; - break; - } - } - } - } - - return mainSubplotID || nextBestMainSubplotID; -} - function shouldShowLinesOrTicks(ax, subplot) { return (ax.ticks || ax.showline) && (subplot === ax._mainSubplot || ax.mirror === 'all' || ax.mirror === 'allticks'); @@ -752,6 +702,8 @@ exports.doAutoRangeAndConstraints = function(gd) { for(var i = 0; i < axList.length; i++) { var ax = axList[i]; cleanAxisConstraints(gd, ax); + // in case margins changed, update scale + ax.setScale(); doAutoRange(gd, ax); } diff --git a/src/plots/cartesian/autorange.js b/src/plots/cartesian/autorange.js index 3fe613d5e7d..c77914096f7 100644 --- a/src/plots/cartesian/autorange.js +++ b/src/plots/cartesian/autorange.js @@ -237,10 +237,6 @@ function concatExtremes(gd, ax) { } function doAutoRange(gd, ax) { - if(!ax._length) ax.setScale(); - - var axIn; - if(ax.autorange) { ax.range = getAutoRange(gd, ax); @@ -250,7 +246,7 @@ function doAutoRange(gd, ax) { // doAutoRange will get called on fullLayout, // but we want to report its results back to layout - axIn = ax._input; + var axIn = ax._input; // before we edit _input, store preGUI values var edits = {}; @@ -262,15 +258,16 @@ function doAutoRange(gd, ax) { axIn.autorange = ax.autorange; } - if(ax._anchorAxis && ax._anchorAxis.rangeslider) { - var axeRangeOpts = ax._anchorAxis.rangeslider[ax._name]; + var anchorAx = ax._anchorAxis; + + if(anchorAx && anchorAx.rangeslider) { + var axeRangeOpts = anchorAx.rangeslider[ax._name]; if(axeRangeOpts) { if(axeRangeOpts.rangemode === 'auto') { axeRangeOpts.range = getAutoRange(gd, ax); } } - axIn = ax._anchorAxis._input; - axIn.rangeslider[ax._name] = Lib.extendFlat({}, axeRangeOpts); + anchorAx._input.rangeslider[ax._name] = Lib.extendFlat({}, axeRangeOpts); } } diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 00b4ca78dbf..0be3e7972ba 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1592,7 +1592,7 @@ axes.draw = function(gd, arg, opts) { var axList = (!arg || arg === 'redraw') ? axes.listIds(gd) : arg; - Lib.syncOrAsync(axList.map(function(axId) { + return Lib.syncOrAsync(axList.map(function(axId) { return function() { if(!axId) return; @@ -1628,12 +1628,20 @@ axes.drawOne = function(gd, ax, opts) { var counterLetter = axes.counterLetter(axId); var mainSubplot = ax._mainSubplot; var mainLinePosition = ax._mainLinePosition; + var mainMirrorPosition = ax._mainMirrorPosition; var mainPlotinfo = fullLayout._plots[mainSubplot]; var mainAxLayer = mainPlotinfo[axLetter + 'axislayer']; var subplotsWithAx = axes.getSubplots(gd, ax); var vals = ax._vals = axes.calcTicks(ax); + // Add a couple of axis properties that should cause us to recreate + // elements. Used in d3 data function. + var axInfo = [ax.mirror, mainLinePosition, mainMirrorPosition].join('_'); + for(i = 0; i < vals.length; i++) { + vals[i].axInfo = axInfo; + } + if(!ax.visible) return; // stash selections to avoid DOM queries e.g. @@ -1679,6 +1687,7 @@ axes.drawOne = function(gd, ax, opts) { axes.drawGrid(gd, ax, { vals: gridVals, + counterAxis: counterAxis, layer: plotinfo.gridlayer.select('.' + axId), path: gridPath, transFn: transFn @@ -1698,7 +1707,7 @@ axes.drawOne = function(gd, ax, opts) { if(ax.ticks) { var mainTickPath = axes.makeTickPath(ax, mainLinePosition, tickSigns[2]); if(ax._anchorAxis && ax.mirror && ax.mirror !== true) { - mainTickPath += axes.makeTickPath(ax, ax._mainMirrorPosition, tickSigns[3]); + mainTickPath += axes.makeTickPath(ax, mainMirrorPosition, tickSigns[3]); } axes.drawTicks(gd, ax, { @@ -1866,38 +1875,42 @@ axes.drawOne = function(gd, ax, opts) { } } + var hasRangeSlider = Registry.getComponentMethod('rangeslider', 'isVisible')(ax); + function doAutoMargins() { - var pushKey = ax._name + '.automargin'; + var push, rangeSliderPush; - if(!ax.automargin) { - Plots.autoMargin(gd, pushKey); - return; + if(hasRangeSlider) { + rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax); } + Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush); var s = ax.side.charAt(0); - var push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0}; + if(ax.automargin && (!hasRangeSlider || s !== 'b')) { + push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0}; - if(axLetter === 'x') { - push.y = (ax.anchor === 'free' ? ax.position : - ax._anchorAxis.domain[s === 't' ? 1 : 0]); - push[s] += ax._boundingBox.height; - } else { - push.x = (ax.anchor === 'free' ? ax.position : - ax._anchorAxis.domain[s === 'r' ? 1 : 0]); - push[s] += ax._boundingBox.width; - } + if(axLetter === 'x') { + push.y = (ax.anchor === 'free' ? ax.position : + ax._anchorAxis.domain[s === 't' ? 1 : 0]); + push[s] += ax._boundingBox.height; + } else { + push.x = (ax.anchor === 'free' ? ax.position : + ax._anchorAxis.domain[s === 'r' ? 1 : 0]); + push[s] += ax._boundingBox.width; + } - if(ax.title.text !== fullLayout._dfltTitle[axLetter]) { - push[s] += ax.title.font.size; + if(ax.title.text !== fullLayout._dfltTitle[axLetter]) { + push[s] += ax.title.font.size; + } } - Plots.autoMargin(gd, pushKey, push); + Plots.autoMargin(gd, axAutoMarginID(ax), push); } seq.push(calcBoundingBox, doAutoMargins); if(!opts.skipTitle && - !((ax.rangeslider || {}).visible && ax._boundingBox && ax.side === 'bottom') + !(hasRangeSlider && ax._boundingBox && ax.side === 'bottom') ) { seq.push(function() { return drawTitle(gd, ax); }); } @@ -2142,10 +2155,8 @@ axes.makeLabelFns = function(ax, shift, angle) { return out; }; -function makeDataFn(ax) { - return function(d) { - return [d.text, d.x, ax.mirror, d.font, d.fontSize, d.fontColor].join('_'); - }; +function tickDataFn(d) { + return [d.text, d.x, d.axInfo, d.font, d.fontSize, d.fontColor].join('_'); } /** @@ -2170,7 +2181,7 @@ axes.drawTicks = function(gd, ax, opts) { var cls = ax._id + 'tick'; var ticks = opts.layer.selectAll('path.' + cls) - .data(ax.ticks ? opts.vals : [], makeDataFn(ax)); + .data(ax.ticks ? opts.vals : [], tickDataFn); ticks.exit().remove(); @@ -2200,6 +2211,8 @@ axes.drawTicks = function(gd, ax, opts) { * @param {object} opts * - {array of object} vals (calcTicks output-like) * - {d3 selection} layer + * - {object} counterAxis (full axis object corresponding to counter axis) + * optional - only required if this axis supports zero lines * - {string or fn} path * - {fn} transFn * - {boolean} crisp (set to false to unset crisp-edge SVG rendering) @@ -2208,26 +2221,39 @@ axes.drawGrid = function(gd, ax, opts) { opts = opts || {}; var cls = ax._id + 'grid'; + var vals = opts.vals; + var counterAx = opts.counterAxis; + if(ax.showgrid === false) { + vals = []; + } + else if(counterAx && axes.shouldShowZeroLine(gd, ax, counterAx)) { + var isArrayMode = ax.tickmode === 'array'; + for(var i = 0; i < vals.length; i++) { + var xi = vals[i].x; + if(isArrayMode ? !xi : (Math.abs(xi) < ax.dtick / 100)) { + vals = vals.slice(0, i).concat(vals.slice(i + 1)); + // In array mode you can in principle have multiple + // ticks at 0, so test them all. Otherwise once we found + // one we can stop. + if(isArrayMode) i--; + else break; + } + } + } var grid = opts.layer.selectAll('path.' + cls) - .data((ax.showgrid === false) ? [] : opts.vals, makeDataFn(ax)); + .data(vals, tickDataFn); grid.exit().remove(); grid.enter().append('path') .classed(cls, 1) - .classed('crisp', opts.crisp !== false) - .attr('d', opts.path) - .each(function(d) { - if(ax.zeroline && (ax.type === 'linear' || ax.type === '-') && - Math.abs(d.x) < ax.dtick / 100) { - d3.select(this).remove(); - } - }); + .classed('crisp', opts.crisp !== false); ax._gw = Drawing.crispRound(gd, ax.gridwidth, 1); grid.attr('transform', opts.transFn) + .attr('d', opts.path) .call(Color.stroke, ax.gridcolor || '#ddd') .style('stroke-width', ax._gw + 'px'); @@ -2266,7 +2292,6 @@ axes.drawZeroLine = function(gd, ax, opts) { .classed(cls, 1) .classed('zl', 1) .classed('crisp', opts.crisp !== false) - .attr('d', opts.path) .each(function() { // use the fact that only one element can enter to trigger a sort. // If several zerolines enter at the same time we will sort once per, @@ -2277,6 +2302,7 @@ axes.drawZeroLine = function(gd, ax, opts) { }); zl.attr('transform', opts.transFn) + .attr('d', opts.path) .call(Color.stroke, ax.zerolinecolor || Color.defaultLine) .style('stroke-width', Drawing.crispRound(gd, ax.zerolinewidth, ax._gw || 1) + 'px'); }; @@ -2316,7 +2342,7 @@ axes.drawLabels = function(gd, ax, opts) { var lastAngle = (ax._tickAngles || {})[cls]; var tickLabels = opts.layer.selectAll('g.' + cls) - .data(ax.showticklabels ? vals : [], makeDataFn(ax)); + .data(ax.showticklabels ? vals : [], tickDataFn); var labelsReady = []; @@ -2533,7 +2559,7 @@ function drawDividers(gd, ax, opts) { var vals = opts.vals; var dividers = opts.layer.selectAll('path.' + cls) - .data(vals, makeDataFn(ax)); + .data(vals, tickDataFn); dividers.exit().remove(); @@ -2738,14 +2764,17 @@ axes.allowAutoMargin = function(gd) { for(var i = 0; i < axList.length; i++) { var ax = axList[i]; if(ax.automargin) { - Plots.allowAutoMargin(gd, ax._name + '.automargin'); + Plots.allowAutoMargin(gd, axAutoMarginID(ax)); } - if(ax.rangeslider && ax.rangeslider.visible) { - Plots.allowAutoMargin(gd, 'rangeslider' + ax._id); + if(Registry.getComponentMethod('rangeslider', 'isVisible')(ax)) { + Plots.allowAutoMargin(gd, rangeSliderAutoMarginID(ax)); } } }; +function axAutoMarginID(ax) { return ax._id + '.automargin'; } +function rangeSliderAutoMarginID(ax) { return ax._id + '.rangeslider'; } + // swap all the presentation attributes of the axes showing these traces axes.swap = function(gd, traces) { var axGroups = makeAxisGroups(gd, traces); diff --git a/src/plots/cartesian/constraints.js b/src/plots/cartesian/constraints.js index 70859a2ad0b..36c27ec08b3 100644 --- a/src/plots/cartesian/constraints.js +++ b/src/plots/cartesian/constraints.js @@ -139,7 +139,6 @@ exports.enforce = function enforceAxisConstraints(gd) { var getPad = makePadFn(ax); updateDomain(ax, factor); - ax.setScale(); var m = Math.abs(ax._m); var extremes = concatExtremes(gd, ax); var minArray = extremes.min; @@ -206,4 +205,5 @@ function updateDomain(ax, factor) { center + (inputDomain[0] - center) / factor, center + (inputDomain[1] - center) / factor ]; + ax.setScale(); } diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index e0e6e18dc08..f9c7bec1f84 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -239,11 +239,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var anchoredAxis = layoutOut[id2name(axLayoutOut.anchor)]; - var fixedRangeDflt = ( - anchoredAxis && - anchoredAxis.rangeslider && - anchoredAxis.rangeslider.visible - ); + var fixedRangeDflt = getComponentMethod('rangeslider', 'isVisible')(anchoredAxis); coerce('fixedrange', fixedRangeDflt); } diff --git a/src/plots/plots.js b/src/plots/plots.js index b8ce8d5fe6c..5642342266d 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -500,6 +500,9 @@ plots.supplyDefaults = function(gd, opts) { // set up containers for margin calculations initMargins(newFullLayout); + // collect and do some initial calculations for rangesliders + Registry.getComponentMethod('rangeslider', 'makeData')(newFullLayout); + // update object references in calcdata if(!skipUpdateCalc && oldCalcdata.length === newFullData.length) { plots.supplyDefaultsUpdateCalc(oldCalcdata, newFullData); @@ -835,8 +838,9 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa // while we're at it, link overlaying axes to their main axes and // anchored axes to the axes they're anchored to var axList = axisIDs.list(mockGd, null, true); + var ax; for(i = 0; i < axList.length; i++) { - var ax = axList[i]; + ax = axList[i]; var mainAx = null; if(ax.overlaying) { @@ -864,8 +868,57 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa null : axisIDs.getFromId(mockGd, ax.anchor); } + + // finally, we can find the main subplot for each axis + // (on which the ticks & labels are drawn) + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + ax._mainSubplot = findMainSubplot(ax, newFullLayout); + } }; +function findMainSubplot(ax, fullLayout) { + var subplotList = fullLayout._subplots; + var ids = subplotList.cartesian.concat(subplotList.gl2d || []); + var mockGd = {_fullLayout: fullLayout}; + + var isX = ax._id.charAt(0) === 'x'; + var anchorAx = ax._mainAxis._anchorAxis; + var mainSubplotID = ''; + var nextBestMainSubplotID = ''; + var anchorID = ''; + + // First try the main ID with the anchor + if(anchorAx) { + anchorID = anchorAx._mainAxis._id; + mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id); + } + + // Then look for a subplot with the counteraxis overlaying the anchor + // If that fails just use the first subplot including this axis + if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) { + mainSubplotID = ''; + + for(var j = 0; j < ids.length; j++) { + var id = ids[j]; + var yIndex = id.indexOf('y'); + var idPart = isX ? id.substr(0, yIndex) : id.substr(yIndex); + var counterPart = isX ? id.substr(yIndex) : id.substr(0, yIndex); + + if(idPart === ax._id) { + if(!nextBestMainSubplotID) nextBestMainSubplotID = id; + var counterAx = axisIDs.getFromId(mockGd, counterPart); + if(anchorID && counterAx.overlaying === anchorID) { + mainSubplotID = id; + break; + } + } + } + } + + return mainSubplotID || nextBestMainSubplotID; +} + // This function clears any trace attributes with valType: color and // no set dflt filed in the plot schema. This is needed because groupby (which // is the only transform for which this currently applies) supplies parent diff --git a/test/jasmine/tests/range_slider_test.js b/test/jasmine/tests/range_slider_test.js index 6ba00809e06..a6246a251f4 100644 --- a/test/jasmine/tests/range_slider_test.js +++ b/test/jasmine/tests/range_slider_test.js @@ -28,9 +28,7 @@ function getRangeSliderChild(index) { } function countRangeSliderClipPaths() { - return d3.selectAll('defs').selectAll('*').filter(function() { - return this.id.indexOf('rangeslider') !== -1; - }).size(); + return document.querySelectorAll('defs [id*=rangeslider]').length; } function testTranslate1D(node, val) { From b2a4b769cb73494d060fedd498357ef5f916f5ab Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 10 Dec 2018 22:14:58 -0500 Subject: [PATCH 3/7] fixed range_slider_rangemode baseline --- .../baselines/range_slider_rangemode.png | Bin 34627 -> 34659 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/range_slider_rangemode.png b/test/image/baselines/range_slider_rangemode.png index 0915d37b06d845b1dbea9c5e0c210462abf69f48..40b03a5eb77037bc77598f025194c9028e817e74 100644 GIT binary patch literal 34659 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

6I1?1=Cx-JtrN+gOY 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

B0&0~jj-XsLxfc)=OY4XWJgFQ$qK8{FGGPI}p{ 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 From 177e6f0fc3cf9368fe36e79a03302e7bf8acf4ad Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 28 Nov 2018 18:52:22 -0500 Subject: [PATCH 4/7] use _replotting in cartesian dragbox --- src/plots/cartesian/dragbox.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 6cc75dc0443..8b91a5dab5b 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -516,6 +516,9 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { return; } + // prevent axis drawing from monkeying with margins until we're done + gd._fullLayout._replotting = true; + if(xActive === 'ew' || yActive === 'ns') { if(xActive) dragAxList(xaxes, dx); if(yActive) dragAxList(yaxes, dy); @@ -726,7 +729,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // accumulated MathJax promises - wait for them before we relayout. Lib.syncOrAsync([ Plots.previousPromises, - function() { Registry.call('_guiRelayout', gd, updates); } + function() { + gd._fullLayout._replotting = false; + Registry.call('_guiRelayout', gd, updates); + } ], gd); } From da30f160b510c88c4c884d901fb37704df52c7e4 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 11 Dec 2018 12:31:42 -0500 Subject: [PATCH 5/7] remove setScale categories fallback and update heatmap test comment --- src/plots/cartesian/set_convert.js | 5 ----- test/jasmine/tests/heatmap_test.js | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index b07d5d02b11..57118325b8d 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -419,11 +419,6 @@ module.exports = function setConvert(ax, fullLayout) { ax.setScale = function(usePrivateRange) { var gs = fullLayout._size; - // TODO cleaner way to handle this case - if(!ax._categories) ax._categories = []; - // Add a map to optimize the performance of category collection - if(!ax._categoriesMap) ax._categoriesMap = {}; - // make sure we have a domain (pull it in from the axis // this one is overlaying if necessary) if(ax.overlaying) { diff --git a/test/jasmine/tests/heatmap_test.js b/test/jasmine/tests/heatmap_test.js index 5c4f7f9fc4e..7152e38429d 100644 --- a/test/jasmine/tests/heatmap_test.js +++ b/test/jasmine/tests/heatmap_test.js @@ -298,8 +298,10 @@ describe('heatmap calc', function() { fullTrace._extremes = {}; - // clearCalc used to be (oddly enough) part of supplyDefaults. - // Now it's in doCalcData, which we don't include in this partial pathway. + // we used to call ax.setScale during supplyDefaults, and this had a + // fallback to provide _categories and _categoriesMap. Now neither of + // those is true... anyway the right way to do this though is + // ax.clearCalc. fullLayout.xaxis.clearCalc(); fullLayout.yaxis.clearCalc(); From 68993c313a23552c0202fb6b0aed5ba01ca72f0a Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 11 Dec 2018 13:34:10 -0500 Subject: [PATCH 6/7] minor :palm_tree: for findMainSubplot --- src/plots/plots.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 5642342266d..2ed4c4fe740 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -873,13 +873,11 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa // (on which the ticks & labels are drawn) for(i = 0; i < axList.length; i++) { ax = axList[i]; - ax._mainSubplot = findMainSubplot(ax, newFullLayout); + ax._mainSubplot = findMainSubplot(ax, newFullLayout, ids); } }; -function findMainSubplot(ax, fullLayout) { - var subplotList = fullLayout._subplots; - var ids = subplotList.cartesian.concat(subplotList.gl2d || []); +function findMainSubplot(ax, fullLayout, ids) { var mockGd = {_fullLayout: fullLayout}; var isX = ax._id.charAt(0) === 'x'; From f55e7695209a235ced67c27d39ef4055782b93c4 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 11 Dec 2018 14:56:18 -0500 Subject: [PATCH 7/7] pre-collate counteraxes and subplot ids for each axis this means Axes.getSubplots and Axes.findSubplotsWithAxis are essentially unused internally, but we have some outside callers using getSubplots --- src/components/rangeslider/draw.js | 20 ++++++--------- src/components/rangeslider/helpers.js | 13 +++++----- src/plots/cartesian/axes.js | 7 +++++- src/plots/cartesian/layout_defaults.js | 2 ++ src/plots/plots.js | 34 ++++++++++++++------------ 5 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js index 960d3488a57..1a7fe176df6 100644 --- a/src/components/rangeslider/draw.js +++ b/src/components/rangeslider/draw.js @@ -19,7 +19,7 @@ var Color = require('../color'); var Titles = require('../titles'); var Cartesian = require('../../plots/cartesian'); -var Axes = require('../../plots/cartesian/axes'); +var axisIDs = require('../../plots/cartesian/axis_ids'); var dragElement = require('../dragelement'); var setCursor = require('../../lib/setcursor'); @@ -77,8 +77,8 @@ module.exports = function(gd) { rangeSliders.each(function(axisOpts) { var rangeSlider = d3.select(this); var opts = axisOpts[constants.name]; - var oppAxisOpts = fullLayout[Axes.id2name(axisOpts.anchor)]; - var oppAxisRangeOpts = opts[Axes.id2name(axisOpts.anchor)]; + var oppAxisOpts = fullLayout[axisIDs.id2name(axisOpts.anchor)]; + var oppAxisRangeOpts = opts[axisIDs.id2name(axisOpts.anchor)]; // update range // Expand slider range to the axis range @@ -102,12 +102,7 @@ module.exports = function(gd) { var domain = axisOpts.domain; var tickHeight = (axisOpts._boundingBox || {}).height || 0; - var oppBottom = Infinity; - var subplotData = Axes.getSubplots(gd, axisOpts); - for(var i = 0; i < subplotData.length; i++) { - var oppAxis = Axes.getFromId(gd, subplotData[i].substr(subplotData[i].indexOf('y'))); - oppBottom = Math.min(oppBottom, oppAxis.domain[0]); - } + var oppBottom = opts._oppBottom; opts._width = graphSize.w * (domain[1] - domain[0]); @@ -366,11 +361,10 @@ function addClipPath(rangeSlider, gd, axisOpts, opts) { } function drawRangePlot(rangeSlider, gd, axisOpts, opts) { - var subplotData = Axes.getSubplots(gd, axisOpts), - calcData = gd.calcdata; + var calcData = gd.calcdata; var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName) - .data(subplotData, Lib.identity); + .data(axisOpts._subplotsWith, Lib.identity); rangePlots.enter().append('g') .attr('class', function(id) { return constants.rangePlotClassName + ' ' + id; }) @@ -386,7 +380,7 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) { var plotgroup = d3.select(this), isMainPlot = (i === 0); - var oppAxisOpts = Axes.getFromId(gd, id, 'y'), + var oppAxisOpts = axisIDs.getFromId(gd, id, 'y'), oppAxisName = oppAxisOpts._name, oppAxisRangeOpts = opts[oppAxisName]; diff --git a/src/components/rangeslider/helpers.js b/src/components/rangeslider/helpers.js index b4188f5239c..6009d0d51a1 100644 --- a/src/components/rangeslider/helpers.js +++ b/src/components/rangeslider/helpers.js @@ -8,7 +8,7 @@ 'use strict'; -var Axes = require('../../plots/cartesian/axes'); +var axisIDs = require('../../plots/cartesian/axis_ids'); var constants = require('./constants'); var name = constants.name; @@ -19,7 +19,7 @@ function isVisible(ax) { exports.isVisible = isVisible; exports.makeData = function(fullLayout) { - var axes = Axes.list({ _fullLayout: fullLayout }, 'x', true); + var axes = axisIDs.list({ _fullLayout: fullLayout }, 'x', true); var margin = fullLayout.margin; var rangeSliderData = []; @@ -45,12 +45,13 @@ exports.autoMarginOpts = function(gd, ax) { var opts = ax[name]; var oppBottom = Infinity; - var subplotData = Axes.getSubplots(gd, ax); - for(var j = 0; j < subplotData.length; j++) { - var subplotj = subplotData[j]; - var oppAxis = Axes.getFromId(gd, subplotj.substr(subplotj.indexOf('y'))); + var counterAxes = ax._counterAxes; + for(var j = 0; j < counterAxes.length; j++) { + var counterId = counterAxes[j]; + var oppAxis = axisIDs.getFromId(gd, counterId); oppBottom = Math.min(oppBottom, oppAxis.domain[0]); } + opts._oppBottom = oppBottom; var tickHeight = (ax.side === 'bottom' && ax._boundingBox.height) || 0; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 0be3e7972ba..f413741bc49 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1467,6 +1467,9 @@ axes.getTickFormat = function(ax) { // as an array of items like 'xy', 'x2y', 'x2y2'... // sorted by x (x,x2,x3...) then y // optionally restrict to only subplots containing axis object ax +// +// NOTE: this is currently only used OUTSIDE plotly.js (toolpanel, webapp) +// ideally we get rid of it there (or just copy this there) and remove it here axes.getSubplots = function(gd, ax) { var subplotObj = gd._fullLayout._subplots; var allSubplots = subplotObj.cartesian.concat(subplotObj.gl2d || []); @@ -1485,6 +1488,8 @@ axes.getSubplots = function(gd, ax) { }; // find all subplots with axis 'ax' +// NOTE: this is only used in axes.getSubplots (only used outside plotly.js) and +// gl2d/convert (where it restricts axis subplots to only those with gl2d) axes.findSubplotsWithAxis = function(subplots, ax) { var axMatch = new RegExp( (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$') @@ -1631,7 +1636,7 @@ axes.drawOne = function(gd, ax, opts) { var mainMirrorPosition = ax._mainMirrorPosition; var mainPlotinfo = fullLayout._plots[mainSubplot]; var mainAxLayer = mainPlotinfo[axLetter + 'axislayer']; - var subplotsWithAx = axes.getSubplots(gd, ax); + var subplotsWithAx = ax._subplotsWith; var vals = ax._vals = axes.calcTicks(ax); diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index f9c7bec1f84..6bf2f652fc6 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -156,6 +156,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; }); axLayoutOut._annIndices = []; axLayoutOut._shapeIndices = []; + axLayoutOut._subplotsWith = []; + axLayoutOut._counterAxes = []; // set up some private properties axLayoutOut._name = axLayoutOut._attr = axName; diff --git a/src/plots/plots.js b/src/plots/plots.js index 2ed4c4fe740..aa52e9e05ef 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -811,6 +811,12 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa plotinfo.id = id; } + // add these axis ids to each others' subplot lists + xaxis._counterAxes.push(yaxis._id); + yaxis._counterAxes.push(xaxis._id); + xaxis._subplotsWith.push(id); + yaxis._subplotsWith.push(id); + // update x and y axis layout object refs plotinfo.xaxis = xaxis; plotinfo.yaxis = yaxis; @@ -873,11 +879,13 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa // (on which the ticks & labels are drawn) for(i = 0; i < axList.length; i++) { ax = axList[i]; - ax._mainSubplot = findMainSubplot(ax, newFullLayout, ids); + ax._counterAxes.sort(axisIDs.idSort); + ax._subplotsWith.sort(Lib.subplotSort); + ax._mainSubplot = findMainSubplot(ax, newFullLayout); } }; -function findMainSubplot(ax, fullLayout, ids) { +function findMainSubplot(ax, fullLayout) { var mockGd = {_fullLayout: fullLayout}; var isX = ax._id.charAt(0) === 'x'; @@ -897,19 +905,15 @@ function findMainSubplot(ax, fullLayout, ids) { if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) { mainSubplotID = ''; - for(var j = 0; j < ids.length; j++) { - var id = ids[j]; - var yIndex = id.indexOf('y'); - var idPart = isX ? id.substr(0, yIndex) : id.substr(yIndex); - var counterPart = isX ? id.substr(yIndex) : id.substr(0, yIndex); - - if(idPart === ax._id) { - if(!nextBestMainSubplotID) nextBestMainSubplotID = id; - var counterAx = axisIDs.getFromId(mockGd, counterPart); - if(anchorID && counterAx.overlaying === anchorID) { - mainSubplotID = id; - break; - } + var counterIDs = ax._counterAxes; + for(var j = 0; j < counterIDs.length; j++) { + var counterPart = counterIDs[j]; + var id = isX ? (ax._id + counterPart) : (counterPart + ax._id); + if(!nextBestMainSubplotID) nextBestMainSubplotID = id; + var counterAx = axisIDs.getFromId(mockGd, counterPart); + if(anchorID && counterAx.overlaying === anchorID) { + mainSubplotID = id; + break; } } }