diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js index 9246d98fa26..a424fc97f53 100644 --- a/src/components/colorbar/attributes.js +++ b/src/components/colorbar/attributes.js @@ -157,6 +157,19 @@ module.exports = overrideAll({ tickvals: axesAttrs.tickvals, ticktext: axesAttrs.ticktext, ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}), + ticklabelposition: { + valType: 'enumerated', + values: [ + 'outside', 'inside', + 'outside top', 'inside top', + 'outside bottom', 'inside bottom' + ], + dflt: 'outside', + role: 'info', + description: [ + 'Determines where tick labels are drawn.' + ].join(' ') + }, ticklen: axesAttrs.ticklen, tickwidth: axesAttrs.tickwidth, tickcolor: axesAttrs.tickcolor, diff --git a/src/components/colorbar/defaults.js b/src/components/colorbar/defaults.js index 0a501cd321d..6ad2a117784 100644 --- a/src/components/colorbar/defaults.js +++ b/src/components/colorbar/defaults.js @@ -51,10 +51,14 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) { coerce('bordercolor'); coerce('borderwidth'); coerce('bgcolor'); + var ticklabelposition = coerce('ticklabelposition'); handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear'); var opts = {outerTicks: false, font: layout.font}; + if(ticklabelposition.indexOf('inside') !== -1) { + opts.bgColor = 'black'; // could we instead use the average of colors in the scale? + } handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts); handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts); diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index 85883755a2f..676df2ec886 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -462,20 +462,19 @@ function drawColorBar(g, opts, gd) { (opts.outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0); var vals = Axes.calcTicks(ax); - var transFn = Axes.makeTransFn(ax); var tickSign = Axes.getTickSigns(ax)[2]; Axes.drawTicks(gd, ax, { vals: ax.ticks === 'inside' ? Axes.clipEnds(ax, vals) : vals, layer: axLayer, path: Axes.makeTickPath(ax, shift, tickSign), - transFn: transFn + transFn: Axes.makeTransTickFn(ax) }); return Axes.drawLabels(gd, ax, { vals: vals, layer: axLayer, - transFn: transFn, + transFn: Axes.makeTransTickLabelFn(ax), labelFns: Axes.makeLabelFns(ax, shift) }); } @@ -485,7 +484,11 @@ function drawColorBar(g, opts, gd) { // TODO: why are we redrawing multiple times now with this? // I guess autoMargin doesn't like being post-promise? function positionCB() { - var innerWidth = thickPx + opts.outlinewidth / 2 + Drawing.bBox(axLayer.node()).width; + var innerWidth = thickPx + opts.outlinewidth / 2; + if(ax.ticklabelposition.indexOf('inside') === -1) { + innerWidth += Drawing.bBox(axLayer.node()).width; + } + titleEl = titleCont.select('text'); if(titleEl.node() && !titleEl.classed(cn.jsPlaceholder)) { @@ -681,6 +684,7 @@ function mockColorBarAxis(gd, opts, zrange) { tickwidth: opts.tickwidth, tickcolor: opts.tickcolor, showticklabels: opts.showticklabels, + ticklabelposition: opts.ticklabelposition, tickfont: opts.tickfont, tickangle: opts.tickangle, tickformat: opts.tickformat, diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index e43c0daf850..a0211e4c70f 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -367,7 +367,18 @@ function plot(gd, data, layout, config) { if(hasCartesian) seq.push(positionAndAutorange); seq.push(subroutines.layoutStyles); - if(hasCartesian) seq.push(drawAxes); + if(hasCartesian) { + seq.push( + drawAxes, + function insideTickLabelsAutorange(gd) { + if(gd._fullLayout._insideTickLabelsAutorange) { + relayout(gd, gd._fullLayout._insideTickLabelsAutorange).then(function() { + gd._fullLayout._insideTickLabelsAutorange = undefined; + }); + } + } + ); + } seq.push( subroutines.drawData, @@ -381,9 +392,16 @@ function plot(gd, data, layout, config) { // calculated. Would be much better to separate margin calculations from // component drawing - see https://github.com/plotly/plotly.js/issues/2704 Plots.doAutoMargin, + saveRangeInitialForInsideTickLabels, Plots.previousPromises ); + function saveRangeInitialForInsideTickLabels(gd) { + if(gd._fullLayout._insideTickLabelsAutorange) { + if(graphWasEmpty) Axes.saveRangeInitial(gd, true); + } + } + // even if everything we did was synchronous, return a promise // so that the caller doesn't care which route we took var plotDone = Lib.syncOrAsync(seq, gd); @@ -1961,6 +1979,12 @@ function addAxRangeSequence(seq, rangesAltered) { var ax = Axes.getFromId(gd, id); axIds.push(id); + if((ax.ticklabelposition || '').indexOf('inside') !== -1) { + if(ax._anchorAxis) { + axIds.push(ax._anchorAxis._id); + } + } + if(ax._matchGroup) { for(var id2 in ax._matchGroup) { if(!rangesAltered[id2]) { @@ -2055,7 +2079,7 @@ function _relayout(gd, aobj) { // we're editing the (auto)range of, so we can tell the others constrained // to scale with them that it's OK for them to shrink var rangesAltered = {}; - var axId, ax; + var ax; function recordAlteredAxis(pleafPlus) { var axId = Axes.name2id(pleafPlus.split('.')[0]); @@ -2283,7 +2307,7 @@ function _relayout(gd, aobj) { } // figure out if we need to recalculate axis constraints - for(axId in rangesAltered) { + for(var axId in rangesAltered) { ax = Axes.getFromId(gd, axId); var group = ax && ax._constraintGroup; if(group) { diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 36d66ac20b9..bab023f3651 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -674,6 +674,7 @@ exports.doAutoRangeAndConstraints = function(gd) { for(var i = 0; i < axList.length; i++) { ax = axList[i]; + if(!autoRangeDone[ax._id]) { autoRangeDone[ax._id] = 1; cleanAxisConstraints(gd, ax); diff --git a/src/plots/cartesian/autorange.js b/src/plots/cartesian/autorange.js index 28c44197a0c..7564ac8cd3a 100644 --- a/src/plots/cartesian/autorange.js +++ b/src/plots/cartesian/autorange.js @@ -56,7 +56,8 @@ function getAutoRange(gd, ax) { var i, j; var newRange = []; - var getPad = makePadFn(ax); + var getPadMin = makePadFn(ax, 0); + var getPadMax = makePadFn(ax, 1); var extremes = concatExtremes(gd, ax); var minArray = extremes.min; var maxArray = extremes.max; @@ -97,19 +98,6 @@ function getAutoRange(gd, ax) { // don't allow padding to reduce the data to < 10% of the length var minSpan = axLen / 10; - // find axis rangebreaks in [v0,v1] and compute its length in value space - var calcBreaksLength = function(v0, v1) { - var lBreaks = 0; - if(ax.rangebreaks) { - var rangebreaksOut = ax.locateBreaks(v0, v1); - for(var i = 0; i < rangebreaksOut.length; i++) { - var brk = rangebreaksOut[i]; - lBreaks += brk.max - brk.min; - } - } - return lBreaks; - }; - var mbest = 0; var minpt, maxpt, minbest, maxbest, dp, dv; @@ -117,9 +105,9 @@ function getAutoRange(gd, ax) { minpt = minArray[i]; for(j = 0; j < maxArray.length; j++) { maxpt = maxArray[j]; - dv = maxpt.val - minpt.val - calcBreaksLength(minpt.val, maxpt.val); + dv = maxpt.val - minpt.val - calcBreaksLength(ax, minpt.val, maxpt.val); if(dv > 0) { - dp = axLen - getPad(minpt) - getPad(maxpt); + dp = axLen - getPadMin(minpt) - getPadMax(maxpt); if(dp > minSpan) { if(dv / dp > mbest) { minbest = minpt; @@ -137,8 +125,8 @@ function getAutoRange(gd, ax) { } } - function getMaxPad(prev, pt) { - return Math.max(prev, getPad(pt)); + function maximumPad(prev, pt) { + return Math.max(prev, getPadMax(pt)); } if(minmin === maxmax) { @@ -152,7 +140,7 @@ function getAutoRange(gd, ax) { // 'tozero' pins 0 to the low end, so follow that. newRange = [0, 1]; } else { - var maxPad = (minmin > 0 ? maxArray : minArray).reduce(getMaxPad, 0); + var maxPad = (minmin > 0 ? maxArray : minArray).reduce(maximumPad, 0); // we're pushing a single value away from the edge due to its // padding, with the other end clamped at zero // 0.5 means don't push it farther than the center. @@ -173,7 +161,7 @@ function getAutoRange(gd, ax) { maxbest = {val: 0, pad: 0}; } } else if(nonNegative) { - if(minbest.val - mbest * getPad(minbest) < 0) { + if(minbest.val - mbest * getPadMin(minbest) < 0) { minbest = {val: 0, pad: 0}; } if(maxbest.val <= 0) { @@ -182,12 +170,12 @@ function getAutoRange(gd, ax) { } // in case it changed again... - mbest = (maxbest.val - minbest.val - calcBreaksLength(minpt.val, maxpt.val)) / - (axLen - getPad(minbest) - getPad(maxbest)); + mbest = (maxbest.val - minbest.val - calcBreaksLength(ax, minpt.val, maxpt.val)) / + (axLen - getPadMin(minbest) - getPadMax(maxbest)); newRange = [ - minbest.val - mbest * getPad(minbest), - maxbest.val + mbest * getPad(maxbest) + minbest.val - mbest * getPadMin(minbest), + maxbest.val + mbest * getPadMax(maxbest) ]; } @@ -197,13 +185,41 @@ function getAutoRange(gd, ax) { return Lib.simpleMap(newRange, ax.l2r || Number); } +// find axis rangebreaks in [v0,v1] and compute its length in value space +function calcBreaksLength(ax, v0, v1) { + var lBreaks = 0; + if(ax.rangebreaks) { + var rangebreaksOut = ax.locateBreaks(v0, v1); + for(var i = 0; i < rangebreaksOut.length; i++) { + var brk = rangebreaksOut[i]; + lBreaks += brk.max - brk.min; + } + } + return lBreaks; +} + /* * calculate the pixel padding for ax._min and ax._max entries with * optional extrapad as 5% of the total axis length */ -function makePadFn(ax) { +function makePadFn(ax, max) { // 5% padding for points that specify extrapad: true - var extrappad = ax._length / 20; + var extrappad = 0.05 * ax._length; + + if( + (ax.ticklabelposition || '').indexOf('inside') !== -1 || + ((ax._anchorAxis || {}).ticklabelposition || '').indexOf('inside') !== -1 + ) { + var axReverse = ax.autorange === 'reversed'; + if(!axReverse) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + axReverse = rng[1] < rng[0]; + } + if(axReverse) max = !max; + } + + extrappad = adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max); + extrappad = adjustPadForInsideLabelsOnThisAxis(extrappad, ax, max); // domain-constrained axes: base extrappad on the unconstrained // domain so it's consistent as the domain changes @@ -215,6 +231,96 @@ function makePadFn(ax) { return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); }; } +var TEXTPAD = 3; + +function adjustPadForInsideLabelsOnThisAxis(extrappad, ax, max) { + var ticklabelposition = ax.ticklabelposition || ''; + var has = function(str) { + return ticklabelposition.indexOf(str) !== -1; + }; + + if(!has('inside')) return extrappad; + var isTop = has('top'); + var isLeft = has('left'); + var isRight = has('right'); + var isBottom = has('bottom'); + var isAligned = isBottom || isLeft || isTop || isRight; + + if( + (max && (isLeft || isBottom)) || + (!max && (isRight || isTop)) + ) { + return extrappad; + } + + // increase padding to make more room for inside tick labels of the axis + var fontSize = ax.tickfont ? ax.tickfont.size : 12; + var isX = ax._id.charAt(0) === 'x'; + var morePad = (isX ? 1.2 : 0.6) * fontSize; + + if(isAligned) { + morePad *= 2; + morePad += (ax.tickwidth || 0) / 2; + } + + morePad += TEXTPAD; + + extrappad = Math.max(extrappad, morePad); + + return extrappad; +} + +function adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max) { + var anchorAxis = (ax._anchorAxis || {}); + if((anchorAxis.ticklabelposition || '').indexOf('inside') !== -1) { + // increase padding to make more room for inside tick labels of the counter axis + if(( + !max && ( + anchorAxis.side === 'left' || + anchorAxis.side === 'bottom' + ) + ) || ( + max && ( + anchorAxis.side === 'top' || + anchorAxis.side === 'right' + ) + )) { + var isX = ax._id.charAt(0) === 'x'; + + var morePad = 0; + if(anchorAxis._vals) { + var rad = Lib.deg2rad(anchorAxis._tickAngles[anchorAxis._id + 'tick'] || 0); + var cosA = Math.abs(Math.cos(rad)); + var sinA = Math.abs(Math.sin(rad)); + + // use bounding boxes + anchorAxis._vals.forEach(function(t) { + if(t.bb) { + var w = t.bb.width; + var h = t.bb.height; + + morePad = Math.max(morePad, isX ? + Math.max(w * cosA, h * sinA) : + Math.max(h * cosA, w * sinA) + ); + + // add extra pad around label + morePad += 3; + } + }); + } + + if(anchorAxis.ticks === 'inside' && anchorAxis.ticklabelposition === 'inside') { + morePad += anchorAxis.ticklen || 0; + } + + extrappad = Math.max(extrappad, morePad); + } + } + + return extrappad; +} + function concatExtremes(gd, ax, noMatch) { var axId = ax._id; var fullData = gd._fullData; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index d4be6f51640..0445134133a 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -48,6 +48,8 @@ var CAP_SHIFT = alignmentConstants.CAP_SHIFT; var LINE_SPACING = alignmentConstants.LINE_SPACING; var OPPOSITE_SIDE = alignmentConstants.OPPOSITE_SIDE; +var TEXTPAD = 3; + var axes = module.exports = {}; axes.setConvert = require('./set_convert'); @@ -1927,21 +1929,23 @@ axes.draw = function(gd, arg, opts) { fullLayout._paper.selectAll('g.subplot').each(function(d) { var id = d[0]; var plotinfo = fullLayout._plots[id]; - var xa = plotinfo.xaxis; - var ya = plotinfo.yaxis; - - plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove(); - plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove(); - plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick2').remove(); - plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick2').remove(); - plotinfo.xaxislayer.selectAll('.' + xa._id + 'divider').remove(); - plotinfo.yaxislayer.selectAll('.' + ya._id + 'divider').remove(); - - if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove(); - if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove(); - - fullLayout._infolayer.select('.g-' + xa._id + 'title').remove(); - fullLayout._infolayer.select('.g-' + ya._id + 'title').remove(); + if(plotinfo) { + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove(); + plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove(); + plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick2').remove(); + plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick2').remove(); + plotinfo.xaxislayer.selectAll('.' + xa._id + 'divider').remove(); + plotinfo.yaxislayer.selectAll('.' + ya._id + 'divider').remove(); + + if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove(); + if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove(); + + fullLayout._infolayer.select('.g-' + xa._id + 'title').remove(); + fullLayout._infolayer.select('.g-' + ya._id + 'title').remove(); + } }); } @@ -2042,23 +2046,24 @@ axes.drawOne = function(gd, ax, opts) { if(!ax.visible) return; - var transFn = axes.makeTransFn(ax); - var transTickLabelFn = ax.ticklabelmode === 'period' ? - axes.makeTransPeriodFn(ax) : - axes.makeTransFn(ax); + var transTickFn = axes.makeTransTickFn(ax); + var transTickLabelFn = axes.makeTransTickLabelFn(ax); var tickVals; // We remove zero lines, grid lines, and inside ticks if they're within 1px of the end // The key case here is removing zero lines when the axis bound is zero var valsClipped; + var insideTicks = ax.ticks === 'inside'; + var outsideTicks = ax.ticks === 'outside'; + if(ax.tickson === 'boundaries') { var boundaryVals = getBoundaryVals(ax, vals); valsClipped = axes.clipEnds(ax, boundaryVals); - tickVals = ax.ticks === 'inside' ? valsClipped : boundaryVals; + tickVals = insideTicks ? valsClipped : boundaryVals; } else { valsClipped = axes.clipEnds(ax, vals); - tickVals = ax.ticks === 'inside' ? valsClipped : vals; + tickVals = (insideTicks && ax.ticklabelmode !== 'period') ? valsClipped : vals; } var gridVals = ax._gridVals = valsClipped; @@ -2089,13 +2094,13 @@ axes.drawOne = function(gd, ax, opts) { counterAxis: counterAxis, layer: plotinfo.gridlayer.select('.' + axId), path: gridPath, - transFn: transFn + transFn: transTickFn }); axes.drawZeroLine(gd, ax, { counterAxis: counterAxis, layer: plotinfo.zerolinelayer, path: gridPath, - transFn: transFn + transFn: transTickFn }); } } @@ -2116,7 +2121,7 @@ axes.drawOne = function(gd, ax, opts) { } var tickPath; - if(ax.showdividers && ax.ticks === 'outside' && ax.tickson === 'boundaries') { + if(ax.showdividers && outsideTicks && ax.tickson === 'boundaries') { var dividerLookup = {}; for(i = 0; i < dividerVals.length; i++) { dividerLookup[dividerVals[i].x] = 1; @@ -2132,7 +2137,7 @@ axes.drawOne = function(gd, ax, opts) { vals: tickVals, layer: mainAxLayer, path: tickPath, - transFn: transFn + transFn: transTickFn }); if(ax.mirror === 'allticks') { @@ -2152,7 +2157,7 @@ axes.drawOne = function(gd, ax, opts) { vals: tickVals, layer: plotinfo[axLetter + 'axislayer'], path: spTickPath, - transFn: transFn + transFn: transTickFn }); } @@ -2184,7 +2189,7 @@ axes.drawOne = function(gd, ax, opts) { cls: axId + 'tick2', repositionOnUpdate: true, secondary: true, - transFn: transFn, + transFn: transTickFn, labelFns: axes.makeLabelFns(ax, mainLinePosition + standoff * tickSigns[4]) }); }); @@ -2196,7 +2201,7 @@ axes.drawOne = function(gd, ax, opts) { vals: dividerVals, layer: mainAxLayer, path: axes.makeTickPath(ax, mainLinePosition, tickSigns[4], ax._depth), - transFn: transFn + transFn: transTickFn }); }); } else if(ax.title.hasOwnProperty('standoff')) { @@ -2211,7 +2216,7 @@ axes.drawOne = function(gd, ax, opts) { var s = ax.side.charAt(0); var sMirror = OPPOSITE_SIDE[ax.side].charAt(0); var pos = axes.getPxPosition(gd, ax); - var outsideTickLen = ax.ticks === 'outside' ? ax.ticklen : 0; + var outsideTickLen = outsideTicks ? ax.ticklen : 0; var llbbox; var push; @@ -2467,36 +2472,82 @@ axes.getTickSigns = function(ax) { * - {fn} l2p * @return {fn} function of calcTicks items */ -axes.makeTransFn = function(ax) { - var axLetter = ax._id.charAt(0); - var offset = ax._offset; - return axLetter === 'x' ? - function(d) { return strTranslate(offset + ax.l2p(d.x), 0); } : - function(d) { return strTranslate(0, offset + ax.l2p(d.x)); }; +axes.makeTransTickFn = function(ax) { + return ax._id.charAt(0) === 'x' ? + function(d) { return strTranslate(ax._offset + ax.l2p(d.x), 0); } : + function(d) { return strTranslate(0, ax._offset + ax.l2p(d.x)); }; }; -axes.makeTransPeriodFn = function(ax) { - var axLetter = ax._id.charAt(0); - var offset = ax._offset; - return axLetter === 'x' ? +axes.makeTransTickLabelFn = function(ax) { + var uv = getTickLabelUV(ax); + var u = uv[0]; + var v = uv[1]; + + return ax._id.charAt(0) === 'x' ? function(d) { return strTranslate( - offset + ax.l2p(getPeriodX(d)), - 0 + u + ax._offset + ax.l2p(getPosX(d)), + v ); } : function(d) { return strTranslate( - 0, - offset + ax.l2p(getPeriodX(d)) + v, + u + ax._offset + ax.l2p(getPosX(d)) ); }; }; -function getPeriodX(d) { +function getPosX(d) { return d.periodX !== undefined ? d.periodX : d.x; } +// u is a shift along the axis, +// v is a shift perpendicular to the axis +function getTickLabelUV(ax) { + var ticklabelposition = ax.ticklabelposition || ''; + var has = function(str) { + return ticklabelposition.indexOf(str) !== -1; + }; + + var isTop = has('top'); + var isLeft = has('left'); + var isRight = has('right'); + var isBottom = has('bottom'); + var isInside = has('inside'); + + var isAligned = isBottom || isLeft || isTop || isRight; + + // early return + if(!isAligned && !isInside) return [0, 0]; + + var side = ax.side; + + var u = isAligned ? (ax.tickwidth || 0) / 2 : 0; + var v = TEXTPAD; + + var fontSize = ax.tickfont ? ax.tickfont.size : 12; + if(isBottom || isTop) { + u += fontSize * CAP_SHIFT; + v += (ax.linewidth || 0) / 2; + } + if(isLeft || isRight) { + u += (ax.linewidth || 0) / 2; + v += TEXTPAD; + } + if(isInside && side === 'top') { + v -= fontSize * (1 - CAP_SHIFT); + } + + if(isLeft || isTop) u = -u; + if(side === 'bottom' || side === 'right') v = -v; + + return [ + isAligned ? u : 0, + isInside ? v : 0 + ]; +} + /** * Make axis tick path string * @@ -2542,24 +2593,45 @@ axes.makeTickPath = function(ax, shift, sgn, len) { * - {number} labelShift (gap perpendicular to ticks) */ axes.makeLabelFns = function(ax, shift, angle) { - var axLetter = ax._id.charAt(0); - var ticksOnOutsideLabels = ax.tickson !== 'boundaries' && ax.ticks === 'outside'; + var ticklabelposition = ax.ticklabelposition || ''; + var has = function(str) { + return ticklabelposition.indexOf(str) !== -1; + }; + + var isTop = has('top'); + var isLeft = has('left'); + var isRight = has('right'); + var isBottom = has('bottom'); + var isAligned = isBottom || isLeft || isTop || isRight; + + var insideTickLabels = has('inside'); + var labelsOverTicks = + (ticklabelposition === 'inside' && ax.ticks === 'inside') || + (!insideTickLabels && ax.ticks === 'outside' && ax.tickson !== 'boundaries'); var labelStandoff = 0; var labelShift = 0; - if(ticksOnOutsideLabels) { - labelStandoff += ax.ticklen; + var tickLen = labelsOverTicks ? ax.ticklen : 0; + if(insideTickLabels) { + tickLen *= -1; + } else if(isAligned) { + tickLen = 0; } - if(angle && ax.ticks === 'outside') { - var rad = Lib.deg2rad(angle); - labelStandoff = ax.ticklen * Math.cos(rad) + 1; - labelShift = ax.ticklen * Math.sin(rad); + + if(labelsOverTicks) { + labelStandoff += tickLen; + if(angle) { + var rad = Lib.deg2rad(angle); + labelStandoff = tickLen * Math.cos(rad) + 1; + labelShift = tickLen * Math.sin(rad); + } } - if(ax.showticklabels && (ticksOnOutsideLabels || ax.showline)) { + + if(ax.showticklabels && (labelsOverTicks || ax.showline)) { labelStandoff += 0.2 * ax.tickfont.size; } - labelStandoff += (ax.linewidth || 1) / 2; + labelStandoff += (ax.linewidth || 1) / 2 * (insideTickLabels ? -1 : 1); var out = { labelStandoff: labelStandoff, @@ -2567,42 +2639,101 @@ axes.makeLabelFns = function(ax, shift, angle) { }; var x0, y0, ff, flipIt; + var xQ = 0; + var side = ax.side; + var axLetter = ax._id.charAt(0); + var tickangle = ax.tickangle; + var endSide; if(axLetter === 'x') { - flipIt = ax.side === 'bottom' ? 1 : -1; + endSide = + (!insideTickLabels && side === 'bottom') || + (insideTickLabels && side === 'top'); + + flipIt = endSide ? 1 : -1; + if(insideTickLabels) flipIt *= -1; + x0 = labelShift * flipIt; y0 = shift + labelStandoff * flipIt; - ff = ax.side === 'bottom' ? 1 : -0.2; + ff = endSide ? 1 : -0.2; + if(Math.abs(tickangle) === 90) { + if(insideTickLabels) { + ff += MID_SHIFT; + } else { + if(tickangle === -90 && side === 'bottom') { + ff = CAP_SHIFT; + } else if(tickangle === 90 && side === 'top') { + ff = MID_SHIFT; + } else { + ff = 0.5; + } + } - out.xFn = function(d) { return d.dx + x0; }; + xQ = (MID_SHIFT / 2) * (tickangle / 90); + } + + out.xFn = function(d) { return d.dx + x0 + xQ * d.fontSize; }; out.yFn = function(d) { return d.dy + y0 + d.fontSize * ff; }; out.anchorFn = function(d, a) { + if(isAligned) { + if(isLeft) return 'end'; + if(isRight) return 'start'; + } + if(!isNumeric(a) || a === 0 || a === 180) { return 'middle'; } - return (a * flipIt < 0) ? 'end' : 'start'; + + return ((a * flipIt < 0) !== insideTickLabels) ? 'end' : 'start'; }; out.heightFn = function(d, a, h) { return (a < -60 || a > 60) ? -0.5 * h : - ax.side === 'top' ? -h : + ((ax.side === 'top') !== insideTickLabels) ? -h : 0; }; } else if(axLetter === 'y') { - flipIt = ax.side === 'right' ? 1 : -1; + endSide = + (!insideTickLabels && side === 'left') || + (insideTickLabels && side === 'right'); + + flipIt = endSide ? 1 : -1; + if(insideTickLabels) flipIt *= -1; + x0 = labelStandoff; - y0 = -labelShift * flipIt; - ff = Math.abs(ax.tickangle) === 90 ? 0.5 : 0; + y0 = labelShift * flipIt; + ff = 0; + if(!insideTickLabels && Math.abs(tickangle) === 90) { + if( + (tickangle === -90 && side === 'left') || + (tickangle === 90 && side === 'right') + ) { + ff = CAP_SHIFT; + } else { + ff = 0.5; + } + } - out.xFn = function(d) { return d.dx + shift + (x0 + d.fontSize * ff) * flipIt; }; + if(insideTickLabels) { + var ang = isNumeric(tickangle) ? +tickangle : 0; + if(ang !== 0) { + var rA = Lib.deg2rad(ang); + xQ = Math.abs(Math.sin(rA)) * CAP_SHIFT * flipIt; + ff = 0; + } + } + + out.xFn = function(d) { return d.dx + shift - (x0 + d.fontSize * ff) * flipIt + xQ * d.fontSize; }; out.yFn = function(d) { return d.dy + y0 + d.fontSize * MID_SHIFT; }; out.anchorFn = function(d, a) { if(isNumeric(a) && Math.abs(a) === 90) { return 'middle'; } - return ax.side === 'right' ? 'start' : 'end'; + + return endSide ? 'end' : 'start'; }; out.heightFn = function(d, a, h) { - a *= ax.side === 'left' ? 1 : -1; + if(ax.side === 'right') a *= -1; + return a < -30 ? -h : a < 30 ? -0.5 * h : 0; @@ -2854,6 +2985,8 @@ axes.drawLabels = function(gd, ax, opts) { } function positionLabels(s, angle) { + var isInside = (ax.ticklabelposition || '').indexOf('inside') !== -1; + s.each(function(d) { var thisLabel = d3.select(this); var mathjaxGroup = thisLabel.select('.text-math-group'); @@ -2875,10 +3008,20 @@ axes.drawLabels = function(gd, ax, opts) { } if(mathjaxGroup.empty()) { - thisLabel.select('text').attr({ + var thisText = thisLabel.select('text'); + thisText.attr({ transform: transform, 'text-anchor': anchor }); + + if(isInside) { + // ensure visible + thisText.style({ opacity: 100 }); + + if(ax._hideOutOfRangeInsideTickLabels) { + ax._hideOutOfRangeInsideTickLabels(); + } + } } else { var mjWidth = Drawing.bBox(mathjaxGroup.node()).width; var mjShift = mjWidth * {end: -0.5, start: 0.5}[anchor]; @@ -2887,6 +3030,40 @@ axes.drawLabels = function(gd, ax, opts) { }); } + ax._hideOutOfRangeInsideTickLabels = undefined; + if((ax.ticklabelposition || '').indexOf('inside') !== -1) { + ax._hideOutOfRangeInsideTickLabels = function() { + var rl = Lib.simpleMap(ax.range, ax.r2l); + + // hide inside tick labels that go outside axis end points + var p0 = ax.l2p(rl[0]); + var p1 = ax.l2p(rl[1]); + + var min = Math.min(p0, p1) + ax._offset; + var max = Math.max(p0, p1) + ax._offset; + + var isX = ax._id.charAt(0) === 'x'; + + tickLabels.each(function(d) { + var thisLabel = d3.select(this); + var mathjaxGroup = thisLabel.select('.text-math-group'); + + if(mathjaxGroup.empty()) { + var bb = Drawing.bBox(thisLabel.node()); + var hide = false; + if(isX) { + if(bb.right > max) hide = true; + else if(bb.left < min) hide = true; + } else { + if(bb.bottom > max) hide = true; + else if(bb.top + (ax.tickangle ? 0 : d.fontSize / 4) < min) hide = true; + } + if(hide) thisLabel.select('text').style({ opacity: 0 }); + } // TODO: hide mathjax? + }); + }; + } + // make sure all labels are correctly positioned at their base angle // the positionLabels call above is only for newly drawn labels. // do this without waiting, using the last calculated angle to @@ -2954,11 +3131,24 @@ axes.drawLabels = function(gd, ax, opts) { } else { var vLen = vals.length; var tickSpacing = Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1); + + var ticklabelposition = ax.ticklabelposition || ''; + var has = function(str) { + return ticklabelposition.indexOf(str) !== -1; + }; + var isTop = has('top'); + var isLeft = has('left'); + var isRight = has('right'); + var isBottom = has('bottom'); + var isAligned = isBottom || isLeft || isTop || isRight; + var pad = !isAligned ? 0 : + (ax.tickwidth || 0) + 2 * TEXTPAD; + var rotate90 = (tickSpacing < maxFontSize * 2.5) || ax.type === 'multicategory'; // any overlap at all - set 30 degrees or 90 degrees for(i = 0; i < lbbArray.length - 1; i++) { - if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { + if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1], pad)) { autoangle = rotate90 ? 90 : 30; break; } @@ -2998,6 +3188,26 @@ axes.drawLabels = function(gd, ax, opts) { }); } + var anchorAx = ax._anchorAxis; + if( + anchorAx && anchorAx.autorange && + (ax.ticklabelposition || '').indexOf('inside') !== -1 + ) { + if(!fullLayout._insideTickLabelsAutorange) { + fullLayout._insideTickLabelsAutorange = {}; + } + fullLayout._insideTickLabelsAutorange[anchorAx._name + '.autorange'] = anchorAx.autorange; + + seq.push( + function computeFinalTickLabelBoundingBoxes() { + tickLabels.each(function(d, i) { + var thisLabel = selectTickLabel(this); + ax._vals[i].bb = Drawing.bBox(thisLabel.node()); + }); + } + ); + } + var done = Lib.syncOrAsync(seq); if(done && done.then) gd._promises.push(done); return done; diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index 667ecda5c7f..350f3bbdb78 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -54,9 +54,33 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, var axTemplate = containerOut._template || {}; var axType = containerOut.type || axTemplate.type || '-'; + var ticklabelmode; if(axType === 'date') { var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar); + + if(!options.noTicklabelmode) { + ticklabelmode = coerce('ticklabelmode'); + } + } + + if(!options.noTicklabelposition || axType === 'multicategory') { + Lib.coerce(containerIn, containerOut, { + ticklabelposition: { + valType: 'enumerated', + dflt: 'outside', + values: ticklabelmode === 'period' ? ['outside', 'inside'] : + letter === 'x' ? [ + 'outside', 'inside', + 'outside left', 'inside left', + 'outside right', 'inside right' + ] : [ + 'outside', 'inside', + 'outside top', 'inside top', + 'outside bottom', 'inside bottom' + ] + } + }, 'ticklabelposition'); } setConvert(containerOut, layoutOut); @@ -114,7 +138,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, ) { var ticksonDflt; if(isMultiCategory) ticksonDflt = 'boundaries'; - coerce('tickson', ticksonDflt); + var tickson = coerce('tickson', ticksonDflt); + if(tickson === 'boundaries') { + delete containerOut.ticklabelposition; + } } if(isMultiCategory) { @@ -126,8 +153,6 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, } if(axType === 'date') { - if(!options.noTicklabelmode) coerce('ticklabelmode'); - handleArrayContainerDefaults(containerIn, containerOut, { name: 'rangebreaks', inclusionAttr: 'enabled', diff --git a/src/plots/cartesian/constraints.js b/src/plots/cartesian/constraints.js index 85a4db0edf6..90ead3b020c 100644 --- a/src/plots/cartesian/constraints.js +++ b/src/plots/cartesian/constraints.js @@ -565,7 +565,8 @@ exports.enforce = function enforce(gd) { // *are* expanding to the full domain var outerMin = rangeCenter - halfRange * factor * 1.0001; var outerMax = rangeCenter + halfRange * factor * 1.0001; - var getPad = autorange.makePadFn(ax); + var getPadMin = autorange.makePadFn(ax, 0); + var getPadMax = autorange.makePadFn(ax, 1); updateDomain(ax, factor); var m = Math.abs(ax._m); @@ -576,14 +577,14 @@ exports.enforce = function enforce(gd) { var k; for(k = 0; k < minArray.length; k++) { - newVal = minArray[k].val - getPad(minArray[k]) / m; + newVal = minArray[k].val - getPadMin(minArray[k]) / m; if(newVal > outerMin && newVal < rangeMin) { rangeMin = newVal; } } for(k = 0; k < maxArray.length; k++) { - newVal = maxArray[k].val + getPad(maxArray[k]) / m; + newVal = maxArray[k].val + getPadMax(maxArray[k]) / m; if(newVal < outerMax && newVal > rangeMax) { rangeMax = newVal; } diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index ef6b4205e15..5e808e148e1 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -505,6 +505,29 @@ module.exports = { 'between ticks.' ].join(' ') }, + // ticklabelposition: not used directly, as values depend on direction (similar to side) + // left/right options are for x axes, and top/bottom options are for y axes + ticklabelposition: { + valType: 'enumerated', + values: [ + 'outside', 'inside', + 'outside top', 'inside top', + 'outside left', 'inside left', + 'outside right', 'inside right', + 'outside bottom', 'inside bottom' + ], + dflt: 'outside', + role: 'info', + editType: 'calc', + description: [ + 'Determines where tick labels are drawn with respect to the axis', + 'Please note that', + 'top or bottom has no effect on x axes or when `ticklabelmode` is set to *period*.', + 'Similarly', + 'left or right has no effect on y axes or when `ticklabelmode` is set to *period*.', + 'Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*.' + ].join(' ') + }, mirror: { valType: 'enumerated', values: [true, 'ticks', false, 'all', 'allticks'], diff --git a/src/plots/cartesian/tick_label_defaults.js b/src/plots/cartesian/tick_label_defaults.js index 959b200d828..5b451d3455a 100644 --- a/src/plots/cartesian/tick_label_defaults.js +++ b/src/plots/cartesian/tick_label_defaults.js @@ -10,6 +10,7 @@ 'use strict'; var Lib = require('../../lib'); +var contrast = require('../../components/color').contrast; var layoutAttributes = require('./layout_attributes'); var handleArrayContainerDefaults = require('../array_container_defaults'); @@ -46,10 +47,14 @@ function handleOtherDefaults(containerIn, containerOut, coerce, axType, options) if(showTickLabels) { var font = options.font || {}; var contColor = containerOut.color; - // as with titlefont.color, inherit axis.color only if one was - // explicitly provided - var dfltFontColor = (contColor && contColor !== layoutAttributes.color.dflt) ? + var position = containerOut.ticklabelposition || ''; + var dfltFontColor = position.indexOf('inside') !== -1 ? + contrast(options.bgColor) : + // as with titlefont.color, inherit axis.color only if one was + // explicitly provided + (contColor && contColor !== layoutAttributes.color.dflt) ? contColor : font.color; + Lib.coerceFont(coerce, 'tickfont', { family: font.family, size: font.size, diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index 8bc9d7155ef..b7ff2435868 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -52,6 +52,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { showGrid: true, noTickson: true, noTicklabelmode: true, + noTicklabelposition: true, bgColor: options.bgColor, calendar: options.calendar }, diff --git a/src/plots/plots.js b/src/plots/plots.js index 4d26e8e9331..5e3357b054d 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2060,8 +2060,20 @@ plots.doAutoMargin = function(gd) { Lib.warn('Too many auto-margin redraws.'); } } + + hideOutOfRangeInsideTickLabels(gd); }; +function hideOutOfRangeInsideTickLabels(gd) { + var axList = axisIDs.list(gd, '', true); + for(var i = 0; i < axList.length; i++) { + var ax = axList[i]; + + var hideFn = ax._hideOutOfRangeInsideTickLabels; + if(hideFn) hideFn(); + } +} + var marginKeys = ['l', 'r', 't', 'b', 'p', 'w', 'h']; plots.didMarginChange = function(margin0, margin1) { diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 82d178ecd43..67e32f85d48 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -428,7 +428,7 @@ proto.drawAx = function(ax) { var vals = Axes.calcTicks(ax); var valsClipped = Axes.clipEnds(ax, vals); - var transFn = Axes.makeTransFn(ax); + var transFn = Axes.makeTransTickFn(ax); var tickSign = Axes.getTickSigns(ax)[2]; var caRad = Lib.deg2rad(counterAngle); diff --git a/src/traces/indicator/plot.js b/src/traces/indicator/plot.js index 5c2925652d2..d3a8c96f0ee 100644 --- a/src/traces/indicator/plot.js +++ b/src/traces/indicator/plot.js @@ -264,7 +264,7 @@ function drawBulletGauge(gd, plotGroup, cd, opts) { ax.setScale(); vals = Axes.calcTicks(ax); - transFn = Axes.makeTransFn(ax); + transFn = Axes.makeTransTickFn(ax); tickSign = Axes.getTickSigns(ax)[2]; shift = size.t + size.h; diff --git a/test/image/baselines/1.png b/test/image/baselines/1.png index eb01e3dfafc..fbe4ec8f869 100644 Binary files a/test/image/baselines/1.png and b/test/image/baselines/1.png differ diff --git a/test/image/baselines/bar_multiline_labels.png b/test/image/baselines/bar_multiline_labels.png index 7788b298bd8..cf6aad94e2c 100644 Binary files a/test/image/baselines/bar_multiline_labels.png and b/test/image/baselines/bar_multiline_labels.png differ diff --git a/test/image/baselines/box_grouped-multicategory.png b/test/image/baselines/box_grouped-multicategory.png index 395b10cc106..22605b01a07 100644 Binary files a/test/image/baselines/box_grouped-multicategory.png and b/test/image/baselines/box_grouped-multicategory.png differ diff --git a/test/image/baselines/geo_country-names.png b/test/image/baselines/geo_country-names.png index 921a87a1571..31d5d096d9b 100644 Binary files a/test/image/baselines/geo_country-names.png and b/test/image/baselines/geo_country-names.png differ diff --git a/test/image/baselines/geo_custom-colorscale.png b/test/image/baselines/geo_custom-colorscale.png index f5f38305461..8ed84752d25 100644 Binary files a/test/image/baselines/geo_custom-colorscale.png and b/test/image/baselines/geo_custom-colorscale.png differ diff --git a/test/image/baselines/gl2d_parcoords_rgba_colorscale.png b/test/image/baselines/gl2d_parcoords_rgba_colorscale.png index 5e9f663bad7..4903a01242e 100644 Binary files a/test/image/baselines/gl2d_parcoords_rgba_colorscale.png and b/test/image/baselines/gl2d_parcoords_rgba_colorscale.png differ diff --git a/test/image/baselines/gl3d_coloraxes.png b/test/image/baselines/gl3d_coloraxes.png index 0fd3f7530e5..df31cb21eab 100644 Binary files a/test/image/baselines/gl3d_coloraxes.png and b/test/image/baselines/gl3d_coloraxes.png differ diff --git a/test/image/baselines/gl3d_scatter3d-colorscale-marker-and-line.png b/test/image/baselines/gl3d_scatter3d-colorscale-marker-and-line.png index b5b807b9f5c..91b4fe027c2 100644 Binary files a/test/image/baselines/gl3d_scatter3d-colorscale-marker-and-line.png and b/test/image/baselines/gl3d_scatter3d-colorscale-marker-and-line.png differ diff --git a/test/image/baselines/ticklabelposition-0.png b/test/image/baselines/ticklabelposition-0.png new file mode 100644 index 00000000000..37f4a5c26cd Binary files /dev/null and b/test/image/baselines/ticklabelposition-0.png differ diff --git a/test/image/baselines/ticklabelposition-1.png b/test/image/baselines/ticklabelposition-1.png new file mode 100644 index 00000000000..85c116eb93f Binary files /dev/null and b/test/image/baselines/ticklabelposition-1.png differ diff --git a/test/image/baselines/ticklabelposition-2.png b/test/image/baselines/ticklabelposition-2.png new file mode 100644 index 00000000000..d9d94d1e6de Binary files /dev/null and b/test/image/baselines/ticklabelposition-2.png differ diff --git a/test/image/baselines/ticklabelposition-a.png b/test/image/baselines/ticklabelposition-a.png new file mode 100644 index 00000000000..2270af98e82 Binary files /dev/null and b/test/image/baselines/ticklabelposition-a.png differ diff --git a/test/image/baselines/ticklabelposition-b.png b/test/image/baselines/ticklabelposition-b.png new file mode 100644 index 00000000000..5fbdc96f3cf Binary files /dev/null and b/test/image/baselines/ticklabelposition-b.png differ diff --git a/test/image/baselines/ticklabelposition-c.png b/test/image/baselines/ticklabelposition-c.png new file mode 100644 index 00000000000..09b2c6ea530 Binary files /dev/null and b/test/image/baselines/ticklabelposition-c.png differ diff --git a/test/image/baselines/ticklabelposition-d.png b/test/image/baselines/ticklabelposition-d.png new file mode 100644 index 00000000000..81d2cc7b4a4 Binary files /dev/null and b/test/image/baselines/ticklabelposition-d.png differ diff --git a/test/image/baselines/vertical-tickangles.png b/test/image/baselines/vertical-tickangles.png new file mode 100644 index 00000000000..3cdae815068 Binary files /dev/null and b/test/image/baselines/vertical-tickangles.png differ diff --git a/test/image/mocks/geo_country-names.json b/test/image/mocks/geo_country-names.json index 017b9460c87..c8913decb22 100644 --- a/test/image/mocks/geo_country-names.json +++ b/test/image/mocks/geo_country-names.json @@ -391,6 +391,12 @@ ], "zmin": 0.1, "zmax": 17.5, + "colorbar": { + "dtick": 1, + "ticks": "inside", + "ticklen": 16, + "ticklabelposition": "inside bottom" + }, "colorscale": [ [ 0, diff --git a/test/image/mocks/geo_custom-colorscale.json b/test/image/mocks/geo_custom-colorscale.json index 9f86a33fcde..fc80db14726 100644 --- a/test/image/mocks/geo_custom-colorscale.json +++ b/test/image/mocks/geo_custom-colorscale.json @@ -16,6 +16,11 @@ 60, 40 ], + "colorbar": { + "ticks": "inside", + "ticklen": 16, + "ticklabelposition": "inside bottom" + }, "colorscale": "Greens", "reversescale": true, "zmin": 0, diff --git a/test/image/mocks/gl2d_parcoords_rgba_colorscale.json b/test/image/mocks/gl2d_parcoords_rgba_colorscale.json index 758943ab768..af0071930bc 100644 --- a/test/image/mocks/gl2d_parcoords_rgba_colorscale.json +++ b/test/image/mocks/gl2d_parcoords_rgba_colorscale.json @@ -32,6 +32,10 @@ "rgba(0,0,255,1)" ] ], + "colorbar": { + "ticks": "inside", + "ticklabelposition": "inside" + }, "showscale": true }, "type": "parcoords" diff --git a/test/image/mocks/gl3d_coloraxes.json b/test/image/mocks/gl3d_coloraxes.json index 0f4e16c94a9..0e8c59971e4 100644 --- a/test/image/mocks/gl3d_coloraxes.json +++ b/test/image/mocks/gl3d_coloraxes.json @@ -1247,7 +1247,9 @@ "colorscale": "Viridis", "colorbar": { "x": -0.1, - "xanchor": "right" + "xanchor": "right", + "ticks": "inside", + "ticklabelposition": "inside bottom" } }, "coloraxis2": { @@ -1255,7 +1257,9 @@ "reversescale": true, "colorbar": { "x": 1.1, - "xanchor": "left" + "xanchor": "left", + "ticks": "outside", + "ticklabelposition": "outside bottom" } } } diff --git a/test/image/mocks/gl3d_scatter3d-colorscale-marker-and-line.json b/test/image/mocks/gl3d_scatter3d-colorscale-marker-and-line.json index c4c6bbf63a1..9257cad862e 100644 --- a/test/image/mocks/gl3d_scatter3d-colorscale-marker-and-line.json +++ b/test/image/mocks/gl3d_scatter3d-colorscale-marker-and-line.json @@ -14,6 +14,7 @@ "len": 0.5, "y": 1, "yanchor": "top", + "ticklabelposition": "inside", "title": {"text": "line colorscale", "side": "right"} } }, @@ -25,6 +26,7 @@ "len": 0.5, "y": 0, "yanchor": "bottom", + "ticklabelposition": "inside", "title": {"text": "marker colorscale", "side": "right"} } diff --git a/test/image/mocks/ticklabelposition-0.json b/test/image/mocks/ticklabelposition-0.json new file mode 100644 index 00000000000..4845e55b7d5 --- /dev/null +++ b/test/image/mocks/ticklabelposition-0.json @@ -0,0 +1,138 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": [0, 1000], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": [0, 1000], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": [0, 1000], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": [0, 1000], + "y": [1e-1, 1e+6] + }], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "side": "bottom", + "ticks": "outside", + "ticklabelposition": "outside right", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "autorange": "reversed", + "type": "log", + "side": "left", + "ticks": "inside", + "ticklabelposition": "outside top", + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0.525, 1], + "autorange": "reversed", + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "outside right", + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0, 0.475], + "type": "log", + "side": "right", + "ticks": "inside", + "ticklabelposition": "outside top", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "side": "top", + "ticks": "outside", + "ticklabelposition": "outside left", + "tickfont": { "size": 20 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "autorange": "reversed", + "type": "log", + "side": "right", + "ticks": "outside", + "ticklabelposition": "outside bottom", + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0, 0.475], + "autorange": "reversed", + "side": "top", + "ticks": "inside", + "ticklabelposition": "outside right", + "tickangle": -60, + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0.525, 1], + "type": "log", + "side": "left", + "ticks": "inside", + "ticklabelposition": "outside bottom", + "tickangle": 30, + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "font": { + "family": "Raleway" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 1000, + "height": 1000 + } +} diff --git a/test/image/mocks/ticklabelposition-1.json b/test/image/mocks/ticklabelposition-1.json new file mode 100644 index 00000000000..a96b7af9114 --- /dev/null +++ b/test/image/mocks/ticklabelposition-1.json @@ -0,0 +1,149 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": ["20-12-20", "21-01-20"], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": ["20-10", "21-05-15"], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": ["20", "23"], + "y": [1e-1, 1e+6] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": ["20-12-20", "21-01-20"], + "y": [1e-1, 1e+6] + }], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "type": "date", + "ticklabelmode": "period", + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "autorange": "reversed", + "type": "log", + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside top", + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0.525, 1], + "autorange": "reversed", + "type": "date", + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside left", + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0, 0.475], + "type": "log", + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "type": "date", + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside right", + "tickfont": { "size": 20 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "autorange": "reversed", + "type": "log", + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside bottom", + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0, 0.475], + "autorange": "reversed", + "type": "date", + "ticklabelmode": "period", + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "gridcolor": "white" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0.525, 1], + "type": "log", + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside bottom", + "tickangle": 30, + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "gridcolor": "white" + }, + "font": { + "family": "Raleway" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 1000, + "height": 1000, + "margin": { + "t": 40, + "b": 40, + "l": 40, + "r": 40 + } + } +} diff --git a/test/image/mocks/ticklabelposition-2.json b/test/image/mocks/ticklabelposition-2.json new file mode 100644 index 00000000000..982e1899cf9 --- /dev/null +++ b/test/image/mocks/ticklabelposition-2.json @@ -0,0 +1,167 @@ +{ + "data": [{ + "yaxis": "y", + "xaxis": "x", + "y": ["20-11", "21-03"], + "x": [1e-1, 1e+6] + }, { + "yaxis": "y2", + "xaxis": "x2", + "y": ["20-10", "21-04"], + "x": [1e-1, 1e+6] + }, { + "yaxis": "y3", + "xaxis": "x3", + "y": ["01-01", "01-09"], + "x": [1e-1, 1e+6] + }, { + "yaxis": "y4", + "xaxis": "x4", + "y": ["01", "10"], + "x": [1e-1, 1e+6] + }], + "layout": { + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "type": "date", + "ticklabelmode": "period", + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "autorange": "reversed", + "type": "log", + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside right", + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0.525, 1], + "autorange": "reversed", + "type": "date", + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside bottom", + "tickfont": { "size": 20 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0, 0.475], + "type": "log", + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside right", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "type": "date", + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside bottom", + "tickfont": { "size": 20 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "autorange": "reversed", + "type": "log", + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside left", + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0, 0.475], + "autorange": "reversed", + "type": "date", + "ticklabelmode": "period", + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickangle": -45, + "tickfont": { "size": 16 }, + "ticklen": 8, + "tickwidth": 4, + "linewidth": 4, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0.525, 1], + "type": "log", + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside left", + "tickangle": 45, + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 8, + "linewidth": 1, + "linecolor": "yellow", + "tickcolor": "yellow", + "gridcolor": "rgb(191,191,191)" + }, + "font": { + "family": "Raleway" + }, + "paper_bgcolor": "rgb(191,191,191)", + "plot_bgcolor": "rgb(63,63,63)", + "showlegend": false, + "width": 1000, + "height": 1000, + "margin": { + "t": 40, + "b": 40, + "l": 40, + "r": 40 + } + } +} diff --git a/test/image/mocks/ticklabelposition-a.json b/test/image/mocks/ticklabelposition-a.json new file mode 100644 index 00000000000..c1153a9b6ed --- /dev/null +++ b/test/image/mocks/ticklabelposition-a.json @@ -0,0 +1,134 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": [-5000, 10000], + "y": [-5000, 10000] + }], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0.525, 1], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0, 0.475], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0, 0.475], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0.525, 1], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "font": { + "family": "Raleway" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 800, + "height": 800, + "margin": { + "t": 40, + "b": 40, + "l": 40, + "r": 40 + } + } +} diff --git a/test/image/mocks/ticklabelposition-b.json b/test/image/mocks/ticklabelposition-b.json new file mode 100644 index 00000000000..b1b681e82a7 --- /dev/null +++ b/test/image/mocks/ticklabelposition-b.json @@ -0,0 +1,134 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": [-10000, 10000], + "y": [-10000, 10000] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": [-10000, 10000], + "y": [-10000, 10000] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": [-10000, 10000], + "y": [-10000, 10000] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": [-10000, 10000], + "y": [-10000, 10000] + }], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside right", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside top", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0.525, 1], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside left", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0, 0.475], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside top", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside left", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside bottom", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0, 0.475], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside right", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0.525, 1], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside bottom", + "tickfont": { "size": 16 }, + "ticklen": 16, + "tickwidth": 4, + "linewidth": 10, + "gridcolor": "white" + }, + "font": { + "family": "Raleway" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 800, + "height": 800, + "margin": { + "t": 40, + "b": 40, + "l": 40, + "r": 40 + } + } +} diff --git a/test/image/mocks/ticklabelposition-c.json b/test/image/mocks/ticklabelposition-c.json new file mode 100644 index 00000000000..ddab2398b72 --- /dev/null +++ b/test/image/mocks/ticklabelposition-c.json @@ -0,0 +1,126 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": [-5000, 10000], + "y": [-5000, 10000] + }], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 30, + "gridcolor": "white" + }, + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -60, + "gridcolor": "white" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0.525, 1], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 60, + "gridcolor": "white" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0, 0.475], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -30, + "gridcolor": "white" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -30, + "gridcolor": "white" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 60, + "gridcolor": "white" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0, 0.475], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -60, + "gridcolor": "white" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0.525, 1], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 30, + "gridcolor": "white" + }, + "font": { + "family": "Raleway" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 800, + "height": 800, + "margin": { + "t": 40, + "b": 40, + "l": 40, + "r": 40 + } + } +} diff --git a/test/image/mocks/ticklabelposition-d.json b/test/image/mocks/ticklabelposition-d.json new file mode 100644 index 00000000000..8f5ba43e24f --- /dev/null +++ b/test/image/mocks/ticklabelposition-d.json @@ -0,0 +1,126 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": [-5000, 10000], + "y": [-5000, 10000] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": [-5000, 10000], + "y": [-5000, 10000] + }], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 90, + "gridcolor": "white" + }, + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 90, + "gridcolor": "white" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0.525, 1], + "side": "bottom", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -90, + "gridcolor": "white" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0, 0.475], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -90, + "gridcolor": "white" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -90, + "gridcolor": "white" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "side": "right", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 90, + "gridcolor": "white" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0, 0.475], + "side": "top", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": 90, + "gridcolor": "white" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0.525, 1], + "side": "left", + "ticks": "inside", + "ticklabelposition": "inside", + "tickfont": { "size": 16 }, + "ticklen": 0, + "tickangle": -90, + "gridcolor": "white" + }, + "font": { + "family": "Raleway" + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 800, + "height": 800, + "margin": { + "t": 40, + "b": 40, + "l": 40, + "r": 40 + } + } +} diff --git a/test/image/mocks/vertical-tickangles.json b/test/image/mocks/vertical-tickangles.json new file mode 100644 index 00000000000..d55a60d8b16 --- /dev/null +++ b/test/image/mocks/vertical-tickangles.json @@ -0,0 +1,94 @@ +{ + "data": [{ + "xaxis": "x", + "yaxis": "y", + "x": ["Gg", "Pp"], + "y": ["Qq", "Yy"] + }, { + "xaxis": "x2", + "yaxis": "y2", + "x": ["Gg", "Pp"], + "y": ["Qq", "Yy"] + }, { + "xaxis": "x3", + "yaxis": "y3", + "x": ["Gg", "Pp"], + "y": ["Qq", "Yy"] + }, { + "xaxis": "x4", + "yaxis": "y4", + "x": ["Gg", "Pp"], + "y": ["Qq", "Yy"] + }], + "layout": { + "xaxis": { + "anchor": "y", + "domain": [0, 0.475], + "side": "bottom", + "tickangle": 90, + "gridcolor": "white" + }, + "yaxis": { + "anchor": "x", + "domain": [0, 0.475], + "side": "left", + "tickangle": 0, + "gridcolor": "white" + }, + "xaxis2": { + "anchor": "y2", + "domain": [0.525, 1], + "side": "bottom", + "tickangle": 0, + "gridcolor": "white" + }, + "yaxis2": { + "anchor": "x2", + "domain": [0, 0.475], + "side": "right", + "tickangle": 0, + "gridcolor": "white" + }, + "xaxis3": { + "anchor": "y3", + "domain": [0.525, 1], + "side": "top", + "tickangle": -90, + "gridcolor": "white" + }, + "yaxis3": { + "anchor": "x3", + "domain": [0.525, 1], + "side": "right", + "tickangle": 90, + "gridcolor": "white" + }, + "xaxis4": { + "anchor": "y4", + "domain": [0, 0.475], + "side": "top", + "tickangle": 0, + "gridcolor": "white" + }, + "yaxis4": { + "anchor": "x4", + "domain": [0.525, 1], + "side": "left", + "tickangle": -90, + "gridcolor": "white" + }, + "font": { + "size": 40 + }, + "plot_bgcolor": "lightblue", + "showlegend": false, + "width": 600, + "height": 600, + "margin": { + "t": 80, + "b": 80, + "l": 80, + "r": 80 + } + } +} diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index d38894dd782..5c4cc342d1e 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -573,6 +573,119 @@ describe('Test axes', function() { .toEqual(tinycolor.mix('#444', bgColor, frac).toRgbString()); }); + it('should default to a dark color for tickfont when plotting background is light', function() { + layoutIn = { + plot_bgcolor: 'lightblue', + xaxis: { + showgrid: true, + ticklabelposition: 'inside' + } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.tickfont.color).toEqual('#444'); + }); + + it('should default to a light color for tickfont when plotting background is dark', function() { + layoutIn = { + plot_bgcolor: 'darkblue', + xaxis: { + showgrid: true, + ticklabelposition: 'inside' + } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.tickfont.color).toEqual('#fff'); + }); + + it('should not coerce ticklabelposition on *multicategory* axes for now', function() { + layoutIn = { + xaxis: {type: 'multicategory'}, + yaxis: {type: 'multicategory'} + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.ticklabelposition).toBeUndefined(); + expect(layoutOut.yaxis.ticklabelposition).toBeUndefined(); + }); + + ['category', 'linear', 'date'].forEach(function(type) { + it('should coerce ticklabelposition on *' + type + '* axes', function() { + layoutIn = { + xaxis: {type: type}, + yaxis: {type: type} + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.ticklabelposition).toBe('outside'); + expect(layoutOut.yaxis.ticklabelposition).toBe('outside'); + }); + }); + + ['category', 'linear', 'date'].forEach(function(type) { + it('should be able to set ticklabelposition to *inside* on *' + type + '* axes', function() { + layoutIn = { + xaxis: {type: type, ticklabelposition: 'inside'}, + yaxis: {type: type, ticklabelposition: 'inside'} + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.ticklabelposition).toBe('inside'); + expect(layoutOut.yaxis.ticklabelposition).toBe('inside'); + }); + }); + + ['inside left', 'inside right', 'outside left', 'outside right'].forEach(function(ticklabelposition) { + ['category', 'linear', 'date'].forEach(function(type) { + it('should be able to set ticklabelposition to *' + ticklabelposition + '* on xaxis for *' + type + '* axes', function() { + layoutIn = { + xaxis: {type: type, ticklabelposition: ticklabelposition}, + yaxis: {type: type, ticklabelposition: ticklabelposition} + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.ticklabelposition).toBe(ticklabelposition); + expect(layoutOut.yaxis.ticklabelposition).toBe('outside', ticklabelposition + ' is not a valid input on yaxis'); + }); + }); + }); + + ['inside top', 'inside bottom', 'outside top', 'outside bottom'].forEach(function(ticklabelposition) { + ['category', 'linear', 'date'].forEach(function(type) { + it('should be able to set ticklabelposition to *' + ticklabelposition + '* on yaxis for *' + type + '* axes', function() { + layoutIn = { + xaxis: {type: type, ticklabelposition: ticklabelposition}, + yaxis: {type: type, ticklabelposition: ticklabelposition} + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.ticklabelposition).toBe('outside', ticklabelposition + ' is not a valid input on yaxis'); + expect(layoutOut.yaxis.ticklabelposition).toBe(ticklabelposition); + }); + }); + }); + + [ + 'inside left', 'inside right', 'outside left', 'outside right', + 'inside top', 'inside bottom', 'outside top', 'outside bottom' + ].forEach(function(ticklabelposition) { + it('should not be able to set ticklabelposition to *' + ticklabelposition + '* when ticklabelmode is *period*', function() { + layoutIn = { + xaxis: {type: 'date', ticklabelmode: 'period', ticklabelposition: ticklabelposition}, + yaxis: {type: 'date', ticklabelmode: 'period', ticklabelposition: ticklabelposition} + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.ticklabelposition).toBe('outside', ticklabelposition + ' is not a valid input with period mode'); + expect(layoutOut.yaxis.ticklabelposition).toBe('outside', ticklabelposition + ' is not a valid input with period mode'); + }); + }); + + it('should be able to set ticklabelposition to *inside* on yaxis when ticklabelmode is *period*', function() { + layoutIn = { + xaxis: {type: 'date', ticklabelmode: 'period', ticklabelposition: 'inside'}, + yaxis: {type: 'date', ticklabelmode: 'period', ticklabelposition: 'inside'} + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.ticklabelposition).toBe('inside'); + expect(layoutOut.yaxis.ticklabelposition).toBe('inside'); + }); + it('should inherit calendar from the layout', function() { layoutOut.calendar = 'nepali'; layoutIn = { diff --git a/test/jasmine/tests/mock_test.js b/test/jasmine/tests/mock_test.js index 7cd61e5b034..ccf250c9ac7 100644 --- a/test/jasmine/tests/mock_test.js +++ b/test/jasmine/tests/mock_test.js @@ -81,6 +81,8 @@ var list = [ 'axes_category_descending', 'axes_category_descending_with_gaps', 'axes_category_null', + 'axes_chain_scaleanchor_matches', + 'axes_chain_scaleanchor_matches2', 'axes_custom-ticks_log-date', 'axes_enumerated_ticks', 'axes_free_default', @@ -959,6 +961,13 @@ var list = [ 'tick-percent', 'tickformat', 'tickformatstops', + 'ticklabelposition-0', + 'ticklabelposition-1', + 'ticklabelposition-2', + 'ticklabelposition-a', + 'ticklabelposition-b', + 'ticklabelposition-c', + 'ticklabelposition-d', 'tickson_boundaries', 'titles-avoid-labels', 'trace_metatext', @@ -1016,6 +1025,7 @@ var list = [ 'updatemenus', 'updatemenus_positioning', 'updatemenus_toggle', + 'vertical-tickangles', 'violin_bandwidth-edge-cases', 'violin_box_multiple_widths', 'violin_box_overlay', @@ -1154,6 +1164,8 @@ figs['axes_category_categoryarray_truncated_tails'] = require('@mocks/axes_categ // figs['axes_category_descending'] = require('@mocks/axes_category_descending'); // figs['axes_category_descending_with_gaps'] = require('@mocks/axes_category_descending_with_gaps'); figs['axes_category_null'] = require('@mocks/axes_category_null'); +figs['axes_chain_scaleanchor_matches'] = require('@mocks/axes_chain_scaleanchor_matches'); +figs['axes_chain_scaleanchor_matches2'] = require('@mocks/axes_chain_scaleanchor_matches2'); figs['axes_custom-ticks_log-date'] = require('@mocks/axes_custom-ticks_log-date'); figs['axes_enumerated_ticks'] = require('@mocks/axes_enumerated_ticks'); figs['axes_free_default'] = require('@mocks/axes_free_default'); @@ -2032,6 +2044,13 @@ figs['tick-increment'] = require('@mocks/tick-increment'); figs['tick-percent'] = require('@mocks/tick-percent'); figs['tickformat'] = require('@mocks/tickformat'); figs['tickformatstops'] = require('@mocks/tickformatstops'); +figs['ticklabelposition-0'] = require('@mocks/ticklabelposition-0'); +figs['ticklabelposition-1'] = require('@mocks/ticklabelposition-1'); +figs['ticklabelposition-2'] = require('@mocks/ticklabelposition-2'); +figs['ticklabelposition-a'] = require('@mocks/ticklabelposition-a'); +figs['ticklabelposition-b'] = require('@mocks/ticklabelposition-b'); +figs['ticklabelposition-c'] = require('@mocks/ticklabelposition-c'); +figs['ticklabelposition-d'] = require('@mocks/ticklabelposition-d'); figs['tickson_boundaries'] = require('@mocks/tickson_boundaries'); // figs['titles-avoid-labels'] = require('@mocks/titles-avoid-labels'); // figs['trace_metatext'] = require('@mocks/trace_metatext'); @@ -2089,6 +2108,7 @@ figs['uniformtext_treemap_coffee-maxdepth3'] = require('@mocks/uniformtext_treem // figs['updatemenus'] = require('@mocks/updatemenus'); figs['updatemenus_positioning'] = require('@mocks/updatemenus_positioning'); figs['updatemenus_toggle'] = require('@mocks/updatemenus_toggle'); +figs['vertical-tickangles'] = require('@mocks/vertical-tickangles'); figs['violin_bandwidth-edge-cases'] = require('@mocks/violin_bandwidth-edge-cases'); figs['violin_box_multiple_widths'] = require('@mocks/violin_box_multiple_widths'); figs['violin_box_overlay'] = require('@mocks/violin_box_overlay');