From ce19abb0f5203ec3018018bb873f963c8b9d6ae6 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 24 Sep 2021 08:08:58 -0700 Subject: [PATCH] expose window, normalize (#551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expose window, normalize * Update test/plots/aapl-bollinger.js Co-authored-by: Philippe Rivière * update test snapshot Co-authored-by: Philippe Rivière --- README.md | 16 +++++++ src/index.js | 4 +- src/transforms/normalize.js | 2 +- src/transforms/window.js | 2 +- test/output/aaplBollinger.svg | 86 +++++++++++++++++++++++++++++++++++ test/plots/aapl-bollinger.js | 20 ++++++++ test/plots/index.js | 1 + 7 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 test/output/aaplBollinger.svg create mode 100644 test/plots/aapl-bollinger.js diff --git a/README.md b/README.md index b32559ed1d..13dd900356 100644 --- a/README.md +++ b/README.md @@ -1340,6 +1340,14 @@ Plot.mapY("cumsum", {y: d3.randomNormal()}) Equivalent to Plot.map({y: *map*, y1: *map*, y2: *map*}, *options*), but ignores any of **y**, **y1**, and **y2** not present in *options*. +#### Plot.normalize(*basis*) + +```js +Plot.map({y: Plot.normalize("first")}, {x: "Date", y: "Close", stroke: "Symbol"}) +``` + +Returns a normalize map method for the given *basis*, suitable for use with Plot.map. + #### Plot.normalizeX(*basis*, *options*) ```js @@ -1356,6 +1364,14 @@ Plot.normalizeY("first", {x: "Date", y: "Close", stroke: "Symbol"}) Like [Plot.mapY](#plotmapymap-options), but applies the normalize map method with the given *basis*. +#### Plot.window(*k*) + +```js +Plot.map({y: Plot.window(24)}, {x: "Date", y: "Close", stroke: "Symbol"}) +``` + +Returns a window map method for the given window size *k*, suitable for use with Plot.map. For additional options to the window transform, replace the number *k* with an object with properties *k*, *anchor*, or *reduce*. + #### Plot.windowX(*k*, *options*) ```js diff --git a/src/index.js b/src/index.js index 6a8f601e44..00e00ebeee 100644 --- a/src/index.js +++ b/src/index.js @@ -16,9 +16,9 @@ export {reverse} from "./transforms/reverse.js"; export {sort} from "./transforms/sort.js"; export {bin, binX, binY} from "./transforms/bin.js"; export {group, groupX, groupY, groupZ} from "./transforms/group.js"; -export {normalizeX, normalizeY} from "./transforms/normalize.js"; +export {normalize, normalizeX, normalizeY} from "./transforms/normalize.js"; export {map, mapX, mapY} from "./transforms/map.js"; -export {windowX, windowY} from "./transforms/window.js"; +export {window, windowX, windowY} from "./transforms/window.js"; export {selectFirst, selectLast, selectMaxX, selectMaxY, selectMinX, selectMinY} from "./transforms/select.js"; export {stackX, stackX1, stackX2, stackY, stackY1, stackY2} from "./transforms/stack.js"; export {formatIsoDate, formatWeekday, formatMonth} from "./format.js"; diff --git a/src/transforms/normalize.js b/src/transforms/normalize.js index f55460b132..66812489a3 100644 --- a/src/transforms/normalize.js +++ b/src/transforms/normalize.js @@ -13,7 +13,7 @@ export function normalizeY(basis, options) { return mapY(normalize(basis), options); } -function normalize(basis) { +export function normalize(basis) { if (basis === undefined) return normalizeFirst; if (typeof basis === "function") return normalizeBasis((I, S) => basis(take(S, I))); switch ((basis + "").toLowerCase()) { diff --git a/src/transforms/window.js b/src/transforms/window.js index c72a8212c2..d8050fb7b8 100644 --- a/src/transforms/window.js +++ b/src/transforms/window.js @@ -11,7 +11,7 @@ export function windowY(windowOptions = {}, options) { return mapY(window(windowOptions), options); } -function window(options = {}) { +export function window(options = {}) { if (typeof options === "number") options = {k: options}; let {k, reduce, shift, anchor = maybeShift(shift)} = options; if (!((k = Math.floor(k)) > 0)) throw new Error("invalid k"); diff --git a/test/output/aaplBollinger.svg b/test/output/aaplBollinger.svg new file mode 100644 index 0000000000..78a111203f --- /dev/null +++ b/test/output/aaplBollinger.svg @@ -0,0 +1,86 @@ + + + + + 60 + + + + 70 + + + + 80 + + + + 90 + + + + 100 + + + + 110 + + + + 120 + + + + 130 + + + + 140 + + + + 150 + + + + 160 + + + + 170 + + + + 180 + + + + 190 + ↑ Close + + + + 2014 + + + 2015 + + + 2016 + + + 2017 + + + 2018 + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/aapl-bollinger.js b/test/plots/aapl-bollinger.js new file mode 100644 index 0000000000..ad5423a123 --- /dev/null +++ b/test/plots/aapl-bollinger.js @@ -0,0 +1,20 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function() { + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + return Plot.plot({ + y: { + grid: true + }, + marks: [ + Plot.areaY(AAPL, Plot.map({y1: bollinger(20, -2), y2: bollinger(20, 2)}, {x: "Date", y: "Close", fillOpacity: 0.2})), + Plot.line(AAPL, Plot.map({y: bollinger(20, 0)}, {x: "Date", y: "Close", stroke: "blue"})), + Plot.line(AAPL, {x: "Date", y: "Close", strokeWidth: 1}) + ] + }); +} + +function bollinger(N, K) { + return Plot.window({k: N, reduce: Y => d3.mean(Y) + K * d3.deviation(Y), anchor: "end"}); +} diff --git a/test/plots/index.js b/test/plots/index.js index 4f7abecfef..33b7797717 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -1,3 +1,4 @@ +export {default as aaplBollinger} from "./aapl-bollinger.js"; export {default as aaplCandlestick} from "./aapl-candlestick.js"; export {default as aaplChangeVolume} from "./aapl-change-volume.js"; export {default as aaplClose} from "./aapl-close.js";