Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions docs/marks/area.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Plot.areaY(aapl, {x: "Date", y: "Close"}).plot()

The area mark has three constructors: [areaY](#areaY) for when the baseline and topline share *x* values, as in a time-series area chart where time goes right→ (or ←left); [areaX](#areaX) for when the baseline and topline share *y* values, as in a time-series area chart where time goes up↑ (or down↓); and lastly the rarely-used [area](#area) where the baseline and topline share neither *x* nor *y* values.

The area mark is often paired with a [line](./line.md) and [rule](./rule.md) mark to accentuate the topline and baseline.
The **line** option <VersionBadge pr="2407" /> strokes the topline. It is often paired with a [rule](./rule.md) mark to denote the baseline.

:::plot https://observablehq.com/@observablehq/plot-area-and-line
```js
Expand All @@ -29,8 +29,7 @@ Plot.plot({
grid: true
},
marks: [
Plot.areaY(aapl, {x: "Date", y: "Close", fillOpacity: 0.3}),
Plot.lineY(aapl, {x: "Date", y: "Close"}),
Plot.areaY(aapl, {x: "Date", y: "Close", line: true}),
Plot.ruleY([0])
]
})
Expand Down Expand Up @@ -94,8 +93,7 @@ Plot.plot({
reverse: true
},
marks: [
Plot.areaY(aapl, {x: "Date", y: "Close", fillOpacity: 0.3}),
Plot.lineY(aapl, {x: "Date", y: "Close"}),
Plot.areaY(aapl, {x: "Date", y: "Close", line: true}),
Plot.ruleY([0])
]
})
Expand All @@ -111,8 +109,7 @@ Plot.plot({
grid: true
},
marks: [
Plot.areaX(aapl, {y: "Date", x: "Close", fillOpacity: 0.3}),
Plot.lineX(aapl, {y: "Date", x: "Close"}),
Plot.areaX(aapl, {y: "Date", x: "Close", line: true}),
Plot.ruleX([0])
]
})
Expand All @@ -128,8 +125,7 @@ Plot.plot({
grid: true
},
marks: [
Plot.areaY(aapl, {x: "Date", y: (d) => d.Date.getUTCMonth() < 3 ? NaN : d.Close, fillOpacity: 0.3}),
Plot.lineY(aapl, {x: "Date", y: (d) => d.Date.getUTCMonth() < 3 ? NaN : d.Close}),
Plot.areaY(aapl, {x: "Date", y: (d) => d.Date.getUTCMonth() < 3 ? NaN : d.Close, line: true}),
Plot.ruleY([0])
]
})
Expand Down Expand Up @@ -308,7 +304,7 @@ Plot.areaY(observations, {x: "date", y: "temperature", interval: "day"})

The **interval** option is recommended to “regularize” sampled data; for example, if your data represents timestamped temperature measurements and you expect one sample per day, use "day" as the interval.

The **areaY** mark draws the region between a baseline (*y1*) and a topline (*y2*) as in an area chart. When the baseline is *y* = 0, the *y* channel can be specified instead of *y1* and *y2*.
The **areaY** mark draws the region between a vertically-separated baseline (*y1*) and topline (*y2*) as in an area chart. When the baseline is *y* = 0, the *y* channel can be specified instead of *y1* and *y2*. If the **line** option <VersionBadge pr="2407" /> is true, the **stroke** applies exclusively to the topline, and the **fillOpacity** defaults to 0.3.

## areaX(*data*, *options*) {#areaX}

Expand All @@ -326,6 +322,8 @@ Plot.areaX(observations, {y: "date", x: "temperature", interval: "day"})

The **interval** option is recommended to “regularize” sampled data; for example, if your data represents timestamped temperature measurements and you expect one sample per day, use "day" as the interval.

The **areaX** mark draws the region between a horizontally-separated baseline (*x1*) and topline (*x2*) as in a vertical area chart. When the baseline is *x* = 0, the *x* channel can be specified instead of *x1* and *x2*. If the **line** option <VersionBadge pr="2407" /> is true, the **stroke** applies exclusively to the topline, and the **fillOpacity** defaults to 0.3.

## area(*data*, *options*) {#area}

```js
Expand Down
11 changes: 9 additions & 2 deletions src/marks/area.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {ChannelValue, ChannelValueDenseBinSpec, ChannelValueSpec} from "../channel.js";
import type {CurveOptions} from "../curve.js";
import type {Data, MarkOptions, RenderableMark} from "../mark.js";
import {MarkerOptions} from "../marker.js";
import type {BinOptions, BinReducer} from "../transforms/bin.js";
import type {StackOptions} from "../transforms/stack.js";

Expand Down Expand Up @@ -42,6 +43,9 @@ export interface AreaOptions extends MarkOptions, StackOptions, CurveOptions {
* **stroke** if a channel.
*/
z?: ChannelValue;

/** Shorthand for setting both the fill and the stroke. */
color?: ChannelValueSpec;
}

/** Options for the areaX mark. */
Expand Down Expand Up @@ -124,6 +128,9 @@ export interface AreaYOptions extends Omit<AreaOptions, "x1" | "x2">, BinOptions
reduce?: BinReducer;
}

/** The area mark’s line option. */
export type AreaLineOptions = {line?: false} | ({line: true} & MarkerOptions);

/**
* Returns a new area mark with the given *data* and *options*. The area mark is
* rarely used directly; it is only needed when the baseline and topline have
Expand Down Expand Up @@ -163,7 +170,7 @@ export function area(data?: Data, options?: AreaOptions): Area;
* channels. When any of these channels are used, setting an explicit **z**
* channel (possibly to null) is strongly recommended.
*/
export function areaX(data?: Data, options?: AreaXOptions): Area;
export function areaX(data?: Data, options?: AreaXOptions & AreaLineOptions): Area;

/**
* Returns a new horizontally-oriented area mark for the given *data* and
Expand Down Expand Up @@ -195,7 +202,7 @@ export function areaX(data?: Data, options?: AreaXOptions): Area;
* channels. When any of these channels are used, setting an explicit **z**
* channel (possibly to null) is strongly recommended.
*/
export function areaY(data?: Data, options?: AreaYOptions): Area;
export function areaY(data?: Data, options?: AreaYOptions & AreaLineOptions): Area;

/** The area mark. */
export class Area extends RenderableMark {}
80 changes: 72 additions & 8 deletions src/marks/area.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import {area as shapeArea} from "d3";
import {area as shapeArea, line as shapeLine} from "d3";
import {create} from "../context.js";
import {maybeCurve} from "../curve.js";
import {Mark} from "../mark.js";
import {applyGroupedMarkers, markers} from "../marker.js";
import {first, maybeZ, second} from "../options.js";
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyGroupedChannelStyles} from "../style.js";
import {groupIndex} from "../style.js";
import {groupIndex, offset} from "../style.js";
import {maybeDenseIntervalX, maybeDenseIntervalY} from "../transforms/bin.js";
import {maybeStackX, maybeStackY} from "../transforms/stack.js";

const defaults = {
const areaDefaults = {
ariaLabel: "area",
strokeWidth: 1,
strokeLinecap: "round",
strokeLinejoin: "round",
strokeMiterlimit: 1
};

const areaLineDefaults = {
ariaLabel: "area-line",
fillOpacity: 0.3,
stroke: "currentColor",
strokeWidth: 1.5,
strokeLinecap: "round",
strokeLinejoin: "round",
strokeMiterlimit: 1
};

export class Area extends Mark {
constructor(data, options = {}) {
constructor(data, options = {}, defaults = areaDefaults) {
const {x1, y1, x2, y2, z, curve, tension} = options;
super(
data,
Expand Down Expand Up @@ -65,17 +76,70 @@ export class Area extends Mark {
}
}

class AreaLine extends Area {
constructor(data, options = {}) {
super(data, options, areaLineDefaults);
markers(this, options);
}
render(index, scales, channels, dimensions, context) {
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales, 0, 0)
.call((g) =>
g
.selectAll()
.data(groupIndex(index, [X1, Y1, X2, Y2], this, channels))
.enter()
.append("g")
.call(applyDirectStyles, this)
.call(applyGroupedChannelStyles, this, channels)
.call((e) =>
e
.append("path")
.attr("stroke", "none")
.attr(
"d",
shapeArea()
.curve(this.curve)
.defined((i) => i >= 0)
.x0((i) => X1[i])
.y0((i) => Y1[i])
.x1((i) => X2[i])
.y1((i) => Y2[i])
)
)
.call((e) =>
e
.append("path")
.call(applyGroupedMarkers, this, channels, context)
.attr("fill", "none")
.attr("transform", offset ? `translate(${offset},${offset})` : null)
.attr(
"d",
shapeLine()
.curve(this.curve)
.defined((i) => i >= 0)
.x((i) => X2[i])
.y((i) => Y2[i])
)
)
)
.node();
}
}

export function area(data, options) {
if (options === undefined) return areaY(data, {x: first, y: second});
return new Area(data, options);
}

export function areaX(data, options) {
const {x, y, fill, z = x === fill ? null : undefined, ...rest} = maybeDenseIntervalY(options);
return new Area(data, maybeStackX({...rest, x, y1: y, y2: undefined, z, fill}));
const {x, y, line, color, stroke = color, fill = color, z = x === fill || x === stroke ? null : undefined, ...rest} = maybeDenseIntervalY(options); // prettier-ignore
return new (line ? AreaLine : Area)(data, maybeStackX({...rest, x, y1: y, y2: undefined, z, stroke, fill}));
}

export function areaY(data, options) {
const {x, y, fill, z = y === fill ? null : undefined, ...rest} = maybeDenseIntervalX(options);
return new Area(data, maybeStackY({...rest, x1: x, x2: undefined, y, z, fill}));
const {x, y, line, color, stroke = color, fill = color, z = y === fill || y === stroke ? null : undefined, ...rest} = maybeDenseIntervalX(options); // prettier-ignore
return new (line ? AreaLine : Area)(data, maybeStackY({...rest, x1: x, x2: undefined, y, z, stroke, fill}));
}
10 changes: 5 additions & 5 deletions test/output/aaplClose.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 1 addition & 5 deletions test/plots/aapl-close.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ test(async function aaplClose() {
const aapl = await d3.csv<any>("data/aapl.csv", d3.autoType);
return Plot.plot({
y: {grid: true},
marks: [
Plot.areaY(aapl, {x: "Date", y: "Close", fillOpacity: 0.1}),
Plot.lineY(aapl, {x: "Date", y: "Close"}),
Plot.ruleY([0])
]
marks: [Plot.areaY(aapl, {x: "Date", y: "Close", line: true}), Plot.ruleY([0])]
});
});

Expand Down