Skip to content

Commit 16cc0cd

Browse files
Filchaichontat
authored andcommitted
Axes and grids now support the filter option (observablehq#1665)
* Axes and grids now support {filter, sort, reverse} closes observablehq#1457 closes observablehq#1655
1 parent 6b787cf commit 16cc0cd

8 files changed

+817
-505
lines changed

src/marks/axis.js

+46-44
Original file line numberDiff line numberDiff line change
@@ -507,54 +507,56 @@ function labelOptions(
507507

508508
function axisMark(mark, k, ariaLabel, data, options, initialize) {
509509
let channels;
510-
const m = mark(
511-
data,
512-
initializer(options, function (data, facets, _channels, scales, dimensions, context) {
513-
const initializeFacets = data == null && (k === "fx" || k === "fy");
514-
const {[k]: scale} = scales;
515-
if (!scale) throw new Error(`missing scale: ${k}`);
516-
let {ticks, tickSpacing, interval} = options;
517-
if (isTemporalScale(scale) && typeof ticks === "string") (interval = ticks), (ticks = undefined);
518-
if (data == null) {
519-
if (isIterable(ticks)) {
520-
data = arrayify(ticks);
521-
} else if (scale.ticks) {
522-
if (ticks !== undefined) {
523-
data = scale.ticks(ticks);
510+
511+
function axisInitializer(data, facets, _channels, scales, dimensions, context) {
512+
const initializeFacets = data == null && (k === "fx" || k === "fy");
513+
const {[k]: scale} = scales;
514+
if (!scale) throw new Error(`missing scale: ${k}`);
515+
let {ticks, tickSpacing, interval} = options;
516+
if (isTemporalScale(scale) && typeof ticks === "string") (interval = ticks), (ticks = undefined);
517+
if (data == null) {
518+
if (isIterable(ticks)) {
519+
data = arrayify(ticks);
520+
} else if (scale.ticks) {
521+
if (ticks !== undefined) {
522+
data = scale.ticks(ticks);
523+
} else {
524+
interval = maybeRangeInterval(interval === undefined ? scale.interval : interval, scale.type);
525+
if (interval !== undefined) {
526+
// For time scales, we could pass the interval directly to
527+
// scale.ticks because it’s supported by d3.utcTicks; but
528+
// quantitative scales and d3.ticks do not support numeric
529+
// intervals for scale.ticks, so we compute them here.
530+
const [min, max] = extent(scale.domain());
531+
data = interval.range(min, interval.offset(interval.floor(max))); // inclusive max
524532
} else {
525-
interval = maybeRangeInterval(interval === undefined ? scale.interval : interval, scale.type);
526-
if (interval !== undefined) {
527-
// For time scales, we could pass the interval directly to
528-
// scale.ticks because it’s supported by d3.utcTicks; but
529-
// quantitative scales and d3.ticks do not support numeric
530-
// intervals for scale.ticks, so we compute them here.
531-
const [min, max] = extent(scale.domain());
532-
data = interval.range(min, interval.offset(interval.floor(max))); // inclusive max
533-
} else {
534-
const [min, max] = extent(scale.range());
535-
ticks = (max - min) / (tickSpacing === undefined ? (k === "x" ? 80 : 35) : tickSpacing);
536-
data = scale.ticks(ticks);
537-
}
533+
const [min, max] = extent(scale.range());
534+
ticks = (max - min) / (tickSpacing === undefined ? (k === "x" ? 80 : 35) : tickSpacing);
535+
data = scale.ticks(ticks);
538536
}
539-
} else {
540-
data = scale.domain();
541-
}
542-
if (k === "y" || k === "x") {
543-
facets = [range(data)];
544-
} else {
545-
channels[k] = {scale: k, value: identity};
546537
}
538+
} else {
539+
data = scale.domain();
547540
}
548-
initialize?.call(this, scale, data, ticks, channels);
549-
const initializedChannels = Object.fromEntries(
550-
Object.entries(channels).map(([name, channel]) => {
551-
return [name, {...channel, value: valueof(data, channel.value)}];
552-
})
553-
);
554-
if (initializeFacets) facets = context.filterFacets(data, initializedChannels);
555-
return {data, facets, channels: initializedChannels};
556-
})
557-
);
541+
if (k === "y" || k === "x") {
542+
facets = [range(data)];
543+
} else {
544+
channels[k] = {scale: k, value: identity};
545+
}
546+
}
547+
initialize?.call(this, scale, data, ticks, channels);
548+
const initializedChannels = Object.fromEntries(
549+
Object.entries(channels).map(([name, channel]) => {
550+
return [name, {...channel, value: valueof(data, channel.value)}];
551+
})
552+
);
553+
if (initializeFacets) facets = context.filterFacets(data, initializedChannels);
554+
return {data, facets, channels: initializedChannels};
555+
}
556+
557+
// Apply any basic initializers after the axis initializer computes the ticks.
558+
const basicInitializer = initializer(options).initializer;
559+
const m = mark(data, initializer({...options, initializer: axisInitializer}, basicInitializer));
558560
if (data == null) {
559561
channels = m.channels;
560562
m.channels = {};

test/output/axisFilter.svg

+45
Loading

0 commit comments

Comments
 (0)