diff --git a/draftlogs/6970_fix.md b/draftlogs/6970_fix.md
new file mode 100644
index 00000000000..96922b1925c
--- /dev/null
+++ b/draftlogs/6970_fix.md
@@ -0,0 +1,2 @@
+- Fix positioning of multi-line axis titles with `standoff` [[#6970](https://github.com/plotly/plotly.js/pull/6970)],
+ with thanks to @my-tien for the contribution!
diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js
index 1386cb8418c..eb71c1926df 100644
--- a/src/plots/cartesian/axes.js
+++ b/src/plots/cartesian/axes.js
@@ -4060,9 +4060,7 @@ function approxTitleDepth(ax) {
var fontSize = ax.title.font.size;
var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length;
if(ax.title.hasOwnProperty('standoff')) {
- return extraLines ?
- fontSize * (CAP_SHIFT + (extraLines * LINE_SPACING)) :
- fontSize * CAP_SHIFT;
+ return fontSize * (CAP_SHIFT + (extraLines * LINE_SPACING));
} else {
return extraLines ?
fontSize * (extraLines + 1) * LINE_SPACING :
@@ -4093,9 +4091,20 @@ function drawTitle(gd, ax) {
var axLetter = axId.charAt(0);
var fontSize = ax.title.font.size;
var titleStandoff;
+ var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length;
if(ax.title.hasOwnProperty('standoff')) {
- titleStandoff = ax._depth + ax.title.standoff + approxTitleDepth(ax);
+ // With ax._depth the initial drawing baseline is at the outer axis border (where the
+ // ticklabels are drawn). Since the title text will be drawn above the baseline,
+ // bottom/right axes must be shifted by 1 text line to draw below ticklabels instead of on
+ // top of them, whereas for top/left axes, the first line would be drawn
+ // before the ticklabels, but we need an offset for the descender portion of the first line
+ // and all subsequent lines.
+ if(ax.side === 'bottom' || ax.side === 'right') {
+ titleStandoff = ax._depth + ax.title.standoff + fontSize * CAP_SHIFT;
+ } else if(ax.side === 'top' || ax.side === 'left') {
+ titleStandoff = ax._depth + ax.title.standoff + fontSize * (MID_SHIFT + (extraLines * LINE_SPACING));
+ }
} else {
var isInside = insideTicklabelposition(ax);
diff --git a/test/image/baselines/axis-title-standoff.png b/test/image/baselines/axis-title-standoff.png
index 1464feac871..59fdbec4127 100644
Binary files a/test/image/baselines/axis-title-standoff.png and b/test/image/baselines/axis-title-standoff.png differ
diff --git a/test/image/baselines/line_scatter_change_side.png b/test/image/baselines/line_scatter_change_side.png
index faed272b20f..ea9e7d6e1c5 100644
Binary files a/test/image/baselines/line_scatter_change_side.png and b/test/image/baselines/line_scatter_change_side.png differ
diff --git a/test/image/baselines/line_scatter_change_side_shrunk.png b/test/image/baselines/line_scatter_change_side_shrunk.png
index 6aff06c20d7..9a4eee7a117 100644
Binary files a/test/image/baselines/line_scatter_change_side_shrunk.png and b/test/image/baselines/line_scatter_change_side_shrunk.png differ
diff --git a/test/image/baselines/line_scatter_regular_side.png b/test/image/baselines/line_scatter_regular_side.png
index 570f5ca7521..cac503c85d4 100644
Binary files a/test/image/baselines/line_scatter_regular_side.png and b/test/image/baselines/line_scatter_regular_side.png differ
diff --git a/test/image/baselines/line_scatter_regular_side_shrunk.png b/test/image/baselines/line_scatter_regular_side_shrunk.png
index c6c9b2b636d..422c0160080 100644
Binary files a/test/image/baselines/line_scatter_regular_side_shrunk.png and b/test/image/baselines/line_scatter_regular_side_shrunk.png differ
diff --git a/test/image/baselines/mult-yaxes-simple.png b/test/image/baselines/mult-yaxes-simple.png
index ee5345f52fb..549917d680a 100644
Binary files a/test/image/baselines/mult-yaxes-simple.png and b/test/image/baselines/mult-yaxes-simple.png differ
diff --git a/test/image/baselines/petrophysics.png b/test/image/baselines/petrophysics.png
index 6edfcd9305a..aba9c94bad0 100644
Binary files a/test/image/baselines/petrophysics.png and b/test/image/baselines/petrophysics.png differ
diff --git a/test/image/baselines/zz-axis_title_standoff0.png b/test/image/baselines/zz-axis_title_standoff0.png
new file mode 100644
index 00000000000..4f6f3609cfe
Binary files /dev/null and b/test/image/baselines/zz-axis_title_standoff0.png differ
diff --git a/test/image/mocks/axis-title-standoff.json b/test/image/mocks/axis-title-standoff.json
index 30789e6dd3e..40900aabaaa 100644
--- a/test/image/mocks/axis-title-standoff.json
+++ b/test/image/mocks/axis-title-standoff.json
@@ -26,7 +26,7 @@
},
"yaxis": {
"title": {
- "text": "Y
Axis (standoff:8)",
+ "text": "Y
Axis (standoff:0)",
"standoff": 0,
"font": {"size": 8}
},
diff --git a/test/image/mocks/zz-axis_title_standoff0.json b/test/image/mocks/zz-axis_title_standoff0.json
new file mode 100644
index 00000000000..0ef01d7b405
--- /dev/null
+++ b/test/image/mocks/zz-axis_title_standoff0.json
@@ -0,0 +1,155 @@
+{
+ "data": [
+ {
+ "mode": "markers",
+ "x": [
+ 100,
+ 200,
+ 300,
+ 400,
+ 500
+ ],
+ "y": [
+ 200,
+ 300,
+ 400,
+ 500,
+ 600
+ ],
+ "type": "scatter"
+ },
+ {
+ "mode": "markers",
+ "x": [
+ 100,
+ 200,
+ 300,
+ 400,
+ 500
+ ],
+ "y": [
+ 200,
+ 300,
+ 400,
+ 500,
+ 600
+ ],
+ "type": "scatter",
+ "xaxis": "x2",
+ "yaxis": "y2"
+ },
+ {
+ "mode": "markers",
+ "x": [
+ 100,
+ 200,
+ 300,
+ 400,
+ 500
+ ],
+ "y": [
+ 200,
+ 300,
+ 400,
+ 500,
+ 600
+ ],
+ "type": "scatter",
+ "xaxis": "x3",
+ "yaxis": "y3"
+ },
+ {
+ "mode": "markers",
+ "x": [
+ 100,
+ 200,
+ 300,
+ 400,
+ 500
+ ],
+ "y": [
+ 200,
+ 300,
+ 400,
+ 500,
+ 600
+ ],
+ "type": "scatter",
+ "xaxis": "x4",
+ "yaxis": "y4"
+ }
+ ],
+ "layout": {
+ "width": 800,
+ "legend": {
+ "visible": false
+ },
+ "grid": {
+ "rows": 2,
+ "columns": 2,
+ "pattern": "independent",
+ "xgap": 0.4
+ },
+ "xaxis": {
+ "title": {
+ "text": "______xaxis______",
+ "standoff": 0
+ },
+ "side": "top",
+ "automargin": true
+ },
+ "xaxis3": {
+ "title": {
+ "text": "¯¯¯¯¯¯xaxis3¯¯¯¯¯¯",
+ "standoff": 0
+ },
+ "automargin": true
+ },
+ "yaxis": {
+ "title": {
+ "text": "______yaxis______",
+ "standoff": 0
+ },
+ "automargin": true
+ },
+ "yaxis3": {
+ "title": {
+ "text": "¯¯¯¯¯¯yaxis3¯¯¯¯¯¯",
+ "standoff": 0
+ },
+ "side": "right",
+ "automargin": true
+ },
+ "xaxis2": {
+ "title": {
+ "text": "xaxis2
some
more
______text______",
+ "standoff": 0
+ },
+ "side": "top",
+ "automargin": true
+ },
+ "xaxis4": {
+ "title": {
+ "text": "¯¯¯¯¯¯xaxis4¯¯¯¯¯¯
some
more
text",
+ "standoff": 0
+ },
+ "side": "bottom",
+ "automargin": true
+ },
+ "yaxis2": {
+ "title": {
+ "text": "¯¯¯¯¯¯yaxis2¯¯¯¯¯¯
some
more
text",
+ "standoff": 0
+ },
+ "side": "right",
+ "automargin": true
+ },
+ "yaxis4": {
+ "title": {
+ "text": "yaxis4
some
more
______text______",
+ "standoff": 0
+ },
+ "automargin": true
+ }
+ }
+}