Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding automargin support to plot titles #6428

Merged
merged 63 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
68c6165
Initial pass with hard-coded automargin params
hannahker Jan 6, 2023
ad6eb00
Remove role from schema
hannahker Jan 7, 2023
0826ddd
Merge branch 'master' into automargin-title
hannahker Jan 18, 2023
45d9cbf
Move automargin call
hannahker Jan 26, 2023
c6e31be
lint
archmoj Jan 27, 2023
f46014f
rename mock and generate baseline
archmoj Jan 27, 2023
587dfcb
update pie_automargin-margin0 baseline
archmoj Jan 27, 2023
3dae109
adjust pie test
archmoj Jan 27, 2023
61487a8
Simplify automargin title to use existing functions
hannahker Jan 30, 2023
8679e7d
Account for padding around title
hannahker Feb 6, 2023
e8e49a5
Adjust mock to yref=paper
hannahker Feb 7, 2023
2b59e2c
Temp fix for yref=paper
hannahker Feb 7, 2023
5e4ab42
Merge branch 'master' into automargin-title
hannahker Feb 16, 2023
4bb109e
Improve handling for paper ref and setting defaults
hannahker Feb 16, 2023
7ce1234
Fix formatting
hannahker Feb 16, 2023
b274642
Add jasmine test
hannahker Feb 16, 2023
cc15ff6
Simplify mock
hannahker Feb 17, 2023
938d52d
Fix test formatting
hannahker Feb 17, 2023
75636e2
Add simple implementation for container behaviour
hannahker Feb 18, 2023
b7e3295
Apply review comments
hannahker Feb 28, 2023
04f8825
Add more tests for yref=container
hannahker Feb 28, 2023
ad202d9
Ajust default setting
hannahker Feb 28, 2023
9611672
Remove outdated mock
hannahker Mar 1, 2023
c4d1076
Update mock baselines
hannahker Mar 1, 2023
21a9e59
Fix syntax
hannahker Mar 1, 2023
78cecff
Remove f from test
hannahker Mar 1, 2023
fd111d5
Set yanchor properly when title is at bottom
hannahker Mar 6, 2023
2a47c00
Make defaults setting logic more readable
hannahker Mar 7, 2023
52b37c0
Add reservedMargins logic
hannahker Mar 7, 2023
4322c7b
Revert some changes to test
hannahker Mar 7, 2023
73ccaf4
Fix bugs
hannahker Mar 8, 2023
a3462d2
Fix more bugs from redraw
hannahker Mar 8, 2023
33bbbef
Apply review comments
hannahker Mar 9, 2023
9af5cbd
Add jasmine test for interaction with x axis
hannahker Mar 9, 2023
67cf8da
Push pie test fix
hannahker Mar 9, 2023
16c0bcf
Fix scooting behaviour to account for reserved margin space
hannahker Mar 9, 2023
17c3244
Check if automargin is necessary before applying
hannahker Mar 10, 2023
c014702
Update tests
hannahker Mar 10, 2023
e59b18f
Warn when title.y is being set to 1
hannahker Mar 10, 2023
ddaf013
Fix syntax and another mock update
hannahker Mar 10, 2023
de7ca48
Apply review comments
hannahker Mar 10, 2023
e77126c
Calc bb for title
hannahker Mar 10, 2023
26ca553
Fix warning log
hannahker Mar 10, 2023
e5e8a2a
Update parameter description
hannahker Mar 10, 2023
d70fb46
Reposition properly
hannahker Mar 10, 2023
616057f
Merge branch 'master' into automargin-title
hannahker Mar 10, 2023
3523406
Only reposition title if necessary
hannahker Mar 10, 2023
90bd374
Fix syntax
hannahker Mar 10, 2023
158791f
Fix tests
hannahker Mar 10, 2023
c4e706e
Merge branch 'automargin-title-dev' into automargin-title
hannahker Mar 10, 2023
087c162
Adjust tests to match rendering in CI
hannahker Mar 13, 2023
c4d75d4
Update mocks
hannahker Mar 13, 2023
a257836
Add review comment
hannahker Mar 13, 2023
662628e
Update mock baselines
hannahker Mar 13, 2023
e4b5f29
Adjust titles on mocks
hannahker Mar 13, 2023
3dcaab8
Update baselines
hannahker Mar 13, 2023
2a0011a
Add milti-line title
hannahker Mar 14, 2023
a8185f4
Update baselines
hannahker Mar 14, 2023
0c0e47e
Revert dy change
hannahker Mar 14, 2023
7f89c73
move smart default for title.y and title.yanchor to supply defaults
archmoj Mar 10, 2023
132c57b
Adjust dy to fix offset
hannahker Mar 15, 2023
7d4e352
baseline update
archmoj Mar 15, 2023
ad7c6f0
Add draftlog
hannahker Mar 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/6428_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add `title.automargin` to enable automatic margining for both container and paper referenced titles [[#6428](https://github.com/plotly/plotly.js/pull/6428)], with thanks to [Gamma Technologies](https://www.gtisoft.com/) for sponsoring the related development.
18 changes: 14 additions & 4 deletions src/components/titles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,21 @@ function draw(gd, titleClass, options) {
var pad = isNumeric(avoid.pad) ? avoid.pad : 2;

var titlebb = Drawing.bBox(titleGroup.node());

// Account for reservedMargins
var reservedMargins = {t: 0, b: 0, l: 0, r: 0};
var margins = gd._fullLayout._reservedMargin;
for(var key in margins) {
for(var side in margins[key]) {
var val = margins[key][side];
reservedMargins[side] = Math.max(reservedMargins[side], val);
}
}
var paperbb = {
left: 0,
top: 0,
right: fullLayout.width,
bottom: fullLayout.height
left: reservedMargins.l,
top: reservedMargins.t,
right: fullLayout.width - reservedMargins.r,
bottom: fullLayout.height - reservedMargins.b
};

var maxshift = avoid.maxShift ||
Expand Down
1 change: 1 addition & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ function _doPlot(gd, data, layout, config) {

subroutines.drawMarginPushers(gd);
Axes.allowAutoMargin(gd);
if(gd._fullLayout.title.text && gd._fullLayout.title.automargin) Plots.allowAutoMargin(gd, 'title.automargin');

// TODO can this be moved elsewhere?
if(fullLayout._has('pie')) {
Expand Down
118 changes: 112 additions & 6 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var Registry = require('../registry');
var Plots = require('../plots/plots');

var Lib = require('../lib');
var svgTextUtils = require('../lib/svg_text_utils');
var clearGlCanvases = require('../lib/clear_gl_canvases');

var Color = require('../components/color');
Expand Down Expand Up @@ -397,24 +398,120 @@ function findCounterAxisLineWidth(ax, side, counterAx, axList) {
}

exports.drawMainTitle = function(gd) {
var title = gd._fullLayout.title;
var fullLayout = gd._fullLayout;

var textAnchor = getMainTitleTextAnchor(fullLayout);
var dy = getMainTitleDy(fullLayout);
var y = getMainTitleY(fullLayout, dy);
var x = getMainTitleX(fullLayout, textAnchor);

Titles.draw(gd, 'gtitle', {
propContainer: fullLayout,
propName: 'title.text',
placeholder: fullLayout._dfltTitle.plot,
attributes: {
x: getMainTitleX(fullLayout, textAnchor),
y: getMainTitleY(fullLayout, dy),
attributes: ({
x: x,
y: y,
'text-anchor': textAnchor,
dy: dy
}
})
});

if(title.text && title.automargin) {
var titleObj = d3.selectAll('.gtitle');
var titleHeight = Drawing.bBox(titleObj.node()).height;
var pushMargin = needsMarginPush(gd, title, titleHeight);
if(pushMargin > 0) {
applyTitleAutoMargin(gd, y, pushMargin, titleHeight);
// Re-position the title once we know where it needs to be
titleObj.attr({
x: x,
y: y,
'text-anchor': textAnchor,
dy: getMainTitleDyAdj(title.yanchor)
}).call(svgTextUtils.positionText, x, y);
}
}
};


function isOutsideContainer(gd, title, position, y, titleHeight) {
var plotHeight = title.yref === 'paper' ? gd._fullLayout._size.h : gd._fullLayout.height;
var yPosTop = Lib.isTopAnchor(title) ? y : y - titleHeight; // Standardize to the top of the title
var yPosRel = position === 'b' ? plotHeight - yPosTop : yPosTop; // Position relative to the top or bottom of plot
if((Lib.isTopAnchor(title) && position === 't') || Lib.isBottomAnchor(title) && position === 'b') {
return false;
} else {
return yPosRel < titleHeight;
}
}

function containerPushVal(position, titleY, titleYanchor, height, titleDepth) {
var push = 0;
if(titleYanchor === 'middle') {
push += titleDepth / 2;
}
if(position === 't') {
if(titleYanchor === 'top') {
push += titleDepth;
}
push += (height - titleY * height);
} else {
if(titleYanchor === 'bottom') {
push += titleDepth;
}
push += titleY * height;
}
return push;
}

function needsMarginPush(gd, title, titleHeight) {
var titleY = title.y;
var titleYanchor = title.yanchor;
var position = titleY > 0.5 ? 't' : 'b';
var curMargin = gd._fullLayout.margin[position];
var pushMargin = 0;
if(title.yref === 'paper') {
pushMargin = (
titleHeight +
title.pad.t +
title.pad.b
);
} else if(title.yref === 'container') {
pushMargin = (
containerPushVal(position, titleY, titleYanchor, gd._fullLayout.height, titleHeight) +
title.pad.t +
title.pad.b
);
}
if(pushMargin > curMargin) {
return pushMargin;
}
return 0;
}

function applyTitleAutoMargin(gd, y, pushMargin, titleHeight) {
var titleID = 'title.automargin';
var title = gd._fullLayout.title;
var position = title.y > 0.5 ? 't' : 'b';
var push = {
x: title.x,
y: title.y,
t: 0,
b: 0
};
var reservedPush = {};

if(title.yref === 'paper' && isOutsideContainer(gd, title, position, y, titleHeight)) {
push[position] = pushMargin;
} else if(title.yref === 'container') {
reservedPush[position] = pushMargin;
gd._fullLayout._reservedMargin[titleID] = reservedPush;
}
Plots.allowAutoMargin(gd, titleID);
Plots.autoMargin(gd, titleID, push);
}

function getMainTitleX(fullLayout, textAnchor) {
var title = fullLayout.title;
var gs = fullLayout._size;
Expand All @@ -439,7 +536,6 @@ function getMainTitleY(fullLayout, dy) {
var title = fullLayout.title;
var gs = fullLayout._size;
var vPadShift = 0;

if(dy === '0em' || !dy) {
vPadShift = -title.pad.b;
} else if(dy === alignmentConstants.CAP_SHIFT + 'em') {
Expand All @@ -459,6 +555,16 @@ function getMainTitleY(fullLayout, dy) {
}
}

function getMainTitleDyAdj(yanchor) {
if(yanchor === 'top') {
return alignmentConstants.CAP_SHIFT + 0.3 + 'em';
} else if(yanchor === 'bottom') {
return '-0.3em';
} else {
return alignmentConstants.MID_SHIFT + 'em';
}
}

function getMainTitleTextAnchor(fullLayout) {
var title = fullLayout.title;

Expand Down
15 changes: 15 additions & 0 deletions src/plots/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ module.exports = {
'Padding is muted if the respective anchor value is *middle*/*center*.'
].join(' ')
}),
automargin: {
valType: 'boolean',
dflt: false,
editType: 'plot',
description: [
'Determines whether the title can automatically push the figure margins.',
'If `yref=\'paper\'` then the margin will expand to ensure that the title doesn\’t',
'overlap with the edges of the container. If `yref=\'container\'` then the margins',
'will ensure that the title doesn\’t overlap with the plot area, tick labels,',
'and axis titles. If `automargin=true` and the margins need to be expanded,',
'then y will be set to a default 1 and yanchor will be set to an appropriate',
'default to ensure that minimal margin space is needed. Note that when `yref=\'paper\'`,',
'only 1 or 0 are allowed y values. Invalid values will be reset to the default 1.'
].join(' ')
},
editType: 'layoutstyle'
},
uniformtext: {
Expand Down
64 changes: 51 additions & 13 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,15 +1480,41 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {

coerce('title.text', layoutOut._dfltTitle.plot);
coerce('title.xref');
coerce('title.yref');
coerce('title.x');
coerce('title.y');
coerce('title.xanchor');
coerce('title.yanchor');
var titleYref = coerce('title.yref');
coerce('title.pad.t');
coerce('title.pad.r');
coerce('title.pad.b');
coerce('title.pad.l');
var titleAutomargin = coerce('title.automargin');

coerce('title.x');
coerce('title.xanchor');
coerce('title.y');
coerce('title.yanchor');

if(titleAutomargin) {
// when automargin=true
// title.y is 1 or 0 if paper ref
// 'auto' is not supported for either title.y or title.yanchor

// TODO: mention this smart default in the title.y and title.yanchor descriptions

if(titleYref === 'paper') {
if(layoutOut.title.y !== 0) layoutOut.title.y = 1;

if(layoutOut.title.yanchor === 'auto') {
layoutOut.title.yanchor = layoutOut.title.y === 0 ? 'top' : 'bottom';
}
}

if(titleYref === 'container') {
if(layoutOut.title.y === 'auto') layoutOut.title.y = 1;

if(layoutOut.title.yanchor === 'auto') {
layoutOut.title.yanchor = layoutOut.title.y < 0.5 ? 'bottom' : 'top';
}
}
}

var uniformtextMode = coerce('uniformtext.mode');
if(uniformtextMode) {
Expand Down Expand Up @@ -1862,6 +1888,7 @@ function initMargins(fullLayout) {
}
if(!fullLayout._pushmargin) fullLayout._pushmargin = {};
if(!fullLayout._pushmarginIds) fullLayout._pushmarginIds = {};
if(!fullLayout._reservedMargin) fullLayout._reservedMargin = {};
}

// non-negotiable - this is the smallest height we will allow users to specify via explicit margins
Expand Down Expand Up @@ -1979,8 +2006,16 @@ plots.doAutoMargin = function(gd) {

var gs = fullLayout._size;
var margin = fullLayout.margin;
var reservedMargins = {t: 0, b: 0, l: 0, r: 0};
var oldMargins = Lib.extendFlat({}, gs);

var margins = gd._fullLayout._reservedMargin;
for(var key in margins) {
for(var side in margins[key]) {
var val = margins[key][side];
reservedMargins[side] = Math.max(reservedMargins[side], val);
}
}
// adjust margins for outside components
// fullLayout.margin is the requested margin,
// fullLayout._size has margins and plotsize after adjustment
Expand Down Expand Up @@ -2016,14 +2051,16 @@ plots.doAutoMargin = function(gd) {
var pl = pushleft.size;
var fb = pushbottom.val;
var pb = pushbottom.size;
var availableWidth = width - reservedMargins.r - reservedMargins.l;
var availableHeight = height - reservedMargins.t - reservedMargins.b;

for(var k2 in pushMargin) {
if(isNumeric(pl) && pushMargin[k2].r) {
var fr = pushMargin[k2].r.val;
var pr = pushMargin[k2].r.size;
if(fr > fl) {
var newL = (pl * fr + (pr - width) * fl) / (fr - fl);
var newR = (pr * (1 - fl) + (pl - width) * (1 - fr)) / (fr - fl);
var newL = (pl * fr + (pr - availableWidth) * fl) / (fr - fl);
var newR = (pr * (1 - fl) + (pl - availableWidth) * (1 - fr)) / (fr - fl);
if(newL + newR > ml + mr) {
ml = newL;
mr = newR;
Expand All @@ -2035,8 +2072,8 @@ plots.doAutoMargin = function(gd) {
var ft = pushMargin[k2].t.val;
var pt = pushMargin[k2].t.size;
if(ft > fb) {
var newB = (pb * ft + (pt - height) * fb) / (ft - fb);
var newT = (pt * (1 - fb) + (pb - height) * (1 - ft)) / (ft - fb);
var newB = (pb * ft + (pt - availableHeight) * fb) / (ft - fb);
var newT = (pt * (1 - fb) + (pb - availableHeight) * (1 - ft)) / (ft - fb);
if(newB + newT > mb + mt) {
mb = newB;
mt = newT;
Expand Down Expand Up @@ -2078,10 +2115,11 @@ plots.doAutoMargin = function(gd) {
}
}

gs.l = Math.round(ml);
gs.r = Math.round(mr);
gs.t = Math.round(mt);
gs.b = Math.round(mb);

gs.l = Math.round(ml) + reservedMargins.l;
gs.r = Math.round(mr) + reservedMargins.r;
gs.t = Math.round(mt) + reservedMargins.t;
gs.b = Math.round(mb) + reservedMargins.b;
gs.p = Math.round(margin.pad);
gs.w = Math.round(width) - gs.l - gs.r;
gs.h = Math.round(height) - gs.t - gs.b;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions test/image/mocks/zzz-automargin-title-container-bottom.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"data": [
{
"showlegend": false,
"type": "scatter",
"x": [
1,
2,
3
],
"y": [
4,
5,
6
]
}],
"layout": {
"height": 300,
"width": 400,
"margin": {"t":0, "b": 0, "l": 0, "r": 0},
"xaxis": {"automargin": true, "title": {"text": "x-axis title"}},
"title": {
"automargin": true,
"text": "Container | pad | y=0",
"pad": {"t": 15, "b": 10},
"y": 0,
"yref": "container"
}
}
}
29 changes: 29 additions & 0 deletions test/image/mocks/zzz-automargin-title-container.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"data": [
{
"showlegend": false,
"type": "scatter",
"x": [
1,
2,
3
],
"y": [
4,
5,
6
]
}],
"layout": {
"height": 300,
"width": 400,
"margin": {"t":0, "b": 0, "l": 0, "r": 0},
"xaxis": {"automargin": true, "title": {"text": "x-axis title"}},
"title": {
"automargin": true,
"text": "Container | no-pad | y=1",
"pad": {"t": 0, "b": 0},
"yref": "container"
}
}
}
Loading