Skip to content

Commit 0dea987

Browse files
committed
derive x & y scale domains from geometry
1 parent 60be56d commit 0dea987

File tree

9 files changed

+180
-30
lines changed

9 files changed

+180
-30
lines changed

src/dimensions.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function createDimensions(scales, marks, options = {}) {
3838
// specified explicitly, adjust the automatic height accordingly.
3939
let {
4040
width = 640,
41-
height = autoHeight(scales, marks, options, {
41+
height = autoHeight(scales, options, {
4242
width,
4343
marginTopDefault,
4444
marginRightDefault,
@@ -89,14 +89,13 @@ export function createDimensions(scales, marks, options = {}) {
8989

9090
function autoHeight(
9191
{x, y, fy, fx},
92-
marks,
9392
{projection, aspectRatio},
9493
{width, marginTopDefault, marginRightDefault, marginBottomDefault, marginLeftDefault}
9594
) {
9695
const nfy = fy ? fy.scale.domain().length : 1;
9796

9897
// If a projection is specified, use its natural aspect ratio (if known).
99-
const ar = projectionAspectRatio(projection, marks);
98+
const ar = projectionAspectRatio(projection);
10099
if (ar) {
101100
const nfx = fx ? fx.scale.domain().length : 1;
102101
const far = ((1.1 * nfy - 0.1) / (1.1 * nfx - 0.1)) * ar; // 0.1 is default facet padding

src/marks/geo.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class Geo extends Mark {
2222
super(
2323
data,
2424
{
25-
geometry: {value: options.geometry},
25+
geometry: {value: options.geometry, scale: "projection"},
2626
r: {value: vr, scale: "r", filter: positive, optional: true}
2727
},
2828
withDefaultSort(options),

src/plot.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {Mark} from "./mark.js";
88
import {axisFx, axisFy, axisX, axisY, gridFx, gridFy, gridX, gridY} from "./marks/axis.js";
99
import {frame} from "./marks/frame.js";
1010
import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeIntervalTransform} from "./options.js";
11-
import {createProjection} from "./projection.js";
11+
import {createProjection, getGeometryChannels} from "./projection.js";
1212
import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js";
1313
import {innerDimensions, outerDimensions} from "./scales.js";
1414
import {position, registry as scaleRegistry} from "./scales/index.js";
@@ -404,15 +404,26 @@ function addScaleChannels(channelsByScale, stateByMark, filter = yes) {
404404
const channel = channels[name];
405405
const {scale} = channel;
406406
if (scale != null && filter(scale)) {
407-
const scaleChannels = channelsByScale.get(scale);
408-
if (scaleChannels !== undefined) scaleChannels.push(channel);
409-
else channelsByScale.set(scale, [channel]);
407+
if (scale === "projection") {
408+
// TODO only do this if there’s no projection
409+
const [x, y] = getGeometryChannels(channel);
410+
addScaleChannel(channelsByScale, "x", x);
411+
addScaleChannel(channelsByScale, "y", y);
412+
} else {
413+
addScaleChannel(channelsByScale, scale, channel);
414+
}
410415
}
411416
}
412417
}
413418
return channelsByScale;
414419
}
415420

421+
function addScaleChannel(channelsByScale, scale, channel) {
422+
const scaleChannels = channelsByScale.get(scale);
423+
if (scaleChannels !== undefined) scaleChannels.push(channel);
424+
else channelsByScale.set(scale, [channel]);
425+
}
426+
416427
// Returns the facet groups, and possibly fx and fy channels, associated with
417428
// the top-level facet option {data, x, y}.
418429
function maybeTopFacet(facet, options) {
@@ -486,8 +497,8 @@ function inferAxes(marks, channelsByScale, options) {
486497
} = options;
487498

488499
// Disable axes if the corresponding scale is not present.
489-
if (projection || (!isScaleOptions(x) && !hasScaleChannel("x", marks))) xAxis = xGrid = null;
490-
if (projection || (!isScaleOptions(y) && !hasScaleChannel("y", marks))) yAxis = yGrid = null;
500+
if (projection || (!isScaleOptions(x) && !hasPositionChannel("x", marks))) xAxis = xGrid = null;
501+
if (projection || (!isScaleOptions(y) && !hasPositionChannel("y", marks))) yAxis = yGrid = null;
491502
if (!channelsByScale.has("fx")) fxAxis = fxGrid = null;
492503
if (!channelsByScale.has("fy")) fyAxis = fyGrid = null;
493504

@@ -613,10 +624,11 @@ function hasAxis(marks, k) {
613624
return marks.some((m) => m.ariaLabel?.startsWith(prefix));
614625
}
615626

616-
function hasScaleChannel(k, marks) {
627+
function hasPositionChannel(k, marks) {
617628
for (const mark of marks) {
618629
for (const key in mark.channels) {
619-
if (mark.channels[key].scale === k) {
630+
const {scale} = mark.channels[key];
631+
if (scale === k || scale === "projection") {
620632
return true;
621633
}
622634
}

src/projection.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
geoOrthographic,
1515
geoPath,
1616
geoStereographic,
17+
geoStream,
1718
geoTransform,
1819
geoTransverseMercator
1920
} from "d3";
@@ -229,10 +230,10 @@ export function project(cx, cy, values, projection) {
229230
// construct the projection), we have to test the raw projection option rather
230231
// than the materialized projection; therefore we must be extremely careful that
231232
// the logic of this function exactly matches Projection above!
232-
export function projectionAspectRatio(projection, marks) {
233+
export function projectionAspectRatio(projection) {
233234
if (typeof projection?.stream === "function") return defaultAspectRatio;
234235
if (isObject(projection)) projection = projection.type;
235-
if (projection == null) return hasGeometry(marks) ? defaultAspectRatio : undefined;
236+
if (projection == null) return;
236237
if (typeof projection !== "function") {
237238
const {aspectRatio} = namedProjection(projection);
238239
if (aspectRatio) return aspectRatio;
@@ -254,7 +255,22 @@ export function applyPosition(channels, scales, {projection}) {
254255
return position;
255256
}
256257

257-
function hasGeometry(marks) {
258-
for (const mark of marks) if (mark.channels.geometry) return true;
259-
return false;
258+
export function getGeometryChannels(channel) {
259+
const X = [];
260+
const Y = [];
261+
const x = {scale: "x", value: X};
262+
const y = {scale: "y", value: Y};
263+
const sink = {
264+
point(x, y) {
265+
X.push(x);
266+
Y.push(y);
267+
},
268+
lineStart() {},
269+
lineEnd() {},
270+
polygonStart() {},
271+
polygonEnd() {},
272+
sphere() {}
273+
};
274+
for (const object of channel.value) geoStream(object, sink);
275+
return [x, y];
260276
}

src/scales/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export const opacity = Symbol("opacity");
2222
// Symbol scales have a default range of categorical symbols.
2323
export const symbol = Symbol("symbol");
2424

25+
// There isn’t really a projection scale; this represents x and y for geometry.
26+
export const projection = Symbol("projection");
27+
2528
// TODO Rather than hard-coding the list of known scale names, collect the names
2629
// and categories for each plot specification, so that custom marks can register
2730
// custom scales.
@@ -34,5 +37,6 @@ export const registry = new Map([
3437
["color", color],
3538
["opacity", opacity],
3639
["symbol", symbol],
37-
["length", length]
40+
["length", length],
41+
["projection", projection]
3842
]);

test/output/geoLine.svg

Lines changed: 65 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)