diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index bcb310641d..7e1f1fd2bb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,7 +7,7 @@ on:
branches: [main]
jobs:
- build:
+ test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -16,11 +16,12 @@ jobs:
node-version: 16
cache: 'yarn'
- run: yarn --frozen-lockfile
+ - run: yarn test:mocha
+ - run: yarn test:tsc
- run: |
echo ::add-matcher::.github/eslint.json
yarn run eslint src test --format=compact
- - run: yarn run prettier --check src test
- - run: yarn test:mocha
+ - run: yarn test:prettier
- run: yarn prepublishOnly
- run: yarn docs:build
- uses: actions/upload-artifact@v3
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7de2c7826..9b0b90fd94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,62 @@
Year: **Current (2023)** · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
+## 0.6.11
+
+[Released September 20, 2023.](https://github.com/observablehq/plot/releases/tag/v0.6.11)
+
+The **tip** mark option can now pass options to the derived [tip mark](https://observablehq.com/plot/marks/tip); the options object can also specify the **pointer** option to control the derived tip’s pointer mode (_x_, _y_, or _xy_). The new **format** tip mark option enables greater control over order and formatting of channels.
+
+
+
+```js
+Plot.dot(olympians, {
+ x: "weight",
+ y: "height",
+ stroke: "sex",
+ channels: {
+ name: "name",
+ nationality: "nationality",
+ sport: "sport"
+ },
+ tip: {
+ format: {
+ name: true, // show name first
+ y: (d) => `${d}m`, // units in meters
+ x: (d) => `${d}kg`, // units in kilograms
+ stroke: false // suppress stroke channel
+ }
+ }
+}).plot()
+```
+
+Axes for ordinal scales now generalize the scale’s temporal or quantitative **interval** if any, resulting in more readable ticks. For instance, the bar chart below of monthly values now sports multi-line tick labels.
+
+
+
+```js
+Plot.plot({
+ x: {interval: "month"},
+ marks: [
+ Plot.barY(aapl, Plot.groupX({y: "median"}, {x: "Date", y: "Close"}))
+ ]
+})
+```
+
+Plot now recognizes CSS Color Module [Level 4](https://www.w3.org/TR/css-color-4/) and [Level 5](https://www.w3.org/TR/css-color-5/) syntax as literal colors, making it easier to use modern color syntax such as [oklab()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab), [color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix), and alternative color spaces such as [display-p3](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color).
+
+A channel value can now be given a label by specifying it as a {value, label} object; this may affect the label used in axes, legends, and tips.
+
+This release also includes numerous bug fixes:
+- exposed ordinal domains are now correctly deduplicated;
+- the default symbol set is now inferred correctly when **fill** is *currentColor*;
+- the **fontVariant** axis option now applies to the axis label in addition to ticks;
+- the tip mark is no longer briefly visible before asynchronous rendering;
+- the bin transform no longer generates undefined colors for empty bins;
+- the bin transform now uses the **interval** option to reduce *x1* & *x2* (and *y1* & *y2*);
+- the stack transform now correctly handles the *exclude* **facet** option;
+- the tree transform now correctly handles escaping with the **delimiter** option.
+
## 0.6.10
[Released August 14, 2023.](https://github.com/observablehq/plot/releases/tag/v0.6.10)
diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css
index 53cc17fd48..748b25a4b4 100644
--- a/docs/.vitepress/theme/custom.css
+++ b/docs/.vitepress/theme/custom.css
@@ -13,6 +13,8 @@
}
:root {
+ --vp-c-red: #f43f5e;
+ --vp-c-green: #10b981;
--vp-c-blue: #0092ff;
--vp-c-purple: #a463f2;
--vp-c-brand-1: var(--vp-c-purple-1);
diff --git a/docs/community.md b/docs/community.md
index 75d20a1aea..4923b1e9e1 100644
--- a/docs/community.md
+++ b/docs/community.md
@@ -10,8 +10,6 @@ Please star ⭐️ our [GitHub repo](https://github.com/observablehq/plot) to sh
Plot is getting better all the time; catch up on [recent releases](https://github.com/observablehq/plot/releases) by reading our [CHANGELOG](https://github.com/observablehq/plot/blob/main/CHANGELOG.md).
-For email updates, sign up for the [Observable Plot Twist](https://observablehq.com/@observablehq/plot-twist-newsletter-signup) newsletter. (See our [back issues](https://observablehq.com/collection/@observablehq/newsletters/2) and [blog](https://observablehq.com/blog), too.) This monthly newsletter will let you know about new features in Plot, inspiring work by the community, upcoming workshops and community events, and more.
-
And of course, follow us on [Observable](https://observablehq.com/@observablehq?tab=profile), [Mastodon](https://vis.social/@observablehq), [Twitter](https://twitter.com/observablehq), and [LinkedIn](https://www.linkedin.com/company/observable)!
## Getting help
diff --git a/docs/features/marks.md b/docs/features/marks.md
index 927fcbd3f8..99f805f0a6 100644
--- a/docs/features/marks.md
+++ b/docs/features/marks.md
@@ -490,7 +490,7 @@ All marks support the following style options:
If the **clip** option is *frame* (or equivalently true), the mark is clipped to the frame’s dimensions; if the **clip** option is null (or equivalently false), the mark is not clipped. If the **clip** option is *sphere*, then a [geographic projection](./projections.md) is required and the mark will be clipped to the projected sphere (_e.g._, the front hemisphere when using the orthographic projection).
-If the **tip** option is true, a [tip mark](../marks/tip.md) with the [pointer transform](../interactions/pointer.md) will be derived from this mark and placed atop all other marks, offering details on demand. If the **tip** option is set to *x*, *y*, or *xy*, [pointerX](../interactions/pointer.md#pointerX), [pointerY](../interactions/pointer.md#pointerY), or [pointer](../interactions/pointer.md#pointer) will be used, respectively; otherwise the pointing mode will be chosen automatically. (If the **tip** mark option is truthy, the **title** channel is no longer applied using an SVG title element as this would conflict with the tip mark.)
+If the **tip** option is true, a [tip mark](../marks/tip.md) with the [pointer transform](../interactions/pointer.md) will be derived from this mark and placed atop all other marks, offering details on demand. If the **tip** option is set to an options object, these options will be passed to the derived tip mark. If the **tip** option (or, if an object, its **pointer** option) is set to *x*, *y*, or *xy*, [pointerX](../interactions/pointer.md#pointerX), [pointerY](../interactions/pointer.md#pointerY), or [pointer](../interactions/pointer.md#pointer) will be used, respectively; otherwise the pointing mode will be chosen automatically. (If the **tip** mark option is truthy, the **title** channel is no longer applied using an SVG title element as this would conflict with the tip mark.)
For all marks except [text](../marks/text.md), the **dx** and **dy** options are rendered as a transform property, possibly including a 0.5px offset on low-density screens.
diff --git a/docs/features/scales.md b/docs/features/scales.md
index cb263345c4..52f3e2fc39 100644
--- a/docs/features/scales.md
+++ b/docs/features/scales.md
@@ -940,7 +940,7 @@ Plot implicitly generates an [axis mark](../marks/axis.md) for position scales i
* **tickPadding** - the separation between the tick and its label (in pixels; default 3)
* **tickFormat** - either a function or specifier string to format tick values; see [Formats](./formats.md)
* **tickRotate** - whether to rotate tick labels (an angle in degrees clockwise; default 0)
-* **fontVariant** - the font-variant attribute for ticks; defaults to *tabular-nums* if quantitative
+* **fontVariant** - the font-variant attribute; defaults to *tabular-nums* if quantitative
* **label** - a string to label the axis
* **labelAnchor** - the label anchor: *top*, *right*, *bottom*, *left*, or *center*
* **labelArrow** - the label arrow: *auto* (default), *up*, *right*, *down*, *left*, *none*, or true
diff --git a/docs/marks/tip.md b/docs/marks/tip.md
index 75aadc0cc1..69e950fb26 100644
--- a/docs/marks/tip.md
+++ b/docs/marks/tip.md
@@ -129,7 +129,37 @@ Plot.rectY(olympians, Plot.binX({y: "sum"}, {x: "weight", y: (d) => d.sex === "m
```
:::
-The tip mark does not provide options for formatting channel names or values. When a channel is bound to a scale, the scale’s label is shown instead of the channel name. If you desire greater customization, please upvote [#1612](https://github.com/observablehq/plot/issues/1612).
+The order and formatting of channels in the tip can be customized with the **format** option , which accepts a key-value object mapping channel names to formats. Each [format](../features/formats.md) can be a string (for number or time formats), a function that receives the value as input and returns a string, true to use the default format, and null or false to suppress. The order of channels in the tip follows their order in the format object followed by any additional channels.
+
+A channel’s label can be specified alongside its value as a {value, label} object; if a channel label is not specified, the associated scale’s label is used, if any; if there is no associated scale, or if the scale has no label, the channel name is used instead.
+
+:::plot defer https://observablehq.com/@observablehq/plot-tip-format
+```js
+Plot.dot(olympians, {
+ x: "weight",
+ y: "height",
+ stroke: "sex",
+ channels: {
+ name: "name",
+ nationality: {
+ value: "nationality",
+ label: "country"
+ },
+ sport: "sport"
+ },
+ tip: {
+ format: {
+ name: true,
+ sport: true,
+ nationality: true,
+ y: (d) => `${d}m`,
+ x: (d) => `${d}kg`,
+ stroke: false
+ }
+ }
+}).plot()
+```
+:::
The tip mark supports nine different orientations specified by the **anchor** option: the four sides (*top*, *right*, *bottom*, *left*), the four corners (*top-left*, *top-right*, *bottom-right*, *bottom-left*), and *middle*. Note that when *middle* is used, the tip will obscure its anchor point.
diff --git a/docs/transforms/tree.md b/docs/transforms/tree.md
index c836b30f65..3f0d1c9a23 100644
--- a/docs/transforms/tree.md
+++ b/docs/transforms/tree.md
@@ -60,7 +60,7 @@ Given a text file, you can use `text.split("\n")` to split the contents into mul
The following options control how the tabular data is organized into a hierarchy:
* **path** - a column specifying each node’s hierarchy location; defaults to identity
-* **delimiter** - the path separator; defaults to forward slash (/)
+* **delimiter** - the path separator, a single character; defaults to forward slash (/)
The **path** column is typically slash-separated, as with UNIX-based file systems or URLs.
diff --git a/img/temporal-ordinal.png b/img/temporal-ordinal.png
new file mode 100644
index 0000000000..b9b1cc009b
Binary files /dev/null and b/img/temporal-ordinal.png differ
diff --git a/img/tip-custom.png b/img/tip-custom.png
new file mode 100644
index 0000000000..a146632214
Binary files /dev/null and b/img/tip-custom.png differ
diff --git a/package.json b/package.json
index 839bc01bf3..efb33b6b36 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.10",
+ "version": "0.6.11",
"author": {
"name": "Observable, Inc.",
"url": "https://observablehq.com"
diff --git a/src/channel.d.ts b/src/channel.d.ts
index 51819817a6..a45a191a6f 100644
--- a/src/channel.d.ts
+++ b/src/channel.d.ts
@@ -56,7 +56,8 @@ export type ChannelName =
| "y"
| "y1"
| "y2"
- | "z";
+ | "z"
+ | (string & Record); // custom channel; see also https://github.com/microsoft/TypeScript/issues/29729
/**
* An object literal of channel definitions. This is also used to represent
diff --git a/src/transforms/bin.d.ts b/src/transforms/bin.d.ts
index 61edab725f..b630278af9 100644
--- a/src/transforms/bin.d.ts
+++ b/src/transforms/bin.d.ts
@@ -164,7 +164,7 @@ export type BinYInputs = Omit & {y?: ChannelValueBinSpec} & BinOption
export type BinInputs = Omit & {x?: ChannelValueBinSpec; y?: ChannelValueBinSpec} & BinOptions;
/** Output channels (and options) for the bin transform. */
-export type BinOutputs = ChannelReducers & GroupOutputOptions & BinOptions;
+export type BinOutputs = ChannelReducers | (GroupOutputOptions & BinOptions);
/**
* Bins on the **x** channel; then subdivides bins on the first channel of
diff --git a/src/transforms/group.d.ts b/src/transforms/group.d.ts
index 24b422aa40..2ecbf22310 100644
--- a/src/transforms/group.d.ts
+++ b/src/transforms/group.d.ts
@@ -39,7 +39,7 @@ export interface GroupOutputOptions {
}
/** Output channels (and options) for the group transform. */
-export type GroupOutputs = ChannelReducers & GroupOutputOptions;
+export type GroupOutputs = ChannelReducers | GroupOutputOptions;
/**
* Groups on the first channel of **z**, **fill**, or **stroke**, if any, and
diff --git a/yarn.lock b/yarn.lock
index 0684227352..7670376b3c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -419,17 +419,17 @@
isoformat "^0.2.0"
"@observablehq/runtime@^5.7.3":
- version "5.9.2"
- resolved "https://registry.yarnpkg.com/@observablehq/runtime/-/runtime-5.9.2.tgz#a786886da93dfaf55582e1586b607520d9703bcb"
- integrity sha512-cRnlbTrVG5Y5FESJ3K8DiH59Ic72025e7vWVkTClGKecjyXQnpalh9Nbt1krHT9TP+nj2SclgtkiJC6bR3fMHg==
+ version "5.9.3"
+ resolved "https://registry.yarnpkg.com/@observablehq/runtime/-/runtime-5.9.3.tgz#a4a766b8eee7d6d2ee2ed29bb3e73d777fc9bcaf"
+ integrity sha512-dRgqbClP4QiOSlInp6NaBXRPK7fJ2LtyPY/xppR8p9COTgwAPlP0/wHL8d1OD4f6AgwWXMqotiZqmduXsZgkSQ==
dependencies:
"@observablehq/inspector" "^5.0.0"
"@observablehq/stdlib" "^5.0.0"
"@observablehq/stdlib@^5.0.0":
- version "5.8.2"
- resolved "https://registry.yarnpkg.com/@observablehq/stdlib/-/stdlib-5.8.2.tgz#0e5f73064783ed9d23a20feecd4a42bdfb996a30"
- integrity sha512-68Xo97UJzJNOTDkZFSOqpGifMJ7KCXGXB0EljWIYzu9CzpaAjiakdJgMm2h7xHdPRh8HKoFIEPqeVg7YKjQh0Q==
+ version "5.8.3"
+ resolved "https://registry.yarnpkg.com/@observablehq/stdlib/-/stdlib-5.8.3.tgz#5ddd760c60aa9102c9b1323230cbe4c9da248d3d"
+ integrity sha512-XmuwqzAMZ8H0ICJfzd5wV3WD6nLlC2XwhMIdu2QDZppTSxGafATMSbgZ2JaiNPCejenkpERGmoC3W83FUGuNeg==
dependencies:
d3-array "^3.2.0"
d3-dsv "^3.0.1"
@@ -720,9 +720,9 @@
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
"@types/geojson@*":
- version "7946.0.10"
- resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249"
- integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==
+ version "7946.0.11"
+ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.11.tgz#012c17cb2256ad8de78560da851ab914a7b9b40e"
+ integrity sha512-L7A0AINMXQpVwxHJ4jxD6/XjZ4NDufaRlUJHjNIFKYUFBH1SvOW+neaqb0VTRSLW5suSrSu19ObFEFnfNcr+qg==
"@types/json-schema@^7.0.12":
version "7.0.13"
@@ -735,9 +735,9 @@
integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==
"@types/node@^20.5.0":
- version "20.6.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.2.tgz#a065925409f59657022e9063275cd0b9bd7e1b12"
- integrity sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==
+ version "20.6.3"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.3.tgz#5b763b321cd3b80f6b8dde7a37e1a77ff9358dd9"
+ integrity sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==
"@types/resolve@1.20.2":
version "1.20.2"
@@ -1971,9 +1971,9 @@ glob@^8.0.3, glob@^8.1.0:
once "^1.3.0"
globals@^13.19.0:
- version "13.21.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571"
- integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==
+ version "13.22.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.22.0.tgz#0c9fcb9c48a2494fbb5edbfee644285543eba9d8"
+ integrity sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==
dependencies:
type-fest "^0.20.2"
@@ -2856,9 +2856,9 @@ slash@^3.0.0:
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
smob@^1.0.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/smob/-/smob-1.4.0.tgz#ac9751fe54b1fc1fc8286a628d4e7f824273b95a"
- integrity sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/smob/-/smob-1.4.1.tgz#66270e7df6a7527664816c5b577a23f17ba6f5b5"
+ integrity sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==
source-map-js@^1.0.2:
version "1.0.2"
@@ -2948,9 +2948,9 @@ tar@^6.1.11:
yallist "^4.0.0"
terser@^5.17.4:
- version "5.19.4"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.4.tgz#941426fa482bf9b40a0308ab2b3cd0cf7c775ebd"
- integrity sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.20.0.tgz#ea42aea62578703e33def47d5c5b93c49772423e"
+ integrity sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2"
@@ -3065,9 +3065,9 @@ vite@^4.4.9:
fsevents "~2.3.2"
vitepress@^1.0.0-rc.12:
- version "1.0.0-rc.14"
- resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.0.0-rc.14.tgz#d014df7890ca26d3ef73a5c05b2a73cc659e961c"
- integrity sha512-yChIeXOAcNvVnSVjhziH1vte0uhKb00PuZf7KdIMfx3ixTMAz73Nn+6gREvCv0SdH+anteGUKz5eljv0ygcgGQ==
+ version "1.0.0-rc.15"
+ resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.0.0-rc.15.tgz#a0810e91ed2bb6000ea78c084a7263d7519840de"
+ integrity sha512-5criiHoEibkT/du7t6wQ2xQVsuTNuirQZbMAi0M9Hp0YzJoJvEX68Ej9p2PtNC84bYb/CxAh5QkMtMutk03lHw==
dependencies:
"@docsearch/css" "^3.5.2"
"@docsearch/js" "^3.5.2"