Skip to content

Commit 19c81eb

Browse files
Filmbostock
andauthored
projection supports dodgeX/dodgeY (#1181)
* projection supports dodgeX/dodgeY * test plots * DRY Position * simpler examples Co-authored-by: Mike Bostock <[email protected]>
1 parent c323b78 commit 19c81eb

File tree

11 files changed

+44691
-30
lines changed

11 files changed

+44691
-30
lines changed

src/marks/density.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {contourDensity, create, geoPath} from "d3";
2-
import {valueObject} from "../channel.js";
32
import {isTypedArray, maybeTuple, maybeZ} from "../options.js";
43
import {Mark} from "../plot.js";
5-
import {maybeProject} from "../projection.js";
4+
import {Position} from "../projection.js";
65
import {coerceNumbers} from "../scales.js";
76
import {
87
applyFrameAnchor,
@@ -93,17 +92,8 @@ function densityInitializer(options, fillDensity, strokeDensity) {
9392
const [cx, cy] = applyFrameAnchor(this, dimensions);
9493
const {width, height} = dimensions;
9594

96-
// Extract the (possibly) scaled values for the x and y channels.
97-
const position = valueObject({...(channels.x && {x: channels.x}), ...(channels.y && {y: channels.y})}, scales);
98-
99-
// Apply the projection.
100-
if (context.projection) maybeProject("x", "y", channels, position, context);
101-
102-
// Coerce the x and y channels to numbers (so that null is properly treated
103-
// as an undefined value rather than being coerced to zero).
104-
let {x: X, y: Y} = position;
105-
if (X) X = coerceNumbers(X);
106-
if (Y) Y = coerceNumbers(Y);
95+
// Get the (either scaled or projected) xy channels.
96+
const {x: X, y: Y} = Position(channels, scales, context);
10797

10898
// Group any of the input channels according to the first index associated
10999
// with each z-series or facet. Drop any channels not be needed for

src/projection.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import {
1717
geoTransform,
1818
geoTransverseMercator
1919
} from "d3";
20+
import {valueObject} from "./channel.js";
2021
import {constant, isObject} from "./options.js";
22+
import {coerceNumbers} from "./scales.js";
2123
import {warn} from "./warnings.js";
2224

2325
const pi = Math.PI;
@@ -250,3 +252,13 @@ export function projectionAspectRatio(projection, geometry) {
250252
}
251253
return defaultAspectRatio;
252254
}
255+
256+
// Extract the (possibly) scaled values for the x and y channels, and apply the
257+
// projection if any.
258+
export function Position(channels, scales, context) {
259+
const position = valueObject({...(channels.x && {x: channels.x}), ...(channels.y && {y: channels.y})}, scales);
260+
if (context.projection) maybeProject("x", "y", channels, position, context);
261+
if (position.x) position.x = coerceNumbers(position.x);
262+
if (position.y) position.y = coerceNumbers(position.y);
263+
return position;
264+
}

src/transforms/dodge.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {finite, positive} from "../defined.js";
33
import {identity, maybeNamed, number, valueof} from "../options.js";
44
import {coerceNumbers} from "../scales.js";
55
import {initializer} from "./basic.js";
6+
import {Position} from "../projection.js";
67

78
const anchorXLeft = ({marginLeft}) => [1, marginLeft];
89
const anchorXRight = ({width, marginRight}) => [-1, width - marginRight];
@@ -67,9 +68,10 @@ function dodge(y, x, anchor, padding, options) {
6768
options = {...options, channels: {r: {value: r, scale: "r"}, ...maybeNamed(channels)}};
6869
if (sort === undefined && reverse === undefined) options.sort = {channel: "r", order: "descending"};
6970
}
70-
return initializer(options, function (data, facets, {[x]: X, r: R}, scales, dimensions) {
71-
if (!X) throw new Error(`missing channel: ${x}`);
72-
X = coerceNumbers(valueof(X.value, scales[X.scale] || identity));
71+
return initializer(options, function (data, facets, channels, scales, dimensions, context) {
72+
let {[x]: X, r: R} = channels;
73+
if (!channels[x]) throw new Error(`missing channel: ${x}`);
74+
({[x]: X} = Position(channels, scales, context));
7375
const r = R ? undefined : this.r !== undefined ? this.r : options.r !== undefined ? number(options.r) : 3;
7476
if (R) R = coerceNumbers(valueof(R.value, scales[R.scale] || identity));
7577
let [ky, ty] = anchor(dimensions);

src/transforms/hexbin.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import {valueObject} from "../channel.js";
2-
import {coerceNumbers} from "../scales.js";
31
import {sqrt3} from "../symbols.js";
42
import {isNoneish, number, valueof} from "../options.js";
53
import {initializer} from "./basic.js";
64
import {hasOutput, maybeGroup, maybeOutputs, maybeSubgroup} from "./group.js";
7-
import {maybeProject} from "../projection.js";
5+
import {Position} from "../projection.js";
86

97
// We don’t want the hexagons to align with the edges of the plot frame, as that
108
// would cause extreme x-values (the upper bound of the default x-scale domain)
@@ -37,17 +35,8 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
3735
if (X === undefined) throw new Error("missing channel: x");
3836
if (Y === undefined) throw new Error("missing channel: y");
3937

40-
// Extract the (possibly) scaled values for the x and y channels.
41-
const position = valueObject({x: X, y: Y}, scales);
42-
43-
// Apply the projection.
44-
if (context.projection) maybeProject("x", "y", channels, position, context);
45-
46-
// Coerce the x and y channels to numbers (so that null is properly
47-
// treated as an undefined value rather than being coerced to zero).
48-
({x: X, y: Y} = position);
49-
X = coerceNumbers(X);
50-
Y = coerceNumbers(Y);
38+
// Get the (either scaled or projected) xy channels.
39+
({x: X, y: Y} = Position(channels, scales, context));
5140

5241
// Extract the values for channels that are eligible for grouping; not all
5342
// marks define a z channel, so compute one if it not already computed. If z

test/data/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ https://www.bls.gov/
3030
1983 ASA Data Exposition
3131
http://lib.stat.cmu.edu/datasets/
3232

33+
## cities-10k.csv
34+
Geonames
35+
https://www.geonames.org/about.html
36+
https://public.opendatasoft.com/explore/dataset/geonames-all-cities-with-a-population-1000/
37+
3338
## covid-ihme-projected-deaths.csv
3439
Institute for Health Metrics and Evaluation
3540
https://covid19.healthdata.org/

0 commit comments

Comments
 (0)