diff --git a/src/options.js b/src/options.js
index 0a1603eaa7..f70a118ef8 100644
--- a/src/options.js
+++ b/src/options.js
@@ -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;
diff --git a/src/plot.js b/src/plot.js
index 6862ebf42d..2d3795fc17 100644
--- a/src/plot.js
+++ b/src/plot.js
@@ -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";
@@ -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;
diff --git a/src/transforms/window.js b/src/transforms/window.js
index 7e4c7096a3..a9a918927a 100644
--- a/src/transforms/window.js
+++ b/src/transforms/window.js
@@ -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";
@@ -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
@@ -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;
}
}
@@ -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);
}
}
};
@@ -128,7 +124,7 @@ 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]]);
}
}
@@ -136,10 +132,10 @@ function reduceArray(f) {
: {
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);
}
}
};
diff --git a/test/output/pointerNonFaceted.svg b/test/output/pointerNonFaceted.svg
new file mode 100644
index 0000000000..e3c7e45bdd
--- /dev/null
+++ b/test/output/pointerNonFaceted.svg
@@ -0,0 +1,2623 @@
+
\ No newline at end of file
diff --git a/test/plots/pointer.ts b/test/plots/pointer.ts
index d6ea70904a..1e9df45067 100644
--- a/test/plots/pointer.ts
+++ b/test/plots/pointer.ts
@@ -34,3 +34,14 @@ export async function pointerViewof() {
plot.oninput = oninput; // update during interaction
return html`${plot}${textarea}`;
}
+
+export async function pointerNonFaceted() {
+ const aapl = await d3.csv("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"}))
+ ]
+ });
+}