Skip to content

Commit 06d7abf

Browse files
committed
[poc] split axes pushMargin from draw
[hopefully tmp] recompute ax._depth in drawOne ... but maybe we could just reuse the version computed in Axes.pushMargin? ++ add getMutiCategoryStandoff wrapper (used for both pushMargin and draw), also remove getLabelLevelBbox stash getter (for now) as getting this to work across pushMargin <--> drawOne might be tricky (especially in optimised relayout scenarios)
1 parent b47aa96 commit 06d7abf

File tree

1 file changed

+217
-123
lines changed

1 file changed

+217
-123
lines changed

src/plots/cartesian/axes.js

+217-123
Original file line numberDiff line numberDiff line change
@@ -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+
22292323
function tickDataFn(d) {
22302324
return [d.text, d.x, d.axInfo, d.font, d.fontSize, d.fontColor].join('_');
22312325
}

0 commit comments

Comments
 (0)