Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

geometries and projections #1111

Merged
merged 43 commits into from
Nov 26, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
bf05165
geometry mark
mbostock Nov 23, 2022
79d4538
pRetTIEr
mbostock Nov 23, 2022
1c2fd49
project x & y; re-fix #1043
mbostock Nov 23, 2022
ca62bc2
more projections, and options
mbostock Nov 23, 2022
5945fa3
add missing test
mbostock Nov 23, 2022
8027a85
PretTier
mbostock Nov 23, 2022
587420d
projection + hexbin
mbostock Nov 24, 2022
f63a8eb
projection.stream for x and y
mbostock Nov 24, 2022
d320bb7
test null projection
mbostock Nov 24, 2022
a85bc7a
another comment
mbostock Nov 24, 2022
8191c5a
remove TODO’s
mbostock Nov 24, 2022
8d63435
simplify walmarts
mbostock Nov 24, 2022
854b4ac
Update README
mbostock Nov 24, 2022
7c0711c
sphere, graticule
mbostock Nov 24, 2022
9c8a785
update graticule snapshot
mbostock Nov 24, 2022
119e3e8
Walmart data provenance
Fil Nov 25, 2022
4bc985e
r channel for geometries; sort by R.
Fil Nov 25, 2022
06ac9a9
Use the facets dimensions, and respect margins. Note that this change…
Fil Nov 25, 2022
6b6c05d
clip: "sphere"
mbostock Nov 25, 2022
fe09ac7
allow projection configuration function
mbostock Nov 25, 2022
355467a
revert dimensions changes
mbostock Nov 25, 2022
5d0e16c
withDefaultSort
mbostock Nov 25, 2022
ce63d6d
Merge branch 'fil/geo' into mbostock/geo
mbostock Nov 25, 2022
2bb4ff0
tweak faceted walmarts test
mbostock Nov 25, 2022
d93b812
fix auto height for pre-projected geometry
mbostock Nov 25, 2022
15c6880
more projections
mbostock Nov 26, 2022
d973dbb
projection + density
mbostock Nov 26, 2022
e2b1691
all marks can clip to sphere
Fil Nov 26, 2022
2e4ddf3
test voronoi clip to sphere
Fil Nov 26, 2022
4b561a4
error if sphere clip lacks projection
mbostock Nov 26, 2022
d0e41cb
error if projection and band-requiring channels
mbostock Nov 26, 2022
c0cd002
Update README.md
mbostock Nov 26, 2022
1114a5b
Update README.md
mbostock Nov 26, 2022
ed1b7e5
pReTTIER
mbostock Nov 26, 2022
cf8476e
projection requires x and y channels
mbostock Nov 26, 2022
11a8e83
Update README
mbostock Nov 26, 2022
23d3c83
Update README
mbostock Nov 26, 2022
c2c4d49
Update README
mbostock Nov 26, 2022
30b1acd
Update README
mbostock Nov 26, 2022
dce3b72
a more interesting voronoi map
Fil Nov 26, 2022
48da147
geo
mbostock Nov 26, 2022
3efd94d
use fitExtent for armadillo
mbostock Nov 26, 2022
4690c5e
more data provenance
mbostock Nov 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"prettier": "^2.7.1",
"rollup": "2",
"rollup-plugin-terser": "7",
"topojson-client": "^3.1.0",
"tsx": "^3.8.0",
"typescript": "^4.6.4",
"typescript-module-alias": "^1.0.2",
Expand Down
9 changes: 7 additions & 2 deletions src/channel.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ascending, descending, rollup, sort} from "d3";
import {first, isIterable, labelof, map, maybeValue, range, valueof} from "./options.js";
import {applyProjection} from "./projection.js";
import {registry} from "./scales/index.js";
import {maybeReduce} from "./transforms/group.js";

Expand All @@ -24,13 +25,17 @@ export function Channels(descriptors, data) {
}

// TODO Use Float64Array for scales with numeric ranges, e.g. position?
export function valueObject(channels, scales) {
return Object.fromEntries(
export function valueObject(channels, scales, {projection}) {
const values = Object.fromEntries(
Object.entries(channels).map(([name, {scale: scaleName, value}]) => {
const scale = scales[scaleName];
return [name, scale === undefined ? value : map(value, scale)];
})
);
if (projection) {
applyProjection(values, projection);
}
return values;
}

// Note: mutates channel.domain! This is set to a function so that it is lazily
Expand Down
5 changes: 3 additions & 2 deletions src/context.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {creator, select} from "d3";
import {maybeProjection} from "./projection.js";

export function Context({document = window.document} = {}) {
return {document};
export function Context({document = window.document, projection} = {}, dimensions) {
return {document, projection: maybeProjection(projection, dimensions)};
}

export function create(name, {document}) {
Expand Down
9 changes: 5 additions & 4 deletions src/dimensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ export function Dimensions(
scales,
{x: {axis: xAxis} = {}, y: {axis: yAxis} = {}, fx: {axis: fxAxis} = {}, fy: {axis: fyAxis} = {}},
{
projection,
width = 640,
height = autoHeight(scales),
height = autoHeight(scales, projection),
facet: {
margin: facetMargin,
marginTop: facetMarginTop = facetMargin !== undefined ? facetMargin : fxAxis === "top" ? 30 : 0,
Expand Down Expand Up @@ -43,8 +44,8 @@ export function Dimensions(
};
}

function autoHeight({y, fy, fx}) {
function autoHeight({y, fy, fx}, projection) {
const nfy = fy ? fy.scale.domain().length : 1;
const ny = y ? (isOrdinalScale(y) ? y.scale.domain().length : Math.max(7, 17 / nfy)) : 1;
return !!(y || fy) * Math.max(1, Math.min(60, ny * nfy)) * 20 + !!fx * 30 + 60;
const ny = y ? (isOrdinalScale(y) ? y.scale.domain().length : Math.max(7, 17 / nfy)) : projection != null ? 17 : 1;
return !!(y || fy || projection != null) * Math.max(1, Math.min(60, ny * nfy)) * 20 + !!fx * 30 + 60;
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {delaunayLink, delaunayMesh, hull, voronoi, voronoiMesh} from "./marks/de
export {Density, density} from "./marks/density.js";
export {Dot, dot, dotX, dotY, circle, hexagon} from "./marks/dot.js";
export {Frame, frame} from "./marks/frame.js";
export {Geometry, geometry} from "./marks/geometry.js";
export {Hexgrid, hexgrid} from "./marks/hexgrid.js";
export {Image, image} from "./marks/image.js";
export {Line, line, lineX, lineY} from "./marks/line.js";
Expand Down
9 changes: 6 additions & 3 deletions src/marks/delaunay.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class DelaunayLink extends Mark {
markers(this, options);
}
render(index, scales, channels, dimensions, context) {
const {x, y} = scales;
const {x: X, y: Y, z: Z} = channels;
const {curve} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
Expand Down Expand Up @@ -124,7 +125,7 @@ class DelaunayLink extends Mark {

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call(
Z
? (g) =>
Expand Down Expand Up @@ -155,6 +156,7 @@ class AbstractDelaunayMark extends Mark {
);
}
render(index, scales, channels, dimensions, context) {
const {x, y} = scales;
const {x: X, y: Y, z: Z} = channels;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const xi = X ? (i) => X[i] : constant(cx);
Expand All @@ -173,7 +175,7 @@ class AbstractDelaunayMark extends Mark {

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call(
Z
? (g) =>
Expand Down Expand Up @@ -223,6 +225,7 @@ class Voronoi extends Mark {
);
}
render(index, scales, channels, dimensions, context) {
const {x, y} = scales;
const {x: X, y: Y, z: Z} = channels;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const xi = X ? (i) => X[i] : constant(cx);
Expand All @@ -243,7 +246,7 @@ class Voronoi extends Mark {

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call(
Z
? (g) =>
Expand Down
2 changes: 1 addition & 1 deletion src/marks/density.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class Density extends Mark {
const path = geoPath();
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {})
.call((g) =>
g
.selectAll()
Expand Down
3 changes: 2 additions & 1 deletion src/marks/dot.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ export class Dot extends Mark {
}
}
render(index, scales, channels, dimensions, context) {
const {x, y} = scales;
const {x: X, y: Y, r: R, rotate: A, symbol: S} = channels;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const circle = this.symbol === symbolCircle;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
.selectAll()
Expand Down
70 changes: 70 additions & 0 deletions src/marks/geometry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {geoPath} from "d3";
import {create} from "../context.js";
import {identity} from "../options.js";
import {Mark} from "../plot.js";
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";

const defaults = {
ariaLabel: "geometry",
fill: "none",
stroke: "currentColor",
strokeWidth: 1,
strokeLinecap: "round",
strokeLinejoin: "round",
strokeMiterlimit: 1
};

export class Geometry extends Mark {
constructor(data, options = {}) {
const {geometry = identity} = options;
super(
data,
{
geometry: {value: geometry}
},
options,
defaults
);
}
render(index, scales, channels, dimensions, context) {
const {geometry} = channels;
const {projection} = context;
const path = geoPath(projection);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call((g) =>
g
.selectAll()
.data(index)
.enter()
.append("path")
.call(applyDirectStyles, this)
.attr("d", (i) => path(geometry[i]))
.call(applyChannelStyles, this, channels)
)
.node();
}
}

export function geometry(data, options) {
switch (data?.type) {
case "FeatureCollection":
data = data.features;
break;
case "GeometryCollection":
data = data.geometries;
break;
case "Feature":
case "LineString":
case "MultiLineString":
case "MultiPoint":
case "MultiPolygon":
case "Point":
case "Polygon":
case "Sphere":
data = [data];
break;
}
return new Geometry(data, options);
}
7 changes: 4 additions & 3 deletions src/marks/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
applyChannelStyles,
applyDirectStyles,
applyIndirectStyles,
applyTransform,
applyAttr,
impliedString,
applyFrameAnchor
applyFrameAnchor,
applyTransform
} from "../style.js";

const defaults = {
Expand Down Expand Up @@ -66,11 +66,12 @@ export class Image extends Mark {
this.frameAnchor = maybeFrameAnchor(frameAnchor);
}
render(index, scales, channels, dimensions, context) {
const {x, y} = scales;
const {x: X, y: Y, width: W, height: H, src: S} = channels;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
.selectAll()
Expand Down
2 changes: 1 addition & 1 deletion src/marks/rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class Rect extends Mark {
const {insetTop, insetRight, insetBottom, insetLeft, rx, ry} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, {x: X1 && X2 ? x : null, y: Y1 && Y2 ? y : null}, 0, 0)
.call(applyTransform, this, {x: X1 && X2 && x, y: Y1 && Y2 && y}, 0, 0)
.call((g) =>
g
.selectAll()
Expand Down
5 changes: 3 additions & 2 deletions src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,14 @@ export class Text extends Mark {
this.frameAnchor = maybeFrameAnchor(frameAnchor);
}
render(index, scales, channels, dimensions, context) {
const {x, y} = scales;
const {x: X, y: Y, rotate: R, text: T, fontSize: FS} = channels;
const {rotate} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyIndirectTextStyles, this, T, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {x: X && x, y: Y && y})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes #1043.

.call((g) =>
g
.selectAll()
Expand Down Expand Up @@ -140,7 +141,7 @@ function applyMultilineText(selection, {monospace, lineAnchor, lineHeight, lineW
selection.each(function (i) {
const lines = linesof(formatDefault(T[i]));
const n = lines.length;
const y = lineAnchor === "top" ? 0.71 : lineAnchor === "bottom" ? -0.29 - n : (164 - n * 100) / 200;
const y = lineAnchor === "top" ? 0.71 : lineAnchor === "bottom" ? 1 - n : (164 - n * 100) / 200;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reverts #1061.

if (n > 1) {
for (let i = 0; i < n; ++i) {
if (!lines[i]) continue;
Expand Down
3 changes: 2 additions & 1 deletion src/marks/vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class Vector extends Mark {
this.frameAnchor = maybeFrameAnchor(frameAnchor);
}
render(index, scales, channels, dimensions, context) {
const {x, y} = scales;
const {x: X, y: Y, length: L, rotate: R} = channels;
const {length, rotate, anchor} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
Expand All @@ -51,7 +52,7 @@ export class Vector extends Mark {
return create("svg:g", context)
.attr("fill", "none")
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
.selectAll()
Expand Down
6 changes: 3 additions & 3 deletions src/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function plot(options = {}) {
const scales = ScaleFunctions(scaleDescriptors);
const axes = Axes(scaleDescriptors, options);
const dimensions = Dimensions(scaleDescriptors, axes, options);
const context = Context(options);
const context = Context(options, dimensions);

autoScaleRange(scaleDescriptors, dimensions);
autoAxisTicks(scaleDescriptors, axes);
Expand Down Expand Up @@ -160,9 +160,9 @@ export function plot(options = {}) {

autoScaleLabels(channelsByScale, scaleDescriptors, axes, dimensions, options);

// Compute value objects, applying scales as needed.
// Compute value objects, applying scales and projection as needed.
for (const state of stateByMark.values()) {
state.values = valueObject(state.channels, scales);
state.values = valueObject(state.channels, scales, context);
}

const {width, height} = dimensions;
Expand Down
Loading