Skip to content

Commit 64538a8

Browse files
committed
auto-margin top and bottom (closes #1859)
fix a bug with inferred labels, which can be empty ("Date", "Year"… are filtered out). observe marginLeft and marginRight when specified in the axis mark (this wasn't tested and had lead to a regression)
1 parent a8ee323 commit 64538a8

File tree

7 files changed

+386
-15
lines changed

7 files changed

+386
-15
lines changed

src/dimensions.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {isOrdinalScale} from "./scales.js";
44
import {offset} from "./style.js";
55
import {defaultWidth, monospaceWidth} from "./marks/text.js";
66
import {outerDimensions} from "./scales.js";
7+
import {formatAxisLabel} from "./marks/axis.js";
78

89
const marginMedium = 60;
910
const marginLarge = 90;
@@ -21,18 +22,28 @@ export function autoMarginK(
2122
dimensions,
2223
context
2324
) {
25+
const actualLabel = formatAxisLabel(scale, scales[scale], {...options, label});
2426
let {data, facets, channels} = stateByMark.get(mark);
2527
if (mark.initializer) ({channels} = mark.initializer(data, facets, {}, scales, dimensions, context));
26-
const width = mark.monospace ? monospaceWidth : defaultWidth;
27-
const labelPenalty =
28-
(label ?? scales[scale].label ?? "") !== "" &&
29-
(labelAnchor === "center" || (labelAnchor == null && scales[scale].bandwidth));
30-
const l =
31-
max(channels.text.value, (t) => (t ? width(`${t}`) : NaN)) * (mark.monospace ? 0.6 : 1) + (labelPenalty ? 100 : 0);
32-
const m = l >= 500 ? marginLarge : l >= 295 ? marginMedium : null;
28+
if (scale === "y" || scale === "fy") {
29+
const width = mark.monospace ? monospaceWidth : defaultWidth;
30+
const labelPenalty = actualLabel && (labelAnchor === "center" || (labelAnchor == null && scales[scale].bandwidth));
31+
const l =
32+
max(channels.text.value, (t) => (t ? width(`${t}`) : NaN)) * (mark.monospace ? 0.6 : 1) +
33+
(labelPenalty ? 100 : 0);
34+
const m = l >= 500 ? marginLarge : l >= 295 ? marginMedium : null;
35+
return m === null
36+
? options
37+
: scale === "fy"
38+
? {...options, facet: {[margin]: m, ...options.facet}}
39+
: {[margin]: m, ...options};
40+
}
41+
// For the x scale, we bump the margin only if the axis uses multi-line ticks!
42+
const re = new RegExp(/\n/);
43+
const m = actualLabel && channels.text.value.some((d) => re.test(d)) ? 40 : null;
3344
return m === null
3445
? options
35-
: scale === "fy"
46+
: scale === "fx"
3647
? {...options, facet: {[margin]: m, ...options.facet}}
3748
: {[margin]: m, ...options};
3849
}
@@ -53,9 +64,21 @@ export function createDimensions(scales, marks, options = {}) {
5364
// revise the dimensions if necessary.
5465
const autoMargins = [];
5566
for (const m of marks) {
56-
let {marginTop, marginRight, marginBottom, marginLeft, autoMarginRight, autoMarginLeft, frameAnchor} = m;
57-
if (autoMarginLeft && frameAnchor === "left") autoMargins.push(["marginLeft", autoMarginLeft, m]);
67+
let {
68+
marginTop,
69+
marginRight,
70+
marginBottom,
71+
marginLeft,
72+
autoMarginTop,
73+
autoMarginRight,
74+
autoMarginBottom,
75+
autoMarginLeft,
76+
frameAnchor
77+
} = m;
78+
if (autoMarginTop) autoMargins.push(["marginTop", autoMarginTop, m]);
5879
if (autoMarginRight && frameAnchor === "right") autoMargins.push(["marginRight", autoMarginRight, m]);
80+
if (autoMarginBottom) autoMargins.push(["marginBottom", autoMarginBottom, m]);
81+
if (autoMarginLeft && frameAnchor === "left") autoMargins.push(["marginLeft", autoMarginLeft, m]);
5982
if (marginTop > marginTopDefault) marginTopDefault = marginTop;
6083
if (marginRight > marginRightDefault) marginRightDefault = marginRight;
6184
if (marginBottom > marginBottomDefault) marginBottomDefault = marginBottom;

src/marks/axis.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@ function axisKy(
101101
marginRight === undefined &&
102102
anchor === "right" &&
103103
x == null && {scale: k, labelAnchor, label};
104+
marginRight ??= margin === undefined ? (anchor === "right" ? 40 : 0) : margin;
104105
const autoMarginLeft = margin === undefined &&
105106
marginLeft === undefined &&
106107
anchor === "left" &&
107108
x == null && {scale: k, labelAnchor, label};
108-
marginRight = margin === undefined ? (anchor === "right" ? 40 : 0) : margin;
109-
marginLeft = margin === undefined ? (anchor === "left" ? 40 : 0) : margin;
109+
marginLeft ??= margin === undefined ? (anchor === "left" ? 40 : 0) : margin;
110110
return marks(
111111
tickSize && !isNoneish(stroke)
112112
? axisTickKy(k, anchor, data, {
@@ -194,9 +194,9 @@ function axisKx(
194194
tickRotate,
195195
y,
196196
margin,
197-
marginTop = margin === undefined ? (anchor === "top" ? 30 : 0) : margin,
197+
marginTop,
198198
marginRight = margin === undefined ? 20 : margin,
199-
marginBottom = margin === undefined ? (anchor === "bottom" ? 30 : 0) : margin,
199+
marginBottom,
200200
marginLeft = margin === undefined ? 20 : margin,
201201
label,
202202
labelAnchor,
@@ -210,6 +210,16 @@ function axisKx(
210210
tickRotate = number(tickRotate);
211211
if (labelAnchor !== undefined) labelAnchor = keyword(labelAnchor, "labelAnchor", ["center", "left", "right"]);
212212
labelArrow = maybeLabelArrow(labelArrow);
213+
const autoMarginTop = margin === undefined &&
214+
marginTop === undefined &&
215+
anchor === "top" &&
216+
y == null && {scale: k, labelAnchor, label};
217+
marginTop ??= margin === undefined ? (anchor === "top" ? 30 : 0) : margin;
218+
const autoMarginBottom = margin === undefined &&
219+
marginBottom === undefined &&
220+
anchor === "bottom" &&
221+
y == null && {scale: k, labelAnchor, label};
222+
marginBottom ??= margin === undefined ? (anchor === "bottom" ? 30 : 0) : margin;
213223
return marks(
214224
tickSize && !isNoneish(stroke)
215225
? axisTickKx(k, anchor, data, {
@@ -239,6 +249,8 @@ function axisKx(
239249
marginRight,
240250
marginBottom,
241251
marginLeft,
252+
autoMarginTop,
253+
autoMarginBottom,
242254
...options
243255
})
244256
: null,
@@ -645,7 +657,10 @@ function axisMark(mark, k, data, properties, options, initialize) {
645657
}
646658
if (properties !== undefined) Object.assign(m, properties);
647659
m.autoMarginLeft = options.autoMarginLeft;
660+
m.autoMarginTop = options.autoMarginTop;
648661
m.autoMarginRight = options.autoMarginRight;
662+
m.autoMarginBottom = options.autoMarginBottom;
663+
m.autoMarginLeft = options.autoMarginLeft;
649664
if (m.clip === undefined) m.clip = false; // don’t clip axes by default
650665
return m;
651666
}
@@ -719,7 +734,7 @@ function inferFontVariant(scale) {
719734

720735
// Takes the scale label, and if this is not an ordinal scale and the label was
721736
// inferred from an associated channel, adds an orientation-appropriate arrow.
722-
function formatAxisLabel(k, scale, {anchor, label = scale.label, labelAnchor, labelArrow} = {}) {
737+
export function formatAxisLabel(k, scale, {anchor, label = scale.label, labelAnchor, labelArrow} = {}) {
723738
if (label == null || (label.inferred && hasTemporalDomain(scale) && /^(date|time|year)$/i.test(label))) return;
724739
label = String(label); // coerce to a string after checking if inferred
725740
if (labelArrow === "auto") labelArrow = (!scale.bandwidth || scale.interval) && !/[]/.test(label);

test/output/aaplCloseAxisMargins.svg

Lines changed: 55 additions & 0 deletions
Loading

test/output/aaplCloseLabel.svg

Lines changed: 123 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)