@@ -1564,6 +1564,190 @@ axes.makeClipPaths = function(gd) {
15641564 } ) ;
15651565} ;
15661566
1567+ axes . pushMargin = function ( gd ) {
1568+ var axList = axes . list ( gd , '' , true ) ;
1569+
1570+ return Lib . syncOrAsync ( axList . map ( function ( ax ) {
1571+ return function ( ) { return pushMarginOne ( gd , ax ) ; } ;
1572+ } ) ) ;
1573+ } ;
1574+
1575+ /**
1576+ * Compute push margin value for one axis
1577+ *
1578+ * @param {DOM element } gd
1579+ * @param {object } ax (full) axis object
1580+ *
1581+ * When required to compute margin push values, it fills in:
1582+ * - ax._selections
1583+ * - ax._tickAngles
1584+ * - ax._depth
1585+ */
1586+ function pushMarginOne ( gd , ax ) {
1587+ var fullLayout = gd . _fullLayout ;
1588+ var hasRangeSlider = Registry . getComponentMethod ( 'rangeslider' , 'isVisible' ) ( ax ) ;
1589+ var seq = [ ] ;
1590+
1591+ if ( hasRangeSlider || ax . automargin ) {
1592+ var axId = ax . _id ;
1593+ var axLetter = axId . charAt ( 0 ) ;
1594+ var counterLetter = axes . counterLetter ( axId ) ;
1595+
1596+ ax . setScale ( ) ;
1597+
1598+ var dummyLayer = Lib . ensureSingle ( Drawing . tester , 'g' , 'dummy-' + axId ) ;
1599+ var transFn = axes . makeTransFn ( ax ) ;
1600+ var vals = axes . calcTicks ( ax ) ;
1601+ var mainLinePosition = axes . getAxisLinePosition ( gd , ax ) ;
1602+
1603+ // stash selections to avoid DOM queries
1604+ ax . _selections = { } ;
1605+ // stash tick angle (including the computed 'auto' values) per tick-label class
1606+ ax . _tickAngles = { } ;
1607+ // measure [in px] between axis position and outward-most part of bounding box
1608+ ax . _depth = null ;
1609+
1610+ seq . push ( function ( ) {
1611+ return axes . drawLabels ( gd , ax , {
1612+ vals : vals ,
1613+ layer : dummyLayer ,
1614+ transFn : transFn ,
1615+ labelFns : axes . makeLabelFns ( ax , mainLinePosition )
1616+ } ) ;
1617+ } ) ;
1618+
1619+ var sgn = { l : - 1 , t : - 1 , r : 1 , b : 1 } [ ax . side . charAt ( 0 ) ] ;
1620+
1621+ if ( ax . type === 'multicategory' ) {
1622+ seq . push ( function ( ) {
1623+ var standoff = getMultiCategoryStandoff ( ax ) ;
1624+ return axes . drawLabels ( gd , ax , {
1625+ vals : getSecondaryLabelVals ( ax , vals ) ,
1626+ layer : dummyLayer ,
1627+ cls : axId + 'tick2' ,
1628+ secondary : true ,
1629+ transFn : transFn ,
1630+ labelFns : axes . makeLabelFns ( ax , mainLinePosition + standoff * sgn )
1631+ } ) ;
1632+ } ) ;
1633+ }
1634+
1635+ seq . push ( function ( ) {
1636+ var s = ax . side . charAt ( 0 ) ;
1637+ var sMirror = OPPOSITE_SIDE [ ax . side ] . charAt ( 0 ) ;
1638+ var pos = axes . getPxPosition ( gd , ax ) ;
1639+ var outsideTickLen = ax . ticks === 'outside' ? ax . ticklen : 0 ;
1640+ var llbbox ;
1641+
1642+ var push ;
1643+ var mirrorPush ;
1644+ var rangeSliderPush ;
1645+
1646+ if ( ax . type === 'multicategory' ) {
1647+ llbbox = calcLabelLevelBbox ( ax , axId + 'tick2' ) ;
1648+ ax . _depth = sgn * ( llbbox [ ax . side ] - mainLinePosition ) ;
1649+ } else {
1650+ llbbox = calcLabelLevelBbox ( ax , axId + 'tick' ) ;
1651+ if ( axLetter === 'x' && s === 'b' ) {
1652+ ax . _depth = Math . max ( llbbox . width > 0 ? llbbox . bottom - pos : 0 , outsideTickLen ) ;
1653+ }
1654+ }
1655+
1656+ if ( ax . automargin ) {
1657+ push = { x : 0 , y : 0 , r : 0 , l : 0 , t : 0 , b : 0 } ;
1658+ var domainIndices = [ 0 , 1 ] ;
1659+
1660+ if ( axLetter === 'x' ) {
1661+ if ( s === 'b' ) {
1662+ push [ s ] = ax . _depth ;
1663+ } else {
1664+ push [ s ] = ax . _depth = Math . max (
1665+ llbbox . width > 0 ? pos - llbbox . top : 0 ,
1666+ outsideTickLen
1667+ ) ;
1668+ domainIndices . reverse ( ) ;
1669+ }
1670+
1671+ if ( llbbox . width > 0 ) {
1672+ var rExtra = llbbox . right - ( ax . _offset + ax . _length ) ;
1673+ if ( rExtra > 0 ) {
1674+ push . x = 1 ;
1675+ push . r = rExtra ;
1676+ }
1677+ var lExtra = ax . _offset - llbbox . left ;
1678+ if ( lExtra > 0 ) {
1679+ push . x = 0 ;
1680+ push . l = lExtra ;
1681+ }
1682+ }
1683+ } else {
1684+ if ( s === 'l' ) {
1685+ push [ s ] = ax . _depth = Math . max (
1686+ llbbox . height > 0 ? pos - llbbox . left : 0 ,
1687+ outsideTickLen
1688+ ) ;
1689+ } else {
1690+ push [ s ] = ax . _depth = Math . max (
1691+ llbbox . height > 0 ? llbbox . right - pos : 0 ,
1692+ outsideTickLen
1693+ ) ;
1694+ domainIndices . reverse ( ) ;
1695+ }
1696+
1697+ if ( llbbox . height > 0 ) {
1698+ var bExtra = llbbox . bottom - ( ax . _offset + ax . _length ) ;
1699+ if ( bExtra > 0 ) {
1700+ push . y = 0 ;
1701+ push . b = bExtra ;
1702+ }
1703+ var tExtra = ax . _offset - llbbox . top ;
1704+ if ( tExtra > 0 ) {
1705+ push . y = 1 ;
1706+ push . t = tExtra ;
1707+ }
1708+ }
1709+ }
1710+
1711+ push [ counterLetter ] = ax . anchor === 'free' ?
1712+ ax . position :
1713+ ax . _anchorAxis . domain [ domainIndices [ 0 ] ] ;
1714+
1715+ if ( ax . title . text !== fullLayout . _dfltTitle [ axLetter ] ) {
1716+ var extraLines = ( ax . title . text . match ( svgTextUtils . BR_TAG_ALL ) || [ ] ) . length ;
1717+ push [ s ] += extraLines ?
1718+ ax . title . font . size * ( extraLines + 1 ) * LINE_SPACING :
1719+ ax . title . font . size ;
1720+ }
1721+
1722+ if ( ax . mirror ) {
1723+ mirrorPush = { x : 0 , y : 0 , r : 0 , l : 0 , t : 0 , b : 0 } ;
1724+
1725+ mirrorPush [ sMirror ] = ax . linewidth ;
1726+ if ( ax . mirror && ax . mirror !== true ) mirrorPush [ sMirror ] += outsideTickLen ;
1727+
1728+ if ( ax . mirror === true || ax . mirror === 'ticks' ) {
1729+ mirrorPush [ counterLetter ] = ax . _anchorAxis . domain [ domainIndices [ 1 ] ] ;
1730+ } else if ( ax . mirror === 'all' || ax . mirror === 'allticks' ) {
1731+ mirrorPush [ counterLetter ] = [ ax . _counterDomainMin , ax . _counterDomainMax ] [ domainIndices [ 1 ] ] ;
1732+ }
1733+ }
1734+ }
1735+
1736+ if ( hasRangeSlider ) {
1737+ rangeSliderPush = Registry . getComponentMethod ( 'rangeslider' , 'autoMarginOpts' ) ( gd , ax ) ;
1738+ }
1739+
1740+ Plots . autoMargin ( gd , axId + '.automargin' , push ) ;
1741+ Plots . autoMargin ( gd , axId + '.automargin.mirro' , mirrorPush ) ;
1742+ Plots . autoMargin ( gd , axId + '.rangeslider' , rangeSliderPush ) ;
1743+
1744+ dummyLayer . remove ( ) ;
1745+ } ) ;
1746+ }
1747+
1748+ return Lib . syncOrAsync ( seq ) ;
1749+ }
1750+
15671751/**
15681752 * Main multi-axis drawing routine!
15691753 *
@@ -1668,6 +1852,7 @@ axes.drawOne = function(gd, ax, opts) {
16681852 var mainPlotinfo = fullLayout . _plots [ ax . _mainSubplot ] ;
16691853 var mainAxLayer = mainPlotinfo [ axLetter + 'axislayer' ] ;
16701854 var subplotsWithAx = ax . _subplotsWith ;
1855+ var hasRangeSlider = Registry . getComponentMethod ( 'rangeslider' , 'isVisible' ) ( ax ) ;
16711856
16721857 var vals = ax . _vals = axes . calcTicks ( ax ) ;
16731858
@@ -1688,16 +1873,6 @@ axes.drawOne = function(gd, ax, opts) {
16881873 // depth can be expansive to compute, so we only do so when required
16891874 ax . _depth = null ;
16901875
1691- // calcLabelLevelBbox can be expensive,
1692- // so make sure to not call it twice during the same Axes.drawOne call
1693- // by stashing label-level bounding boxes per tick-label class
1694- var llbboxes = { } ;
1695- function getLabelLevelBbox ( suffix ) {
1696- var cls = axId + ( suffix || 'tick' ) ;
1697- if ( ! llbboxes [ cls ] ) llbboxes [ cls ] = calcLabelLevelBbox ( ax , cls ) ;
1698- return llbboxes [ cls ] ;
1699- }
1700-
17011876 if ( ! ax . visible ) return ;
17021877
17031878 var transFn = axes . makeTransFn ( ax ) ;
@@ -1814,14 +1989,10 @@ axes.drawOne = function(gd, ax, opts) {
18141989 } ) ;
18151990
18161991 if ( ax . type === 'multicategory' ) {
1817- var pad = { x : 2 , y : 10 } [ axLetter ] ;
18181992 var sgn = { l : - 1 , t : - 1 , r : 1 , b : 1 } [ ax . side . charAt ( 0 ) ] ;
18191993
18201994 seq . push ( function ( ) {
1821- var bboxKey = { x : 'height' , y : 'width' } [ axLetter ] ;
1822- var standoff = getLabelLevelBbox ( ) [ bboxKey ] + pad +
1823- ( ax . _tickAngles [ axId + 'tick' ] ? ax . tickfont . size * LINE_SPACING : 0 ) ;
1824-
1995+ var standoff = getMultiCategoryStandoff ( ax ) ;
18251996 return axes . drawLabels ( gd , ax , {
18261997 vals : getSecondaryLabelVals ( ax , vals ) ,
18271998 layer : mainAxLayer ,
@@ -1834,7 +2005,7 @@ axes.drawOne = function(gd, ax, opts) {
18342005 } ) ;
18352006
18362007 seq . push ( function ( ) {
1837- ax . _depth = sgn * ( getLabelLevelBbox ( 'tick2' ) [ ax . side ] - mainLinePosition ) ;
2008+ ax . _depth = sgn * ( calcLabelLevelBbox ( ax , axId + 'tick2' ) [ ax . side ] - mainLinePosition ) ;
18382009
18392010 return drawDividers ( gd , ax , {
18402011 vals : dividerVals ,
@@ -1843,115 +2014,18 @@ axes.drawOne = function(gd, ax, opts) {
18432014 transFn : transFn
18442015 } ) ;
18452016 } ) ;
2017+ } else if ( hasRangeSlider && ax . side === 'bottom' ) {
2018+ // TODO do we really need to recompute _depth here??
2019+ // could we instead reuse the stashed version from pushMargin ??
2020+ seq . push ( function ( ) {
2021+ var llbbox = calcLabelLevelBbox ( ax , axId + 'tick' ) ;
2022+ var pos = axes . getPxPosition ( gd , ax ) ;
2023+ var outsideTickLen = ax . ticks === 'outside' ? ax . ticklen : 0 ;
2024+ ax . _depth = Math . max ( llbbox . width > 0 ? llbbox . bottom - pos : 0 , outsideTickLen ) ;
2025+ } ) ;
18462026 }
18472027
1848- var hasRangeSlider = Registry . getComponentMethod ( 'rangeslider' , 'isVisible' ) ( ax ) ;
1849-
1850- seq . push ( function ( ) {
1851- var s = ax . side . charAt ( 0 ) ;
1852- var sMirror = OPPOSITE_SIDE [ ax . side ] . charAt ( 0 ) ;
1853- var pos = axes . getPxPosition ( gd , ax ) ;
1854- var outsideTickLen = ax . ticks === 'outside' ? ax . ticklen : 0 ;
1855- var llbbox ;
1856-
1857- var push ;
1858- var mirrorPush ;
1859- var rangeSliderPush ;
1860-
1861- if ( ax . automargin || hasRangeSlider ) {
1862- if ( ax . type === 'multicategory' ) {
1863- llbbox = getLabelLevelBbox ( 'tick2' ) ;
1864- } else {
1865- llbbox = getLabelLevelBbox ( ) ;
1866- if ( axLetter === 'x' && s === 'b' ) {
1867- ax . _depth = Math . max ( llbbox . width > 0 ? llbbox . bottom - pos : 0 , outsideTickLen ) ;
1868- }
1869- }
1870- }
1871-
1872- if ( ax . automargin ) {
1873- push = { x : 0 , y : 0 , r : 0 , l : 0 , t : 0 , b : 0 } ;
1874- var domainIndices = [ 0 , 1 ] ;
1875-
1876- if ( axLetter === 'x' ) {
1877- if ( s === 'b' ) {
1878- push [ s ] = ax . _depth ;
1879- } else {
1880- push [ s ] = ax . _depth = Math . max ( llbbox . width > 0 ? pos - llbbox . top : 0 , outsideTickLen ) ;
1881- domainIndices . reverse ( ) ;
1882- }
1883-
1884- if ( llbbox . width > 0 ) {
1885- var rExtra = llbbox . right - ( ax . _offset + ax . _length ) ;
1886- if ( rExtra > 0 ) {
1887- push . x = 1 ;
1888- push . r = rExtra ;
1889- }
1890- var lExtra = ax . _offset - llbbox . left ;
1891- if ( lExtra > 0 ) {
1892- push . x = 0 ;
1893- push . l = lExtra ;
1894- }
1895- }
1896- } else {
1897- if ( s === 'l' ) {
1898- push [ s ] = ax . _depth = Math . max ( llbbox . height > 0 ? pos - llbbox . left : 0 , outsideTickLen ) ;
1899- } else {
1900- push [ s ] = ax . _depth = Math . max ( llbbox . height > 0 ? llbbox . right - pos : 0 , outsideTickLen ) ;
1901- domainIndices . reverse ( ) ;
1902- }
1903-
1904- if ( llbbox . height > 0 ) {
1905- var bExtra = llbbox . bottom - ( ax . _offset + ax . _length ) ;
1906- if ( bExtra > 0 ) {
1907- push . y = 0 ;
1908- push . b = bExtra ;
1909- }
1910- var tExtra = ax . _offset - llbbox . top ;
1911- if ( tExtra > 0 ) {
1912- push . y = 1 ;
1913- push . t = tExtra ;
1914- }
1915- }
1916- }
1917-
1918- push [ counterLetter ] = ax . anchor === 'free' ?
1919- ax . position :
1920- ax . _anchorAxis . domain [ domainIndices [ 0 ] ] ;
1921-
1922- if ( ax . title . text !== fullLayout . _dfltTitle [ axLetter ] ) {
1923- var extraLines = ( ax . title . text . match ( svgTextUtils . BR_TAG_ALL ) || [ ] ) . length ;
1924- push [ s ] += extraLines ?
1925- ax . title . font . size * ( extraLines + 1 ) * LINE_SPACING :
1926- ax . title . font . size ;
1927- }
1928-
1929- if ( ax . mirror ) {
1930- mirrorPush = { x : 0 , y : 0 , r : 0 , l : 0 , t : 0 , b : 0 } ;
1931-
1932- mirrorPush [ sMirror ] = ax . linewidth ;
1933- if ( ax . mirror && ax . mirror !== true ) mirrorPush [ sMirror ] += outsideTickLen ;
1934-
1935- if ( ax . mirror === true || ax . mirror === 'ticks' ) {
1936- mirrorPush [ counterLetter ] = ax . _anchorAxis . domain [ domainIndices [ 1 ] ] ;
1937- } else if ( ax . mirror === 'all' || ax . mirror === 'allticks' ) {
1938- mirrorPush [ counterLetter ] = [ ax . _counterDomainMin , ax . _counterDomainMax ] [ domainIndices [ 1 ] ] ;
1939- }
1940- }
1941- }
1942-
1943- if ( hasRangeSlider ) {
1944- rangeSliderPush = Registry . getComponentMethod ( 'rangeslider' , 'autoMarginOpts' ) ( gd , ax ) ;
1945- }
1946-
1947- Plots . autoMargin ( gd , axAutoMarginID ( ax ) , push ) ;
1948- Plots . autoMargin ( gd , axMirrorAutoMarginID ( ax ) , mirrorPush ) ;
1949- Plots . autoMargin ( gd , rangeSliderAutoMarginID ( ax ) , rangeSliderPush ) ;
1950- } ) ;
1951-
1952- if ( ! opts . skipTitle &&
1953- ! ( hasRangeSlider && ax . side === 'bottom' )
1954- ) {
2028+ if ( ! opts . skipTitle && ! ( hasRangeSlider && ax . side === 'bottom' ) ) {
19552029 seq . push ( function ( ) { return drawTitle ( gd , ax ) ; } ) ;
19562030 }
19572031
@@ -2226,6 +2300,26 @@ axes.makeLabelFns = function(ax, shift, angle) {
22262300 return out ;
22272301} ;
22282302
2303+ /**
2304+ * Compute multicategory multi-level standoff
2305+ *
2306+ * @param {object } ax (full) axis object
2307+ * - {string} _id
2308+ * - {object} _selections
2309+ * - {object} _tickAngles
2310+ * - {number} tickfont.size
2311+ * @return {number }
2312+ */
2313+ function getMultiCategoryStandoff ( ax ) {
2314+ var axId = ax . _id ;
2315+ var axLetter = axId . charAt ( 0 ) ;
2316+ var pad = { x : 2 , y : 10 } [ axLetter ] ;
2317+ var bboxKey = { x : 'height' , y : 'width' } [ axLetter ] ;
2318+ var cls = axId + 'tick' ;
2319+ return calcLabelLevelBbox ( ax , cls ) [ bboxKey ] + pad +
2320+ ( ax . _tickAngles [ cls ] ? ax . tickfont . size * LINE_SPACING : 0 ) ;
2321+ }
2322+
22292323function tickDataFn ( d ) {
22302324 return [ d . text , d . x , d . axInfo , d . font , d . fontSize , d . fontColor ] . join ( '_' ) ;
22312325}
0 commit comments