diff --git a/src/plot.js b/src/plot.js
index 039a269ada..ce45be71da 100644
--- a/src/plot.js
+++ b/src/plot.js
@@ -239,6 +239,7 @@ export function plot(options = {}) {
// Compute value objects, applying scales and projection as needed.
for (const [mark, state] of stateByMark) {
state.values = mark.scale(state.channels, scales, context);
+ state.values.data = state.data; // expose transformed data for advanced usage
}
const {width, height} = dimensions;
diff --git a/test/output/renderFilterPointer.svg b/test/output/renderFilterPointer.svg
new file mode 100644
index 0000000000..fde385100e
--- /dev/null
+++ b/test/output/renderFilterPointer.svg
@@ -0,0 +1,123 @@
+
\ No newline at end of file
diff --git a/test/plots/index.ts b/test/plots/index.ts
index 0a412b4b67..6729901023 100644
--- a/test/plots/index.ts
+++ b/test/plots/index.ts
@@ -244,6 +244,7 @@ export * from "./raster-vapor.js";
export * from "./raster-walmart.js";
export * from "./rect-band.js";
export * from "./reducer-scale-override.js";
+export * from "./render-filter.js";
export * from "./seattle-precipitation-density.js";
export * from "./seattle-precipitation-rule.js";
export * from "./seattle-precipitation-sum.js";
diff --git a/test/plots/render-filter.ts b/test/plots/render-filter.ts
new file mode 100644
index 0000000000..6cefc5bde5
--- /dev/null
+++ b/test/plots/render-filter.ts
@@ -0,0 +1,56 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export async function renderFilterPointer() {
+ const bls = await d3.csv("data/bls-metro-unemployment.csv", d3.autoType);
+ const pointerInactive = renderFilter(true);
+ const pointerContext = renderFilter(false);
+ const pointerFocus = renderFilter(false);
+ const plot = Plot.plot({
+ y: {grid: true},
+ color: {scheme: "BuRd", symmetric: false},
+ marks: [
+ Plot.ruleY([0]),
+ Plot.lineY(bls, pointerInactive({x: "date", y: "unemployment", z: "division", tip: true})),
+ Plot.lineY(bls, pointerContext({x: "date", y: "unemployment", z: "division", stroke: "#ccc"})),
+ Plot.lineY(bls, pointerFocus({x: "date", y: "unemployment", z: "division", stroke: "red"}))
+ ]
+ });
+ plot.addEventListener("input", () => {
+ if (plot.value === null) {
+ pointerInactive.update(true);
+ pointerContext.update(false);
+ pointerFocus.update(false);
+ } else {
+ const division = plot.value.division;
+ pointerInactive.update(false);
+ pointerContext.update((d) => d.division !== division);
+ pointerFocus.update((d) => d.division === division);
+ }
+ });
+ return plot;
+}
+
+// TODO track separate rendered elements per facet
+function renderFilter(initialTest = true) {
+ let update;
+ return Object.assign(
+ function apply(options) {
+ return {
+ ...options,
+ render(index, scales, values, dimensions, context, next) {
+ const {data} = values;
+ const filter = (test) => typeof test === "function" ? index.filter((i) => test(data[i], i, data)) : test ? index : []; // prettier-ignore
+ let g = next(filter(initialTest), scales, values, dimensions, context);
+ update = (test) => void g.replaceWith((g = next(filter(test), scales, values, dimensions, context)));
+ return g;
+ }
+ };
+ },
+ {
+ update(test) {
+ return update?.(test);
+ }
+ }
+ );
+}