plotly.js
Version:
The open source javascript graphing library that powers plotly
386 lines (316 loc) • 13.2 kB
JavaScript
/**
* Copyright 2012-2020, 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 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._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, fullData);
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 = {
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]
};
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', unifiedHover ? 'hovered data' : undefined);
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;
}
handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
letter: axLetter,
counterAxes: counterAxes[axLetter],
overlayableAxes: getOverlayableAxes(axLetter, axName),
grid: layoutOut.grid
});
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
});
};