Skip to content

Commit 67fbf48

Browse files
committed
api tweaks; documentation
1 parent cadd4c8 commit 67fbf48

File tree

6 files changed

+136
-45
lines changed

6 files changed

+136
-45
lines changed

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export default defineConfig({
7979
{text: "Auto", link: "/marks/auto"},
8080
{text: "Axis", link: "/marks/axis"},
8181
{text: "Bar", link: "/marks/bar"},
82+
{text: "Bollinger", link: "/marks/bollinger"},
8283
{text: "Box", link: "/marks/box"},
8384
{text: "Cell", link: "/marks/cell"},
8485
{text: "Contour", link: "/marks/contour"},

docs/marks/bollinger.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<script setup>
2+
3+
import * as Plot from "@observablehq/plot";
4+
import * as d3 from "d3";
5+
import {ref} from "vue";
6+
import aapl from "../data/aapl.ts";
7+
8+
const n = ref(20);
9+
const k = ref(2);
10+
11+
</script>
12+
13+
# Bollinger mark
14+
15+
The **bollinger mark** is a [composite mark](../features/marks.md#marks) consisting of a [line](./line.md) representing a moving average, and an [area](./area.md) representing volatility as a band; the band thickness is proportional to the deviation of nearby values. The bollinger mark is often used to analyze the price of financial instruments such as stocks.
16+
17+
For example, the chart below shows the price of Apple stock from 2013–2018, with window size *n* of {{n}} days and radius *k* of {{k}} standard deviations.
18+
19+
<p>
20+
<label class="label-input">
21+
<span>Window size (n):</span>
22+
<input type="range" v-model.number="n" min="1" max="100" step="1" />
23+
<span style="font-variant-numeric: tabular-nums;">{{n.toLocaleString("en-US")}}</span>
24+
</label>
25+
<label class="label-input">
26+
<span>Radius (k):</span>
27+
<input type="range" v-model.number="k" min="0" max="10" step="0.1" />
28+
<span style="font-variant-numeric: tabular-nums;">{{k.toLocaleString("en-US")}}</span>
29+
</label>
30+
</p>
31+
32+
:::plot hidden
33+
```js
34+
Plot.bollingerY(aapl, {x: "Date", y: "Close", n, k}).plot()
35+
```
36+
:::
37+
38+
```js-vue
39+
Plot.bollingerY(aapl, {x: "Date", y: "Close", n: {{n}}, k: {{k}}}).plot()
40+
```
41+
42+
For more control, you can also use the [bollinger map method](#bollinger) directly with the [map transform](../transforms/map.md).
43+
44+
:::plot
45+
```js
46+
Plot.plot({
47+
marks: [
48+
Plot.lineY(aapl, Plot.mapY(Plot.bollinger(20, -2), {x: "Date", y: "Close", stroke: "red"})),
49+
Plot.lineY(aapl, Plot.mapY(Plot.bollinger(20, 2), {x: "Date", y: "Close", stroke: "green"})),
50+
Plot.lineY(aapl, Plot.mapY(Plot.bollinger(20, 0), {x: "Date", y: "Close"}))
51+
]
52+
})
53+
```
54+
:::
55+
56+
The bollinger mark has two constructors: the common [bollingerY](#bollingerY) for when time goes right→ (or ←left); and the rare [bollingerX](#bollingerX) for when time goes up↑ (or down↓).
57+
58+
:::plot
59+
```js
60+
Plot.bollingerX(aapl, {y: "Date", x: "Close"}).plot()
61+
```
62+
:::
63+
64+
As [shorthand](../features/shorthand.md), you can pass an array of numbers as data. Below, the *x* axis represents the zero-based index into the data (*i.e.*, trading days since May 13, 2013).
65+
66+
:::plot
67+
```js
68+
Plot.bollingerY(aapl.map((d) => d.Close)).plot()
69+
```
70+
:::
71+
72+
## Bollinger options
73+
74+
The bollinger mark is a [composite mark](../features/marks.md#marks) consisting of two marks:
75+
76+
* an [area](../marks/area.md) representing volatility as a band, and
77+
* a [line](../marks/line.md) representing a moving average
78+
79+
In addition to the standard mark options which are passed through to the underlying area and line, the bollinger mark supports the following options:
80+
81+
* **n** - the window size (corresponding to the window transform’s **k** option), an integer
82+
* **k** - the band radius, a number representing a multiple of standard deviations
83+
* **color** - the fill color of the area, and the stroke color of the line; defaults to *currentColor*
84+
* **opacity** - the fill opacity of the area; defaults to 0.2
85+
* **fill** - the fill color of the area; defaults to **color**
86+
* **fillOpacity** - the fill opacity of the area; defaults to **paocity**
87+
* **stroke** - the stroke color of the line; defaults to **color**
88+
* **strokeOpacity** - the stroke opacity of the line; defaults to 1
89+
* **strokeWidth** - the stroke width of the line in pixels; defaults to 1.5
90+
91+
## bollingerX(*data*, *options*) {#bollingerX}
92+
93+
```js
94+
Plot.bollingerX(aapl, {y: "Date", x: "Close"})
95+
```
96+
97+
Returns a bollinger mark for when time goes up↑ (or down↓). If the **x** option is not specified, it defaults to the identity function, as when *data* is an array of numbers [*x₀*, *x₁*, *x₂*, …]. If the **y** option is not specified, it defaults to [0, 1, 2, …].
98+
99+
## bollingerY(*data*, *options*) {#bollingerY}
100+
101+
```js
102+
Plot.bollingerY(aapl, {x: "Date", y: "Close"})
103+
```
104+
105+
Returns a bollinger mark for when time goes right→ (or ←left). If the **y** option is not specified, it defaults to the identity function, as when *data* is an array of numbers [*y₀*, *y₁*, *y₂*, …]. If the **x** option is not specified, it defaults to [0, 1, 2, …].
106+
107+
TODO Describe the **interval** option inherited from line/area.
108+
109+
## bollinger(*n*, *k*) {#bollinger}
110+
111+
```js
112+
Plot.lineY(data, Plot.map({y: Plot.bollinger(20, 0)}, {x: "Date", y: "Close"}))
113+
```
114+
115+
Returns a bollinger map method for use with the [map transform](../transforms/map.md).

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export {Arrow, arrow} from "./marks/arrow.js";
55
export {auto, autoSpec} from "./marks/auto.js";
66
export {axisX, axisY, axisFx, axisFy, gridX, gridY, gridFx, gridFy} from "./marks/axis.js";
77
export {BarX, BarY, barX, barY} from "./marks/bar.js";
8-
export {bollinger, bollingerX, bollingerY, bollingerBandX, bollingerBandY} from "./marks/bollinger.js";
8+
export {bollinger, bollingerX, bollingerY} from "./marks/bollinger.js";
99
export {boxX, boxY} from "./marks/box.js";
1010
export {Cell, cell, cellX, cellY} from "./marks/cell.js";
1111
export {Contour, contour} from "./marks/contour.js";

src/marks/bollinger.d.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type {CompoundMark, Data, MarkOptions} from "../mark.js";
2-
import type {Transformed} from "../transforms/basic.js";
32
import type {Map} from "../transforms/map.js";
43
import type {AreaXOptions, AreaYOptions} from "./area.js";
54
import type {LineXOptions, LineYOptions} from "./line.js";
@@ -26,16 +25,10 @@ export type BollingerXOptions = BollingerOptions & AreaXOptions & LineXOptions;
2625
export type BollingerYOptions = BollingerOptions & AreaYOptions & LineYOptions;
2726

2827
/** TODO */
29-
export function bollingerBandX(data?: Data, options?: BollingerXOptions): CompoundMark;
28+
export function bollingerX(data?: Data, options?: BollingerXOptions): CompoundMark;
3029

3130
/** TODO */
32-
export function bollingerBandY(data?: Data, options?: BollingerYOptions): CompoundMark;
33-
34-
/** TODO */
35-
export function bollingerX<T>(n: number, k: number, options?: T): Transformed<T>;
36-
37-
/** TODO */
38-
export function bollingerY<T>(n: number, k: number, options?: T): Transformed<T>;
31+
export function bollingerY(data?: Data, options?: BollingerYOptions): CompoundMark;
3932

4033
/** TODO */
4134
export function bollinger(n: number, k: number): Map;

src/marks/bollinger.js

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,70 +6,52 @@ import {areaX, areaY} from "./area.js";
66
import {lineX, lineY} from "./line.js";
77
import {identity} from "../options.js";
88

9-
export function bollingerBandX(
9+
export function bollingerX(
1010
data,
1111
{
12-
x,
12+
x = identity,
1313
y,
1414
n = 20,
1515
k = 2,
1616
color = "currentColor",
17+
opacity = 0.2,
1718
fill = color,
18-
fillOpacity = 0.2,
19-
stroke,
19+
fillOpacity = opacity,
20+
stroke = color,
2021
strokeOpacity,
2122
strokeWidth,
2223
...options
2324
} = {}
2425
) {
2526
return marks(
26-
areaX(data, bollingerX(n, k, {x, y1: y, y2: y, fill, fillOpacity, ...options})),
27-
lineX(data, bollingerX(n, 0, {x, y, stroke, strokeOpacity, strokeWidth, ...options}))
27+
areaX(data, map({x1: bollinger(n, -k), x2: bollinger(n, k)}, {x1: x, x2: x, y, fill, fillOpacity, ...options})),
28+
lineX(data, map({x: bollinger(n, 0)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options}))
2829
);
2930
}
3031

31-
export function bollingerBandY(
32+
export function bollingerY(
3233
data,
3334
{
3435
x,
35-
y,
36+
y = identity,
3637
n = 20,
3738
k = 2,
3839
color = "currentColor",
40+
opacity = 0.2,
3941
fill = color,
40-
fillOpacity = 0.2,
42+
fillOpacity = opacity,
4143
stroke = color,
4244
strokeOpacity,
4345
strokeWidth,
4446
...options
4547
} = {}
4648
) {
4749
return marks(
48-
areaY(data, bollingerY(n, k, {x, y1: y, y2: y, fill, fillOpacity, ...options})),
49-
lineY(data, bollingerY(n, 0, {x, y, stroke, strokeOpacity, strokeWidth, ...options}))
50+
areaY(data, map({y1: bollinger(n, -k), y2: bollinger(n, k)}, {x, y1: y, y2: y, fill, fillOpacity, ...options})),
51+
lineY(data, map({y: bollinger(n, 0)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options}))
5052
);
5153
}
5254

53-
export function bollingerX(n, k, options) {
54-
let {x, x1, x2} = options;
55-
if (x === undefined && x1 === undefined && x2 === undefined) options = {...options, x: (x = identity)};
56-
const outputs = {};
57-
if (x != null) outputs.x = bollinger(n, k);
58-
if (x1 != null) outputs.x1 = bollinger(n, -k);
59-
if (x2 != null) outputs.x2 = bollinger(n, k);
60-
return map(outputs, options);
61-
}
62-
63-
export function bollingerY(n, k, options) {
64-
let {y, y1, y2} = options;
65-
if (y === undefined && y1 === undefined && y2 === undefined) options = {...options, y: (y = identity)};
66-
const outputs = {};
67-
if (y != null) outputs.y = bollinger(n, k);
68-
if (y1 != null) outputs.y1 = bollinger(n, -k);
69-
if (y2 != null) outputs.y2 = bollinger(n, k);
70-
return map(outputs, options);
71-
}
72-
7355
export function bollinger(n, k) {
74-
return window({k: n, reduce: (Y) => mean(Y) + k * deviation(Y), strict: true, anchor: "end"});
56+
return window({k: n, reduce: (Y) => mean(Y) + k * (deviation(Y) || 0), strict: true, anchor: "end"});
7557
}

test/plots/aapl-bollinger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export async function aaplBollinger() {
88
grid: true
99
},
1010
marks: [
11-
Plot.bollingerBandY(AAPL, {x: "Date", y: "Close", stroke: "blue"}),
11+
Plot.bollingerY(AAPL, {x: "Date", y: "Close", stroke: "blue"}),
1212
Plot.line(AAPL, {x: "Date", y: "Close", strokeWidth: 1})
1313
]
1414
});
@@ -25,7 +25,7 @@ export async function aaplBollingerGridInterval() {
2525
Plot.gridX({tickSpacing: 40, stroke: "#fff", strokeOpacity: 1, strokeWidth: 0.5}),
2626
Plot.gridX({tickSpacing: 80, stroke: "#fff", strokeOpacity: 1}),
2727
Plot.axisX({tickSpacing: 80}),
28-
Plot.bollingerBandY(AAPL, {x: "Date", y: "Close", stroke: "blue"}),
28+
Plot.bollingerY(AAPL, {x: "Date", y: "Close", stroke: "blue"}),
2929
Plot.line(AAPL, {x: "Date", y: "Close", strokeWidth: 1})
3030
]
3131
});
@@ -42,7 +42,7 @@ export async function aaplBollingerGridSpacing() {
4242
Plot.gridX({interval: "3 months", stroke: "#fff", strokeOpacity: 1, strokeWidth: 0.5}),
4343
Plot.gridX({interval: "1 year", stroke: "#fff", strokeOpacity: 1}),
4444
Plot.axisX({interval: "1 year"}),
45-
Plot.bollingerBandY(AAPL, {x: "Date", y: "Close", stroke: "blue"}),
45+
Plot.bollingerY(AAPL, {x: "Date", y: "Close", stroke: "blue"}),
4646
Plot.line(AAPL, {x: "Date", y: "Close", strokeWidth: 1})
4747
]
4848
});

0 commit comments

Comments
 (0)