Skip to content

Commit a8a7ccc

Browse files
committed
scale.integer and possible years heuristic
1 parent 8ef2184 commit a8a7ccc

File tree

5 files changed

+61
-28
lines changed

5 files changed

+61
-28
lines changed

src/marks/axis.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -570,13 +570,17 @@ function inferTextChannel(scale, ticks, tickFormat) {
570570

571571
// D3’s ordinal scales simply use toString by default, but if the ordinal scale
572572
// domain (or ticks) are numbers or dates (say because we’re applying a time
573-
// interval to the ordinal scale), we want Plot’s default formatter.
573+
// interval to the ordinal scale), we want Plot’s default formatter. As a
574+
// heuristic to represent years on a linear scale without a comma, when the
575+
// scale is integer and the domain covers [1500...2200] we default to "d".
574576
function inferTickFormat(scale, ticks, tickFormat) {
575577
switch (scale.type) {
576578
case "point":
577579
case "band":
578580
return tickFormat === undefined
579-
? formatDefault
581+
? isYearFormat(scale)
582+
? format("d")
583+
: formatDefault
580584
: typeof tickFormat === "string"
581585
? (isTemporal(scale.domain) ? utcFormat : format)(tickFormat)
582586
: constant(tickFormat);
@@ -593,12 +597,20 @@ function inferTickFormat(scale, ticks, tickFormat) {
593597
.domain(scale.domain)
594598
.tickFormat(isIterable(ticks) ? null : ticks, tickFormat);
595599
default:
596-
return scaleLinear()
597-
.domain(scale.domain)
598-
.tickFormat(isIterable(ticks) ? null : ticks, tickFormat);
600+
return isYearFormat(scale)
601+
? format("d")
602+
: scaleLinear()
603+
.domain(scale.domain)
604+
.tickFormat(isIterable(ticks) ? null : ticks, tickFormat);
599605
}
600606
}
601607

608+
function isYearFormat({integer, domain}) {
609+
if (integer !== true) return;
610+
for (const d of domain) if (typeof d !== "number" || d < 1500 || d > 2200) return;
611+
return true;
612+
}
613+
602614
function inferTickFunction({type, domain}) {
603615
if (type === "point" || type === "band") return;
604616
const S = type === "log" ? scaleLog : type === "time" ? scaleTime : type === "utc" ? scaleUtc : scaleLinear;

src/scales.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export function Scales(
7676
label = key === "fx" || key === "fy" ? facetLabel : globalLabel,
7777
percent,
7878
transform,
79+
interval,
7980
inset,
8081
insetTop = inset !== undefined ? inset : key === "y" ? globalInsetTop : 0, // not fy
8182
insetRight = inset !== undefined ? inset : key === "x" ? globalInsetRight : 0, // not fx
@@ -87,6 +88,32 @@ export function Scales(
8788
scale.percent = !!percent;
8889
scale.label = label === undefined ? inferScaleLabel(channels, scale) : label;
8990
scale.transform = transform;
91+
92+
// A flag indicating that a sample of the values on every channel are
93+
// integers; an axis may use that information to decide its tickFormat.
94+
if (
95+
!transform &&
96+
!scale.percent &&
97+
(scale.type === "linear" || scale.type === "point" || scale.type === "band")
98+
) {
99+
if (typeof interval === "number") {
100+
scale.integer = interval === Math.floor(interval);
101+
} else {
102+
channels: for (const {value} of channels) {
103+
let c = 0;
104+
for (const x of value) {
105+
if (typeof x !== "number" || (x && x > Math.floor(x))) {
106+
scale.integer = false;
107+
break channels;
108+
} else if (isFinite(x)) {
109+
scale.integer = true;
110+
if (++c > 40) break;
111+
}
112+
}
113+
}
114+
}
115+
}
116+
90117
if (key === "x" || key === "fx") {
91118
scale.insetLeft = +insetLeft;
92119
scale.insetRight = +insetRight;
@@ -567,11 +594,23 @@ export function exposeScales(scales) {
567594
};
568595
}
569596

570-
function censorScale({label, ...scale}) {
597+
function censorScale({label, integer, ...scale}) {
571598
return scale;
572599
}
573600

574-
function instantiateScale({scale, type, domain, range, interpolate, interval, transform, percent, pivot, label}) {
601+
function instantiateScale({
602+
scale,
603+
type,
604+
domain,
605+
range,
606+
interpolate,
607+
interval,
608+
transform,
609+
percent,
610+
pivot,
611+
label,
612+
integer
613+
}) {
575614
if (type === "identity") return {type, apply: (d) => d, invert: (d) => d};
576615
const unknown = scale.unknown ? scale.unknown() : undefined;
577616
return {
@@ -580,6 +619,7 @@ function instantiateScale({scale, type, domain, range, interpolate, interval, tr
580619
...(range !== undefined && {range: slice(range)}), // defensive copy
581620
...(transform !== undefined && {transform}),
582621
...(percent && {percent}), // only exposed if truthy
622+
...(integer !== undefined && {integer}),
583623
...(unknown !== undefined && {unknown}),
584624
...(interval !== undefined && {interval}),
585625

test/plots/energy-production.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export default async function () {
3232
.map((d) => ({...d, Year: +d.YYYYMM.slice(0, 4), Value: +d.Value}));
3333
return Plot.plot({
3434
x: {
35-
tickFormat: "d",
3635
label: null
3736
},
3837
y: {

test/plots/yearly-requests-line.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,5 @@ export default async function () {
1515
[2012, 9],
1616
[2019, 20]
1717
];
18-
return Plot.plot({
19-
label: null,
20-
x: {
21-
interval: 1,
22-
tickFormat: "", // TODO https://github.com/observablehq/plot/issues/768
23-
inset: 20
24-
},
25-
y: {
26-
zero: true
27-
},
28-
marks: [Plot.line(requests)]
29-
});
18+
return Plot.plot({label: null, x: {interval: 1, inset: 20}, y: {zero: true}, marks: [Plot.line(requests)]});
3019
}

test/plots/yearly-requests.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,5 @@ export default async function () {
1515
[2012, 9],
1616
[2019, 20]
1717
];
18-
return Plot.plot({
19-
label: null,
20-
x: {
21-
interval: 1,
22-
tickFormat: "" // TODO https://github.com/observablehq/plot/issues/768
23-
},
24-
marks: [Plot.barY(requests, {x: "0", y: "1"})]
25-
});
18+
return Plot.plot({label: null, x: {interval: 1}, marks: [Plot.barY(requests, {x: "0", y: "1"})]});
2619
}

0 commit comments

Comments
 (0)