'use strict'; var d3 = require('@plotly/d3'); var Registry = require('../registry'); var Plots = require('../plots/plots'); var Lib = require('../lib'); var svgTextUtils = require('../lib/svg_text_utils'); var clearGlCanvases = require('../lib/clear_gl_canvases'); 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 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; var SVG_TEXT_ANCHOR_START = 'start'; var SVG_TEXT_ANCHOR_MIDDLE = 'middle'; var SVG_TEXT_ANCHOR_END = 'end'; var zindexSeparator = require('../plots/cartesian/constants').zindexSeparator; exports.layoutStyles = function(gd) { return Lib.syncOrAsync([Plots.doAutoMargin, lsInner], gd); }; function overlappingDomain(xDomain, yDomain, domains) { for(var i = 0; i < domains.length; i++) { var existingX = domains[i][0]; var existingY = domains[i][1]; if(existingX[0] >= xDomain[1] || existingX[1] <= xDomain[0]) { continue; } if(existingY[0] < yDomain[1] && existingY[1] > yDomain[0]) { return true; } } return false; } function lsInner(gd) { var fullLayout = gd._fullLayout; var gs = fullLayout._size; var pad = gs.p; var axList = Axes.list(gd, '', true); 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', height: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroHeight && !gd.layout.height) ? '100%' : fullLayout.height + 'px' }) .selectAll('.main-svg') .call(Drawing.setSize, fullLayout.width, fullLayout.height); gd._context.setBackground(gd, fullLayout.paper_bgcolor); exports.drawMainTitle(gd); ModeBar.manage(gd); // _has('cartesian') means SVG specifically, not GL2D - but GL2D // can still get here because it makes some of the SVG structure // for shared features like selections. if(!fullLayout._has('cartesian')) { return Plots.previousPromises(gd); } function getLinePosition(ax, counterAx, side) { var lwHalf = ax._lw / 2; if(ax._id.charAt(0) === 'x') { if(!counterAx) return gs.t + gs.h * (1 - (ax.position || 0)) + (lwHalf % 1); else if(side === 'top') return counterAx._offset - pad - lwHalf; return counterAx._offset + counterAx._length + pad + lwHalf; } if(!counterAx) return gs.l + gs.w * (ax.position || 0) + (lwHalf % 1); else if(side === 'right') return counterAx._offset + counterAx._length + pad + lwHalf; return counterAx._offset - pad - lwHalf; } // some preparation of axis position info for(i = 0; i < axList.length; i++) { ax = axList[i]; var counterAx = ax._anchorAxis; // clear axis line positions, to be set in the subplot loop below ax._linepositions = {}; // stash crispRounded linewidth so we don't need to pass gd all over the place ax._lw = Drawing.crispRound(gd, ax.linewidth, 1); // figure out the main axis line and main mirror line position. // it's easier to follow the logic if we handle these separately from // ax._linepositions, which are only used by mirror=allticks // for non-main-subplot ticks, and mirror=all(ticks)? for zero line // hiding logic ax._mainLinePosition = getLinePosition(ax, counterAx, ax.side); ax._mainMirrorPosition = (ax.mirror && counterAx) ? getLinePosition(ax, counterAx, alignmentConstants.OPPOSITE_SIDE[ax.side]) : null; } // figure out which backgrounds we need to draw, // and in which layers to put them var lowerBackgroundIDs = []; var backgroundIds = []; var lowerDomains = []; // no need to draw background when paper and plot color are the same color, // activate mode just for large splom (which benefit the most from this // optimization), but this could apply to all cartesian subplots. var noNeedForBg = ( Color.opacity(fullLayout.paper_bgcolor) === 1 && Color.opacity(fullLayout.plot_bgcolor) === 1 && fullLayout.paper_bgcolor === fullLayout.plot_bgcolor ); for(subplot in fullLayout._plots) { plotinfo = fullLayout._plots[subplot]; if(plotinfo.mainplot) { // mainplot is a reference to the main plot this one is overlaid on // so if it exists, this is an overlaid plot and we don't need to // give it its own background if(plotinfo.bg) { plotinfo.bg.remove(); } plotinfo.bg = undefined; } else { var xDomain = plotinfo.xaxis.domain; var yDomain = plotinfo.yaxis.domain; var plotgroup = plotinfo.plotgroup; if(overlappingDomain(xDomain, yDomain, lowerDomains) && subplot.indexOf(zindexSeparator) === -1) { var pgNode = plotgroup.node(); var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg'); pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]); backgroundIds.push(subplot); } else { plotgroup.select('rect.bg').remove(); lowerDomains.push([xDomain, yDomain]); if(!noNeedForBg) { lowerBackgroundIDs.push(subplot); backgroundIds.push(subplot); } } } } // now create all the lower-layer backgrounds at once now that // we have the list of subplots that need them var lowerBackgrounds = fullLayout._bgLayer.selectAll('.bg') .data(lowerBackgroundIDs); lowerBackgrounds.enter().append('rect') .classed('bg', true); lowerBackgrounds.exit().remove(); lowerBackgrounds.each(function(subplot) { fullLayout._plots[subplot].bg = d3.select(this); }); // style all backgrounds for(i = 0; i < backgroundIds.length; i++) { plotinfo = fullLayout._plots[backgroundIds[i]]; xa = plotinfo.xaxis; ya = plotinfo.yaxis; if(plotinfo.bg && xa._offset !== undefined && ya._offset !== undefined) { plotinfo.bg .call(Drawing.setRect, xa._offset - pad, ya._offset - pad, xa._length + 2 * pad, ya._length + 2 * pad) .call(Color.fill, fullLayout.plot_bgcolor) .style('stroke-width', 0); } } if(!fullLayout._hasOnlyLargeSploms) { for(subplot in fullLayout._plots) { plotinfo = fullLayout._plots[subplot]; xa = plotinfo.xaxis; ya = plotinfo.yaxis; // Clip so that data only shows up on the plot area. var clipId = plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot'; var plotClip = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) { s.classed('plotclip', true) .append('rect'); }); plotinfo.clipRect = plotClip.select('rect').attr({ width: xa._length, height: ya._length }); Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset); var plotClipId; var layerClipId; if(plotinfo._hasClipOnAxisFalse) { plotClipId = null; layerClipId = clipId; } else { plotClipId = clipId; layerClipId = null; } Drawing.setClipUrl(plotinfo.plot, plotClipId, gd); // stash layer clipId value (null or same as clipId) // to DRY up Drawing.setClipUrl calls on trace-module and trace layers // downstream plotinfo.layerClipId = layerClipId; } } var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop, leftYLineWidth, rightYLineWidth; var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight, connectYBottom, connectYTop; var extraSubplot; function xLinePath(y) { return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight; } function xLinePathFree(y) { return 'M' + xa._offset + ',' + y + 'h' + xa._length; } function yLinePath(x) { return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom; } function yLinePathFree(x) { if(ya._shift !== undefined) { x += ya._shift; } return 'M' + x + ',' + ya._offset + 'v' + ya._length; } function mainPath(ax, pathFn, pathFnFree) { if(!ax.showline || subplot !== ax._mainSubplot) return ''; if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition); var out = pathFn(ax._mainLinePosition); if(ax.mirror) out += pathFn(ax._mainMirrorPosition); return out; } for(subplot in fullLayout._plots) { plotinfo = fullLayout._plots[subplot]; xa = plotinfo.xaxis; ya = plotinfo.yaxis; /* * x lines get longer where they meet y lines, to make a crisp corner. * The x lines get the padding (margin.pad) plus the y line width to * fill up the corner nicely. Free x lines are excluded - they always * span exactly the data area of the plot * * | XXXXX * | XXXXX * | * +------ * x1 * ----- * x2 */ var xPath = 'M0,0'; if(shouldShowLinesOrTicks(xa, subplot)) { leftYLineWidth = findCounterAxisLineWidth(xa, 'left', ya, axList); xLinesXLeft = xa._offset - (leftYLineWidth ? (pad + leftYLineWidth) : 0); rightYLineWidth = findCounterAxisLineWidth(xa, 'right', ya, axList); xLinesXRight = xa._offset + xa._length + (rightYLineWidth ? (pad + rightYLineWidth) : 0); xLinesYBottom = getLinePosition(xa, ya, 'bottom'); xLinesYTop = getLinePosition(xa, ya, 'top'); // save axis line positions for extra ticks to reference // each subplot that gets ticks from "allticks" gets an entry: // [left or bottom, right or top] extraSubplot = (!xa._anchorAxis || subplot !== xa._mainSubplot); if(extraSubplot && (xa.mirror === 'allticks' || xa.mirror === 'all')) { xa._linepositions[subplot] = [xLinesYBottom, xLinesYTop]; } xPath = mainPath(xa, xLinePath, xLinePathFree); if(extraSubplot && xa.showline && (xa.mirror === 'all' || xa.mirror === 'allticks')) { xPath += xLinePath(xLinesYBottom) + xLinePath(xLinesYTop); } plotinfo.xlines .style('stroke-width', xa._lw + 'px') .call(Color.stroke, xa.showline ? xa.linecolor : 'rgba(0,0,0,0)'); } plotinfo.xlines.attr('d', xPath); /* * y lines that meet x axes get longer only by margin.pad, because * the x axes fill in the corner space. Free y axes, like free x axes, * always span exactly the data area of the plot * * | | XXXX * y2| y1| XXXX * | | XXXX * | * +----- */ var yPath = 'M0,0'; if(shouldShowLinesOrTicks(ya, subplot)) { connectYBottom = findCounterAxisLineWidth(ya, 'bottom', xa, axList); yLinesYBottom = ya._offset + ya._length + (connectYBottom ? pad : 0); connectYTop = findCounterAxisLineWidth(ya, 'top', xa, axList); yLinesYTop = ya._offset - (connectYTop ? pad : 0); yLinesXLeft = getLinePosition(ya, xa, 'left'); yLinesXRight = getLinePosition(ya, xa, 'right'); extraSubplot = (!ya._anchorAxis || subplot !== ya._mainSubplot); if(extraSubplot && (ya.mirror === 'allticks' || ya.mirror === 'all')) { ya._linepositions[subplot] = [yLinesXLeft, yLinesXRight]; } yPath = mainPath(ya, yLinePath, yLinePathFree); if(extraSubplot && ya.showline && (ya.mirror === 'all' || ya.mirror === 'allticks')) { yPath += yLinePath(yLinesXLeft) + yLinePath(yLinesXRight); } plotinfo.ylines .style('stroke-width', ya._lw + 'px') .call(Color.stroke, ya.showline ? ya.linecolor : 'rgba(0,0,0,0)'); } plotinfo.ylines.attr('d', yPath); } Axes.makeClipPaths(gd); return Plots.previousPromises(gd); } function shouldShowLinesOrTicks(ax, subplot) { return (ax.ticks || ax.showline) && (subplot === ax._mainSubplot || ax.mirror === 'all' || ax.mirror === 'allticks'); } /* * should we draw a line on counterAx at this side of ax? * It's assumed that counterAx is known to overlay the subplot we're working on * but it may not be its main axis. */ function shouldShowLineThisSide(ax, side, counterAx) { // does counterAx get a line at all? if(!counterAx.showline || !counterAx._lw) return false; // are we drawing *all* lines for counterAx? if(counterAx.mirror === 'all' || counterAx.mirror === 'allticks') return true; var anchorAx = counterAx._anchorAxis; // is this a free axis? free axes can only have a subplot side-line with all(ticks)? mirroring if(!anchorAx) return false; // in order to handle cases where the user forgot to anchor this axis correctly // (because its default anchor has the same domain on the relevant end) // check whether the relevant position is the same. var sideIndex = alignmentConstants.FROM_BL[side]; if(counterAx.side === side) { return anchorAx.domain[sideIndex] === ax.domain[sideIndex]; } return counterAx.mirror && anchorAx.domain[1 - sideIndex] === ax.domain[1 - sideIndex]; } /* * Is there another axis intersecting `side` end of `ax`? * First look at `counterAx` (the axis for this subplot), * then at all other potential counteraxes on or overlaying this subplot. * Take the line width from the first one that has a line. */ function findCounterAxisLineWidth(ax, side, counterAx, axList) { if(shouldShowLineThisSide(ax, side, counterAx)) { return counterAx._lw; } for(var i = 0; i < axList.length; i++) { var axi = axList[i]; if(axi._mainAxis === counterAx._mainAxis && shouldShowLineThisSide(ax, side, axi)) { return axi._lw; } } return 0; } exports.drawMainTitle = function(gd) { var title = gd._fullLayout.title; var fullLayout = gd._fullLayout; var textAnchor = getMainTitleTextAnchor(fullLayout); var dy = getMainTitleDy(fullLayout); var y = getMainTitleY(fullLayout, dy); var x = getMainTitleX(fullLayout, textAnchor); Titles.draw(gd, 'gtitle', { propContainer: fullLayout, propName: 'title.text', subtitlePropName: 'title.subtitle.text', placeholder: fullLayout._dfltTitle.plot, subtitlePlaceholder: fullLayout._dfltTitle.subtitle, attributes: ({ x: x, y: y, 'text-anchor': textAnchor, dy: dy }), }); if(title.text && title.automargin) { var titleObj = d3.selectAll('.gtitle'); var titleHeight = Drawing.bBox(d3.selectAll('.g-gtitle').node()).height; var pushMargin = needsMarginPush(gd, title, titleHeight); if(pushMargin > 0) { applyTitleAutoMargin(gd, y, pushMargin, titleHeight); // Re-position the title once we know where it needs to be titleObj.attr({ x: x, y: y, 'text-anchor': textAnchor, dy: getMainTitleDyAdj(title.yanchor) }).call(svgTextUtils.positionText, x, y); var extraLines = (title.text.match(svgTextUtils.BR_TAG_ALL) || []).length; if(extraLines) { var delta = alignmentConstants.LINE_SPACING * extraLines + alignmentConstants.MID_SHIFT; if(title.y === 0) { delta = -delta; } titleObj.selectAll('.line').each(function() { var newDy = +(this.getAttribute('dy')).slice(0, -2) - delta + 'em'; this.setAttribute('dy', newDy); }); } // If there is a subtitle var subtitleObj = d3.selectAll('.gtitle-subtitle'); if(subtitleObj.node()) { // Get bottom edge of title bounding box var titleBB = titleObj.node().getBBox(); var titleBottom = titleBB.y + titleBB.height; var subtitleY = titleBottom + Titles.SUBTITLE_PADDING_EM * title.subtitle.font.size; subtitleObj.attr({ x: x, y: subtitleY, 'text-anchor': textAnchor, dy: getMainTitleDyAdj(title.yanchor) }).call(svgTextUtils.positionText, x, subtitleY); } } } }; function isOutsideContainer(gd, title, position, y, titleHeight) { var plotHeight = title.yref === 'paper' ? gd._fullLayout._size.h : gd._fullLayout.height; var yPosTop = Lib.isTopAnchor(title) ? y : y - titleHeight; // Standardize to the top of the title var yPosRel = position === 'b' ? plotHeight - yPosTop : yPosTop; // Position relative to the top or bottom of plot if((Lib.isTopAnchor(title) && position === 't') || Lib.isBottomAnchor(title) && position === 'b') { return false; } else { return yPosRel < titleHeight; } } function containerPushVal(position, titleY, titleYanchor, height, titleDepth) { var push = 0; if(titleYanchor === 'middle') { push += titleDepth / 2; } if(position === 't') { if(titleYanchor === 'top') { push += titleDepth; } push += (height - titleY * height); } else { if(titleYanchor === 'bottom') { push += titleDepth; } push += titleY * height; } return push; } function needsMarginPush(gd, title, titleHeight) { var titleY = title.y; var titleYanchor = title.yanchor; var position = titleY > 0.5 ? 't' : 'b'; var curMargin = gd._fullLayout.margin[position]; var pushMargin = 0; if(title.yref === 'paper') { pushMargin = ( titleHeight + title.pad.t + title.pad.b ); } else if(title.yref === 'container') { pushMargin = ( containerPushVal(position, titleY, titleYanchor, gd._fullLayout.height, titleHeight) + title.pad.t + title.pad.b ); } if(pushMargin > curMargin) { return pushMargin; } return 0; } function applyTitleAutoMargin(gd, y, pushMargin, titleHeight) { var titleID = 'title.automargin'; var title = gd._fullLayout.title; var position = title.y > 0.5 ? 't' : 'b'; var push = { x: title.x, y: title.y, t: 0, b: 0 }; var reservedPush = {}; if(title.yref === 'paper' && isOutsideContainer(gd, title, position, y, titleHeight)) { push[position] = pushMargin; } else if(title.yref === 'container') { reservedPush[position] = pushMargin; gd._fullLayout._reservedMargin[titleID] = reservedPush; } Plots.allowAutoMargin(gd, titleID); Plots.autoMargin(gd, titleID, push); } function getMainTitleX(fullLayout, textAnchor) { var title = fullLayout.title; var gs = fullLayout._size; var hPadShift = 0; if(textAnchor === SVG_TEXT_ANCHOR_START) { hPadShift = title.pad.l; } else if(textAnchor === SVG_TEXT_ANCHOR_END) { hPadShift = -title.pad.r; } switch(title.xref) { case 'paper': return gs.l + gs.w * title.x + hPadShift; case 'container': default: return fullLayout.width * title.x + hPadShift; } } function getMainTitleY(fullLayout, dy) { var title = fullLayout.title; var gs = fullLayout._size; var vPadShift = 0; if(dy === '0em' || !dy) { vPadShift = -title.pad.b; } else if(dy === alignmentConstants.CAP_SHIFT + 'em') { vPadShift = title.pad.t; } if(title.y === 'auto') { return gs.t / 2; } else { switch(title.yref) { case 'paper': return gs.t + gs.h - gs.h * title.y + vPadShift; case 'container': default: return fullLayout.height - fullLayout.height * title.y + vPadShift; } } } function getMainTitleDyAdj(yanchor) { if(yanchor === 'top') { return alignmentConstants.CAP_SHIFT + 0.3 + 'em'; } else if(yanchor === 'bottom') { return '-0.3em'; } else { return alignmentConstants.MID_SHIFT + 'em'; } } function getMainTitleTextAnchor(fullLayout) { var title = fullLayout.title; var textAnchor = SVG_TEXT_ANCHOR_MIDDLE; if(Lib.isRightAnchor(title)) { textAnchor = SVG_TEXT_ANCHOR_END; } else if(Lib.isLeftAnchor(title)) { textAnchor = SVG_TEXT_ANCHOR_START; } return textAnchor; } function getMainTitleDy(fullLayout) { var title = fullLayout.title; var dy = '0em'; if(Lib.isTopAnchor(title)) { dy = alignmentConstants.CAP_SHIFT + 'em'; } else if(Lib.isMiddleAnchor(title)) { dy = alignmentConstants.MID_SHIFT + 'em'; } return dy; } exports.doTraceStyle = function(gd) { var calcdata = gd.calcdata; var editStyleCalls = []; var i; for(i = 0; i < calcdata.length; i++) { var cd = calcdata[i]; var cd0 = cd[0] || {}; var trace = cd0.trace || {}; var _module = trace._module || {}; // See if we need to do arraysToCalcdata // call it regardless of what change we made, in case // supplyDefaults brought in an array that was already // in gd.data but not in gd._fullData previously var arraysToCalcdata = _module.arraysToCalcdata; if(arraysToCalcdata) arraysToCalcdata(cd, trace); var editStyle = _module.editStyle; if(editStyle) editStyleCalls.push({fn: editStyle, cd0: cd0}); } if(editStyleCalls.length) { for(i = 0; i < editStyleCalls.length; i++) { var edit = editStyleCalls[i]; edit.fn(gd, edit.cd0); } clearGlCanvases(gd); exports.redrawReglTraces(gd); } Plots.style(gd); Registry.getComponentMethod('legend', 'draw')(gd); return Plots.previousPromises(gd); }; exports.doColorBars = function(gd) { Registry.getComponentMethod('colorbar', 'draw')(gd); return Plots.previousPromises(gd); }; // force plot() to redo the layout and replot with the modified layout exports.layoutReplot = function(gd) { var layout = gd.layout; gd.layout = undefined; return Registry.call('_doPlot', gd, '', layout); }; exports.doLegend = function(gd) { Registry.getComponentMethod('legend', 'draw')(gd); return Plots.previousPromises(gd); }; exports.doTicksRelayout = function(gd) { Axes.draw(gd, 'redraw'); if(gd._fullLayout._hasOnlyLargeSploms) { Registry.subplotsRegistry.splom.updateGrid(gd); clearGlCanvases(gd); exports.redrawReglTraces(gd); } exports.drawMainTitle(gd); return Plots.previousPromises(gd); }; exports.doModeBar = function(gd) { var fullLayout = gd._fullLayout; ModeBar.manage(gd); for(var i = 0; i < fullLayout._basePlotModules.length; i++) { var updateFx = fullLayout._basePlotModules[i].updateFx; if(updateFx) updateFx(gd); } return Plots.previousPromises(gd); }; exports.doCamera = function(gd) { var fullLayout = gd._fullLayout; var sceneIds = fullLayout._subplots.gl3d; for(var i = 0; i < sceneIds.length; i++) { var sceneLayout = fullLayout[sceneIds[i]]; var scene = sceneLayout._scene; scene.setViewport(sceneLayout); } }; exports.drawData = function(gd) { var fullLayout = gd._fullLayout; clearGlCanvases(gd); // loop over the base plot modules present on graph var basePlotModules = fullLayout._basePlotModules; for(var i = 0; i < basePlotModules.length; i++) { basePlotModules[i].plot(gd); } exports.redrawReglTraces(gd); // styling separate from drawing Plots.style(gd); // draw components that can be drawn on axes, // and that do not push the margins Registry.getComponentMethod('selections', 'draw')(gd); Registry.getComponentMethod('shapes', 'draw')(gd); Registry.getComponentMethod('annotations', 'draw')(gd); Registry.getComponentMethod('images', 'draw')(gd); // Mark the first render as complete fullLayout._replotting = false; return Plots.previousPromises(gd); }; // Draw (or redraw) all regl-based traces in one go, // useful during drag and selection where buffers of targeted traces are updated, // but all traces need to be redrawn following clearGlCanvases. // // Note that _module.plot for regl trace does NOT draw things // on the canvas, they only update the buffers. // Drawing is perform here. // // TODO try adding per-subplot option using gl.SCISSOR_TEST for // non-overlaying, disjoint subplots. // // TODO try to include parcoords in here. // https://github.com/plotly/plotly.js/issues/3069 exports.redrawReglTraces = function(gd) { var fullLayout = gd._fullLayout; if(fullLayout._has('regl')) { var fullData = gd._fullData; var cartesianIds = []; var polarIds = []; var i, sp; if(fullLayout._hasOnlyLargeSploms) { fullLayout._splomGrid.draw(); } // N.B. // - Loop over fullData (not _splomScenes) to preserve splom trace-to-trace ordering // - Fill list if subplot ids (instead of fullLayout._subplots) to handle cases where all traces // of a given module are `visible !== true` for(i = 0; i < fullData.length; i++) { var trace = fullData[i]; if(trace.visible === true && trace._length !== 0) { if(trace.type === 'splom') { fullLayout._splomScenes[trace.uid].draw(); } else if(trace.type === 'scattergl') { Lib.pushUnique(cartesianIds, trace.xaxis + trace.yaxis); } else if(trace.type === 'scatterpolargl') { Lib.pushUnique(polarIds, trace.subplot); } } } for(i = 0; i < cartesianIds.length; i++) { sp = fullLayout._plots[cartesianIds[i]]; if(sp._scene) sp._scene.draw(); } for(i = 0; i < polarIds.length; i++) { sp = fullLayout[polarIds[i]]._subplot; if(sp._scene) sp._scene.draw(); } } }; exports.doAutoRangeAndConstraints = function(gd) { var axList = Axes.list(gd, '', true); var ax; var autoRangeDone = {}; for(var i = 0; i < axList.length; i++) { ax = axList[i]; if(!autoRangeDone[ax._id]) { autoRangeDone[ax._id] = 1; cleanAxisConstraints(gd, ax); doAutoRange(gd, ax); // For matching axes, just propagate this autorange to the group. // The extra arg to doAutoRange avoids recalculating the range, // since doAutoRange by itself accounts for all matching axes. but // there are other side-effects of doAutoRange that we still want. var matchGroup = ax._matchGroup; if(matchGroup) { for(var id2 in matchGroup) { var ax2 = Axes.getFromId(gd, id2); doAutoRange(gd, ax2, ax.range); autoRangeDone[id2] = 1; } } } } 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) { // TODO: rangesliders really belong in marginPushers but they need to be // drawn after data - can we at least get the margin pushing part separated // out and done earlier? Registry.getComponentMethod('rangeslider', 'draw')(gd); // TODO: rangeselector only needs to be here (in addition to drawMarginPushers) // because the margins need to be fully determined before we can call // autorange and update axis ranges (which rangeselector needs to know which // button is active). Can we break out its automargin step from its draw step? Registry.getComponentMethod('rangeselector', 'draw')(gd); }; exports.drawMarginPushers = function(gd) { Registry.getComponentMethod('legend', 'draw')(gd); Registry.getComponentMethod('rangeselector', 'draw')(gd); Registry.getComponentMethod('sliders', 'draw')(gd); Registry.getComponentMethod('updatemenus', 'draw')(gd); Registry.getComponentMethod('colorbar', 'draw')(gd); };