diff --git a/src/marks/area.d.ts b/src/marks/area.d.ts
index 34d71c63e9..ccf471e8d0 100644
--- a/src/marks/area.d.ts
+++ b/src/marks/area.d.ts
@@ -1,11 +1,12 @@
import type {ChannelValue, ChannelValueDenseBinSpec, ChannelValueSpec} from "../channel.js";
import type {CurveOptions} from "../curve.js";
import type {Data, MarkOptions, RenderableMark} from "../mark.js";
+import type {MarkerOptions} from "../marker.js";
import type {BinOptions, BinReducer} from "../transforms/bin.js";
import type {StackOptions} from "../transforms/stack.js";
/** Options for the area, areaX, and areaY marks. */
-export interface AreaOptions extends MarkOptions, StackOptions, CurveOptions {
+export interface AreaOptions extends MarkOptions, StackOptions, CurveOptions, MarkerOptions {
/**
* The required primary (starting, often left) horizontal position channel,
* representing the area’s baseline, typically bound to the *x* scale. For
@@ -45,7 +46,9 @@ export interface AreaOptions extends MarkOptions, StackOptions, CurveOptions {
/**
* Whether a line should be drawn connecting the points with coordinates *x2*,
- * *y2*; the **stroke** then applies to that line and defaults to *currentColor*.
+ * *y2*; the **stroke** then applies to that line and defaults to
+ * *currentColor*, and **fillOpacity** defaults to 0.3. Marker options are
+ * applied to the line path.
*/
line?: boolean;
}
diff --git a/src/marks/area.js b/src/marks/area.js
index feaf688c6b..c02694accd 100644
--- a/src/marks/area.js
+++ b/src/marks/area.js
@@ -2,12 +2,16 @@ 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, offset} from "../style.js";
import {maybeDenseIntervalX, maybeDenseIntervalY} from "../transforms/bin.js";
import {maybeStackX, maybeStackY} from "../transforms/stack.js";
+const noStroke = {stroke: null, strokeWidth: null, strokeOpacity: null, strokeLinejoin: null, strokeLinecap: null, strokeMiterlimit: null, strokeDasharray: null, strokeDashoffset: null}; // prettier-ignore
+const noFill = {fill: null, fillOpacity: null};
+
const defaults = {
ariaLabel: "area",
strokeWidth: 1,
@@ -29,47 +33,47 @@ export class Area extends Mark {
z: {value: maybeZ(options), optional: true}
},
options,
- line ? {...defaults, stroke: "currentColor", strokeWidth: 1.5} : defaults
+ line ? {...defaults, stroke: "currentColor", strokeWidth: 1.5, fillOpacity: 0.3} : defaults
);
this.z = z;
this.curve = maybeCurve(curve, tension);
this.line = !!line;
+ if (this.line) markers(this, options);
}
filter(index) {
return index;
}
render(index, scales, channels, dimensions, context) {
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
+ const areaShape = shapeArea()
+ .curve(this.curve)
+ .defined((i) => i >= 0)
+ .x0((i) => X1[i])
+ .y0((i) => Y1[i])
+ .x1((i) => X2[i])
+ .y1((i) => Y2[i]);
return create("svg:g", context)
- .call(applyIndirectStyles, this, dimensions, context)
+ .call(this.line ? () => {} : applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales, 0, 0)
.call((g) => {
- let enter = g
+ const enter = g
.selectAll()
.data(groupIndex(index, [X1, Y1, X2, Y2], this, channels))
.enter();
- if (this.line) enter = enter.append("g");
- const area = enter
- .append("path")
- .call(applyDirectStyles, this)
- .call(applyGroupedChannelStyles, this, channels)
- .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])
- );
if (this.line) {
- area.attr("stroke", "none");
- enter
+ const group = enter.append("g");
+ group
.append("path")
.call(applyDirectStyles, this)
- .call(applyGroupedChannelStyles, this, channels)
- .attr("fill", "none")
+ .call(applyIndirectStyles, {...this, ...noStroke}, dimensions, context)
+ .call(applyGroupedChannelStyles, this, {...channels, ...noStroke})
+ .attr("d", areaShape);
+ group
+ .append("path")
+ .call(applyDirectStyles, this)
+ .call(applyIndirectStyles, {...this, ...noFill, fill: "none"}, dimensions, context)
+ .call(applyGroupedChannelStyles, this, {...channels, ...noFill})
+ .call(applyGroupedMarkers, this, channels, context)
.attr("transform", offset ? `translate(${offset},${offset})` : null)
.attr(
"d",
@@ -79,6 +83,12 @@ export class Area extends Mark {
.x((i) => X2[i])
.y((i) => Y2[i])
);
+ } else {
+ enter
+ .append("path")
+ .call(applyDirectStyles, this)
+ .call(applyGroupedChannelStyles, this, channels)
+ .attr("d", areaShape);
}
})
.node();
diff --git a/test/output/aaplClose.svg b/test/output/aaplClose.svg
index 03de000d1e..35db15bf9d 100644
--- a/test/output/aaplClose.svg
+++ b/test/output/aaplClose.svg
@@ -66,10 +66,10 @@
2017
2018
-
+
-
-
+
+
diff --git a/test/output/aaplCloseClip.svg b/test/output/aaplCloseClip.svg
index 090be022ae..4f2a5527b0 100644
--- a/test/output/aaplCloseClip.svg
+++ b/test/output/aaplCloseClip.svg
@@ -85,13 +85,8 @@
-
-
-
-
-
-
-
+
+
diff --git a/test/output/aaplCloseLine.svg b/test/output/aaplCloseLine.svg
index 7bda0a87f4..24101b1bab 100644
--- a/test/output/aaplCloseLine.svg
+++ b/test/output/aaplCloseLine.svg
@@ -68,10 +68,10 @@
Apr
May
-
+
-
-
+
+
diff --git a/test/output/areaLine.svg b/test/output/areaLine.svg
new file mode 100644
index 0000000000..c4a0d6200e
--- /dev/null
+++ b/test/output/areaLine.svg
@@ -0,0 +1,57 @@
+
\ No newline at end of file
diff --git a/test/output/availability.svg b/test/output/availability.svg
index 0192d6d85d..41cf2f2716 100644
--- a/test/output/availability.svg
+++ b/test/output/availability.svg
@@ -48,10 +48,10 @@
Jan2021
Apr
-
+
-
-
+
+
diff --git a/test/output/downloads.svg b/test/output/downloads.svg
index 0c00636cca..8cc1694102 100644
--- a/test/output/downloads.svg
+++ b/test/output/downloads.svg
@@ -63,10 +63,10 @@
-
+
-
-
+
+
\ No newline at end of file
diff --git a/test/output/musicRevenue.svg b/test/output/musicRevenue.svg
index 4441b59835..86d35c26fe 100644
--- a/test/output/musicRevenue.svg
+++ b/test/output/musicRevenue.svg
@@ -80,143 +80,143 @@
2010
2015
-
+
- 8 - Track
+ 8 - Track
Tape
- 8 - Track
+ 8 - Track
Tape
- CD
+ CD
Disc
- CD
+ CD
Disc
- CD Single
+ CD Single
Disc
- CD Single
+ CD Single
Disc
- Cassette
+ Cassette
Tape
- Cassette
+ Cassette
Tape
- Cassette Single
+ Cassette Single
Tape
- Cassette Single
+ Cassette Single
Tape
- DVD Audio
+ DVD Audio
Other
- DVD Audio
+ DVD Audio
Other
- Download Album
+ Download Album
Download
- Download Album
+ Download Album
Download
- Download Music Video
+ Download Music Video
Download
- Download Music Video
+ Download Music Video
Download
- Download Single
+ Download Single
Download
- Download Single
+ Download Single
Download
- Kiosk
+ Kiosk
Other
- Kiosk
+ Kiosk
Other
- LP/EP
+ LP/EP
Vinyl
- LP/EP
+ LP/EP
Vinyl
- Limited Tier Paid Subscription
+ Limited Tier Paid Subscription
Streaming
- Limited Tier Paid Subscription
+ Limited Tier Paid Subscription
Streaming
- Music Video (Physical)
+ Music Video (Physical)
Other
- Music Video (Physical)
+ Music Video (Physical)
Other
- On-Demand Streaming (Ad-Supported)
+ On-Demand Streaming (Ad-Supported)
Streaming
- On-Demand Streaming (Ad-Supported)
+ On-Demand Streaming (Ad-Supported)
Streaming
- Other Ad-Supported Streaming
+ Other Ad-Supported Streaming
Streaming
- Other Ad-Supported Streaming
+ Other Ad-Supported Streaming
Streaming
- Other Digital
+ Other Digital
Download
- Other Digital
+ Other Digital
Download
- Other Tapes
+ Other Tapes
Tape
- Other Tapes
+ Other Tapes
Tape
- Paid Subscription
+ Paid Subscription
Streaming
- Paid Subscription
+ Paid Subscription
Streaming
- Ringtones & Ringbacks
+ Ringtones & Ringbacks
Download
- Ringtones & Ringbacks
+ Ringtones & Ringbacks
Download
- SACD
+ SACD
Disc
- SACD
+ SACD
Disc
- SoundExchange Distributions
+ SoundExchange Distributions
Streaming
- SoundExchange Distributions
+ SoundExchange Distributions
Streaming
- Synchronization
+ Synchronization
Other
- Synchronization
+ Synchronization
Other
- Vinyl Single
+ Vinyl Single
Vinyl
- Vinyl Single
+ Vinyl Single
Vinyl
diff --git a/test/output/ridgeline.svg b/test/output/ridgeline.svg
index ace4941f82..4cf5f90428 100644
--- a/test/output/ridgeline.svg
+++ b/test/output/ridgeline.svg
@@ -161,233 +161,233 @@
12 PM
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
diff --git a/test/plots/aapl-close.ts b/test/plots/aapl-close.ts
index 33e4cdbe61..35957069f1 100644
--- a/test/plots/aapl-close.ts
+++ b/test/plots/aapl-close.ts
@@ -14,7 +14,7 @@ test(async function aaplCloseLine() {
const aapl = (await d3.csv("data/aapl.csv", d3.autoType)).slice(-120);
return Plot.plot({
y: {grid: true},
- marks: [Plot.areaY(aapl, {x: "Date", y: "Close", fillOpacity: 0.1, line: true, stroke: "red"}), Plot.ruleY([0])]
+ marks: [Plot.areaY(aapl, {x: "Date", y: "Close", line: true, stroke: "red"}), Plot.ruleY([0])]
});
});
@@ -36,7 +36,7 @@ test(async function aaplCloseClip() {
clip: true,
x: {domain: [new Date(Date.UTC(2015, 0, 1)), new Date(Date.UTC(2015, 3, 1))]},
y: {grid: true},
- marks: [Plot.areaY(aapl, {x: "Date", y: "Close", fillOpacity: 0.1, line: true}), Plot.ruleY([0], {clip: false})]
+ marks: [Plot.areaY(aapl, {x: "Date", y: "Close", line: true}), Plot.ruleY([0], {clip: false})]
});
});
diff --git a/test/plots/area-line.ts b/test/plots/area-line.ts
new file mode 100644
index 0000000000..a0369d1a69
--- /dev/null
+++ b/test/plots/area-line.ts
@@ -0,0 +1,21 @@
+import * as Plot from "@observablehq/plot";
+import {test} from "test/plot";
+
+test(function areaLine() {
+ return Plot.plot({
+ width: 300,
+ height: 200,
+ insetTop: 10,
+ marks: [
+ Plot.frame(),
+ Plot.areaY([1, 3, 2, 5, 8, 6, 4, 7, 9, 3], {
+ line: true,
+ stroke: "steelblue",
+ strokeWidth: 2,
+ fill: "steelblue",
+ marker: "circle",
+ markerEnd: "arrow"
+ })
+ ]
+ });
+});
diff --git a/test/plots/availability.ts b/test/plots/availability.ts
index 1399c26efc..6855ba9d98 100644
--- a/test/plots/availability.ts
+++ b/test/plots/availability.ts
@@ -7,7 +7,15 @@ test(async function availability() {
return Plot.plot({
height: 180,
marks: [
- Plot.areaY(data, {x: "date", y: "value", interval: "day", curve: "step", fill: "#f2f2fe", line: true}),
+ Plot.areaY(data, {
+ x: "date",
+ y: "value",
+ interval: "day",
+ curve: "step",
+ fill: "#f2f2fe",
+ fillOpacity: 1,
+ line: true
+ }),
Plot.ruleY([0])
]
});
diff --git a/test/plots/downloads.ts b/test/plots/downloads.ts
index 86aba409de..741e9f68db 100644
--- a/test/plots/downloads.ts
+++ b/test/plots/downloads.ts
@@ -12,7 +12,6 @@ test(async function downloads() {
interval: "day",
y: "downloads",
curve: "step",
- fill: "#ccc",
line: true,
strokeWidth: 1
})
diff --git a/test/plots/index.ts b/test/plots/index.ts
index 1e6f376920..0ecc7fc287 100644
--- a/test/plots/index.ts
+++ b/test/plots/index.ts
@@ -10,6 +10,7 @@ import "./aapl-volume-rect.js";
import "./aapl-volume.js";
import "./anscombe-quartet.js";
import "./arc.js";
+import "./area-line.js";
import "./armadillo.js";
import "./arrow.js";
import "./arrow-dates.js";
diff --git a/test/plots/music-revenue.ts b/test/plots/music-revenue.ts
index 37a2f15eba..07271ad85e 100644
--- a/test/plots/music-revenue.ts
+++ b/test/plots/music-revenue.ts
@@ -19,6 +19,7 @@ test(async function musicRevenue() {
fill: "group",
title: (d: any) => `${d.format}\n${d.group}`,
line: true,
+ fillOpacity: 1,
stroke: "white",
strokeWidth: 1
}),
diff --git a/test/plots/ridgeline.ts b/test/plots/ridgeline.ts
index a1acd997e1..0dd0ec6e11 100644
--- a/test/plots/ridgeline.ts
+++ b/test/plots/ridgeline.ts
@@ -22,6 +22,7 @@ test(async function ridgeline() {
sort: "date",
fill: "color-mix(in oklab, var(--plot-background), currentColor 20%)",
line: true,
+ fillOpacity: 1,
strokeWidth: 1
})
]