Skip to content

Commit

Permalink
default ordinal time axis
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Aug 6, 2023
1 parent 0de63df commit bc7ec6e
Show file tree
Hide file tree
Showing 14 changed files with 556 additions and 235 deletions.
36 changes: 28 additions & 8 deletions src/marks/axis.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {extent, format, timeFormat, utcFormat} from "d3";
import {extent, format, timeFormat, timeTicks, utcFormat, utcTicks} from "d3";
import {formatDefault} from "../format.js";
import {marks} from "../mark.js";
import {radians} from "../math.js";
import {arrayify, constant, identity, keyword, number, range, valueof} from "../options.js";
import {isIterable, isNoneish, isTemporal, orderof} from "../options.js";
import {isIterable, isNoneish, isTemporal, isTimeInterval, orderof} from "../options.js";
import {maybeColorChannel, maybeNumberChannel, maybeRangeInterval} from "../options.js";
import {isTemporalScale} from "../scales.js";
import {isOrdinalScale, isTemporalScale} from "../scales.js";
import {offset} from "../style.js";
import {formatTimeTicks, isTimeYear, isUtcYear} from "../time.js";
import {initializer} from "../transforms/basic.js";
Expand Down Expand Up @@ -512,8 +512,10 @@ function axisMark(mark, k, ariaLabel, data, options, initialize) {
const initializeFacets = data == null && (k === "fx" || k === "fy");
const {[k]: scale} = scales;
if (!scale) throw new Error(`missing scale: ${k}`);
let {ticks, tickSpacing, interval} = options;
if (isTemporalScale(scale) && typeof ticks === "string") (interval = ticks), (ticks = undefined);
let {ticks, tickSpacing = k === "x" ? 80 : 35, interval} = options;
// TODO what if ticks is a time interval implementation?
// TODO allow ticks to be a function?
if (isTemporalish(scale) && typeof ticks === "string") (interval = ticks), (ticks = undefined);
if (data == null) {
if (isIterable(ticks)) {
data = arrayify(ticks);
Expand All @@ -531,12 +533,30 @@ function axisMark(mark, k, ariaLabel, data, options, initialize) {
data = interval.range(min, interval.offset(interval.floor(max))); // inclusive max
} else {
const [min, max] = extent(scale.range());
ticks = (max - min) / (tickSpacing === undefined ? (k === "x" ? 80 : 35) : tickSpacing);
ticks = (max - min) / tickSpacing;
data = scale.ticks(ticks);
}
}
} else {
data = scale.domain();
// ordinal time
// TODO determine whether the interval is utc or local time?
// TODO assert that the interval is one of utcTick’s known intervals?
// TODO add ceil to the RangeIntervalImplementation interface?
if (isTimeInterval(scale.interval)) {
const type = "utc";
const [start, stop] = extent(data);
if (interval) {
data = maybeRangeInterval(interval, type).range(start, stop);
data = data.map(scale.interval.ceil ?? scale.interval.floor, scale.interval);
} else {
if (ticks === undefined) {
const [min, max] = extent(scale.range());
ticks = (max - min) / tickSpacing;
}
data = ticks < data.length ? (type === "time" ? timeTicks : utcTicks)(start, stop, ticks) : data;
}
}
}
if (k === "y" || k === "x") {
facets = [range(data)];
Expand Down Expand Up @@ -575,7 +595,7 @@ function inferTextChannel(scale, data, ticks, tickFormat, anchor) {
// domain (or ticks) are numbers or dates (say because we’re applying a time
// interval to the ordinal scale), we want Plot’s default formatter.
export function inferTickFormat(scale, data, ticks, tickFormat, anchor) {
return tickFormat === undefined && isTemporalScale(scale)
return tickFormat === undefined && isTemporalish(scale)
? formatTimeTicks(scale, data, ticks, anchor)
: scale.tickFormat
? scale.tickFormat(isIterable(ticks) ? null : ticks, tickFormat)
Expand Down Expand Up @@ -672,5 +692,5 @@ function maybeLabelArrow(labelArrow = "auto") {
}

function isTemporalish(scale) {
return isTemporalScale(scale) || scale.interval != null;
return isTemporalScale(scale) || (isOrdinalScale(scale) && isTimeInterval(scale.interval));
}
8 changes: 8 additions & 0 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,14 @@ export function maybeNiceInterval(interval, type) {
return interval;
}

export function isTimeInterval(t) {
return isInterval(t) && typeof t?.floor === "function" && t.floor() instanceof Date;
}

export function isInterval(t) {
return typeof t?.range === "function";
}

// This distinguishes between per-dimension options and a standalone value.
export function maybeValue(value) {
return value === undefined || isOptions(value) ? value : {value};
Expand Down
13 changes: 7 additions & 6 deletions src/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function isTimeYear(i) {
return timeYear(date) >= date; // coercing equality
}

export function formatTimeTicks(scale, data, ticks, anchor) {
export function formatTimeTicks(scale, data = scale.domain(), ticks, anchor) {
const format = scale.type === "time" ? timeFormat : utcFormat;
const template =
anchor === "left" || anchor === "right"
Expand Down Expand Up @@ -145,11 +145,12 @@ export function formatTimeTicks(scale, data, ticks, anchor) {
// the ticks show the field that is changing. If the ticks are not available,
// fallback to an approximation based on the desired number of ticks.
function getTimeTicksInterval(scale, data, ticks) {
const medianStep = median(pairs(data, (a, b) => Math.abs(b - a) || NaN));
if (medianStep > 0) return formats[bisector(([, step]) => step).right(formats, medianStep, 1, formats.length) - 1][0];
const [start, stop] = extent(scale.domain());
const count = typeof ticks === "number" ? ticks : 10;
const step = Math.abs(stop - start) / count;
let step = median(pairs(data, (a, b) => Math.abs(b - a) || NaN));
if (!(step > 0)) {
const [start, stop] = extent(scale.domain());
const count = typeof ticks === "number" ? ticks : 10;
step = Math.abs(stop - start) / count;
}
return formats[bisector(([, step]) => Math.log(step)).center(formats, Math.log(step))][0];
}

Expand Down
10 changes: 2 additions & 8 deletions src/transforms/bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
coerceDate,
coerceNumbers,
identity,
isInterval,
isIterable,
isTemporal,
isTimeInterval,
labelof,
map,
maybeApplyInterval,
Expand Down Expand Up @@ -361,14 +363,6 @@ function isTimeThresholds(t) {
return isTimeInterval(t) || (isIterable(t) && isTemporal(t));
}

function isTimeInterval(t) {
return isInterval(t) && typeof t === "function" && t() instanceof Date;
}

function isInterval(t) {
return typeof t?.range === "function";
}

function bing(EX, EY) {
return EX && EY
? function* (I) {
Expand Down
12 changes: 6 additions & 6 deletions test/output/bandClip2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit bc7ec6e

Please sign in to comment.