Skip to content

Commit

Permalink
Add a path factory to the context
Browse files Browse the repository at this point in the history
  • Loading branch information
Fil committed Nov 22, 2024
1 parent 63c8b53 commit a67c5a9
Show file tree
Hide file tree
Showing 25 changed files with 45 additions and 43 deletions.
5 changes: 4 additions & 1 deletion src/context.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {GeoStreamWrapper} from "d3";
import type {GeoPath, GeoStreamWrapper} from "d3";
import type {MarkOptions} from "./mark.js";

/** Additional rendering context provided to marks and initializers. */
Expand All @@ -18,6 +18,9 @@ export interface Context {
/** The current projection, if any. */
projection?: GeoStreamWrapper;

/** A function to draw GeoJSON with the current projection, if any, otherwise with the x and y scales. */
path?: () => GeoPath;

/** The default clip for all marks. */
clip?: MarkOptions["clip"];
}
2 changes: 1 addition & 1 deletion src/marks/area.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Area extends Mark {
render(index, scales, channels, dimensions, context) {
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales, 0, 0)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/arrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class Arrow extends Mark {
const wingScale = headLength / 1.5;

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class AbstractBar extends Mark {
const w = this._width(scales, channels, dimensions);
const h = this._height(scales, channels, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(this._transform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/contour.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class Contour extends AbstractRaster {
const {geometry: G} = channels;
const path = geoPath();
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) => {
g.selectAll()
Expand Down
6 changes: 3 additions & 3 deletions src/marks/delaunay.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class DelaunayLink extends Mark {
}

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call(
Z
Expand Down Expand Up @@ -175,7 +175,7 @@ class AbstractDelaunayMark extends Mark {
}

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call(
Z
Expand Down Expand Up @@ -249,7 +249,7 @@ class Voronoi extends Mark {
const {x, y} = scales;
const {x: X, y: Y, cells: C} = channels;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) => {
g.selectAll()
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 {contours} = channels;
const path = geoPath();
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/dot.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class Dot extends Mark {
const size = R ? undefined : r * r * Math.PI;
if (negative(r)) index = [];
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class Frame extends Mark {
const y2 = height - marginBottom - insetBottom;
return create(anchor ? "svg:line" : rx1y1 || rx1y2 || rx2y1 || rx2y2 ? "svg:path" : "svg:rect", context)
.datum(0)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyDirectStyles, this)
.call(applyChannelStyles, this, channels)
.call(applyTransform, this, {})
Expand Down
7 changes: 3 additions & 4 deletions src/marks/geo.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {geoGraticule10, geoPath} from "d3";
import {geoGraticule10} from "d3";
import {create} from "../context.js";
import {negative, positive} from "../defined.js";
import {Mark} from "../mark.js";
import {identity, maybeNumberChannel} from "../options.js";
import {xyProjection} from "../projection.js";
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
import {centroid} from "../transforms/centroid.js";
import {withDefaultSort} from "./dot.js";
Expand Down Expand Up @@ -36,12 +35,12 @@ export class Geo extends Mark {
}
render(index, scales, channels, dimensions, context) {
const {geometry: G, r: R} = channels;
const path = geoPath(context.projection ?? xyProjection(scales));
const path = context.path();
const {r} = this;
if (negative(r)) index = [];
else if (r !== undefined) path.pointRadius(r);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) => {
g.selectAll()
Expand Down
2 changes: 1 addition & 1 deletion src/marks/hexgrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Hexgrid extends Mark {
}
return create("svg:g", context)
.datum(0)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {}, offset + ox, offset + oy)
.call((g) => g.append("path").call(applyDirectStyles, this).call(applyChannelStyles, this, channels).attr("d", d))
.node();
Expand Down
2 changes: 1 addition & 1 deletion src/marks/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class Image extends Mark {
const {r, width, height, rotate} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class Line extends Mark {
const {x: X, y: Y} = channels;
const {curve} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/linearRegression.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class LinearRegression extends Mark {
const {x: X, y: Y, z: Z} = channels;
const {ci} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class Link extends Mark {
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
const {curve} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/raster.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class Raster extends AbstractRaster {
context2d.putImageData(image, 0, 0);

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class Rect extends Mark {
const bx = x?.bandwidth ? x.bandwidth() : 0;
const by = y?.bandwidth ? y.bandwidth() : 0;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {}, 0, 0)
.call((g) =>
g
Expand Down
4 changes: 2 additions & 2 deletions src/marks/rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class RuleX extends Mark {
const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
const {insetTop, insetBottom} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X && x}, offset, 0)
.call((g) =>
g
Expand Down Expand Up @@ -85,7 +85,7 @@ export class RuleY extends Mark {
const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
const {insetLeft, insetRight} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {y: Y && y}, 0, offset)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class Text extends Mark {
const {rotate} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectTextStyles, this, T, dimensions)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
Expand Down
2 changes: 1 addition & 1 deletion src/marks/tick.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AbstractTick extends Mark {
}
render(index, scales, channels, dimensions, context) {
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(this._transform, this, scales)
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/tip.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class Tip extends Mark {
// We don’t call applyChannelStyles because we only use the channels to
// derive the content of the tip, not its aesthetics.
const g = create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyIndirectTextStyles, this)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
Expand Down
2 changes: 1 addition & 1 deletion src/marks/vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class Vector extends Mark {
const {length, rotate, anchor, shape, r} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X && x, y: Y && y})
.call((g) =>
g
Expand Down
2 changes: 1 addition & 1 deletion src/marks/waffle.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ function waffleRender({render, ...options}) {
if (ry != null) basePatternRect.setAttribute("ry", ry);

return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions, context)
.call(applyIndirectStyles, this, dimensions, context)
.call(this._transform, this, scales)
.call((g) =>
g
Expand Down
9 changes: 7 additions & 2 deletions src/plot.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {creator, select} from "d3";
import {creator, geoPath, select} from "d3";
import {createChannel, inferChannelScale} from "./channel.js";
import {createContext} from "./context.js";
import {createDimensions} from "./dimensions.js";
Expand All @@ -11,7 +11,7 @@ import {frame} from "./marks/frame.js";
import {tip} from "./marks/tip.js";
import {isColor, isIterable, isNone, isScaleOptions} from "./options.js";
import {dataify, lengthof, map, yes, maybeIntervalTransform, subarray} from "./options.js";
import {createProjection, getGeometryChannels, hasProjection} from "./projection.js";
import {createProjection, getGeometryChannels, hasProjection, xyProjection} from "./projection.js";
import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js";
import {innerDimensions, outerDimensions} from "./scales.js";
import {isPosition, registry as scaleRegistry} from "./scales/index.js";
Expand Down Expand Up @@ -236,6 +236,11 @@ export function plot(options = {}) {
facetTranslate = facetTranslator(fx, fy, dimensions);
}

// A path generator for marks that want to draw GeoJSON.
context.path = function () {
return geoPath(this.projection ?? xyProjection(scales));
};

// Compute value objects, applying scales and projection as needed.
for (const [mark, state] of stateByMark) {
state.values = mark.scale(state.channels, scales, context);
Expand Down
19 changes: 7 additions & 12 deletions src/style.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {geoPath, group, namespaces, select} from "d3";
import {group, namespaces, select} from "d3";
import {create} from "./context.js";
import {defined, nonempty} from "./defined.js";
import {formatDefault} from "./format.js";
import {isNone, isNoneish, isRound, maybeColorChannel, maybeNumberChannel} from "./options.js";
import {keyof, number, string} from "./options.js";
import {xyProjection} from "./projection.js";
import {warn} from "./warnings.js";

export const offset = (typeof window !== "undefined" ? window.devicePixelRatio > 1 : typeof it === "undefined") ? 0 : 0.5; // prettier-ignore
Expand Down Expand Up @@ -304,7 +303,7 @@ export function* groupIndex(I, position, mark, channels) {
}

// Note: may mutate selection.node!
function applyClip(selection, mark, scales, dimensions, context) {
function applyClip(selection, mark, dimensions, context) {
let clipUrl;
const {clip = context.clip} = mark;
if (clip === "frame") {
Expand All @@ -318,7 +317,7 @@ function applyClip(selection, mark, scales, dimensions, context) {
});
clipUrl = getFrameClip(context, dimensions);
} else if (clip === "sphere") clipUrl = getProjectionClip(context);
else if (clip?.type) clipUrl = getGeoClip(clip, scales)(context);
else if (clip?.type) clipUrl = getGeoClip(clip)(context);

// Here we’re careful to apply the ARIA attributes to the outer G element when
// clipping is applied, and to apply the ARIA attributes before any other
Expand Down Expand Up @@ -360,19 +359,15 @@ function memoizeGeo(clip) {
};
}

const getGeoClip = memoizeGeo((geo, scales) =>
memoizeClip((clipPath, context) => {
const {projection} = context;
if (!projection && geo.type === "Sphere") throw new Error(`clipping to the sphere requires a projection`);
clipPath.append("path").attr("d", geoPath(projection ?? xyProjection(scales))(geo));
})
const getGeoClip = memoizeGeo((geo) =>
memoizeClip((clipPath, context) => clipPath.append("path").attr("d", context.path()(geo)))
);

const getProjectionClip = getGeoClip({type: "Sphere"});

// Note: may mutate selection.node!
export function applyIndirectStyles(selection, mark, scales, dimensions, context) {
applyClip(selection, mark, scales, dimensions, context);
export function applyIndirectStyles(selection, mark, dimensions, context) {
applyClip(selection, mark, dimensions, context);
applyAttr(selection, "class", mark.className);
applyAttr(selection, "fill", mark.fill);
applyAttr(selection, "fill-opacity", mark.fillOpacity);
Expand Down

0 comments on commit a67c5a9

Please sign in to comment.