'use strict'; var Lib = require('../../lib'); var Color = require('../../components/color'); var isUnifiedHover = require('../../components/fx/helpers').isUnifiedHover; var handleHoverModeDefaults = require('../../components/fx/hovermode_defaults'); var Template = require('../../plot_api/plot_template'); var basePlotLayoutAttributes = require('../layout_attributes'); var layoutAttributes = require('./layout_attributes'); var handleTypeDefaults = require('./type_defaults'); var handleAxisDefaults = require('./axis_defaults'); var constraints = require('./constraints'); var handlePositionDefaults = require('./position_defaults'); var axisIds = require('./axis_ids'); var id2name = axisIds.id2name; var name2id = axisIds.name2id; var AX_ID_PATTERN = require('./constants').AX_ID_PATTERN; var Registry = require('../../registry'); var traceIs = Registry.traceIs; var getComponentMethod = Registry.getComponentMethod; function appendList(cont, k, item) { if(Array.isArray(cont[k])) cont[k].push(item); else cont[k] = [item]; } module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var autotypenumbersDflt = layoutOut.autotypenumbers; var ax2traces = {}; var xaMayHide = {}; var yaMayHide = {}; var xaMustDisplay = {}; var yaMustDisplay = {}; var yaMustNotReverse = {}; var yaMayReverse = {}; var axHasImage = {}; var outerTicks = {}; var noGrids = {}; var i, j; // look for axes in the data for(i = 0; i < fullData.length; i++) { var trace = fullData[i]; if(!traceIs(trace, 'cartesian') && !traceIs(trace, 'gl2d')) continue; var xaName; if(trace.xaxis) { xaName = id2name(trace.xaxis); appendList(ax2traces, xaName, trace); } else if(trace.xaxes) { for(j = 0; j < trace.xaxes.length; j++) { appendList(ax2traces, id2name(trace.xaxes[j]), trace); } } var yaName; if(trace.yaxis) { yaName = id2name(trace.yaxis); appendList(ax2traces, yaName, trace); } else if(trace.yaxes) { for(j = 0; j < trace.yaxes.length; j++) { appendList(ax2traces, id2name(trace.yaxes[j]), trace); } } // logic for funnels if(trace.type === 'funnel') { if(trace.orientation === 'h') { if(xaName) xaMayHide[xaName] = true; if(yaName) yaMayReverse[yaName] = true; } else { if(yaName) yaMayHide[yaName] = true; } } else if(trace.type === 'image') { if(yaName) axHasImage[yaName] = true; if(xaName) axHasImage[xaName] = true; } else { if(yaName) { yaMustDisplay[yaName] = true; yaMustNotReverse[yaName] = true; } if(!traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) { if(xaName) xaMustDisplay[xaName] = true; } } // Two things trigger axis visibility: // 1. is not carpet // 2. carpet that's not cheater // The above check for definitely-not-cheater is not adequate. This // second list tracks which axes *could* be a cheater so that the // full condition triggering hiding is: // *could* be a cheater and *is not definitely visible* if(trace.type === 'carpet' && trace._cheater) { if(xaName) xaMayHide[xaName] = true; } // check for default formatting tweaks if(traceIs(trace, '2dMap')) { outerTicks[xaName] = true; outerTicks[yaName] = true; } if(traceIs(trace, 'oriented')) { var positionAxis = trace.orientation === 'h' ? yaName : xaName; noGrids[positionAxis] = true; } } var subplots = layoutOut._subplots; var xIds = subplots.xaxis; var yIds = subplots.yaxis; var xNames = Lib.simpleMap(xIds, id2name); var yNames = Lib.simpleMap(yIds, id2name); var axNames = xNames.concat(yNames); // plot_bgcolor only makes sense if there's a (2D) plot! // TODO: bgcolor for each subplot, to inherit from the main one var plotBgColor = Color.background; if(xIds.length && yIds.length) { plotBgColor = Lib.coerce(layoutIn, layoutOut, basePlotLayoutAttributes, 'plot_bgcolor'); } var bgColor = Color.combine(plotBgColor, layoutOut.paper_bgcolor); // name of single axis (e.g. 'xaxis', 'yaxis2') var axName; // id of single axis (e.g. 'y', 'x5') var axId; // 'x' or 'y' var axLetter; // input layout axis container var axLayoutIn; // full layout axis container var axLayoutOut; function newAxLayoutOut() { var traces = ax2traces[axName] || []; axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; }); axLayoutOut._annIndices = []; axLayoutOut._shapeIndices = []; axLayoutOut._selectionIndices = []; axLayoutOut._imgIndices = []; axLayoutOut._subplotsWith = []; axLayoutOut._counterAxes = []; axLayoutOut._name = axLayoutOut._attr = axName; axLayoutOut._id = axId; } function coerce(attr, dflt) { return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt); } function coerce2(attr, dflt) { return Lib.coerce2(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt); } function getCounterAxes(axLetter) { return (axLetter === 'x') ? yIds : xIds; } function getOverlayableAxes(axLetter, axName) { var list = (axLetter === 'x') ? xNames : yNames; var out = []; for(var j = 0; j < list.length; j++) { var axName2 = list[j]; if(axName2 !== axName && !(layoutIn[axName2] || {}).overlaying) { out.push(name2id(axName2)); } } return out; } // list of available counter axis names var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')}; // list of all x AND y axis ids var allAxisIds = counterAxes.x.concat(counterAxes.y); // lookup and list of axis ids that axes in axNames have a reference to, // even though they are missing from allAxisIds var missingMatchedAxisIdsLookup = {}; var missingMatchedAxisIds = []; // fill in 'missing' axis lookup when an axis is set to match an axis // not part of the allAxisIds list, save axis type so that we can propagate // it to the missing axes function addMissingMatchedAxis() { var matchesIn = axLayoutIn.matches; if(AX_ID_PATTERN.test(matchesIn) && allAxisIds.indexOf(matchesIn) === -1) { missingMatchedAxisIdsLookup[matchesIn] = axLayoutIn.type; missingMatchedAxisIds = Object.keys(missingMatchedAxisIdsLookup); } } var hovermode = handleHoverModeDefaults(layoutIn, layoutOut); var unifiedHover = isUnifiedHover(hovermode); // first pass creates the containers, determines types, and handles most of the settings for(i = 0; i < axNames.length; i++) { axName = axNames[i]; axId = name2id(axName); axLetter = axName.charAt(0); if(!Lib.isPlainObject(layoutIn[axName])) { layoutIn[axName] = {}; } axLayoutIn = layoutIn[axName]; axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis'); newAxLayoutOut(); var visibleDflt = (axLetter === 'x' && !xaMustDisplay[axName] && xaMayHide[axName]) || (axLetter === 'y' && !yaMustDisplay[axName] && yaMayHide[axName]); var reverseDflt = (axLetter === 'y' && ( (!yaMustNotReverse[axName] && yaMayReverse[axName]) || axHasImage[axName] )); var defaultOptions = { hasMinor: true, letter: axLetter, font: layoutOut.font, outerTicks: outerTicks[axName], showGrid: !noGrids[axName], data: ax2traces[axName] || [], bgColor: bgColor, calendar: layoutOut.calendar, automargin: true, visibleDflt: visibleDflt, reverseDflt: reverseDflt, autotypenumbersDflt: autotypenumbersDflt, splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId], noAutotickangles: axLetter === 'y' }; coerce('uirevision', layoutOut.uirevision); handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions); handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut); var unifiedSpike = unifiedHover && axLetter === hovermode.charAt(0); var spikecolor = coerce2('spikecolor', unifiedHover ? axLayoutOut.color : undefined); var spikethickness = coerce2('spikethickness', unifiedHover ? 1.5 : undefined); var spikedash = coerce2('spikedash', unifiedHover ? 'dot' : undefined); var spikemode = coerce2('spikemode', unifiedHover ? 'across' : undefined); var spikesnap = coerce2('spikesnap'); var showSpikes = coerce('showspikes', !!unifiedSpike || !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap); if(!showSpikes) { delete axLayoutOut.spikecolor; delete axLayoutOut.spikethickness; delete axLayoutOut.spikedash; delete axLayoutOut.spikemode; delete axLayoutOut.spikesnap; } // If it exists, the the domain of the axis for the anchor of the overlaying axis var overlayingAxis = id2name(axLayoutIn.overlaying); var overlayingAnchorDomain = [0, 1]; if(layoutOut[overlayingAxis] !== undefined) { var overlayingAnchor = id2name(layoutOut[overlayingAxis].anchor); if(layoutOut[overlayingAnchor] !== undefined) { overlayingAnchorDomain = layoutOut[overlayingAnchor].domain; } } handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, { letter: axLetter, counterAxes: counterAxes[axLetter], overlayableAxes: getOverlayableAxes(axLetter, axName), grid: layoutOut.grid, overlayingDomain: overlayingAnchorDomain }); coerce('title.standoff'); addMissingMatchedAxis(); axLayoutOut._input = axLayoutIn; } // coerce the 'missing' axes i = 0; while(i < missingMatchedAxisIds.length) { axId = missingMatchedAxisIds[i++]; axName = id2name(axId); axLetter = axName.charAt(0); if(!Lib.isPlainObject(layoutIn[axName])) { layoutIn[axName] = {}; } axLayoutIn = layoutIn[axName]; axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis'); newAxLayoutOut(); var defaultOptions2 = { letter: axLetter, font: layoutOut.font, outerTicks: outerTicks[axName], showGrid: !noGrids[axName], data: [], bgColor: bgColor, calendar: layoutOut.calendar, automargin: true, visibleDflt: false, reverseDflt: false, autotypenumbersDflt: autotypenumbersDflt, splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId] }; coerce('uirevision', layoutOut.uirevision); axLayoutOut.type = missingMatchedAxisIdsLookup[axId] || 'linear'; handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2, layoutOut); handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, { letter: axLetter, counterAxes: counterAxes[axLetter], overlayableAxes: getOverlayableAxes(axLetter, axName), grid: layoutOut.grid }); coerce('fixedrange'); addMissingMatchedAxis(); axLayoutOut._input = axLayoutIn; } // quick second pass for range slider and selector defaults var rangeSliderDefaults = getComponentMethod('rangeslider', 'handleDefaults'); var rangeSelectorDefaults = getComponentMethod('rangeselector', 'handleDefaults'); for(i = 0; i < xNames.length; i++) { axName = xNames[i]; axLayoutIn = layoutIn[axName]; axLayoutOut = layoutOut[axName]; rangeSliderDefaults(layoutIn, layoutOut, axName); if(axLayoutOut.type === 'date') { rangeSelectorDefaults( axLayoutIn, axLayoutOut, layoutOut, yNames, axLayoutOut.calendar ); } coerce('fixedrange'); } for(i = 0; i < yNames.length; i++) { axName = yNames[i]; axLayoutIn = layoutIn[axName]; axLayoutOut = layoutOut[axName]; var anchoredAxis = layoutOut[id2name(axLayoutOut.anchor)]; var fixedRangeDflt = getComponentMethod('rangeslider', 'isVisible')(anchoredAxis); coerce('fixedrange', fixedRangeDflt); } // Finally, handle scale constraints and matching axes. // // We need to do this after all axes have coerced both `type` // (so we link only axes of the same type) and // `fixedrange` (so we can avoid linking from OR TO a fixed axis). constraints.handleDefaults(layoutIn, layoutOut, { axIds: allAxisIds.concat(missingMatchedAxisIds).sort(axisIds.idSort), axHasImage: axHasImage }); };