From da90c341dc7686046953f7999fe643efaa88b21f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?=
Date: Tue, 6 Aug 2024 17:19:45 -0400
Subject: [PATCH 01/35] link new chart examples to forkable notebooks (#2123)
---
docs/marks/rect.md | 6 +++---
docs/marks/waffle.md | 10 +++++-----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/docs/marks/rect.md b/docs/marks/rect.md
index 676e8a781c..adabe46f7d 100644
--- a/docs/marks/rect.md
+++ b/docs/marks/rect.md
@@ -196,7 +196,7 @@ To round corners, use the **r** option. If the combined corner radii exceed the
-:::plot hidden defer
+:::plot hidden defer https://observablehq.com/@observablehq/plot-rounded-rects
```js
Plot.plot({
marks: [
@@ -218,7 +218,7 @@ Plot.plot({
To round corners on a specific side, use the **rx1**, **ry1**, **rx2**, or **ry2** options. When stacking rounded rects vertically, use a positive **ry2** and a corresponding negative **ry1**; likewise for stacking rounded rects horizontally, use a positive **rx2** and a negative **rx1**. Use the **clip** option to hide the “wings” below zero.
-:::plot defer
+:::plot defer https://observablehq.com/@observablehq/plot-rounded-rects
```js
Plot.plot({
color: {legend: true},
@@ -232,7 +232,7 @@ Plot.plot({
You can even round specific corners using the **rx1y1**, **rx2y1**, **rx2y2**, and **rx1y2** options.
-:::plot defer
+:::plot defer https://observablehq.com/@observablehq/plot-rounded-rects
```js
Plot.plot({
color: {legend: true},
diff --git a/docs/marks/waffle.md b/docs/marks/waffle.md
index 84be6e66a0..ddb9591d62 100644
--- a/docs/marks/waffle.md
+++ b/docs/marks/waffle.md
@@ -29,7 +29,7 @@ onMounted(() => {
The **waffle mark** is similar to the [bar mark](./bar.md) in that it displays a quantity (or quantitative extent) for a given category; but unlike a bar, a waffle is subdivided into square cells that allow easier counting. Waffles are useful for reading exact quantities. How quickly can you count the pears 🍐 below? How many more apples 🍎 are there than bananas 🍌?
-:::plot
+:::plot https://observablehq.com/@observablehq/plot-simple-waffle
```js
Plot.waffleY([212, 207, 315, 11], {x: ["apples", "bananas", "oranges", "pears"]}).plot({height: 420})
```
@@ -37,7 +37,7 @@ Plot.waffleY([212, 207, 315, 11], {x: ["apples", "bananas", "oranges", "pears"]}
The waffle mark is often used with the [group transform](../transforms/group.md) to compute counts. The chart below compares the number of female and male athletes in the 2012 Olympics.
-:::plot
+:::plot https://observablehq.com/@observablehq/plot-waffle-group
```js
Plot.waffleY(olympians, Plot.groupX({y: "count"}, {x: "sex"})).plot({x: {label: null}})
```
@@ -62,7 +62,7 @@ The **unit** option determines the quantity each waffle cell represents; it defa
-:::plot
+:::plot https://observablehq.com/@observablehq/plot-waffle-unit
```js
Plot.waffleY(olympians, Plot.groupZ({y: "count"}, {fx: "date_of_birth", unit})).plot({fx: {interval: "5 years", label: null}})
```
@@ -76,7 +76,7 @@ While waffles typically represent integer quantities, say to count people or day
Like bars, waffles can be [stacked](../transforms/stack.md), and implicitly apply the stack transform when only a single quantitative channel is supplied.
-:::plot
+:::plot https://observablehq.com/@observablehq/plot-stacked-waffles
```js
Plot.waffleY(olympians, Plot.groupZ({y: "count"}, {fill: "sex", sort: "sex", fx: "weight", unit: 10})).plot({fx: {interval: 10}, color: {legend: true}})
```
@@ -84,7 +84,7 @@ Plot.waffleY(olympians, Plot.groupZ({y: "count"}, {fill: "sex", sort: "sex", fx:
Waffles can also be used to highlight a proportion of the whole. The chart below recreates a graphic of survey responses from [“Teens in Syria”](https://www.economist.com/graphic-detail/2015/08/19/teens-in-syria) by _The Economist_ (August 19, 2015); positive responses are in orange, while negative responses are in gray. The **rx** option is used to produce circles instead of squares.
-:::plot
+:::plot https://observablehq.com/@observablehq/plot-survey-waffle
```js
Plot.plot({
axis: null,
From 1d01e254282a9cc3b8352f0b362ae3e2d35ad6f0 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Wed, 7 Aug 2024 08:58:34 -0400
Subject: [PATCH 02/35] lax className (#2126)
---
src/mark.js | 6 +++---
test/output/classNameOnMarks.svg | 2 +-
test/plots/class-name.ts | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/mark.js b/src/mark.js
index 21f86d805c..080e9a3f03 100644
--- a/src/mark.js
+++ b/src/mark.js
@@ -2,9 +2,9 @@ import {channelDomain, createChannels, valueObject} from "./channel.js";
import {defined} from "./defined.js";
import {maybeFacetAnchor} from "./facet.js";
import {maybeClip, maybeNamed, maybeValue} from "./options.js";
-import {dataify, isDomainSort, isObject, isOptions, keyword, range, singleton} from "./options.js";
+import {dataify, isDomainSort, isObject, isOptions, keyword, range, singleton, string} from "./options.js";
import {project} from "./projection.js";
-import {maybeClassName, styles} from "./style.js";
+import {styles} from "./style.js";
import {basic, initializer} from "./transforms/basic.js";
export class Mark {
@@ -72,7 +72,7 @@ export class Mark {
this.marginLeft = +marginLeft;
this.clip = maybeClip(clip);
this.tip = maybeTip(tip);
- this.className = className ? maybeClassName(className) : null;
+ this.className = string(className);
// Super-faceting currently disallow position channels; in the future, we
// could allow position to be specified in fx and fy in addition to (or
// instead of) x and y.
diff --git a/test/output/classNameOnMarks.svg b/test/output/classNameOnMarks.svg
index d06c154f19..3225c3a636 100644
--- a/test/output/classNameOnMarks.svg
+++ b/test/output/classNameOnMarks.svg
@@ -54,7 +54,7 @@
units →
-
+
diff --git a/test/plots/class-name.ts b/test/plots/class-name.ts
index df37884502..f7dffc85c4 100644
--- a/test/plots/class-name.ts
+++ b/test/plots/class-name.ts
@@ -12,7 +12,7 @@ export async function classNameOnMarks() {
marks: [
Plot.barX(
sales,
- Plot.groupY({x: "sum"}, {x: "units", y: "fruit", sort: {y: "x", reverse: true}, className: "fruitbars"})
+ Plot.groupY({x: "sum"}, {x: "units", y: "fruit", sort: {y: "x", reverse: true}, className: "fruit units"})
),
Plot.ruleX([0])
]
From e50254d0340181cc1b8cca749da96a1e10abfc94 Mon Sep 17 00:00:00 2001
From: CobusT
Date: Tue, 20 Aug 2024 08:17:10 -0700
Subject: [PATCH 03/35] Documentation font changes (#2139)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* branding changes
* removed 'Enterprise' link from footer
* css tweaks
* Fixed color of button text on main page.
* Use Vitepress headers for faster font loading
See https://vitepress.dev/reference/site-config#example-adding-google-fonts
Note that we're using the (slightly faster?) "preload as stylesheet" strategy —as we do in Framework— over "preconnect" that Vitepress suggests.
* normalize hex color to lowercase
* scope a.button to .promo, to remove an !important
(a.button is not used anywhere else; introduced in #1757)
* base
* fixed h1 heading font and removed h1 margin
* revert the change to vp-font-family-base
* rename base classes
* merged 2 root sections of css
---------
Co-authored-by: Philippe Rivière
---
docs/.vitepress/config.ts | 3 ++
docs/.vitepress/theme/CustomFooter.vue | 1 -
docs/.vitepress/theme/ObservablePromo.vue | 2 +-
docs/.vitepress/theme/custom.css | 35 ++++++++++++++++-------
4 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 2271044fca..7a8b9e91f8 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -25,6 +25,9 @@ export default defineConfig({
}
},
head: [
+ ["link", {rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: ""}],
+ ["link", {rel: "preload", as: "style", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Spline+Sans+Mono:ital,wght@0,300..700;1,300..700&display=swap"}],
+ ["link", {rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Spline+Sans+Mono:ital,wght@0,300..700;1,300..700&display=swap"}],
["link", {rel: "apple-touch-icon", href: "https://static.observablehq.com/favicon-512.0667824687f99c942a02e06e2db1a060911da0bf3606671676a255b1cf97b4fe.png"}],
["link", {rel: "icon", type: "image/png", href: "https://static.observablehq.com/favicon-512.0667824687f99c942a02e06e2db1a060911da0bf3606671676a255b1cf97b4fe.png", sizes: "512x512"}],
["script", {async: "", src: "https://www.googletagmanager.com/gtag/js?id=G-9B88TP6PKQ"}],
diff --git a/docs/.vitepress/theme/CustomFooter.vue b/docs/.vitepress/theme/CustomFooter.vue
index 6bda2ef804..e026f21983 100644
--- a/docs/.vitepress/theme/CustomFooter.vue
+++ b/docs/.vitepress/theme/CustomFooter.vue
@@ -25,7 +25,6 @@
diff --git a/docs/features/interactions.md b/docs/features/interactions.md
index bf1dae5741..4cd5a27b3a 100644
--- a/docs/features/interactions.md
+++ b/docs/features/interactions.md
@@ -70,6 +70,6 @@ With the exception of render transforms (see the [pointer transform](https://git
That said, you can simply throw away an old plot and replace it with a new one! This allows plotting of dynamic data: data which can change in real-time as it streams in, or because it is derived in response to external inputs such as range sliders and search boxes.
-On Observable, you can use [viewof](https://observablehq.com/@observablehq/views) in conjunction with [Observable Inputs](https://observablehq.com/@observablehq/inputs) (or other plots!) for interactivity. If your cell references another cell, it will automatically re-run whenever the upstream cell’s value changes. For example, try dragging the slider in this [hexbin example](https://observablehq.com/@observablehq/plot-hexbin-binwidth?intent=fork). In React, use [useEffect](https://react.dev/reference/react/useEffect) and [useRef](https://react.dev/reference/react/useRef) to re-render the plot when data changes. In Vue, use [ref](https://vuejs.org/api/reactivity-core.html#ref). For more, see our [getting started guide](../getting-started.md).
+On Observable, you can use [viewof](https://observablehq.com/@observablehq/views) in conjunction with [Observable Inputs](https://observablehq.com/@observablehq/inputs) (or other plots!) for interactivity. If your cell references another cell, it will automatically re-run whenever the upstream cell’s value changes. For example, try dragging the slider in this [hexbin example](https://observablehq.com/@observablehq/plot-hexbin-binwidth). In React, use [useEffect](https://react.dev/reference/react/useEffect) and [useRef](https://react.dev/reference/react/useRef) to re-render the plot when data changes. In Vue, use [ref](https://vuejs.org/api/reactivity-core.html#ref). For more, see our [getting started guide](../getting-started.md).
You can also manipulate the SVG that Plot creates, if you are comfortable using lower-level APIs; see examples by [Mike Freeman](https://observablehq.com/@mkfreeman/plot-animation) and [Philippe Rivière](https://observablehq.com/@fil/plot-animate-a-bar-chart).
diff --git a/docs/features/markers.md b/docs/features/markers.md
index 82f306c8c6..66ced34e7b 100644
--- a/docs/features/markers.md
+++ b/docs/features/markers.md
@@ -30,7 +30,7 @@ A **marker** defines a graphic drawn on vertices of a [line](../marks/line.md) o
-:::plot https://observablehq.com/@observablehq/plot-line-chart-with-markers?intent=fork
+:::plot https://observablehq.com/@observablehq/plot-line-chart-with-markers
```js-vue
Plot.plot({
marks: [
From 12ecfa7dc266fb5538ca5902defd3401e4bcbe85 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Fri, 27 Sep 2024 10:13:13 -0700
Subject: [PATCH 11/35] =?UTF-8?q?don=E2=80=99t=20stack=20with=20indexOf=20?=
=?UTF-8?q?(#2179)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* test areaY shorthand with null & NaN
* don’t stack with indexOf
---
src/marks/area.js | 4 +-
src/transforms/identity.js | 8 ++--
test/output/shorthandAreaYNaN.svg | 75 ++++++++++++++++++++++++++++++
test/output/shorthandAreaYNull.svg | 75 ++++++++++++++++++++++++++++++
test/plots/shorthand-areaY.ts | 10 ++++
5 files changed, 166 insertions(+), 6 deletions(-)
create mode 100644 test/output/shorthandAreaYNaN.svg
create mode 100644 test/output/shorthandAreaYNull.svg
diff --git a/src/marks/area.js b/src/marks/area.js
index bd8393926c..628088445a 100644
--- a/src/marks/area.js
+++ b/src/marks/area.js
@@ -78,10 +78,10 @@ export function area(data, options) {
export function areaX(data, options) {
const {y = indexOf, ...rest} = maybeDenseIntervalY(options);
- return new Area(data, maybeStackX(maybeIdentityX({...rest, y1: y, y2: undefined})));
+ return new Area(data, maybeStackX(maybeIdentityX({...rest, y1: y, y2: undefined}, y === indexOf ? "x2" : "x")));
}
export function areaY(data, options) {
const {x = indexOf, ...rest} = maybeDenseIntervalX(options);
- return new Area(data, maybeStackY(maybeIdentityY({...rest, x1: x, x2: undefined})));
+ return new Area(data, maybeStackY(maybeIdentityY({...rest, x1: x, x2: undefined}, x === indexOf ? "y2" : "y")));
}
diff --git a/src/transforms/identity.js b/src/transforms/identity.js
index 909511cc68..3b2ddf54cc 100644
--- a/src/transforms/identity.js
+++ b/src/transforms/identity.js
@@ -1,9 +1,9 @@
import {hasX, hasY, identity} from "../options.js";
-export function maybeIdentityX(options = {}) {
- return hasX(options) ? options : {...options, x: identity};
+export function maybeIdentityX(options = {}, k = "x") {
+ return hasX(options) ? options : {...options, [k]: identity};
}
-export function maybeIdentityY(options = {}) {
- return hasY(options) ? options : {...options, y: identity};
+export function maybeIdentityY(options = {}, k = "y") {
+ return hasY(options) ? options : {...options, [k]: identity};
}
diff --git a/test/output/shorthandAreaYNaN.svg b/test/output/shorthandAreaYNaN.svg
new file mode 100644
index 0000000000..3605ae55cd
--- /dev/null
+++ b/test/output/shorthandAreaYNaN.svg
@@ -0,0 +1,75 @@
+
\ No newline at end of file
diff --git a/test/output/shorthandAreaYNull.svg b/test/output/shorthandAreaYNull.svg
new file mode 100644
index 0000000000..3605ae55cd
--- /dev/null
+++ b/test/output/shorthandAreaYNull.svg
@@ -0,0 +1,75 @@
+
\ No newline at end of file
diff --git a/test/plots/shorthand-areaY.ts b/test/plots/shorthand-areaY.ts
index 205425e766..9693c0bb87 100644
--- a/test/plots/shorthand-areaY.ts
+++ b/test/plots/shorthand-areaY.ts
@@ -8,3 +8,13 @@ export async function shorthandAreaY() {
];
return Plot.areaY(numbers).plot();
}
+
+export async function shorthandAreaYNaN() {
+ const numbers = [57.5, 37.6, 48.5, 42.4, NaN, NaN, NaN, NaN, 66.5, 67.7, 49.2, 58.7, 41.4, 54.4, 41.7, 49.8, 60.2, 44.5, 47.4, 33.5]; // prettier-ignore
+ return Plot.areaY(numbers).plot();
+}
+
+export async function shorthandAreaYNull() {
+ const numbers = [57.5, 37.6, 48.5, 42.4, null, null, null, null, 66.5, 67.7, 49.2, 58.7, 41.4, 54.4, 41.7, 49.8, 60.2, 44.5, 47.4, 33.5]; // prettier-ignore
+ return Plot.areaY(numbers).plot();
+}
From 572fa9e24cd5f7f1468212f7c778da89f139a268 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?=
Date: Wed, 2 Oct 2024 17:48:37 +0200
Subject: [PATCH 12/35] fix error when building the documentation site (#2185)
---
docs/components/PlotRender.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/components/PlotRender.js b/docs/components/PlotRender.js
index 52cfa420f2..bb058759e1 100644
--- a/docs/components/PlotRender.js
+++ b/docs/components/PlotRender.js
@@ -1,9 +1,12 @@
import * as Plot from "@observablehq/plot";
import {h, withDirectives} from "vue";
+class Event {}
+
class Document {
constructor() {
this.documentElement = new Element(this, "html");
+ this.defaultView = {Event};
}
createElementNS(namespace, tagName) {
return new Element(this, tagName);
From 027dd09e5bf054511d71ed35d6027df7faf4ddba Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Wed, 2 Oct 2024 20:14:49 -0700
Subject: [PATCH 13/35] Update README.md
---
README.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/README.md b/README.md
index c9e7e78577..01f3985be1 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,13 @@
[**Observable Plot**](https://observablehq.com/plot/) is a free, [open-source](./LICENSE), JavaScript library for visualizing tabular data, focused on accelerating exploratory data analysis. It has a concise, memorable, yet expressive API, featuring [scales](https://observablehq.com/plot/features/scales) and [layered marks](https://observablehq.com/plot/features/marks) in the *grammar of graphics* style.
+
+
+
+
+
+Daily downloads of Observable Plot · [oss-analytics](https://github.com/observablehq/oss-analytics/)
+
## Documentation 📚
https://observablehq.com/plot/
From 9122f18afae894d3cbedcd8a73d1b3171a6f2bdb Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Fri, 4 Oct 2024 09:18:16 -0700
Subject: [PATCH 14/35] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 01f3985be1..0ae76c204b 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-Daily downloads of Observable Plot · [oss-analytics](https://github.com/observablehq/oss-analytics/)
+Daily downloads of Observable Plot · [oss-analytics](https://observablehq.observablehq.cloud/oss-analytics/)
## Documentation 📚
From 93f010b53433887da95f128847cdfd2fea0ffb48 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Sat, 5 Oct 2024 18:50:02 -0700
Subject: [PATCH 15/35] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0ae76c204b..e15bc17f26 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Observable Plot
-[](https://observablehq.com/plot/)
+[](https://observablehq.com/plot/)
[**Observable Plot**](https://observablehq.com/plot/) is a free, [open-source](./LICENSE), JavaScript library for visualizing tabular data, focused on accelerating exploratory data analysis. It has a concise, memorable, yet expressive API, featuring [scales](https://observablehq.com/plot/features/scales) and [layered marks](https://observablehq.com/plot/features/marks) in the *grammar of graphics* style.
From 6a0aa7e56c591d59ffbb4e260b5dead40338b8fd Mon Sep 17 00:00:00 2001
From: CobusT
Date: Thu, 10 Oct 2024 21:08:29 -0700
Subject: [PATCH 16/35] Add the Made by Observable button to the top nav
(#2191)
* added made by logo and github links
* colors for Github arrows
* cleaned up GitHub links
* search button rounded corners
* renamed component
* made clickable popover for touch devices
* content close to done
* button rendering in javascript
* after ui review
* tweaks
* using external component
* using external component
* cleaned up
* cleaned up changes to package.json and yarn.lock
* renamed
* add custom element to config
* remote made-by-observable
* changed component source to static site
* fixed component name
* prettier
* prettier
* prettier
* remove unnecessary style
* updated comment
---------
Co-authored-by: Mike Bostock
---
docs/.vitepress/config.ts | 30 ++++-----
docs/.vitepress/theme/CustomLayout.vue | 22 ++++++
docs/.vitepress/theme/VersionAndStars.vue | 82 +++++++++++++++++++++++
docs/.vitepress/theme/stargazers.data.ts | 28 ++++++++
4 files changed, 146 insertions(+), 16 deletions(-)
create mode 100644 docs/.vitepress/theme/VersionAndStars.vue
create mode 100644 docs/.vitepress/theme/stargazers.data.ts
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 7a8b9e91f8..1edd74a8cf 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -6,7 +6,7 @@ import plot from "./markdown-it-plot.js";
// https://vitepress.dev/reference/site-config
// prettier-ignore
export default defineConfig({
- title: "Observable Plot",
+ title: "Plot",
description: "The JavaScript library for exploratory data visualization",
appearance: "force-auto",
base: "/plot/",
@@ -17,6 +17,16 @@ export default defineConfig({
{find: "@observablehq/plot", replacement: path.resolve("./src/index.js")},
{find: /^.*\/VPFooter\.vue$/, replacement: fileURLToPath(new URL("./theme/CustomFooter.vue", import.meta.url))}
]
+ },
+ define: {
+ __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
+ }
+ },
+ vue: {
+ template: {
+ compilerOptions: {
+ isCustomElement: (tag) => tag.startsWith("observable-")
+ }
}
},
markdown: {
@@ -31,10 +41,11 @@ export default defineConfig({
["link", {rel: "apple-touch-icon", href: "https://static.observablehq.com/favicon-512.0667824687f99c942a02e06e2db1a060911da0bf3606671676a255b1cf97b4fe.png"}],
["link", {rel: "icon", type: "image/png", href: "https://static.observablehq.com/favicon-512.0667824687f99c942a02e06e2db1a060911da0bf3606671676a255b1cf97b4fe.png", sizes: "512x512"}],
["script", {async: "", src: "https://www.googletagmanager.com/gtag/js?id=G-9B88TP6PKQ"}],
- ["script", {}, "window.dataLayer=window.dataLayer||[];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js',new Date());\ngtag('config','G-9B88TP6PKQ');"]
+ ["script", {}, "window.dataLayer=window.dataLayer||[];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js',new Date());\ngtag('config','G-9B88TP6PKQ');"],
+ ["script", {async: "", defer: "", src: "https://static.observablehq.com/assets/components/observable-made-by.js"}],
],
sitemap: {
- hostname: 'https://observablehq.com/plot/'
+ hostname: "https://observablehq.com/plot/"
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
@@ -43,12 +54,6 @@ export default defineConfig({
light: "/observable-light.svg",
dark: "/observable-dark.svg"
},
- nav: [
- {text: "Home", link: "/"},
- {text: "Examples", link: "https://observablehq.com/@observablehq/plot-gallery"},
- {text: "Community", link: "/community"},
- {text: "D3", link: "https://d3js.org"}
- ],
sidebar: [
{
text: "Introduction",
@@ -148,13 +153,6 @@ export default defineConfig({
search: {
provider: "local"
},
- socialLinks: [
- {icon: "github", link: "https://github.com/observablehq/plot"},
- {icon: "x", link: "https://twitter.com/observablehq"},
- {icon: "slack", link: "https://observablehq.com/slack/join"},
- {icon: "linkedin", link: "https://www.linkedin.com/company/observable"},
- {icon: "youtube", link: "https://www.youtube.com/c/Observablehq"}
- ],
footer: {
message: "Library released under ISC License.",
copyright: `Copyright 2020–${new Date().getUTCFullYear()} Observable, Inc.`
diff --git a/docs/.vitepress/theme/CustomLayout.vue b/docs/.vitepress/theme/CustomLayout.vue
index 17511b6035..8cbd0f98d8 100644
--- a/docs/.vitepress/theme/CustomLayout.vue
+++ b/docs/.vitepress/theme/CustomLayout.vue
@@ -3,6 +3,7 @@
import DefaultTheme from "vitepress/theme-without-fonts";
import ExamplesGrid from "./ExamplesGrid.vue";
import ObservablePromo from "./ObservablePromo.vue";
+import VersionAndStars from "./VersionAndStars.vue";
const {Layout} = DefaultTheme;
@@ -16,6 +17,12 @@ const {Layout} = DefaultTheme;
+
+
+
+
+
+
@@ -35,4 +42,19 @@ const {Layout} = DefaultTheme;
background-color: rgba(37, 37, 41, 0.5);
}
+/* Remove unnecessary elements that are empty in our implementation */
+.VPNavBarExtra,
+.VPNavBarHamburger {
+ display: none !important;
+}
+
+/* rounded corners for search field */
+@media (min-width: 768px) {
+ .DocSearch-Button {
+ border-radius: 1000px;
+ padding-right: 0.75rem;
+ height: 2rem;
+ }
+}
+
diff --git a/docs/.vitepress/theme/VersionAndStars.vue b/docs/.vitepress/theme/VersionAndStars.vue
new file mode 100644
index 0000000000..0522b556cc
--- /dev/null
+++ b/docs/.vitepress/theme/VersionAndStars.vue
@@ -0,0 +1,82 @@
+
+
+
+
+ {{ version }}
+
+
+ GitHub️ {{ formattedStarCount }}
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/stargazers.data.ts b/docs/.vitepress/theme/stargazers.data.ts
new file mode 100644
index 0000000000..fabf38df8a
--- /dev/null
+++ b/docs/.vitepress/theme/stargazers.data.ts
@@ -0,0 +1,28 @@
+const REPO = "observablehq/plot";
+
+export default {
+ async load() {
+ let stargazers_count;
+ try {
+ ({stargazers_count} = await github(`/repos/${REPO}`));
+ } catch (error) {
+ if (process.env.CI) throw error;
+ stargazers_count = NaN;
+ }
+ return stargazers_count;
+ }
+};
+
+async function github(
+ path,
+ {
+ authorization = process.env.GITHUB_TOKEN && `token ${process.env.GITHUB_TOKEN}`,
+ accept = "application/vnd.github.v3+json"
+ } = {}
+) {
+ const url = new URL(path, "https://api.github.com");
+ const headers = {...(authorization && {authorization}), accept};
+ const response = await fetch(url, {headers});
+ if (!response.ok) throw new Error(`fetch error: ${response.status} ${url}`);
+ return await response.json();
+}
From d70783541626f1cc9fc45eabc8b0b1fc4e47145a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?=
Date: Fri, 11 Oct 2024 12:53:28 +0200
Subject: [PATCH 17/35] fix handling of null values in arrow vectors (#2195)
* closes #2194
---
src/options.js | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/options.js b/src/options.js
index ac9caca472..144fd3b37b 100644
--- a/src/options.js
+++ b/src/options.js
@@ -68,8 +68,12 @@ function maybeTypedArrowify(vector, type) {
return vector == null
? vector
: (type === undefined || type === Array) && isArrowDateType(vector.type)
- ? coerceDates(vector.toArray())
- : maybeTypedArrayify(vector.toArray(), type);
+ ? coerceDates(vectorToArray(vector))
+ : maybeTypedArrayify(vectorToArray(vector), type);
+}
+
+function vectorToArray(vector) {
+ return vector.nullCount ? vector.toJSON() : vector.toArray();
}
export const singleton = [null]; // for data-less decoration marks, e.g. frame
From 07cab1eef18861f807f2ae19cb2ac78e53bf1659 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Fri, 18 Oct 2024 08:39:45 -0700
Subject: [PATCH 18/35] Update README.md
---
README.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index e15bc17f26..c23c18d037 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,12 @@
[**Observable Plot**](https://observablehq.com/plot/) is a free, [open-source](./LICENSE), JavaScript library for visualizing tabular data, focused on accelerating exploratory data analysis. It has a concise, memorable, yet expressive API, featuring [scales](https://observablehq.com/plot/features/scales) and [layered marks](https://observablehq.com/plot/features/marks) in the *grammar of graphics* style.
-
-
-
-
+
+
+
+
+
+Daily downloads of Observable Plot · [oss-analytics](https://observablehq.observablehq.cloud/oss-analytics/)
From 27e8fff5fb4adaf671eb3db87b2ae542801ad220 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?=
Date: Sat, 19 Oct 2024 19:14:21 +0200
Subject: [PATCH 19/35] waffle href (#2203)
* waffle href
* fix syntax
* ariaLabel, href; fix test
* test title, too
---------
Co-authored-by: Mike Bostock
---
src/marks/waffle.js | 4 +-
test/output/waffleHref.svg | 423 +++++++++++++++++++++++++++++++++++++
test/plots/waffle.ts | 18 ++
3 files changed, 444 insertions(+), 1 deletion(-)
create mode 100644 test/output/waffleHref.svg
diff --git a/src/marks/waffle.js b/src/marks/waffle.js
index c9d8771d21..883a4a1b14 100644
--- a/src/marks/waffle.js
+++ b/src/marks/waffle.js
@@ -35,6 +35,7 @@ export class WaffleY extends BarY {
function waffleRender(y) {
return function (index, scales, values, dimensions, context) {
+ const {ariaLabel, href, title, ...visualValues} = values;
const {unit, gap, rx, ry, round} = this;
const {document} = context;
const Y1 = values.channels[`${y}1`].value;
@@ -86,7 +87,7 @@ function waffleRender(y) {
.attr("id", (i) => `${patternId}-${i}`)
.select("rect")
.call(applyDirectStyles, this)
- .call(applyChannelStyles, this, values)
+ .call(applyChannelStyles, this, visualValues)
)
.call((g) =>
g
@@ -104,6 +105,7 @@ function waffleRender(y) {
)
.attr("fill", (i) => `url(#${patternId}-${i})`)
.attr("stroke", this.stroke == null ? null : (i) => `url(#${patternId}-${i})`)
+ .call(applyChannelStyles, this, {ariaLabel, href, title})
)
.node();
};
diff --git a/test/output/waffleHref.svg b/test/output/waffleHref.svg
new file mode 100644
index 0000000000..b8b5e3ad46
--- /dev/null
+++ b/test/output/waffleHref.svg
@@ -0,0 +1,423 @@
+
\ No newline at end of file
diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts
index 455efd3a4a..fdfa98a025 100644
--- a/test/plots/waffle.ts
+++ b/test/plots/waffle.ts
@@ -246,3 +246,21 @@ export async function waffleYGrouped() {
marks: [Plot.waffleY(athletes, Plot.groupX({y: "count"}, {x: "sport", unit: 10})), Plot.ruleY([0])]
});
}
+
+export function waffleHref() {
+ return Plot.plot({
+ inset: 10,
+ marks: [
+ Plot.waffleY(
+ {length: 77},
+ {
+ y: 1,
+ fill: (d, i) => i % 7,
+ href: (d, i) => `/?${i}`,
+ title: (d, i) => `waffle ${i}`,
+ target: "_blank"
+ }
+ )
+ ]
+ });
+}
From 700c6eef9c179fa5bef6bf2a4d5b6a74591d8951 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Mon, 21 Oct 2024 00:21:07 -0700
Subject: [PATCH 20/35] trim waffle slivers (#2205)
---
src/marks/waffle.js | 44 +++----
test/output/waffleHref.svg | 154 ++++++++++++------------
test/output/waffleMultiple.svg | 12 +-
test/output/waffleRound.svg | 4 +-
test/output/waffleShapes.svg | 202 ++++++++++++++++++++++++++++++++
test/output/waffleShorthand.svg | 4 +-
test/output/waffleStroke.svg | 4 +-
test/plots/waffle.ts | 38 ++++++
8 files changed, 351 insertions(+), 111 deletions(-)
create mode 100644 test/output/waffleShapes.svg
diff --git a/src/marks/waffle.js b/src/marks/waffle.js
index 883a4a1b14..4a95d38ec6 100644
--- a/src/marks/waffle.js
+++ b/src/marks/waffle.js
@@ -156,28 +156,28 @@ function wafflePoints(i1, i2, columns) {
if (i2 < i1) {
return wafflePoints(i2, i1, columns);
}
- return [
- [0, Math.ceil(i1 / columns)],
- [Math.floor(i1 % columns), Math.ceil(i1 / columns)],
- [Math.floor(i1 % columns), Math.floor(i1 / columns) + (i1 % 1)],
- [Math.ceil(i1 % columns), Math.floor(i1 / columns) + (i1 % 1)],
- ...(i1 % columns > columns - 1
- ? []
- : [
- [Math.ceil(i1 % columns), Math.floor(i1 / columns)],
- [columns, Math.floor(i1 / columns)]
- ]),
- [columns, Math.floor(i2 / columns)],
- [Math.ceil(i2 % columns), Math.floor(i2 / columns)],
- [Math.ceil(i2 % columns), Math.floor(i2 / columns) + (i2 % 1)],
- [Math.floor(i2 % columns), Math.floor(i2 / columns) + (i2 % 1)],
- ...(i2 % columns < 1
- ? []
- : [
- [Math.floor(i2 % columns), Math.ceil(i2 / columns)],
- [0, Math.ceil(i2 / columns)]
- ])
- ];
+ const x1f = Math.floor(i1 % columns);
+ const x1c = Math.ceil(i1 % columns);
+ const x2f = Math.floor(i2 % columns);
+ const x2c = Math.ceil(i2 % columns);
+ const y1f = Math.floor(i1 / columns);
+ const y1c = Math.ceil(i1 / columns);
+ const y2f = Math.floor(i2 / columns);
+ const y2c = Math.ceil(i2 / columns);
+ const points = [];
+ if (y2c > y1c) points.push([0, y1c]);
+ points.push([x1f, y1c], [x1f, y1f + (i1 % 1)], [x1c, y1f + (i1 % 1)]);
+ if (!(i1 % columns > columns - 1)) {
+ points.push([x1c, y1f]);
+ if (y2f > y1f) points.push([columns, y1f]);
+ }
+ if (y2f > y1f) points.push([columns, y2f]);
+ points.push([x2c, y2f], [x2c, y2f + (i2 % 1)], [x2f, y2f + (i2 % 1)]);
+ if (!(i2 % columns < 1)) {
+ points.push([x2f, y2c]);
+ if (y2c > y1c) points.push([0, y2c]);
+ }
+ return points;
}
function maybeRound(round) {
diff --git a/test/output/waffleHref.svg b/test/output/waffleHref.svg
index b8b5e3ad46..2d304de127 100644
--- a/test/output/waffleHref.svg
+++ b/test/output/waffleHref.svg
@@ -265,159 +265,159 @@
- waffle 0
+ waffle 0
- waffle 1
+ waffle 1
- waffle 2
+ waffle 2
- waffle 3
+ waffle 3
- waffle 4
+ waffle 4
- waffle 5
+ waffle 5
- waffle 6
+ waffle 6
- waffle 7
+ waffle 7
- waffle 8
+ waffle 8
- waffle 9
+ waffle 9
- waffle 10
+ waffle 10
- waffle 11
+ waffle 11
- waffle 12
+ waffle 12
- waffle 13
+ waffle 13
- waffle 14
+ waffle 14
- waffle 15
+ waffle 15
- waffle 16
+ waffle 16
- waffle 17
+ waffle 17
- waffle 18
+ waffle 18
- waffle 19
+ waffle 19
- waffle 20
+ waffle 20
- waffle 21
+ waffle 21
- waffle 22
+ waffle 22
- waffle 23
+ waffle 23
- waffle 24
+ waffle 24
- waffle 25
+ waffle 25
- waffle 26
+ waffle 26
- waffle 27
+ waffle 27
- waffle 28
+ waffle 28
- waffle 29
+ waffle 29
- waffle 30
+ waffle 30
- waffle 31
+ waffle 31
- waffle 32
+ waffle 32
- waffle 33
+ waffle 33
- waffle 34
+ waffle 34
- waffle 35
+ waffle 35
- waffle 36
+ waffle 36
- waffle 37
+ waffle 37
- waffle 38
+ waffle 38
- waffle 39
+ waffle 39
- waffle 40
+ waffle 40
- waffle 41
+ waffle 41
- waffle 42
+ waffle 42
- waffle 43
+ waffle 43
- waffle 44
+ waffle 44
- waffle 45
+ waffle 45
- waffle 46
+ waffle 46
- waffle 47
+ waffle 47
- waffle 48
+ waffle 48
- waffle 49
+ waffle 49
- waffle 50
+ waffle 50
- waffle 51
+ waffle 51
- waffle 52
+ waffle 52
- waffle 53
+ waffle 53
- waffle 54
+ waffle 54
- waffle 55
+ waffle 55
- waffle 56
+ waffle 56
- waffle 57
+ waffle 57
- waffle 58
+ waffle 58
- waffle 59
+ waffle 59
- waffle 60
+ waffle 60
- waffle 61
+ waffle 61
- waffle 62
+ waffle 62
- waffle 63
+ waffle 63
- waffle 64
+ waffle 64
- waffle 65
+ waffle 65
- waffle 66
+ waffle 66
- waffle 67
+ waffle 67
- waffle 68
+ waffle 68
- waffle 69
+ waffle 69
- waffle 70
+ waffle 70
- waffle 71
+ waffle 71
- waffle 72
+ waffle 72
- waffle 73
+ waffle 73
- waffle 74
+ waffle 74
- waffle 75
+ waffle 75
- waffle 76
+ waffle 76
\ No newline at end of file
diff --git a/test/output/waffleMultiple.svg b/test/output/waffleMultiple.svg
index 4884b906c3..ea367932f7 100644
--- a/test/output/waffleMultiple.svg
+++ b/test/output/waffleMultiple.svg
@@ -66,12 +66,12 @@
-
-
+
+
-
+
@@ -92,11 +92,11 @@
-
-
+
+
-
+
\ No newline at end of file
diff --git a/test/output/waffleRound.svg b/test/output/waffleRound.svg
index 8dd553d469..d9f48ecc15 100644
--- a/test/output/waffleRound.svg
+++ b/test/output/waffleRound.svg
@@ -66,7 +66,7 @@
-
+
@@ -92,7 +92,7 @@
-
+
diff --git a/test/output/waffleShapes.svg b/test/output/waffleShapes.svg
new file mode 100644
index 0000000000..c1e66aeb8a
--- /dev/null
+++ b/test/output/waffleShapes.svg
@@ -0,0 +1,202 @@
+
\ No newline at end of file
diff --git a/test/output/waffleShorthand.svg b/test/output/waffleShorthand.svg
index 2c942bbba9..5b5b97656b 100644
--- a/test/output/waffleShorthand.svg
+++ b/test/output/waffleShorthand.svg
@@ -66,7 +66,7 @@
-
+
@@ -92,7 +92,7 @@
-
+
diff --git a/test/output/waffleStroke.svg b/test/output/waffleStroke.svg
index ff8bc9a54a..2ade48d2f0 100644
--- a/test/output/waffleStroke.svg
+++ b/test/output/waffleStroke.svg
@@ -66,7 +66,7 @@
-
+
@@ -92,7 +92,7 @@
-
+
diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts
index fdfa98a025..d5fe683bdf 100644
--- a/test/plots/waffle.ts
+++ b/test/plots/waffle.ts
@@ -264,3 +264,41 @@ export function waffleHref() {
]
});
}
+
+export function waffleShapes() {
+ const k = 10;
+ let offset = 0;
+ const waffle = (y1, y2) => {
+ y1 += offset;
+ y2 += offset;
+ offset = Math.ceil(y2 / k) * k;
+ return Plot.waffleY({length: 1}, {y1, y2, multiple: k, fill: y1, stroke: "black"});
+ };
+ return Plot.plot({
+ height: 1200,
+ color: {type: "categorical"},
+ y: {domain: [0, 300]},
+ marks: [
+ Plot.waffleY({length: 1}, {y1: 0, y2: 300, multiple: 10, stroke: "currentColor", strokeOpacity: 0.2, gap: 0}),
+ waffle(0, 1),
+ waffle(0, 0.5),
+ waffle(0.2, 0.8),
+ waffle(0.6, 1.4),
+ waffle(9.6, 10.4),
+ waffle(0.6, 2),
+ waffle(1, 2.4),
+ waffle(0.6, 2.4),
+ waffle(1, 3),
+ waffle(9, 11),
+ waffle(0.6, 3),
+ waffle(1, 3.4),
+ waffle(0.6, 3.4),
+ waffle(7, 20),
+ waffle(7.6, 20),
+ waffle(0, 13),
+ waffle(0, 12.4),
+ waffle(7, 23),
+ waffle(7.6, 22.4)
+ ]
+ });
+}
From e5adcfe8178cdd1868e4772e0112798d1ce94f42 Mon Sep 17 00:00:00 2001
From: Mike Stringer
Date: Thu, 14 Nov 2024 20:04:54 -0600
Subject: [PATCH 21/35] fixed typo (incorrect return value) in
features/interval documentation (#2227)
---
docs/features/intervals.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/features/intervals.md b/docs/features/intervals.md
index f631ecdea0..a672c9e71a 100644
--- a/docs/features/intervals.md
+++ b/docs/features/intervals.md
@@ -21,7 +21,7 @@ The *interval*.**offset** method takes a *value* and returns the corresponding v
```js
Plot.utcInterval("day").offset(new Date("2013-04-12T12:34:56Z"), 1) // 2013-04-13T12:34:56Z
-Plot.utcInterval("day").offset(new Date("2013-04-12T12:34:56Z"), -2) // 2013-03-22T12:34:56Z
+Plot.utcInterval("day").offset(new Date("2013-04-12T12:34:56Z"), -2) // 2013-04-10T12:34:56Z
```
The *interval*.**range** method returns an array of values representing every interval boundary greater than or equal to *start* (inclusive) and less than *stop* (exclusive). The first value in the returned array is the least boundary greater than or equal to *start*; subsequent values are offset by intervals and floored.
From 419232bcc2d0d010851eead2420f771be32c2e47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?=
Date: Fri, 15 Nov 2024 03:12:22 +0100
Subject: [PATCH 22/35] Fix facet positioning of a rendered rendered
SVGSVGElement (#2219)
* Wrap any rendered SVGSVGElement in a SVGGElement
closes #2218
* use x, y instead of a wrapper
(review suggestion)
* inline template
---------
Co-authored-by: Mike Bostock
---
docs/features/marks.md | 2 +-
src/facet.js | 15 +-
src/plot.js | 2 +-
test/output/nestedFacets.html | 1008 +++++++++++++++++++++++++++++++++
test/plots/index.ts | 1 +
test/plots/nested-facets.ts | 53 ++
6 files changed, 1074 insertions(+), 7 deletions(-)
create mode 100644 test/output/nestedFacets.html
create mode 100644 test/plots/nested-facets.ts
diff --git a/docs/features/marks.md b/docs/features/marks.md
index 4d9cb90cd4..7e163907e6 100644
--- a/docs/features/marks.md
+++ b/docs/features/marks.md
@@ -114,7 +114,7 @@ Plot.plot({
```
:::
-Marks may also be a function which returns an SVG element, if you wish to insert arbitrary content. (Here we use [Hypertext Literal](https://github.com/observablehq/htl) to generate an SVG gradient.)
+Marks may also be a function which returns an [SVG element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element), if you wish to insert arbitrary content. (Here we use [Hypertext Literal](https://github.com/observablehq/htl) to generate an SVG gradient.)
:::plot defer https://observablehq.com/@observablehq/plot-gradient-bars
```js
diff --git a/src/facet.js b/src/facet.js
index 5398b344e7..cc3af59ac4 100644
--- a/src/facet.js
+++ b/src/facet.js
@@ -63,11 +63,16 @@ export function facetGroups(data, {fx, fy}) {
}
export function facetTranslator(fx, fy, {marginTop, marginLeft}) {
- return fx && fy
- ? ({x, y}) => `translate(${fx(x) - marginLeft},${fy(y) - marginTop})`
- : fx
- ? ({x}) => `translate(${fx(x) - marginLeft},0)`
- : ({y}) => `translate(0,${fy(y) - marginTop})`;
+ const x = fx ? ({x}) => fx(x) - marginLeft : () => 0;
+ const y = fy ? ({y}) => fy(y) - marginTop : () => 0;
+ return function (d) {
+ if (this.tagName === "svg") {
+ this.setAttribute("x", x(d));
+ this.setAttribute("y", y(d));
+ } else {
+ this.setAttribute("transform", `translate(${x(d)},${y(d)})`);
+ }
+ };
}
// Returns an index that for each facet lists all the elements present in other
diff --git a/src/plot.js b/src/plot.js
index a7383a66c7..829c4624a4 100644
--- a/src/plot.js
+++ b/src/plot.js
@@ -317,7 +317,7 @@ export function plot(options = {}) {
}
}
}
- g?.selectChildren().attr("transform", facetTranslate);
+ g?.selectChildren().each(facetTranslate);
}
}
diff --git a/test/output/nestedFacets.html b/test/output/nestedFacets.html
new file mode 100644
index 0000000000..78280a3cce
--- /dev/null
+++ b/test/output/nestedFacets.html
@@ -0,0 +1,1008 @@
+
\ No newline at end of file
diff --git a/test/plots/index.ts b/test/plots/index.ts
index b13d1ec45b..3176991d75 100644
--- a/test/plots/index.ts
+++ b/test/plots/index.ts
@@ -184,6 +184,7 @@ export * from "./movies-profit-by-genre.js";
export * from "./movies-rating-by-genre.js";
export * from "./multiplication-table.js";
export * from "./music-revenue.js";
+export * from "./nested-facets.js";
export * from "./npm-versions.js";
export * from "./opacity.js";
export * from "./ordinal-bar.js";
diff --git a/test/plots/nested-facets.ts b/test/plots/nested-facets.ts
new file mode 100644
index 0000000000..fa952a4464
--- /dev/null
+++ b/test/plots/nested-facets.ts
@@ -0,0 +1,53 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export async function nestedFacets() {
+ const diamonds = await d3.csv("data/diamonds.csv", d3.autoType);
+ return Plot.plot({
+ width: 960,
+ height: 480,
+ fx: {domain: ["D", "E", "F"]},
+ color: {legend: "ramp", domain: ["IF", "SI1", "I1"]},
+ y: {domain: [51, 71.9], insetTop: 20, labelAnchor: "center"},
+ marginLeft: 40,
+ marginBottom: 40,
+ marginTop: 35,
+ marks: [
+ Plot.axisFx({anchor: "top"}),
+ Plot.frame({anchor: "top", strokeOpacity: 1}),
+ Plot.dot(diamonds, {
+ fx: "color", // outer x facet
+ y: "depth", // shared y scale
+ fill: "clarity", // shared color scale
+ render(index, {scales}, _values, {facet, ...dimensions}) {
+ const data = Array.from(index, (i) => this.data[i]); // subplot dataset as a subset of the data
+ return Plot.plot({
+ ...dimensions,
+ marginTop: 60,
+ ...scales, // shared color scale, shared y scale
+ fx: {axis: "bottom", paddingOuter: 0.1, paddingInner: 0.2}, // inner x facet
+ x: {
+ domain: scales.color.domain,
+ axis: "top",
+ labelAnchor: "left",
+ labelOffset: 16,
+ ...(index["fi"] && {label: null}),
+ grid: true,
+ tickSize: 0
+ }, // new x scale with a common domain and additional axis options
+ y: {...scales.y, grid: 4, axis: null}, // shared y scale with additional options
+ marks: [
+ Plot.frame({anchor: "bottom"}),
+ Plot.boxY(data, {
+ fx: "cut",
+ x: "clarity",
+ y: "depth",
+ fill: "clarity"
+ })
+ ]
+ }) as SVGElement;
+ }
+ })
+ ]
+ });
+}
From 653af6260b26de692ed41356ca3233075e884005 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Thu, 14 Nov 2024 23:43:27 -0800
Subject: [PATCH 23/35] remove module-alias (#2235)
---
package.json | 4 ----
yarn.lock | 5 -----
2 files changed, 9 deletions(-)
diff --git a/package.json b/package.json
index b0126d24f4..f493bcb9bb 100644
--- a/package.json
+++ b/package.json
@@ -39,9 +39,6 @@
"docs:build": "node --experimental-network-imports node_modules/vitepress/dist/node/cli.js build docs",
"docs:preview": "vitepress preview docs"
},
- "_moduleAliases": {
- "@observablehq/plot": "./src/index.js"
- },
"sideEffects": [
"./src/index.js"
],
@@ -67,7 +64,6 @@
"jsdom": "^24.0.0",
"markdown-it-container": "^4.0.0",
"mocha": "^10.0.0",
- "module-alias": "^2.0.0",
"prettier": "~3.0.0",
"rollup": "^4.9.1",
"topojson-client": "^3.1.0",
diff --git a/yarn.lock b/yarn.lock
index 384c7783c2..4d03086e4d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2853,11 +2853,6 @@ mocha@^10.0.0:
yargs-parser "^20.2.9"
yargs-unparser "^2.0.0"
-module-alias@^2.0.0:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.3.tgz#ec2e85c68973bda6ab71ce7c93b763ec96053221"
- integrity sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==
-
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
From ff7d83cf74e9b9e7392c982196d76480fe33afa7 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Thu, 14 Nov 2024 23:45:16 -0800
Subject: [PATCH 24/35] fix tickFormat type inference for empty domain (#2232)
---
src/marks/axis.js | 4 ++--
test/output/tickFormatEmptyDomain.svg | 17 +++++++++++++++++
test/output/tickFormatEmptyFacetDomain.svg | 17 +++++++++++++++++
test/plots/index.ts | 1 +
test/plots/tick-format.ts | 9 +++++++++
5 files changed, 46 insertions(+), 2 deletions(-)
create mode 100644 test/output/tickFormatEmptyDomain.svg
create mode 100644 test/output/tickFormatEmptyFacetDomain.svg
create mode 100644 test/plots/tick-format.ts
diff --git a/src/marks/axis.js b/src/marks/axis.js
index 7c7944a55f..3a0e644909 100644
--- a/src/marks/axis.js
+++ b/src/marks/axis.js
@@ -672,10 +672,10 @@ export function inferTickFormat(scale, data, ticks, tickFormat, anchor) {
? inferTimeFormat(scale.type, data, anchor) ?? formatDefault
: scale.tickFormat
? scale.tickFormat(typeof ticks === "number" ? ticks : null, tickFormat)
+ : typeof tickFormat === "string" && scale.domain().length > 0
+ ? (isTemporal(scale.domain()) ? utcFormat : format)(tickFormat)
: tickFormat === undefined
? formatDefault
- : typeof tickFormat === "string"
- ? (isTemporal(scale.domain()) ? utcFormat : format)(tickFormat)
: constant(tickFormat);
}
diff --git a/test/output/tickFormatEmptyDomain.svg b/test/output/tickFormatEmptyDomain.svg
new file mode 100644
index 0000000000..38e50ca56f
--- /dev/null
+++ b/test/output/tickFormatEmptyDomain.svg
@@ -0,0 +1,17 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/output/tickFormatEmptyFacetDomain.svg b/test/output/tickFormatEmptyFacetDomain.svg
new file mode 100644
index 0000000000..38e50ca56f
--- /dev/null
+++ b/test/output/tickFormatEmptyFacetDomain.svg
@@ -0,0 +1,17 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/plots/index.ts b/test/plots/index.ts
index 3176991d75..f54398dca7 100644
--- a/test/plots/index.ts
+++ b/test/plots/index.ts
@@ -309,6 +309,7 @@ export * from "./style-overrides.js";
export * from "./symbol-set.js";
export * from "./text-overflow.js";
export * from "./this-is-just-to-say.js";
+export * from "./tick-format.js";
export * from "./time-axis.js";
export * from "./tip-format.js";
export * from "./tip.js";
diff --git a/test/plots/tick-format.ts b/test/plots/tick-format.ts
new file mode 100644
index 0000000000..66b2898f9e
--- /dev/null
+++ b/test/plots/tick-format.ts
@@ -0,0 +1,9 @@
+import * as Plot from "@observablehq/plot";
+
+export async function tickFormatEmptyDomain() {
+ return Plot.plot({y: {tickFormat: "%W"}, marks: [Plot.barX([]), Plot.frame()]});
+}
+
+export async function tickFormatEmptyFacetDomain() {
+ return Plot.plot({fy: {tickFormat: "%W"}, marks: [Plot.barX([]), Plot.frame()]});
+}
From 6bea18ef4f4c311aa524b3737f2b73cab6e30d50 Mon Sep 17 00:00:00 2001
From: Mike Bostock
Date: Tue, 19 Nov 2024 08:28:50 -0800
Subject: [PATCH 25/35] handle degenerate domains (#2212)
---
src/scales.js | 9 +++---
src/scales/quantitative.js | 10 +++++--
test/output/colorLegendDomainEmpty.html | 27 ++++++++++++++++++
test/output/colorLegendDomainUnary.html | 29 ++++++++++++++++++++
test/output/colorLegendLinearDomainEmpty.svg | 17 ++++++++++++
test/output/colorLegendLinearDomainUnary.svg | 22 +++++++++++++++
test/plots/legend-color.ts | 16 +++++++++++
7 files changed, 124 insertions(+), 6 deletions(-)
create mode 100644 test/output/colorLegendDomainEmpty.html
create mode 100644 test/output/colorLegendDomainUnary.html
create mode 100644 test/output/colorLegendLinearDomainEmpty.svg
create mode 100644 test/output/colorLegendLinearDomainUnary.svg
diff --git a/src/scales.js b/src/scales.js
index 46a1f45c3c..7d01163ad0 100644
--- a/src/scales.js
+++ b/src/scales.js
@@ -425,10 +425,11 @@ function inferScaleType(key, channels, {type, domain, range, scheme, pivot, proj
if (kind === opacity || kind === length) return "linear";
if (kind === symbol) return "ordinal";
- // If the domain or range has more than two values, assume it’s ordinal. You
- // can still use a “piecewise” (or “polylinear”) scale, but you must set the
- // type explicitly.
- if ((domain || range || []).length > 2) return asOrdinalType(kind);
+ // If a domain or range is explicitly specified and doesn’t have two values,
+ // assume it’s ordinal. You can still use a “piecewise” (or “polylinear”)
+ // scale, but you must set the type explicitly.
+ const n = (domain ?? range)?.length;
+ if (n < 2 || n > 2) return asOrdinalType(kind);
// Otherwise, infer the scale type from the data! Prefer the domain, if
// present, over channels. (The domain and channels should be consistently
diff --git a/src/scales/quantitative.js b/src/scales/quantitative.js
index 88feec4d07..8cf9cadbbb 100644
--- a/src/scales/quantitative.js
+++ b/src/scales/quantitative.js
@@ -80,6 +80,7 @@ export function createScaleQ(
reverse
}
) {
+ domain = maybeRepeat(domain);
interval = maybeRangeInterval(interval, type);
if (type === "cyclical" || type === "sequential") type = "linear"; // shorthand for color schemes
if (typeof interpolate !== "function") interpolate = maybeInterpolator(interpolate); // named interpolator
@@ -88,8 +89,8 @@ export function createScaleQ(
// If an explicit range is specified, and it has a different length than the
// domain, then redistribute the range using a piecewise interpolator.
if (range !== undefined) {
- const n = (domain = arrayify(domain)).length;
- const m = (range = arrayify(range)).length;
+ const n = domain.length;
+ const m = (range = maybeRepeat(range)).length;
if (n !== m) {
if (interpolate.length === 1) throw new Error("invalid piecewise interpolator"); // e.g., turbo
interpolate = piecewise(interpolate, range);
@@ -137,6 +138,11 @@ export function createScaleQ(
return {type, domain, range, scale, interpolate, interval};
}
+function maybeRepeat(values) {
+ values = arrayify(values);
+ return values.length >= 2 ? values : [values[0], values[0]];
+}
+
function maybeNice(nice, type) {
return nice === true ? undefined : typeof nice === "number" ? nice : maybeNiceInterval(nice, type);
}
diff --git a/test/output/colorLegendDomainEmpty.html b/test/output/colorLegendDomainEmpty.html
new file mode 100644
index 0000000000..5e9bfac54d
--- /dev/null
+++ b/test/output/colorLegendDomainEmpty.html
@@ -0,0 +1,27 @@
+
+
+
\ No newline at end of file
diff --git a/test/output/colorLegendDomainUnary.html b/test/output/colorLegendDomainUnary.html
new file mode 100644
index 0000000000..1401790db6
--- /dev/null
+++ b/test/output/colorLegendDomainUnary.html
@@ -0,0 +1,29 @@
+