Skip to content

Commit

Permalink
fix non-faceted pointer (#1713)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock authored Jun 25, 2023
1 parent 9ac7cd1 commit 558a5e9
Show file tree
Hide file tree
Showing 5 changed files with 2,651 additions and 14 deletions.
5 changes: 5 additions & 0 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ export function taker(f) {
return f.length === 1 ? (index, values) => f(take(values, index)) : f;
}

// Uses subarray if available, and otherwise slice.
export function subarray(I, i, j) {
return I.subarray ? I.subarray(i, j) : I.slice(i, j);
}

// Based on InternMap (d3.group).
export function keyof(value) {
return value !== null && typeof value === "object" ? value.valueOf() : value;
Expand Down
6 changes: 4 additions & 2 deletions src/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {Mark} from "./mark.js";
import {axisFx, axisFy, axisX, axisY, gridFx, gridFy, gridX, gridY} from "./marks/axis.js";
import {frame} from "./marks/frame.js";
import {tip} from "./marks/tip.js";
import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeIntervalTransform} from "./options.js";
import {isColor, isIterable, isNone, isScaleOptions} from "./options.js";
import {arrayify, map, yes, maybeIntervalTransform, subarray} from "./options.js";
import {createProjection, getGeometryChannels, hasProjection} from "./projection.js";
import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js";
import {innerDimensions, outerDimensions} from "./scales.js";
Expand Down Expand Up @@ -298,7 +299,8 @@ export function plot(options = {}) {
index = indexes[faceted ? f.i : 0];
index = mark.filter(index, channels, values);
if (index.length === 0) continue;
if (faceted) (index.fx = f.x), (index.fy = f.y), (index.fi = f.i);
if (!faceted && index === indexes[0]) index = subarray(index); // copy before assigning fx, fy, fi
(index.fx = f.x), (index.fy = f.y), (index.fi = f.i);
}
const node = mark.render(index, scales, values, subdimensions, context);
if (node == null) continue;
Expand Down
20 changes: 8 additions & 12 deletions src/transforms/window.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {deviation, max, min, median, mode, variance} from "d3";
import {deviation, max, median, min, mode, variance} from "d3";
import {defined} from "../defined.js";
import {percentile, taker} from "../options.js";
import {percentile, subarray, taker} from "../options.js";
import {warn} from "../warnings.js";
import {mapX, mapY} from "./map.js";

Expand Down Expand Up @@ -83,10 +83,6 @@ function maybeReduce(reduce = "mean") {
return reduceArray(taker(reduce));
}

function slice(I, i, j) {
return I.subarray ? I.subarray(i, j) : I.slice(i, j);
}

// Note that the subarray may include NaN in the non-strict case; we expect the
// function f to handle that itself (e.g., by filtering as needed). The D3
// reducers (e.g., min, max, mean, median) do, and it’s faster to avoid
Expand All @@ -101,7 +97,7 @@ function reduceAccessor(f) {
for (let i = 0; i < k - 1; ++i) if (isNaN(v(i))) ++nans;
for (let i = 0, n = I.length - k + 1; i < n; ++i) {
if (isNaN(v(i + k - 1))) ++nans;
T[I[i + s]] = nans === 0 ? f(slice(I, i, i + k), v) : NaN;
T[I[i + s]] = nans === 0 ? f(subarray(I, i, i + k), v) : NaN;
if (isNaN(v(i))) --nans;
}
}
Expand All @@ -110,10 +106,10 @@ function reduceAccessor(f) {
mapIndex(I, S, T) {
const v = (i) => (S[i] == null ? NaN : +S[i]);
for (let i = -s; i < 0; ++i) {
T[I[i + s]] = f(slice(I, 0, i + k), v);
T[I[i + s]] = f(subarray(I, 0, i + k), v);
}
for (let i = 0, n = I.length - s; i < n; ++i) {
T[I[i + s]] = f(slice(I, i, i + k), v);
T[I[i + s]] = f(subarray(I, i, i + k), v);
}
}
};
Expand All @@ -128,18 +124,18 @@ function reduceArray(f) {
for (let i = 0; i < k - 1; ++i) count += defined(S[I[i]]);
for (let i = 0, n = I.length - k + 1; i < n; ++i) {
count += defined(S[I[i + k - 1]]);
if (count === k) T[I[i + s]] = f(slice(I, i, i + k), S);
if (count === k) T[I[i + s]] = f(subarray(I, i, i + k), S);
count -= defined(S[I[i]]);
}
}
}
: {
mapIndex(I, S, T) {
for (let i = -s; i < 0; ++i) {
T[I[i + s]] = f(slice(I, 0, i + k), S);
T[I[i + s]] = f(subarray(I, 0, i + k), S);
}
for (let i = 0, n = I.length - s; i < n; ++i) {
T[I[i + s]] = f(slice(I, i, i + k), S);
T[I[i + s]] = f(subarray(I, i, i + k), S);
}
}
};
Expand Down
2,623 changes: 2,623 additions & 0 deletions test/output/pointerNonFaceted.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions test/plots/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,14 @@ export async function pointerViewof() {
plot.oninput = oninput; // update during interaction
return html`<figure>${plot}${textarea}</figure>`;
}

export async function pointerNonFaceted() {
const aapl = await d3.csv<any>("data/aapl.csv", d3.autoType);
return Plot.plot({
marks: [
Plot.lineY(aapl, {x: "Date", y: "Close", fy: (d) => d.Close % 2 === 0}),
Plot.ruleX(aapl, {x: "Date", strokeOpacity: 0.1}),
Plot.ruleX(aapl, Plot.pointerX({x: "Date", stroke: "red"}))
]
});
}

0 comments on commit 558a5e9

Please sign in to comment.