@@ -1564,6 +1564,190 @@ axes.makeClipPaths = function(gd) {
1564
1564
} ) ;
1565
1565
} ;
1566
1566
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
+
1567
1751
/**
1568
1752
* Main multi-axis drawing routine!
1569
1753
*
@@ -1668,6 +1852,7 @@ axes.drawOne = function(gd, ax, opts) {
1668
1852
var mainPlotinfo = fullLayout . _plots [ ax . _mainSubplot ] ;
1669
1853
var mainAxLayer = mainPlotinfo [ axLetter + 'axislayer' ] ;
1670
1854
var subplotsWithAx = ax . _subplotsWith ;
1855
+ var hasRangeSlider = Registry . getComponentMethod ( 'rangeslider' , 'isVisible' ) ( ax ) ;
1671
1856
1672
1857
var vals = ax . _vals = axes . calcTicks ( ax ) ;
1673
1858
@@ -1688,16 +1873,6 @@ axes.drawOne = function(gd, ax, opts) {
1688
1873
// depth can be expansive to compute, so we only do so when required
1689
1874
ax . _depth = null ;
1690
1875
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
-
1701
1876
if ( ! ax . visible ) return ;
1702
1877
1703
1878
var transFn = axes . makeTransFn ( ax ) ;
@@ -1814,14 +1989,10 @@ axes.drawOne = function(gd, ax, opts) {
1814
1989
} ) ;
1815
1990
1816
1991
if ( ax . type === 'multicategory' ) {
1817
- var pad = { x : 2 , y : 10 } [ axLetter ] ;
1818
1992
var sgn = { l : - 1 , t : - 1 , r : 1 , b : 1 } [ ax . side . charAt ( 0 ) ] ;
1819
1993
1820
1994
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 ) ;
1825
1996
return axes . drawLabels ( gd , ax , {
1826
1997
vals : getSecondaryLabelVals ( ax , vals ) ,
1827
1998
layer : mainAxLayer ,
@@ -1834,7 +2005,7 @@ axes.drawOne = function(gd, ax, opts) {
1834
2005
} ) ;
1835
2006
1836
2007
seq . push ( function ( ) {
1837
- ax . _depth = sgn * ( getLabelLevelBbox ( 'tick2' ) [ ax . side ] - mainLinePosition ) ;
2008
+ ax . _depth = sgn * ( calcLabelLevelBbox ( ax , axId + 'tick2' ) [ ax . side ] - mainLinePosition ) ;
1838
2009
1839
2010
return drawDividers ( gd , ax , {
1840
2011
vals : dividerVals ,
@@ -1843,115 +2014,18 @@ axes.drawOne = function(gd, ax, opts) {
1843
2014
transFn : transFn
1844
2015
} ) ;
1845
2016
} ) ;
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
+ } ) ;
1846
2026
}
1847
2027
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' ) ) {
1955
2029
seq . push ( function ( ) { return drawTitle ( gd , ax ) ; } ) ;
1956
2030
}
1957
2031
@@ -2226,6 +2300,26 @@ axes.makeLabelFns = function(ax, shift, angle) {
2226
2300
return out ;
2227
2301
} ;
2228
2302
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
+
2229
2323
function tickDataFn ( d ) {
2230
2324
return [ d . text , d . x , d . axInfo , d . font , d . fontSize , d . fontColor ] . join ( '_' ) ;
2231
2325
}
0 commit comments