Skip to content

Commit ccdf56d

Browse files
committed
fix non-faceted pointer
1 parent 428bdbc commit ccdf56d

File tree

5 files changed

+2651
-14
lines changed

5 files changed

+2651
-14
lines changed

src/options.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ export function taker(f) {
231231
return f.length === 1 ? (index, values) => f(take(values, index)) : f;
232232
}
233233

234+
// Uses subarray if available, and otherwise slice.
235+
export function subarray(I, i, j) {
236+
return I.subarray ? I.subarray(i, j) : I.slice(i, j);
237+
}
238+
234239
// Based on InternMap (d3.group).
235240
export function keyof(value) {
236241
return value !== null && typeof value === "object" ? value.valueOf() : value;

src/plot.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {Mark} from "./mark.js";
99
import {axisFx, axisFy, axisX, axisY, gridFx, gridFy, gridX, gridY} from "./marks/axis.js";
1010
import {frame} from "./marks/frame.js";
1111
import {tip} from "./marks/tip.js";
12-
import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeIntervalTransform} from "./options.js";
12+
import {isColor, isIterable, isNone, isScaleOptions} from "./options.js";
13+
import {arrayify, map, yes, maybeIntervalTransform, subarray} from "./options.js";
1314
import {createProjection, getGeometryChannels, hasProjection} from "./projection.js";
1415
import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js";
1516
import {innerDimensions, outerDimensions} from "./scales.js";
@@ -298,7 +299,8 @@ export function plot(options = {}) {
298299
index = indexes[faceted ? f.i : 0];
299300
index = mark.filter(index, channels, values);
300301
if (index.length === 0) continue;
301-
if (faceted) (index.fx = f.x), (index.fy = f.y), (index.fi = f.i);
302+
if (!faceted && index === indexes[0]) index = subarray(index); // copy before assigning fx, fy, fi
303+
(index.fx = f.x), (index.fy = f.y), (index.fi = f.i);
302304
}
303305
const node = mark.render(index, scales, values, subdimensions, context);
304306
if (node == null) continue;

src/transforms/window.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {deviation, max, min, median, mode, variance} from "d3";
1+
import {deviation, max, median, min, mode, variance} from "d3";
22
import {defined} from "../defined.js";
3-
import {percentile, taker} from "../options.js";
3+
import {percentile, subarray, taker} from "../options.js";
44
import {warn} from "../warnings.js";
55
import {mapX, mapY} from "./map.js";
66

@@ -83,10 +83,6 @@ function maybeReduce(reduce = "mean") {
8383
return reduceArray(taker(reduce));
8484
}
8585

86-
function slice(I, i, j) {
87-
return I.subarray ? I.subarray(i, j) : I.slice(i, j);
88-
}
89-
9086
// Note that the subarray may include NaN in the non-strict case; we expect the
9187
// function f to handle that itself (e.g., by filtering as needed). The D3
9288
// reducers (e.g., min, max, mean, median) do, and it’s faster to avoid
@@ -101,7 +97,7 @@ function reduceAccessor(f) {
10197
for (let i = 0; i < k - 1; ++i) if (isNaN(v(i))) ++nans;
10298
for (let i = 0, n = I.length - k + 1; i < n; ++i) {
10399
if (isNaN(v(i + k - 1))) ++nans;
104-
T[I[i + s]] = nans === 0 ? f(slice(I, i, i + k), v) : NaN;
100+
T[I[i + s]] = nans === 0 ? f(subarray(I, i, i + k), v) : NaN;
105101
if (isNaN(v(i))) --nans;
106102
}
107103
}
@@ -110,10 +106,10 @@ function reduceAccessor(f) {
110106
mapIndex(I, S, T) {
111107
const v = (i) => (S[i] == null ? NaN : +S[i]);
112108
for (let i = -s; i < 0; ++i) {
113-
T[I[i + s]] = f(slice(I, 0, i + k), v);
109+
T[I[i + s]] = f(subarray(I, 0, i + k), v);
114110
}
115111
for (let i = 0, n = I.length - s; i < n; ++i) {
116-
T[I[i + s]] = f(slice(I, i, i + k), v);
112+
T[I[i + s]] = f(subarray(I, i, i + k), v);
117113
}
118114
}
119115
};
@@ -128,18 +124,18 @@ function reduceArray(f) {
128124
for (let i = 0; i < k - 1; ++i) count += defined(S[I[i]]);
129125
for (let i = 0, n = I.length - k + 1; i < n; ++i) {
130126
count += defined(S[I[i + k - 1]]);
131-
if (count === k) T[I[i + s]] = f(slice(I, i, i + k), S);
127+
if (count === k) T[I[i + s]] = f(subarray(I, i, i + k), S);
132128
count -= defined(S[I[i]]);
133129
}
134130
}
135131
}
136132
: {
137133
mapIndex(I, S, T) {
138134
for (let i = -s; i < 0; ++i) {
139-
T[I[i + s]] = f(slice(I, 0, i + k), S);
135+
T[I[i + s]] = f(subarray(I, 0, i + k), S);
140136
}
141137
for (let i = 0, n = I.length - s; i < n; ++i) {
142-
T[I[i + s]] = f(slice(I, i, i + k), S);
138+
T[I[i + s]] = f(subarray(I, i, i + k), S);
143139
}
144140
}
145141
};

test/output/pointerNonFaceted.svg

Lines changed: 2623 additions & 0 deletions
Loading

test/plots/pointer.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,14 @@ export async function pointerViewof() {
3434
plot.oninput = oninput; // update during interaction
3535
return html`<figure>${plot}${textarea}</figure>`;
3636
}
37+
38+
export async function pointerNonFaceted() {
39+
const aapl = await d3.csv<any>("data/aapl.csv", d3.autoType);
40+
return Plot.plot({
41+
marks: [
42+
Plot.lineY(aapl, {x: "Date", y: "Close", fy: (d) => d.Close % 2 === 0}),
43+
Plot.ruleX(aapl, {x: "Date", strokeOpacity: 0.1}),
44+
Plot.ruleX(aapl, Plot.pointerX({x: "Date", stroke: "red"}))
45+
]
46+
});
47+
}

0 commit comments

Comments
 (0)