@@ -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