Skip to content

Commit c6bc0b8

Browse files
mbostockFil
authored andcommitted
api index (#1765)
* api index * fix crosshair link * externalize list style
1 parent ee4c502 commit c6bc0b8

File tree

8 files changed

+401
-183
lines changed

8 files changed

+401
-183
lines changed

docs/.vitepress/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ export default defineConfig({
130130
{text: "Crosshair", link: "/interactions/crosshair"},
131131
{text: "Pointer", link: "/interactions/pointer"}
132132
]
133-
}
133+
},
134+
{text: "API index", link: "/api"}
134135
],
135136
search: {
136137
provider: "local"

docs/api.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script setup>
2+
3+
import {data} from "./data/api.data";
4+
5+
</script>
6+
7+
# API index
8+
9+
## Methods
10+
11+
<ul :class="$style.oneline">
12+
<li v-for="({name, href, comment}) in data.methods">
13+
<span><a :href="href">{{ name }}</a> - {{ comment }}</span>
14+
</li>
15+
</ul>
16+
17+
## Options
18+
19+
<ul>
20+
<li v-for="[name, contexts] in data.options">
21+
<b>{{ name }}</b> - <span v-for="({name, href}, index) in contexts"><a :href="href">{{ name }}</a><span v-if="index < contexts.length - 1">, </span></span>
22+
</li>
23+
</ul>
24+
25+
<style module>
26+
27+
ul.oneline span {
28+
display: block;
29+
white-space: nowrap;
30+
overflow: hidden;
31+
text-overflow: ellipsis;
32+
}
33+
34+
</style>

docs/data/api.data.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {rollup, sort} from "d3";
2+
import {Node, FunctionDeclaration, InterfaceDeclaration, Project, VariableDeclaration, VariableStatement} from "ts-morph";
3+
4+
// These interfaces tend to represent things that Plot constructs internally,
5+
// rather than objects that the user is expected to provide.
6+
function isInternalInterface(name) {
7+
return (
8+
name === "AutoSpec" ||
9+
name === "Channel" ||
10+
name === "ChannelDomainOptions" || // TODO
11+
name === "ChannelTransform" ||
12+
name === "Context" ||
13+
name === "Dimensions" ||
14+
name === "Plot" ||
15+
name === "Scale"
16+
);
17+
}
18+
19+
// This tries to get a brief human-readable, one-sentence summary description of
20+
// the exported symbol. We might want to formalize this so that we can be more
21+
// intentional when authoring documentation.
22+
function getDescription(node: FunctionDeclaration | VariableStatement): string {
23+
return node
24+
.getJsDocs()[0]
25+
?.getDescription()
26+
.replace(/\n/g, " ") // replace newlines with spaces
27+
.replace(/[*_]/g, "") // remove bold and italics formatting
28+
.replace(/[.:]($|\s+).*/g, "") // truncate at the first period or colon
29+
.replace(/\[([^[]+)\]\[\d+\]/g, "$1") // strip links (assuming [1] syntax)
30+
.trim();
31+
}
32+
33+
// While we try to keep the source code file structure as close as possible to
34+
// the documentation URL structure, there are some inevitable deviations that
35+
// are codified by this function. When new files are added, please try to keep
36+
// this function up-to-date, and try to generalize patterns so that we
37+
// automatically generate correct links. (TODO Verify that the links are valid.)
38+
function getHref(name: string, path: string): string {
39+
path = path.replace(/\.d\.ts$/, ""); // drop trailing .d.ts
40+
path = path.replace(/([a-z0-9])([A-Z])/, (_, a, b) => `${a}-${b.toLowerCase()}`); // camel case conversion
41+
if (path.split("/").length === 1) path = `features/${path}`; // top-level declarations are features
42+
switch (path) {
43+
case "features/curve":
44+
case "features/format":
45+
case "features/mark":
46+
case "features/plot":
47+
return `${path}s`;
48+
case "features/options":
49+
return "features/transforms";
50+
case "marks/axis": {
51+
switch (name) {
52+
case "gridX":
53+
case "gridY":
54+
case "gridFx":
55+
case "gridFy":
56+
return "marks/grid";
57+
}
58+
break;
59+
}
60+
case "marks/crosshair":
61+
return "interactions/crosshair";
62+
case "transforms/basic": {
63+
switch (name) {
64+
case "filter":
65+
return "transforms/filter";
66+
case "reverse":
67+
case "shuffle":
68+
case "sort":
69+
return "transforms/sort";
70+
}
71+
return "features/transforms";
72+
}
73+
}
74+
return path;
75+
}
76+
77+
function getInterfaceName(name: string, path: string): string {
78+
name = name.replace(/(Transform|Corner|X|Y|Output)?(Defaults|Options|Styles)$/, "");
79+
name = name.replace(/([a-z0-9])([A-Z])/, (_, a, b) => `${a} ${b}`); // camel case conversion
80+
name = name.toLowerCase();
81+
if (name === "curve auto") name = "curve";
82+
if (name === "plot facet") name = "plot";
83+
if (path.startsWith("marks/")) name += " mark";
84+
else if (path.startsWith("transforms/")) name += " transform";
85+
return name;
86+
}
87+
88+
export default {
89+
watch: [],
90+
async load() {
91+
const project = new Project({tsConfigFilePath: "tsconfig.json"});
92+
const allMethods: {name: string; comment: string; href: string}[] = [];
93+
const allOptions: {name: string; context: {name: string; href: string}}[] = [];
94+
const index = project.getSourceFile("src/index.d.ts")!;
95+
for (const [name, declarations] of index.getExportedDeclarations()) {
96+
for (const declaration of declarations) {
97+
if (Node.isInterfaceDeclaration(declaration)) {
98+
if (isInternalInterface(name)) continue;
99+
for (const property of declaration.getProperties()) {
100+
const path = index.getRelativePathTo(declaration.getSourceFile());
101+
const href = getHref(name, path);
102+
if (property.getJsDocs().some((d) => d.getTags().some((d) => Node.isJSDocDeprecatedTag(d)))) continue;
103+
allOptions.push({name: property.getName(), context: {name: getInterfaceName(name, path), href}});
104+
}
105+
} else if (Node.isFunctionDeclaration(declaration)) {
106+
const comment = getDescription(declaration);
107+
if (comment) {
108+
const href = getHref(name, index.getRelativePathTo(declaration.getSourceFile()));
109+
allMethods.push({name, comment, href});
110+
}
111+
} else if (Node.isVariableDeclaration(declaration)) {
112+
const comment = getDescription(declaration.getVariableStatement()!);
113+
if (comment) {
114+
const href = getHref(name, index.getRelativePathTo(declaration.getSourceFile()));
115+
allMethods.push({name, comment, href});
116+
}
117+
}
118+
}
119+
}
120+
return {
121+
methods: sort(allMethods, ({name}) => name),
122+
options: sort(
123+
rollup(
124+
allOptions,
125+
(D) =>
126+
sort(
127+
rollup(
128+
D.map((d) => d.context),
129+
([d]) => d,
130+
(d) => d.name
131+
).values(),
132+
(d) => d.name
133+
),
134+
(d) => d.name
135+
),
136+
([name]) => name
137+
)
138+
};
139+
}
140+
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"prettier": "^3.0.0",
6969
"rollup": "^3.7.0",
7070
"topojson-client": "^3.1.0",
71+
"ts-morph": "^19.0.0",
7172
"typescript": "^5.0.2",
7273
"vite": "^4.0.0",
7374
"vitepress": "^1.0.0-beta.2"

src/mark.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,5 +477,5 @@ export class RenderableMark extends Mark {
477477
/** A compound Mark, comprising other marks. */
478478
export type CompoundMark = Markish[] & Pick<Mark, "plot">;
479479

480-
/** Given an array of marks, returns a compound mark; supports *mark.plot shorthand. */
480+
/** Given an array of marks, returns a compound mark; supports *mark*.plot shorthand. */
481481
export function marks(...marks: Markish[]): CompoundMark;

src/marks/area.d.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export interface AreaYOptions extends Omit<AreaOptions, "x1" | "x2">, BinOptions
125125
}
126126

127127
/**
128-
* Returns a new area with the given *data* and *options*. The area mark is
128+
* Returns a new area mark with the given *data* and *options*. The area mark is
129129
* rarely used directly; it is only needed when the baseline and topline have
130130
* neither *x* nor *y* values in common. Use areaY for a horizontal orientation
131131
* where the baseline and topline share *x* values, or areaX for a vertical
@@ -134,9 +134,10 @@ export interface AreaYOptions extends Omit<AreaOptions, "x1" | "x2">, BinOptions
134134
export function area(data?: Data, options?: AreaOptions): Area;
135135

136136
/**
137-
* Returns a new vertically-oriented area for the given *data* and *options*,
138-
* where the baseline and topline share **y** values, as in a time-series area
139-
* chart where time goes up↑. For example, to plot Apple’s daily stock price:
137+
* Returns a new vertically-oriented area mark for the given *data* and
138+
* *options*, where the baseline and topline share **y** values, as in a
139+
* time-series area chart where time goes up↑. For example, to plot Apple’s
140+
* daily stock price:
140141
*
141142
* ```js
142143
* Plot.areaX(aapl, {y: "Date", x: "Close"})
@@ -165,9 +166,10 @@ export function area(data?: Data, options?: AreaOptions): Area;
165166
export function areaX(data?: Data, options?: AreaXOptions): Area;
166167

167168
/**
168-
* Returns a new horizontally-oriented area for the given *data* and *options*,
169-
* where the baseline and topline share **x** values, as in a time-series area
170-
* chart where time goes right→. For example, to plot Apple’s daily stock price:
169+
* Returns a new horizontally-oriented area mark for the given *data* and
170+
* *options*, where the baseline and topline share **x** values, as in a
171+
* time-series area chart where time goes right→. For example, to plot Apple’s
172+
* daily stock price:
171173
*
172174
* ```js
173175
* Plot.areaY(aapl, {x: "Date", y: "Close"})
@@ -179,8 +181,8 @@ export function areaX(data?: Data, options?: AreaXOptions): Area;
179181
* specified, the other defaults to **y**, which defaults to zero.
180182
*
181183
* If an **interval** is specified, **x** values are binned accordingly,
182-
* allowing zeroes for empty bins instead of interpolating across gaps. This
183-
* is recommended to “regularize” sampled data; for example, if your data
184+
* allowing zeroes for empty bins instead of interpolating across gaps. This is
185+
* recommended to “regularize” sampled data; for example, if your data
184186
* represents timestamped observations and you expect one observation per day,
185187
* use *day* as the **interval**.
186188
*

src/marks/line.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ export interface LineYOptions extends LineOptions, BinOptions {
8383
}
8484

8585
/**
86-
* Returns a new line for the given *data* and *options* by connecting control
87-
* points. If neither the **x** nor **y** options are specified, *data* is
88-
* assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …]
89-
* such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …].
86+
* Returns a new line mark for the given *data* and *options* by connecting
87+
* control points. If neither the **x** nor **y** options are specified, *data*
88+
* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*],
89+
* …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …].
9090
*
9191
* Points along the line are connected in input order. If there are multiple
9292
* series via the **z**, **fill**, or **stroke** channel, series are drawn in

0 commit comments

Comments
 (0)