Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add docs-test #1688

Merged
merged 5 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/features/scales.md
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,7 @@ If the input channel is *data*, then the reducer is passed groups of the mark’

Note: when the value of the sort option is a string or a function, it is interpreted as a mark [sort transform](../transforms/sort.md). To use both sort options and a mark sort transform, use [Plot.sort](../transforms/sort.md#sort-order-options).

## scale(*options*)
## scale(*options*) {#scale-options-1}

You can also create a standalone scale with Plot.**scale**(*options*). The *options* object must define at least one scale; see [Scale options](#scale-options) for how to define a scale. For example, here is a linear color scale with the default domain of [0, 1] and default scheme *turbo*:

Expand Down
2 changes: 1 addition & 1 deletion docs/interactions/pointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ To resolve the horizontal target position, the pointer transform applies the fol

The same precedence applies to the **py**, **y**, **y1**, and **y2** channels.

## pointer(*options*)
## pointer(*options*) {#pointer-options-1}

```js
Plot.tip(penguins, Plot.pointer({x: "culmen_length_mm", y: "culmen_depth_mm"}))
Expand Down
4 changes: 2 additions & 2 deletions docs/marks/bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ The following optional channels are supported:

If neither the **x1** nor **x2** option is specified, the **x** option may be specified as shorthand to apply an implicit [stackX transform](../transforms/stack.md); this is the typical configuration for a horizontal bar chart with bars aligned at *x* = 0. If the **x** option is not specified, it defaults to [identity](../features/transforms.md#identity). If *options* is undefined, then it defaults to **x2** as identity and **y** as the zero-based index [0, 1, 2, …]; this allows an array of numbers to be passed to barX to make a quick sequential bar chart. If the **y** channel is not specified, the bar will span the full vertical extent of the plot (or facet).

If an **interval** is specified, such as d3.utcDay, **x1** and **x2** can be derived from **x**: *interval*.floor(*x*) is invoked for each *x* to produce *x1*, and *interval*.offset(*x1*) is invoked for each *x1* to produce *x2*. If the interval is specified as a number *n*, *x1* and *x2* are taken as the two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **x1** and **x2** can be derived from **x**: *interval*.floor(*x*) is invoked for each *x* to produce *x1*, and *interval*.offset(*x1*) is invoked for each *x1* to produce *x2*. If the interval is specified as a number *n*, *x1* and *x2* are taken as the two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).

## barY(*data*, *options*)

Expand All @@ -251,4 +251,4 @@ The following optional channels are supported:

If neither the **y1** nor **y2** option is specified, the **y** option may be specified as shorthand to apply an implicit [stackY transform](../transforms/stack.md); this is the typical configuration for a vertical bar chart with bars aligned at *y* = 0. If the **y** option is not specified, it defaults to [identity](../features/transforms.md#identity). If *options* is undefined, then it defaults to **y2** as identity and **x** as the zero-based index [0, 1, 2, …]; this allows an array of numbers to be passed to barY to make a quick sequential bar chart. If the **x** channel is not specified, the bar will span the full horizontal extent of the plot (or facet).

If an **interval** is specified, such as d3.utcDay, **y1** and **y2** can be derived from **y**: *interval*.floor(*y*) is invoked for each *y* to produce *y1*, and *interval*.offset(*y1*) is invoked for each *y1* to produce *y2*. If the interval is specified as a number *n*, *y1* and *y2* are taken as the two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **y1** and **y2** can be derived from **y**: *interval*.floor(*y*) is invoked for each *y* to produce *y1*, and *interval*.offset(*y1*) is invoked for each *y1* to produce *y2*. If the interval is specified as a number *n*, *y1* and *y2* are taken as the two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).
4 changes: 2 additions & 2 deletions docs/marks/dot.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ Plot.dotX(cars.map((d) => d["economy (mpg)"]))

Equivalent to [dot](#dot-data-options) except that if the **x** option is not specified, it defaults to the identity function and assumes that *data* = [*x₀*, *x₁*, *x₂*, …].

If an **interval** is specified, such as d3.utcDay, **y** is transformed to (*interval*.floor(*y*) + *interval*.offset(*interval*.floor(*y*))) / 2. If the interval is specified as a number *n*, *y* will be the midpoint of two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **y** is transformed to (*interval*.floor(*y*) + *interval*.offset(*interval*.floor(*y*))) / 2. If the interval is specified as a number *n*, *y* will be the midpoint of two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).

## dotY(*data*, *options*)

Expand All @@ -359,7 +359,7 @@ Plot.dotY(cars.map((d) => d["economy (mpg)"]))

Equivalent to [dot](#dot-data-options) except that if the **y** option is not specified, it defaults to the identity function and assumes that *data* = [*y₀*, *y₁*, *y₂*, …].

If an **interval** is specified, such as d3.utcDay, **x** is transformed to (*interval*.floor(*x*) + *interval*.offset(*interval*.floor(*x*))) / 2. If the interval is specified as a number *n*, *x* will be the midpoint of two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **x** is transformed to (*interval*.floor(*x*) + *interval*.offset(*interval*.floor(*x*))) / 2. If the interval is specified as a number *n*, *x* will be the midpoint of two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).

## circle(*data*, *options*)

Expand Down
2 changes: 1 addition & 1 deletion docs/marks/frame.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ The frame mark supports the [standard mark options](../features/marks.md#mark-op

If the **anchor** option is specified as one of *left*, *right*, *top*, or *bottom*, that side is rendered as a single line (and the **fill**, **fillOpacity**, **rx**, and **ry** options are ignored).

## frame(*options*)
## frame(*options*) {#frame-options-1}

```js
Plot.frame({stroke: "red"})
Expand Down
2 changes: 1 addition & 1 deletion docs/marks/rect.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ The following channels are optional:

Typically either **x1** and **x2** are specified, or **y1** and **y2**, or both.

If an **interval** is specified, such as d3.utcDay, **x1** and **x2** can be derived from **x**: *interval*.floor(*x*) is invoked for each **x** to produce **x1**, and *interval*.offset(*x1*) is invoked for each **x1** to produce **x2**. The same is true for **y**, **y1**, and **y2**, respectively. If the interval is specified as a number *n*, **x1** and **x2** are taken as the two consecutive multiples of *n* that bracket **x**. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **x1** and **x2** can be derived from **x**: *interval*.floor(*x*) is invoked for each **x** to produce **x1**, and *interval*.offset(*x1*) is invoked for each **x1** to produce **x2**. The same is true for **y**, **y1**, and **y2**, respectively. If the interval is specified as a number *n*, **x1** and **x2** are taken as the two consecutive multiples of *n* that bracket **x**. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).

The rect mark supports the [standard mark options](../features/marks.md#mark-options), including insets and rounded corners. The **stroke** defaults to *none*. The **fill** defaults to *currentColor* if the stroke is *none*, and to *none* otherwise.

Expand Down
6 changes: 3 additions & 3 deletions docs/marks/rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ Plot.plot({
```
:::

Rules are also used by the [grid mark](./grid) to draw grid lines.
Rules are also used by the [grid mark](./grid.md) to draw grid lines.

## Rule options

Expand All @@ -160,7 +160,7 @@ If **x** is not specified, it defaults to [identity](../features/transforms.md#i

If **y** is specified, it is shorthand for **y2** with **y1** equal to zero; this is the typical configuration for a vertical lollipop chart with rules aligned at *y* = 0. If **y1** is not specified, the rule will start at the top of the plot (or facet). If **y2** is not specified, the rule will end at the bottom of the plot (or facet).

If an **interval** is specified, such as d3.utcDay, **y1** and **y2** can be derived from **y**: *interval*.floor(*y*) is invoked for each *y* to produce *y1*, and *interval*.offset(*y1*) is invoked for each *y1* to produce *y2*. If the interval is specified as a number *n*, *y1* and *y2* are taken as the two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **y1** and **y2** can be derived from **y**: *interval*.floor(*y*) is invoked for each *y* to produce *y1*, and *interval*.offset(*y1*) is invoked for each *y1* to produce *y2*. If the interval is specified as a number *n*, *y1* and *y2* are taken as the two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).

## ruleY(*data*, *options*)

Expand All @@ -181,4 +181,4 @@ If **y** is not specified, it defaults to [identity](../features/transforms.md#i

If **x** is specified, it is shorthand for **x2** with **x1** equal to zero; this is the typical configuration for a horizontal lollipop chart with rules aligned at *x* = 0. If **x1** is not specified, the rule will start at the left edge of the plot (or facet). If **x2** is not specified, the rule will end at the right edge of the plot (or facet).

If an **interval** is specified, such as d3.utcDay, **x1** and **x2** can be derived from **x**: *interval*.floor(*x*) is invoked for each *x* to produce *x1*, and *interval*.offset(*x1*) is invoked for each *x1* to produce *x2*. If the interval is specified as a number *n*, *x1* and *x2* are taken as the two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **x1** and **x2** can be derived from **x**: *interval*.floor(*x*) is invoked for each *x* to produce *x1*, and *interval*.offset(*x1*) is invoked for each *x1* to produce *x2*. If the interval is specified as a number *n*, *x1* and *x2* are taken as the two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).
4 changes: 2 additions & 2 deletions docs/marks/text.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ Plot.textX(alphabet.map((d) => d.frequency))

Equivalent to [text](#text-data-options), except **x** defaults to [identity](../features/transforms.md#identity) and assumes that *data* = [*x₀*, *x₁*, *x₂*, …].

If an **interval** is specified, such as d3.utcDay, **y** is transformed to (*interval*.floor(*y*) + *interval*.offset(*interval*.floor(*y*))) / 2. If the interval is specified as a number *n*, *y* will be the midpoint of two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **y** is transformed to (*interval*.floor(*y*) + *interval*.offset(*interval*.floor(*y*))) / 2. If the interval is specified as a number *n*, *y* will be the midpoint of two consecutive multiples of *n* that bracket *y*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).

## textY(*data*, *options*)

Expand All @@ -266,4 +266,4 @@ Plot.textY(alphabet.map((d) => d.frequency))

Equivalent to [text](#text-data-options), except **y** defaults to [identity](../features/transforms.md#identity) and assumes that *data* = [*y₀*, *y₁*, *y₂*, …].

If an **interval** is specified, such as d3.utcDay, **x** is transformed to (*interval*.floor(*x*) + *interval*.offset(*interval*.floor(*x*))) / 2. If the interval is specified as a number *n*, *x* will be the midpoint of two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales#scale-options).
If an **interval** is specified, such as d3.utcDay, **x** is transformed to (*interval*.floor(*x*) + *interval*.offset(*interval*.floor(*x*))) / 2. If the interval is specified as a number *n*, *x* will be the midpoint of two consecutive multiples of *n* that bracket *x*. Named UTC intervals such as *day* are also supported; see [scale options](../features/scales.md#scale-options).
74 changes: 74 additions & 0 deletions test/docs-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import assert from "assert";
import {readdir, readFile, stat} from "fs/promises";

it("documentation links point to existing internal anchors", async () => {
const root = "docs";

// Crawl all files, read their links and anchors.
const anchors = new Map();
const links = [];
for await (const file of readMarkdownFiles(root)) {
const text = await readMarkdownSource(root + file);
anchors.set(file, getAnchors(text));
for (const {pathname, hash} of getLinks(file, text)) {
links.push({source: file, target: pathname, hash});
}
}

// Check for broken links.
let errors = [];
for (let {source, target, hash} of links) {
if (!target.endsWith(".md")) {
errors.push(`- ${source} points to ${target} instead of ${target}.md.`);
target += ".md";
}

if (!hash || anchors.get(target).includes(hash.slice(1))) continue;
errors.push(`- ${source} points to missing ${target}${hash}.`);
}
assert(errors.length === 0, new Error(`${errors.length} broken links:\n${errors.join("\n")}`));
});

// Anchors can be derived from headers, or explicitly written as {#names}.
function getAnchors(text) {
const anchors = [];
for (const [, header] of text.matchAll(/^#+ ([*\w][*().,\w\d -]+)\n/gm)) {
anchors.push(
header
.replaceAll(/[^\w\d\s]+/g, " ")
.trim()
.replaceAll(/ +/g, "-")
.toLowerCase()
);
}
for (const [, anchor] of text.matchAll(/ \{#([\w\d-]+)\}/g)) {
anchors.push(anchor);
}
return anchors;
}

// Internal links.
function getLinks(file, text) {
const links = [];
for (const match of text.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) {
const [, link] = match;
if (/^\w+:/.test(link)) continue; // absolute link with protocol
const {pathname, hash} = new URL(link, new URL(file, "https://example.com/"));
links.push({pathname, hash});
}
return links;
}

// In source files, ignore comments.
async function readMarkdownSource(f) {
return (await readFile(f, "utf8")).replaceAll(/<!-- .*? -->/gs, "");
}

// Recursively find all md files in the directory.
async function* readMarkdownFiles(root, subpath = "/") {
for (const fname of await readdir(root + subpath)) {
if (fname.startsWith(".") || fname.endsWith(".js")) continue; // ignore .vitepress etc.
if ((await stat(root + subpath + fname)).isDirectory()) yield* readMarkdownFiles(root, subpath + fname + "/");
else if (fname.endsWith(".md")) yield subpath + fname;
}
}