@@ -32,9 +32,11 @@ var ONESEC = constants.ONESEC;
3232var MINUS_SIGN = constants . MINUS_SIGN ;
3333var BADNUM = constants . BADNUM ;
3434
35- var MID_SHIFT = require ( '../../constants/alignment' ) . MID_SHIFT ;
36- var LINE_SPACING = require ( '../../constants/alignment' ) . LINE_SPACING ;
37- var OPPOSITE_SIDE = require ( '../../constants/alignment' ) . OPPOSITE_SIDE ;
35+ var alignmentConstants = require ( '../../constants/alignment' ) ;
36+ var MID_SHIFT = alignmentConstants . MID_SHIFT ;
37+ var CAP_SHIFT = alignmentConstants . CAP_SHIFT ;
38+ var LINE_SPACING = alignmentConstants . LINE_SPACING ;
39+ var OPPOSITE_SIDE = alignmentConstants . OPPOSITE_SIDE ;
3840
3941var axes = module . exports = { } ;
4042
@@ -1831,7 +1833,6 @@ axes.drawOne = function(gd, ax, opts) {
18311833
18321834 if ( ax . type === 'multicategory' ) {
18331835 var pad = { x : 2 , y : 10 } [ axLetter ] ;
1834- var sgn = { l : - 1 , t : - 1 , r : 1 , b : 1 } [ ax . side . charAt ( 0 ) ] ;
18351836
18361837 seq . push ( function ( ) {
18371838 var bboxKey = { x : 'height' , y : 'width' } [ axLetter ] ;
@@ -1845,20 +1846,24 @@ axes.drawOne = function(gd, ax, opts) {
18451846 repositionOnUpdate : true ,
18461847 secondary : true ,
18471848 transFn : transFn ,
1848- labelFns : axes . makeLabelFns ( ax , mainLinePosition + standoff * sgn )
1849+ labelFns : axes . makeLabelFns ( ax , mainLinePosition + standoff * tickSigns [ 4 ] )
18491850 } ) ;
18501851 } ) ;
18511852
18521853 seq . push ( function ( ) {
1853- ax . _depth = sgn * ( getLabelLevelBbox ( 'tick2' ) [ ax . side ] - mainLinePosition ) ;
1854+ ax . _depth = tickSigns [ 4 ] * ( getLabelLevelBbox ( 'tick2' ) [ ax . side ] - mainLinePosition ) ;
18541855
18551856 return drawDividers ( gd , ax , {
18561857 vals : dividerVals ,
18571858 layer : mainAxLayer ,
1858- path : axes . makeTickPath ( ax , mainLinePosition , sgn , ax . _depth ) ,
1859+ path : axes . makeTickPath ( ax , mainLinePosition , tickSigns [ 4 ] , ax . _depth ) ,
18591860 transFn : transFn
18601861 } ) ;
18611862 } ) ;
1863+ } else if ( ax . title . hasOwnProperty ( 'standoff' ) ) {
1864+ seq . push ( function ( ) {
1865+ ax . _depth = tickSigns [ 4 ] * ( getLabelLevelBbox ( ) [ ax . side ] - mainLinePosition ) ;
1866+ } ) ;
18621867 }
18631868
18641869 var hasRangeSlider = Registry . getComponentMethod ( 'rangeslider' , 'isVisible' ) ( ax ) ;
@@ -1936,10 +1941,7 @@ axes.drawOne = function(gd, ax, opts) {
19361941 ax . _anchorAxis . domain [ domainIndices [ 0 ] ] ;
19371942
19381943 if ( ax . title . text !== fullLayout . _dfltTitle [ axLetter ] ) {
1939- var extraLines = ( ax . title . text . match ( svgTextUtils . BR_TAG_ALL ) || [ ] ) . length ;
1940- push [ s ] += extraLines ?
1941- ax . title . font . size * ( extraLines + 1 ) * LINE_SPACING :
1942- ax . title . font . size ;
1944+ push [ s ] += approxTitleDepth ( ax ) + ( ax . title . standoff || 0 ) ;
19431945 }
19441946
19451947 if ( ax . mirror && ax . anchor !== 'free' ) {
@@ -2097,6 +2099,7 @@ function calcLabelLevelBbox(ax, cls) {
20972099 * - [1]: sign for bottom/left ticks (i.e. positive SVG direction)
20982100 * - [2]: sign for ticks corresponding to 'ax.side'
20992101 * - [3]: sign for ticks mirroring 'ax.side'
2102+ * - [4]: sign of arrow starting at axis pointing towards margin
21002103 */
21012104axes . getTickSigns = function ( ax ) {
21022105 var axLetter = ax . _id . charAt ( 0 ) ;
@@ -2107,6 +2110,10 @@ axes.getTickSigns = function(ax) {
21072110 if ( ( ax . ticks !== 'inside' ) === ( axLetter === 'x' ) ) {
21082111 out = out . map ( function ( v ) { return - v ; } ) ;
21092112 }
2113+ // independent of `ticks`; do not flip this one
2114+ if ( ax . side ) {
2115+ out . push ( { l : - 1 , t : - 1 , r : 1 , b : 1 } [ ax . side . charAt ( 0 ) ] ) ;
2116+ }
21102117 return out ;
21112118} ;
21122119
@@ -2699,42 +2706,84 @@ axes.getPxPosition = function(gd, ax) {
26992706 }
27002707} ;
27012708
2709+ /**
2710+ * Approximate axis title depth (w/o computing its bounding box)
2711+ *
2712+ * @param {object } ax (full) axis object
2713+ * - {string} title.text
2714+ * - {number} title.font.size
2715+ * - {number} title.standoff
2716+ * @return {number } (in px)
2717+ */
2718+ function approxTitleDepth ( ax ) {
2719+ var fontSize = ax . title . font . size ;
2720+ var extraLines = ( ax . title . text . match ( svgTextUtils . BR_TAG_ALL ) || [ ] ) . length ;
2721+ if ( ax . title . hasOwnProperty ( 'standoff' ) ) {
2722+ return extraLines ?
2723+ fontSize * ( CAP_SHIFT + ( extraLines * LINE_SPACING ) ) :
2724+ fontSize * CAP_SHIFT ;
2725+ } else {
2726+ return extraLines ?
2727+ fontSize * ( extraLines + 1 ) * LINE_SPACING :
2728+ fontSize ;
2729+ }
2730+ }
2731+
2732+ /**
2733+ * Draw axis title, compute default standoff if necessary
2734+ *
2735+ * @param {DOM element } gd
2736+ * @param {object } ax (full) axis object
2737+ * - {string} _id
2738+ * - {string} _name
2739+ * - {string} side
2740+ * - {number} title.font.size
2741+ * - {object} _selections
2742+ *
2743+ * - {number} _depth
2744+ * - {number} title.standoff
2745+ * OR
2746+ * - {number} linewidth
2747+ * - {boolean} showticklabels
2748+ */
27022749function drawTitle ( gd , ax ) {
27032750 var fullLayout = gd . _fullLayout ;
27042751 var axId = ax . _id ;
27052752 var axLetter = axId . charAt ( 0 ) ;
27062753 var fontSize = ax . title . font . size ;
27072754
27082755 var titleStandoff ;
2709- if ( ax . type === 'multicategory' ) {
2710- titleStandoff = ax . _depth ;
2756+
2757+ if ( ax . title . hasOwnProperty ( 'standoff' ) ) {
2758+ titleStandoff = ax . _depth + ax . title . standoff + approxTitleDepth ( ax ) ;
27112759 } else {
2712- var offsetBase = 1.5 ;
2713- titleStandoff = 10 + fontSize * offsetBase + ( ax . linewidth ? ax . linewidth - 1 : 0 ) ;
2760+ if ( ax . type === 'multicategory' ) {
2761+ titleStandoff = ax . _depth ;
2762+ } else {
2763+ var offsetBase = 1.5 ;
2764+ titleStandoff = 10 + fontSize * offsetBase + ( ax . linewidth ? ax . linewidth - 1 : 0 ) ;
2765+ }
2766+
2767+ if ( axLetter === 'x' ) {
2768+ titleStandoff += ax . side === 'top' ?
2769+ fontSize * ( ax . showticklabels ? 1 : 0 ) :
2770+ fontSize * ( ax . showticklabels ? 1.5 : 0.5 ) ;
2771+ } else {
2772+ titleStandoff += ax . side === 'right' ?
2773+ fontSize * ( ax . showticklabels ? 1 : 0.5 ) :
2774+ fontSize * ( ax . showticklabels ? 0.5 : 0 ) ;
2775+ }
27142776 }
27152777
27162778 var pos = axes . getPxPosition ( gd , ax ) ;
27172779 var transform , x , y ;
27182780
27192781 if ( axLetter === 'x' ) {
27202782 x = ax . _offset + ax . _length / 2 ;
2721-
2722- if ( ax . side === 'top' ) {
2723- y = - titleStandoff - fontSize * ( ax . showticklabels ? 1 : 0 ) ;
2724- } else {
2725- y = titleStandoff + fontSize * ( ax . showticklabels ? 1.5 : 0.5 ) ;
2726- }
2727- y += pos ;
2783+ y = ( ax . side === 'top' ) ? pos - titleStandoff : pos + titleStandoff ;
27282784 } else {
27292785 y = ax . _offset + ax . _length / 2 ;
2730-
2731- if ( ax . side === 'right' ) {
2732- x = titleStandoff + fontSize * ( ax . showticklabels ? 1 : 0.5 ) ;
2733- } else {
2734- x = - titleStandoff - fontSize * ( ax . showticklabels ? 0.5 : 0 ) ;
2735- }
2736- x += pos ;
2737-
2786+ x = ( ax . side === 'right' ) ? pos + titleStandoff : pos - titleStandoff ;
27382787 transform = { rotate : '-90' , offset : 0 } ;
27392788 }
27402789
@@ -2753,6 +2802,10 @@ function drawTitle(gd, ax) {
27532802 avoid . offsetLeft = translation . x ;
27542803 avoid . offsetTop = translation . y ;
27552804 }
2805+
2806+ if ( ax . title . hasOwnProperty ( 'standoff' ) ) {
2807+ avoid . pad = 0 ;
2808+ }
27562809 }
27572810
27582811 return Titles . draw ( gd , axId + 'title' , {
0 commit comments