diff --git a/CHANGELOG-2021.md b/CHANGELOG-2021.md
index f10862d703..eddba4758a 100644
--- a/CHANGELOG-2021.md
+++ b/CHANGELOG-2021.md
@@ -1,6 +1,6 @@
# Observable Plot - Changelog [2021]
-Year: [Current (2025)](./CHANGELOG.md) · [2024](./CHANGELOG-2024.md) · [2023](./CHANGELOG-2023.md) · [2022](./CHANGELOG-2022.md) · **2021**
+Year: [Current](./CHANGELOG.md) · [2025](./CHANGELOG-2025.md) · [2024](./CHANGELOG-2024.md) · [2023](./CHANGELOG-2023.md) · [2022](./CHANGELOG-2022.md) · **2021**
## 0.3.2
diff --git a/CHANGELOG-2022.md b/CHANGELOG-2022.md
index c763d85da4..42afd2f25c 100644
--- a/CHANGELOG-2022.md
+++ b/CHANGELOG-2022.md
@@ -1,6 +1,6 @@
# Observable Plot - Changelog [2022]
-Year: [Current (2025)](./CHANGELOG.md) · [2024](./CHANGELOG-2024.md) · [2023](./CHANGELOG-2023.md) · **2022** · [2021](./CHANGELOG-2021.md)
+Year: [Current](./CHANGELOG.md) · [2025](./CHANGELOG-2025.md) · [2024](./CHANGELOG-2024.md) · [2023](./CHANGELOG-2023.md) · **2022** · [2021](./CHANGELOG-2021.md)
## 0.6.1
diff --git a/CHANGELOG-2023.md b/CHANGELOG-2023.md
index fa55053e80..dc93353d9f 100644
--- a/CHANGELOG-2023.md
+++ b/CHANGELOG-2023.md
@@ -1,6 +1,6 @@
# Observable Plot - Changelog [2023]
-Year: [Current (2025)](./CHANGELOG.md) · [2024](./CHANGELOG-2024.md) · **2023** · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
+Year: [Current](./CHANGELOG.md) · [2025](./CHANGELOG-2025.md) · [2024](./CHANGELOG-2024.md) · **2023** · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
## 0.6.13
diff --git a/CHANGELOG-2024.md b/CHANGELOG-2024.md
index 0b4e7fd293..936b16e44b 100644
--- a/CHANGELOG-2024.md
+++ b/CHANGELOG-2024.md
@@ -1,6 +1,6 @@
# Observable Plot - Changelog [2024]
-Year: [Current (2025)](./CHANGELOG.md) · **2024** · [2023](./CHANGELOG-2023.md) · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
+Year: [Current](./CHANGELOG.md) · [2025](./CHANGELOG-2025.md) · **2024** · [2023](./CHANGELOG-2023.md) · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
## 0.6.16
diff --git a/CHANGELOG-2025.md b/CHANGELOG-2025.md
new file mode 100644
index 0000000000..8ad757586e
--- /dev/null
+++ b/CHANGELOG-2025.md
@@ -0,0 +1,66 @@
+# Observable Plot - Changelog
+
+Year: [Current](./CHANGELOG.md) · **2025** · [2024](./CHANGELOG-2024.md) · [2023](./CHANGELOG-2023.md) · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
+
+## 0.6.17
+
+[Released February 14, 2025.](https://github.com/observablehq/plot/releases/tag/v0.6.17)
+
+The [**clip** mark option](https://observablehq.com/plot/features/marks#clip) now supports GeoJSON objects 🌎 in addition to the named *frame* and *sphere* clipping methods, allowing the visual extent of marks to be limited to arbitrary polygons. For instance, this Voronoi mesh of world airports is clipped to land boundaries:
+
+[
](https://observablehq.com/@observablehq/plot-world-airports)
+
+```js
+Plot.plot({
+ projection: {type: "orthographic", rotate: [110, -50]},
+ marks: [
+ Plot.dot(airports, {x: "longitude", y: "latitude", fill: "red", r: 1}),
+ Plot.voronoiMesh(airports, {x: "longitude", y: "latitude", clip: land}),
+ Plot.sphere(),
+ Plot.geo(land)
+ ]
+})
+```
+
+The GeoJSON object passed to the **clip** option is rendered as a [`clipPath` element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath) using the same path data that a [geo mark](https://observablehq.com/plot/marks/geo) would produce, respecting the plot’s top-level **projection** option, if any. For performance, `clipPath` elements are shared by marks clipped with the same GeoJSON object. For example, the [raster mark](https://observablehq.com/plot/marks/raster) and [contour mark](https://observablehq.com/plot/marks/contour) below show atmospheric water vapor measurements across the United States from [NASA Earth Observations](https://neo.gsfc.nasa.gov/view.php?datasetId=MYDAL2_M_SKY_WV); both marks are clipped to the nation’s boundary, censoring the (absurd) values that would otherwise be interpolated between Alaska, Southern California, and Hawai’i.
+
+[
](https://observablehq.com/@observablehq/plot-us-water-vapor)
+
+```js
+Plot.raster(vapor, {
+ fill: Plot.identity,
+ width: 360,
+ height: 180,
+ x1: -180, y1: 90, x2: 180, y2: -90,
+ interpolate: "barycentric",
+ blur: 10,
+ clip: nation
+}).plot()
+```
+
+[The code for the map above is too long to reproduce here in its entirety; click the image above for the complete code.]
+
+The **clip** mark option can also be used to clip against arbitrary polygons, not just geographic boundaries. For example, to show the value of [Math.atan2](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2) over the unit circle:
+
+[
](https://observablehq.com/@observablehq/plot-color-angle)
+
+```js
+Plot.raster({
+ x1: -1, x2: 1, y1: -1, y2: 1,
+ fill: (x, y) => Math.atan2(y, x),
+ clip: {
+ type: "Polygon",
+ coordinates: [
+ d3.range(0, 2 * Math.PI, 0.1).map((angle) => [Math.cos(angle), Math.sin(angle)])
+ ]
+ }
+}).plot({width: 300, aspectRatio: 1})
+```
+
+The interactive **tip** associated with a [waffle mark](https://observablehq.com/plot/marks/waffle) is now anchored to the “center” of the visual representation of the associated datum. That center depends on the shape that is referenced. For fun, here’s a chart from our unit tests showing these anchoring points for various amounts of waffling. Baffling!
+
+
+
+---
+
+For earlier changes, continue to the [2024 CHANGELOG](./CHANGELOG-2024.md).
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebc5a79725..8660beef04 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,66 +1,103 @@
# Observable Plot - Changelog
-Year: **Current (2025)** · [2024](./CHANGELOG-2024.md) · [2023](./CHANGELOG-2023.md) · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
+Year: **Current** · [2025](./CHANGELOG-2025.md) · [2024](./CHANGELOG-2024.md) · [2023](./CHANGELOG-2023.md) · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
-## 0.6.17
+## 0.6.18
-[Released February 14, 2025.](https://github.com/observablehq/plot/releases/tag/v0.6.17)
+[Released TK, 2026.](https://github.com/observablehq/plot/releases/tag/v0.6.18)
-The [**clip** mark option](https://observablehq.com/plot/features/marks#clip) now supports GeoJSON objects 🌎 in addition to the named *frame* and *sphere* clipping methods, allowing the visual extent of marks to be limited to arbitrary polygons. For instance, this Voronoi mesh of world airports is clipped to land boundaries:
+The [area mark](https://observablehq.com/plot/marks/area) now supports a **line** option: when set to true, a stroked line is drawn on top of the filled area, removing the need for a separate line mark; when line is true, the the fill opacity defaults to 0.3 and the stroke options (`strokeWidth`, etc.) apply only to that line.
-[
](https://observablehq.com/@observablehq/plot-world-airports)
+
```js
Plot.plot({
- projection: {type: "orthographic", rotate: [110, -50]},
+ height: 300,
marks: [
- Plot.dot(airports, {x: "longitude", y: "latitude", fill: "red", r: 1}),
- Plot.voronoiMesh(airports, {x: "longitude", y: "latitude", clip: land}),
- Plot.sphere(),
- Plot.geo(land)
+ Plot.areaY(traffic, {
+ x: "date",
+ y: "vehicles",
+ fy: "location",
+ fill: "#eee",
+ line: true,
+ fillOpacity: 1,
+ curve: "basis"
+ })
]
})
```
-The GeoJSON object passed to the **clip** option is rendered as a [`clipPath` element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath) using the same path data that a [geo mark](https://observablehq.com/plot/marks/geo) would produce, respecting the plot’s top-level **projection** option, if any. For performance, `clipPath` elements are shared by marks clipped with the same GeoJSON object. For example, the [raster mark](https://observablehq.com/plot/marks/raster) and [contour mark](https://observablehq.com/plot/marks/contour) below show atmospheric water vapor measurements across the United States from [NASA Earth Observations](https://neo.gsfc.nasa.gov/view.php?datasetId=MYDAL2_M_SKY_WV); both marks are clipped to the nation’s boundary, censoring the (absurd) values that would otherwise be interpolated between Alaska, Southern California, and Hawai’i.
+The [raster mark](https://observablehq.com/plot/marks/raster) now supports a **colorSpace** option, allowing interpolation in wide-gamut color spaces such as *display-p3*. For example, this raster plot of penguin body mass vs. flipper length encodes the `island` channel with vivid P3 colors:
-[
](https://observablehq.com/@observablehq/plot-us-water-vapor)
+
```js
-Plot.raster(vapor, {
- fill: Plot.identity,
- width: 360,
- height: 180,
- x1: -180, y1: 90, x2: 180, y2: -90,
- interpolate: "barycentric",
- blur: 10,
- clip: nation
-}).plot()
+Plot.plot({
+ color: {range: ["oklch(90% 0.4 135)", "oklch(75% 0.3 55)", "oklch(65% 0.35 305)"]},
+ marks: [
+ Plot.raster(penguins, {
+ x: "body_mass_g",
+ y: "flipper_length_mm",
+ fill: "island",
+ interpolate: "random-walk",
+ blur: 8,
+ colorSpace: "display-p3"
+ })
+ ]
+})
+```
+
+[Pointer tips](https://observablehq.com/plot/interactions/pointer) are now exclusive by default: when multiple pointer-driven marks overlap, only the closest point is shown. This avoids the common issue of multiple marks creating overlapping tips. This behavior is controlled by the new **pool** [mark option](https://observablehq.com/plot/features/marks#mark-options), which defaults to tru for tip marks; set `pool: false` to restore the previous behavior.
+
+The new top-level [**legend** plot option](https://observablehq.com/plot/features/legends) provides a convenient shorthand for requesting a legend, replacing the more verbose `color: {legend: true}` pattern.
+
+```js
+Plot.plot({
+ legend: true,
+ color: {type: "ordinal", domain: "ABCDEFGHIJ"},
+ marks: [Plot.cellX("ABCDEFGHIJ")]
+})
+```
+
+The new [plot.scale("projection")](https://observablehq.com/plot/features/projections) method exposes the plot’s projection, allowing programmatic access after rendering. The returned object supports *apply* and *invert* for converting between geographic and pixel coordinates, and can be passed as the **projection** option of another plot.
+
+```js
+const plot = Plot.plot({projection: "mercator", marks: [Plot.graticule()]});
+const projection = plot.scale("projection");
+projection.apply([-1.55, 47.22]) // [316.7, 224.2]
+
+// reuse in a new plot with different marks but the same projection
+Plot.plot({projection, marks: [Plot.geo(countries)]})
```
-[The code for the map above is too long to reproduce here in its entirety; click the image above for the complete code.]
+The [opacity scale](https://observablehq.com/plot/features/scales) and associated legend now support *ordinal* and *threshold* types.
-The **clip** mark option can also be used to clip against arbitrary polygons, not just geographic boundaries. For example, to show the value of [Math.atan2](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2) over the unit circle:
+```js
+Plot.cellX(d3.range(10), {fill: "red", opacity: Plot.identity}).plot({
+ opacity: {type: "threshold", legend: true, domain: [2, 5, 8], range: [0.2, 0.4, 0.6, 0.8]}
+})
+```
-[
](https://observablehq.com/@observablehq/plot-color-angle)
+Plot now supports the `light-dark(…)` CSS color syntax, making it easier to define colors that adapt to dark mode. Color and opacity legends also render correctly with `light-dark(…)`, `currentColor`, and display-p3 colors.
```js
-Plot.raster({
- x1: -1, x2: 1, y1: -1, y2: 1,
- fill: (x, y) => Math.atan2(y, x),
- clip: {
- type: "Polygon",
- coordinates: [
- d3.range(0, 2 * Math.PI, 0.1).map((angle) => [Math.cos(angle), Math.sin(angle)])
- ]
- }
-}).plot({width: 300, aspectRatio: 1})
+Plot.barX(alphabet, {x: "frequency", y: "letter", fill: "light-dark(steelblue, orange)"})
```
-The interactive **tip** associated with a [waffle mark](https://observablehq.com/plot/marks/waffle) is now anchored to the “center” of the visual representation of the associated datum. That center depends on the shape that is referenced. For fun, here’s a chart from our unit tests showing these anchoring points for various amounts of waffling. Baffling!
+This release includes several more bug fixes and improvements:
+
+The [tick format](https://observablehq.com/plot/features/scales) now defaults to a comma-less format when tick values are likely year integers.
+
+
+
+The [stack transform](https://observablehq.com/plot/transforms/stack) now preserves NaN values instead of coercing them to zero. The [dodge transform](https://observablehq.com/plot/transforms/dodge) no longer crashes when the radius is negative. The [area mark](https://observablehq.com/plot/marks/area) now respects the **reduce** option when using the [interval transform](https://observablehq.com/plot/transforms/interval).
+
+For line and area marks, the *z* channel now defaults to null when the color channel is redundant with the position channel (*e.g.*, `lineY(data, {y: "value", stroke: "value"})`), preventing the line from being split into single-point series. The default offset in windowless environments (*e.g.* server-side rendering) is now zero, prioritizing high-density displays. The default colors for the [box mark](https://observablehq.com/plot/marks/box) and [tree transform](https://observablehq.com/plot/transforms/tree) now support dark mode.
+
+The [tip mark](https://observablehq.com/plot/marks/tip) no longer crashes when the title channel contains null; it now ignores the contours channel and literal symbols; and the tip arrow is smaller when using a corner anchor. [Markers](https://observablehq.com/plot/features/markers) no longer inherit `strokeDasharray`. The [pointer transform](https://observablehq.com/plot/interactions/pointer) now allows `pointerdown` event propagation.
-
+Various type fixes and documentation improvements; thanks @gka and @jsoref!
---
-For earlier changes, continue to the [2024 CHANGELOG](./CHANGELOG-2024.md).
+For earlier changes, continue to the [2025 CHANGELOG](./CHANGELOG-2025.md).
diff --git a/img/axisYears.png b/img/axisYears.png
new file mode 100644
index 0000000000..928d711229
Binary files /dev/null and b/img/axisYears.png differ
diff --git a/img/raster-p3.png b/img/raster-p3.png
new file mode 100644
index 0000000000..b8e531a6de
Binary files /dev/null and b/img/raster-p3.png differ
diff --git a/img/ridgeline.png b/img/ridgeline.png
new file mode 100644
index 0000000000..f8104d132d
Binary files /dev/null and b/img/ridgeline.png differ
diff --git a/package.json b/package.json
index 007363bf97..c1275cbf77 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@observablehq/plot",
"description": "A JavaScript library for exploratory data visualization.",
- "version": "0.6.17",
+ "version": "0.6.18",
"author": {
"name": "Observable, Inc.",
"url": "https://observablehq.com"