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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + + + ↑ unemployment + + + + + + + + + + + + 2000 + 2002 + 2004 + 2006 + 2008 + 2010 + 2012 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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); + } + } + ); +}