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

differenceX and shiftY #1922

Merged
merged 8 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
10 changes: 9 additions & 1 deletion docs/marks/difference.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,12 @@ These options are passed to the underlying area and line marks; in particular, w
Plot.differenceY(gistemp, {x: "Date", y: "Anomaly"})
```

Returns a new difference with the given *data* and *options*. The mark is a composite of a positive area, negative area, and line. The positive area extends from the bottom of the frame to the line, and is clipped by the area extending from the comparison to the top of the frame. The negative area conversely extends from the top of the frame to the line, and is clipped by the area extending from the comparison to the bottom of the frame.
Returns a new vertical difference with the given *data* and *options*. The mark is a composite of a positive area, negative area, and line. The positive area extends from the bottom of the frame to the line, and is clipped by the area extending from the comparison to the top of the frame. The negative area conversely extends from the top of the frame to the line, and is clipped by the area extending from the comparison to the bottom of the frame.

## differenceX(*data*, *options*) {#differenceX}

```js
Plot.differenceX(gistemp, {y: "Date", x: "Anomaly"})
```

Returns a new horizontal difference with the given *data* and *options*.
10 changes: 10 additions & 0 deletions docs/transforms/shift.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,13 @@ Plot.shiftX("7 days", {x: "Date", y: "Close"})
Derives an **x1** channel from the input **x** channel by shifting values by the given *interval*. The *interval* may be specified as: a name (*second*, *minute*, *hour*, *day*, *week*, *month*, *quarter*, *half*, *year*, *monday*, *tuesday*, *wednesday*, *thursday*, *friday*, *saturday*, *sunday*) with an optional number and sign (*e.g.*, *+3 days* or *-1 year*); or as a number; or as an implementation — such as d3.utcMonth — with *interval*.floor(*value*), *interval*.offset(*value*), and *interval*.range(*start*, *stop*) methods.

The shiftX also transform aliases the **x** channel to **x2** and applies a domain hint to the **x2** channel such that by default the plot shows only the intersection of **x1** and **x2**. For example, if the interval is *+1 year*, the first year of the data is not shown.

## shiftY(*interval*, *options*) {#shiftY}

```js
Plot.shiftY("7 days", {y: "Date", x: "Close"})
```

Derives a **y1** channel from the input **y** channel by shifting values by the given *interval*. (See shiftX above for details.)

The shiftY also transform aliases the **y** channel to **y2** and applies a domain hint to the **y2** channel such that by default the plot shows only the intersection of **y1** and **y2**. For example, if the interval is *+1 year*, the first year of the data is not shown.
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export {Contour, contour} from "./marks/contour.js";
export {crosshair, crosshairX, crosshairY} from "./marks/crosshair.js";
export {delaunayLink, delaunayMesh, hull, voronoi, voronoiMesh} from "./marks/delaunay.js";
export {Density, density} from "./marks/density.js";
export {differenceY} from "./marks/difference.js";
export {differenceX, differenceY} from "./marks/difference.js";
export {Dot, dot, dotX, dotY, circle, hexagon} from "./marks/dot.js";
export {Frame, frame} from "./marks/frame.js";
export {Geo, geo, sphere, graticule} from "./marks/geo.js";
Expand All @@ -39,7 +39,7 @@ export {find, group, groupX, groupY, groupZ} from "./transforms/group.js";
export {hexbin} from "./transforms/hexbin.js";
export {normalize, normalizeX, normalizeY} from "./transforms/normalize.js";
export {map, mapX, mapY} from "./transforms/map.js";
export {shiftX} from "./transforms/shift.js";
export {shiftX, shiftY} from "./transforms/shift.js";
export {window, windowX, windowY} from "./transforms/window.js";
export {select, selectFirst, selectLast, selectMaxX, selectMaxY, selectMinX, selectMinY} from "./transforms/select.js";
export {stackX, stackX1, stackX2, stackY, stackY1, stackY2} from "./transforms/stack.js";
Expand Down
50 changes: 48 additions & 2 deletions src/marks/difference.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {CurveOptions} from "../curve.js";
import type {Data, MarkOptions, RenderableMark} from "../mark.js";

/** Options for the difference mark. */
export interface DifferenceOptions extends MarkOptions, CurveOptions {
export interface DifferenceYOptions extends MarkOptions, CurveOptions {
/**
* The comparison horizontal position channel, typically bound to the *x*
* scale; if not specified, **x** is used.
Expand Down Expand Up @@ -69,6 +69,52 @@ export interface DifferenceOptions extends MarkOptions, CurveOptions {
z?: ChannelValue;
}

export interface DifferenceXOptions extends DifferenceYOptions {
mbostock marked this conversation as resolved.
Show resolved Hide resolved
/**
* The comparison vertical position channel, typically bound to the *y*
* scale; if not specified, **y** is used.
*/
y1?: ChannelValueSpec;

/**
* The primary vertical position channel, typically bound to the *y* scale;
* if not specified, **y1** is used.
*/
y2?: ChannelValueSpec;

/** The vertical position channel, typically bound to the *y* scale. */
y?: ChannelValueSpec;

/**
* The comparison horizontal position channel, typically bound to the *x* scale;
* if not specified, **x** is used. For differenceY, defaults to zero if only
* one *x* and *y* channel is specified.
*/
x1?: ChannelValueSpec;

/**
* The primary horizontal position channel, typically bound to the *x* scale;
* if not specified, **x1** is used.
*/
x2?: ChannelValueSpec;

/** The horizontal position channel, typically bound to the *x* scale. */
x?: ChannelValueSpec;
}

/**
* Returns a new horizontal difference mark for the given the specified *data*
* and *options*.
*
* The mark is a composite of a positive area, negative area, and line. The
* positive area extends from the left of the frame to the line, and is clipped
* by the area extending from the comparison to the right of the frame. The
* negative area conversely extends from the right of the frame to the line, and
* is clipped by the area extending from the comparison to the left of the
* frame.
*/
export function differenceX(data?: Data, options?: DifferenceXOptions): Difference;

/**
* Returns a new vertical difference mark for the given the specified *data* and
* *options*, as in a time-series chart where time goes right→ (or ←left).
Expand All @@ -80,7 +126,7 @@ export interface DifferenceOptions extends MarkOptions, CurveOptions {
* and is clipped by the area extending from the comparison to the bottom of the
* frame.
*/
export function differenceY(data?: Data, options?: DifferenceOptions): Difference;
export function differenceY(data?: Data, options?: DifferenceYOptions): Difference;

/** The difference mark. */
export class Difference extends RenderableMark {}
46 changes: 31 additions & 15 deletions src/marks/difference.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ import {getClipId} from "../style.js";
import {area} from "./area.js";
import {line} from "./line.js";

export function differenceY(
export function differenceX(data, options) {
return differenceK("x", data, options);
}
export function differenceY(data, options) {
mbostock marked this conversation as resolved.
Show resolved Hide resolved
return differenceK("y", data, options);
}

function differenceK(
k,
data,
{
x1,
x2,
y1,
y2,
x = x1 === undefined && x2 === undefined ? indexOf : undefined,
y = y1 === undefined && y2 === undefined ? identity : undefined,
x = x1 === undefined && x2 === undefined ? (k === "y" ? indexOf : identity) : undefined,
y = y1 === undefined && y2 === undefined ? (k === "x" ? indexOf : identity) : undefined,
fill, // ignored
positiveFill = "#3ca951",
negativeFill = "#4269d0",
Expand All @@ -32,8 +40,11 @@ export function differenceY(
) {
[x1, x2] = memoTuple(x, x1, x2);
[y1, y2] = memoTuple(y, y1, y2);
if (x1 === x2 && y1 === y2) y1 = memo(0);
({tip} = withTip({tip}, "x"));
if (x1 === x2 && y1 === y2) {
if (k === "y") y1 = memo(0);
else x1 = memo(0);
}
({tip} = withTip({tip}, k === "y" ? "x" : "y"));
return marks(
!isNoneish(positiveFill)
? Object.assign(
Expand All @@ -45,7 +56,7 @@ export function differenceY(
z,
fill: positiveFill,
fillOpacity: positiveFillOpacity,
render: composeRender(render, clipDifferenceY(true)),
render: composeRender(render, clipDifference(k, true)),
clip,
...options
}),
Expand All @@ -62,7 +73,7 @@ export function differenceY(
z,
fill: negativeFill,
fillOpacity: negativeFillOpacity,
render: composeRender(render, clipDifferenceY(false)),
render: composeRender(render, clipDifference(k, false)),
clip,
...options
}),
Expand Down Expand Up @@ -110,15 +121,20 @@ function memo(v) {
return {transform: (data) => V || (V = valueof(data, value)), label};
}

function clipDifferenceY(positive) {
function clipDifference(k, positive) {
const f = k === "x" ? "y" : "x"; // f is the flipped dimension
const f1 = `${f}1`;
const f2 = `${f}2`;
const k1 = `${k}1`;
const k2 = `${k}2`;
return (index, scales, channels, dimensions, context, next) => {
const {x1, x2} = channels;
const {height} = dimensions;
const y1 = new Float32Array(x1.length);
const y2 = new Float32Array(x2.length);
(positive === inferScaleOrder(scales.y) < 0 ? y1 : y2).fill(height);
const oc = next(index, scales, {...channels, x2: x1, y2}, dimensions, context);
const og = next(index, scales, {...channels, x1: x2, y1}, dimensions, context);
const {[f1]: F1, [f2]: F2} = channels;
const K1 = new Float32Array(F1.length);
const K2 = new Float32Array(F2.length);
const m = dimensions[k === "y" ? "height" : "width"];
(positive === inferScaleOrder(scales[k]) < 0 ? K1 : K2).fill(m);
const oc = next(index, scales, {...channels, [f2]: F1, [k2]: K2}, dimensions, context);
const og = next(index, scales, {...channels, [f1]: F2, [k1]: K1}, dimensions, context);
const c = oc.querySelector("g") ?? oc; // applyClip
const g = og.querySelector("g") ?? og; // applyClip
for (let i = 0; c.firstChild; i += 2) {
Expand Down
7 changes: 7 additions & 0 deletions src/transforms/shift.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ import type {Transformed} from "./basic.js";
* *x* channel according to the specified *interval*.
*/
export function shiftX<T>(interval: Interval, options?: T): Transformed<T>;

/**
* Groups data into series using the first channel of *z*, *fill*, or *stroke*
* (if any), then derives *y1* and *y2* output channels by shifting the input
* *y* channel according to the specified *interval*.
*/
export function shiftY<T>(interval: Interval, options?: T): Transformed<T>;
4 changes: 4 additions & 0 deletions src/transforms/shift.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export function shiftX(interval, options) {
return shiftK("x", interval, options);
}

export function shiftY(interval, options) {
return shiftK("y", interval, options);
}

function shiftK(x, interval, options = {}) {
let offset;
let k = 1;
Expand Down
Loading