docs: add element api interactive demos (#5719)

* docs: add element api interactive demos

* fix: fix cr issues

* refactor: adjust locales in docs
This commit is contained in:
Yuxin 2024-05-10 14:29:40 +08:00 committed by GitHub
parent 80ed0b9cc3
commit ad30bd97d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 1445 additions and 492 deletions

View File

@ -7,7 +7,7 @@ export const elementNodeDonut: TestCase = async (context) => {
{
id: 'donut',
style: {
innerRadius: '60%',
innerR: '60%',
donuts: [
{
color: 'orange',
@ -65,14 +65,14 @@ export const elementNodeDonut: TestCase = async (context) => {
{
id: 'donut-inactive',
style: {
innerRadius: 0,
innerR: 0,
donuts: [{ fill: 'red' }, { fill: 'green' }],
},
},
{
id: 'donut-disabled',
style: {
innerRadius: '50%',
innerR: '50%',
donuts: [{ color: 'green' }, { color: 'red' }],
},
},
@ -86,7 +86,7 @@ export const elementNodeDonut: TestCase = async (context) => {
type: 'donut', // 👈🏻 Node shape type.
style: {
size: 40,
innerRadius: (d: any) => d.style.innerRadius ?? '50%',
innerR: (d: any) => d.style.innerR ?? '50%',
labelText: (d) => d.id,
iconHeight: 20,
iconWidth: 20,

View File

@ -20,8 +20,6 @@ export interface CircleComboStyleProps extends BaseComboStyleProps {}
* <zh/>
*
* <en/> Circle combo
* @remarks
* <img width="300" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*WJhpRJCcFLAAAAAAAAAAAAAADmJ7AQ/original" />
*/
export class CircleCombo extends BaseCombo<CircleComboStyleProps> {
constructor(options: DisplayObjectConfig<CircleComboStyleProps>) {

View File

@ -34,8 +34,6 @@ export interface CubicHorizontalStyleProps extends BaseEdgeStyleProps {
* <zh/> x y
*
* <en/> Please note that when calculating the control points, the distance on the x-axis is mainly considered, and the change on the y-axis is ignored
*
* <img width="300" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*6Gj2R50AJ8AAAAAAAAAAAAAADmJ7AQ/original" /
*/
export class CubicHorizontal extends Cubic {
static defaultStyleProps: Partial<CubicHorizontalStyleProps> = {

View File

@ -34,8 +34,6 @@ export interface CubicVerticalStyleProps extends BaseEdgeStyleProps {
* <zh/> y x
*
* <en/> Please note that when calculating the control points, the distance on the y-axis is mainly considered, and the change on the x-axis is ignored
*
* <img width="300" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*wrDlQKxNHNEAAAAAAAAAAAAADmJ7AQ/original" />
*/
export class CubicVertical extends Cubic {
static defaultStyleProps: Partial<CubicVerticalStyleProps> = {

View File

@ -40,8 +40,6 @@ type ParsedCubicStyleProps = Required<CubicStyleProps>;
* <zh/> 线
*
* <en/> Cubic Bezier curve
* @remarks
* <img width="300" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*cVd-TLQWujYAAAAAAAAAAAAADmJ7AQ/original" />
*/
export class Cubic extends BaseEdge {
static defaultStyleProps: Partial<CubicStyleProps> = {

View File

@ -59,8 +59,6 @@ type ParsedPolylineStyleProps = Required<PolylineStyleProps>;
* <zh/> 线
*
* <en/> Polyline
* @remarks
* <img width="300" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*LeBUQKp9QD0AAAAAAAAAAAAADmJ7AQ/original" />
*/
export class Polyline extends BaseEdge {
static defaultStyleProps: Partial<PolylineStyleProps> = {

View File

@ -40,8 +40,6 @@ type ParsedQuadraticStyleProps = Required<QuadraticStyleProps>;
* <zh/> 线
*
* <en/> Quadratic Bezier curve
* @remarks
* <img width="300" src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*CLx6RqrqMvMAAAAAAAAAAAAADmJ7AQ/original" />
*/
export class Quadratic extends BaseEdge {
static defaultStyleProps: Partial<QuadraticStyleProps> = {

View File

@ -2,7 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g';
import type { Point } from '../../types';
import { getDiamondPoints } from '../../utils/element';
import { getPolygonIntersectPoint } from '../../utils/point';
import type { PolygonStyleProps } from '../shapes/polygon';
import type { PolygonStyleProps } from '../shapes';
import { Polygon } from '../shapes/polygon';
/**

View File

@ -6,6 +6,7 @@ import { parseSize } from '../../utils/size';
import { Circle } from './circle';
import type { BaseStyleProps, DisplayObjectConfig, Group } from '@antv/g';
import type { CategoricalPalette } from '../../palettes/types';
import type { DonutRound, Prefix } from '../../types';
import type { CircleStyleProps } from './circle';
@ -21,7 +22,7 @@ export interface DonutStyleProps extends CircleStyleProps, Prefix<'donut', BaseS
* <en/> Inner ring radius, using percentage or pixel value.
* @defaultValue '50%'
*/
innerRadius?: string | number;
innerR?: string | number;
/**
* <zh/>
*
@ -34,7 +35,7 @@ export interface DonutStyleProps extends CircleStyleProps, Prefix<'donut', BaseS
* <en/> Color or palette.
* @defaultValue 'tableau'
*/
colors?: string | string[];
donutPalette?: string | CategoricalPalette;
}
/**
@ -44,33 +45,43 @@ export interface DonutStyleProps extends CircleStyleProps, Prefix<'donut', BaseS
*/
export class Donut extends Circle {
static defaultStyleProps: Partial<DonutStyleProps> = {
innerRadius: '50%',
innerR: '50%',
donuts: [],
colors: 'tableau',
donutPalette: 'tableau',
};
constructor(options: DisplayObjectConfig<DonutStyleProps>) {
super(deepMix({}, { style: Donut.defaultStyleProps }, options));
}
private parseOuterR() {
const { size } = this.parsedAttributes as Required<DonutStyleProps>;
return Math.min(...parseSize(size)) / 2;
}
private parseInnerR() {
const { innerR } = this.parsedAttributes as Required<DonutStyleProps>;
return isString(innerR) ? (parseInt(innerR) / 100) * this.parseOuterR() : innerR;
}
protected drawDonutShape(attributes: Required<DonutStyleProps>, container: Group): void {
const { donuts, innerRadius = 0, size } = attributes;
const { donuts } = attributes;
if (!donuts?.length) return;
const parsedDonuts = donuts.map((round) => (isNumber(round) ? { value: round } : round) as DonutRound);
const style = subStyleProps<BaseStyleProps>(this.getGraphicStyle(attributes), 'donut');
const colors = getPaletteColors(attributes.colors);
const colors = getPaletteColors(attributes.donutPalette);
if (!colors) return;
const sum = parsedDonuts.reduce((acc, cur) => acc + (cur.value ?? 0), 0);
const outerR = this.parseOuterR();
const innerR = this.parseInnerR();
let start = 0;
parsedDonuts.forEach((round, index) => {
const { value = 0, color = colors[index % colors.length], ...roundStyle } = round;
const outerR = parseSize(size)[0] / 2;
const innerR = isString(innerRadius) ? (outerR * parseInt(innerRadius)) / 100 : innerRadius;
const angle = (sum === 0 ? 1 / parsedDonuts.length : value / sum) * 360;
this.upsert(

View File

@ -2,8 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g';
import { ICON_SIZE_RATIO } from '../../constants/element';
import type { Point } from '../../types';
import { getHexagonPoints } from '../../utils/element';
import type { IconStyleProps } from '../shapes';
import type { PolygonStyleProps } from '../shapes/polygon';
import type { IconStyleProps, PolygonStyleProps } from '../shapes';
import { Polygon } from '../shapes/polygon';
/**
@ -13,9 +12,9 @@ import { Polygon } from '../shapes/polygon';
*/
export interface HexagonStyleProps extends PolygonStyleProps {
/**
* <zh/>
* <zh/>
*
* <en/> outer radius
* <en/> outer radius, default is half of the minimum value of width and height
*/
outerR?: number;
}

View File

@ -2,8 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g';
import { ICON_SIZE_RATIO } from '../../constants/element';
import type { NodePortStyleProps, Point, StarPortPlacement } from '../../types';
import { getPortXYByPlacement, getStarPoints, getStarPorts } from '../../utils/element';
import type { IconStyleProps } from '../shapes';
import type { PolygonStyleProps } from '../shapes/polygon';
import type { IconStyleProps, PolygonStyleProps } from '../shapes';
import { Polygon } from '../shapes/polygon';
/**

View File

@ -5,8 +5,8 @@ import type { NodePortStyleProps, Point, TriangleDirection, TrianglePortPlacemen
import { getIncircleRadius, getTriangleCenter } from '../../utils/bbox';
import { getPortXYByPlacement, getTrianglePoints, getTrianglePorts } from '../../utils/element';
import { subStyleProps } from '../../utils/prefix';
import type { PolygonStyleProps } from '../shapes';
import { IconStyleProps } from '../shapes';
import type { PolygonStyleProps } from '../shapes/polygon';
import { Polygon } from '../shapes/polygon';
/**
@ -19,6 +19,7 @@ export interface TriangleStyleProps extends PolygonStyleProps {
* <zh/>
*
* <en/> The direction of the triangle
* @defaultValue 'up'
*/
direction?: TriangleDirection;
}

View File

@ -15,3 +15,4 @@ export type { BaseShapeStyleProps } from './base-shape';
export type { ContourStyleProps } from './contour';
export type { IconStyleProps } from './icon';
export type { LabelStyleProps } from './label';
export type { PolygonStyleProps } from './polygon';

View File

@ -12,11 +12,12 @@ import { BaseNode } from '../nodes/base-node';
*/
export interface PolygonStyleProps extends BaseNodeStyleProps {
/**
* <zh/>.
* <zh/>
*
* <en/> The vertex coordinates of the polygon
* @internal
*/
points: ([number, number] | [number, number, number])[];
points?: ([number, number] | [number, number, number])[];
}
/**

View File

@ -141,7 +141,13 @@ export type {
StarStyleProps,
TriangleStyleProps,
} from './elements/nodes';
export type { BadgeStyleProps, BaseShapeStyleProps, IconStyleProps, LabelStyleProps } from './elements/shapes';
export type {
BadgeStyleProps,
BaseShapeStyleProps,
IconStyleProps,
LabelStyleProps,
PolygonStyleProps,
} from './elements/shapes';
export type { ContourLabelStyleProps, ContourStyleProps } from './elements/shapes/contour';
export type { AnimationOptions, BaseLayoutOptions, WebWorkerLayoutOptions } from './layouts/types';
export type { CategoricalPalette } from './palettes/types';
@ -223,6 +229,7 @@ export type {
Size,
State,
TransformOptions,
TriangleDirection,
Vector2,
Vector3,
ViewportAnimationEffectTiming,

View File

@ -3,13 +3,13 @@ createGraph(
{
data: {
nodes: [
{ id: 'node1', style: { x: 250, y: 150, parentId: 'combo1' } },
{ id: 'node2', style: { x: 350, y: 150, parentId: 'combo1' } },
{ id: 'node3', style: { x: 250, y: 300, parentId: 'combo2' } },
{ id: 'node1', combo: 'combo1', style: { x: 250, y: 150 } },
{ id: 'node2', combo: 'combo1', style: { x: 350, y: 150 } },
{ id: 'node3', combo: 'combo2', style: { x: 250, y: 300 } },
],
edges: [],
combos: [
{ id: 'combo1', style: { parentId: 'combo2' } },
{ id: 'combo1', combo: 'combo2' },
{ id: 'combo2', style: {} },
],
},

View File

@ -3,13 +3,13 @@ createGraph(
{
data: {
nodes: [
{ id: 'node1', style: { x: 250, y: 150, parentId: 'combo1' } },
{ id: 'node2', style: { x: 350, y: 150, parentId: 'combo1' } },
{ id: 'node3', style: { x: 250, y: 300, parentId: 'combo2' } },
{ id: 'node1', combo: 'combo1', style: { x: 250, y: 150 } },
{ id: 'node2', combo: 'combo1', style: { x: 350, y: 150 } },
{ id: 'node3', combo: 'combo2', style: { x: 250, y: 300 } },
],
edges: [],
combos: [
{ id: 'combo1', style: { parentId: 'combo2' } },
{ id: 'combo1', combo: 'combo2' },
{ id: 'combo2', style: {} },
],
},

View File

@ -3,13 +3,13 @@ createGraph(
{
data: {
nodes: [
{ id: 'node1', style: { x: 250, y: 150, parentId: 'combo1' } },
{ id: 'node2', style: { x: 350, y: 150, parentId: 'combo1' } },
{ id: 'node3', style: { x: 250, y: 300, parentId: 'combo2' } },
{ id: 'node1', combo: 'combo1', style: { x: 250, y: 150 } },
{ id: 'node2', combo: 'combo1', style: { x: 350, y: 150 } },
{ id: 'node3', combo: 'combo2', style: { x: 250, y: 300 } },
],
edges: [],
combos: [
{ id: 'combo1', style: { parentId: 'combo2' } },
{ id: 'combo1', combo: 'combo2' },
{ id: 'combo2', style: {} },
],
},

View File

@ -0,0 +1,71 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{ id: 'node1', combo: 'combo1', style: { x: 250, y: 150 } },
{ id: 'node2', combo: 'combo1', style: { x: 350, y: 150 } },
{ id: 'node3', combo: 'combo2', style: { x: 250, y: 300 } },
],
combos: [
{ id: 'combo1', combo: 'combo2' },
{ id: 'combo2', style: {} },
],
},
combo: {
style: {
labelText: (d) => d.id,
labelPadding: [1, 5],
labelFill: '#fff',
labelBackground: true,
labelBackgroundRadius: 10,
labelBackgroundFill: '#7863FF',
},
},
behaviors: ['collapse-expand'],
plugins: ['grid-line'],
animation: true,
},
{ width: 600, height: 400 },
(gui, graph) => {
const options = {
collapsed: false,
collapsedSize: 32,
collapsedOrigin: [0.5, 0.5],
collapsedMarker: true,
collapsedMarkerFontSize: 12,
collapsedMarkerType: 'child-count',
};
const optionFolder = gui.addFolder('combo2.style');
optionFolder.add(options, 'collapsed');
optionFolder.add(options, 'collapsedSize', 0, 100, 1);
optionFolder.add(options, 'collapsedOrigin', [
[0.5, 0.5],
'left',
'right',
'top',
'bottom',
'left-top',
'left-bottom',
'right-top',
'right-bottom',
'top-left',
'top-right',
'bottom-left',
'bottom-right',
'center',
]);
optionFolder.add(options, 'collapsedMarker');
optionFolder.add(options, 'collapsedMarkerFontSize', 12, 20, 1);
optionFolder.add(options, 'collapsedMarkerType', ['child-count', 'descendant-count', 'node-count']);
optionFolder.onChange(({ property, value }) => {
graph.updateComboData([{ id: 'combo2', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,24 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{ id: 'node1', combo: 'combo1', style: { x: 250, y: 150 } },
{ id: 'node2', combo: 'combo1', style: { x: 350, y: 150 } },
{ id: 'node3', combo: 'combo2', style: { x: 250, y: 300 } },
],
combos: [
{ id: 'combo1', combo: 'combo2' },
{ id: 'combo2', style: {} },
],
},
behaviors: ['collapse-expand'],
plugins: ['grid-line'],
animation: true,
},
{ width: 600, height: 400 },
(gui, graph) => {
gui.add({ type: 'circle' }, 'type').disable();
},
);
```

View File

@ -0,0 +1,25 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{ id: 'node1', combo: 'combo1', style: { x: 250, y: 150 } },
{ id: 'node2', combo: 'combo1', style: { x: 350, y: 150 } },
{ id: 'node3', combo: 'combo2', style: { x: 250, y: 300 } },
],
combos: [
{ id: 'combo1', combo: 'combo2' },
{ id: 'combo2', style: {} },
],
},
combo: { type: 'rect' },
behaviors: ['collapse-expand'],
plugins: ['grid-line'],
animation: true,
},
{ width: 600, height: 400 },
(gui, graph) => {
gui.add({ type: 'rect' }, 'type').disable();
},
);
```

View File

@ -0,0 +1,122 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{ id: 'node1', style: { x: 150, y: 250 } },
{ id: 'node2', style: { x: 400, y: 250 } },
],
edges: [{ id: 'edge1', source: 'node1', target: 'node2', style: { labelText: 'node1 👉 node2' } }],
},
node: { style: { labelText: (d) => d.id } },
behaviors: ['drag-canvas', 'drag-element'],
plugins: ['grid-line'],
},
{ width: 600, height: 500 },
(gui, graph) => {
const options = {
lineWidth: 1,
opacity: 1,
stroke: '#99add1',
startArrow: false,
startArrowSize: 8,
startArrowType: 'triangle',
endArrow: false,
endArrowSize: 8,
endArrowType: 'triangle',
label: true,
labelAutoRotate: true,
labelMaxWidth: '80%',
labelOffsetX: 0,
labelOffsetY: 0,
labelPadding: 0,
labelPlacement: 'center',
labelText: 'node1 👉 node2',
labelBackground: false,
labelBackgroundFill: '#fff',
labelBackgroundStroke: '#fff',
labelBackgroundLineDash: 0,
labelBackgroundLineWidth: 0,
labelBackgroundOpacity: 0.5,
labelBackgroundRadius: 0,
halo: false,
haloLineDash: 0,
haloLineWidth: 12,
haloStrokeOpacity: 0.25,
};
const optionFolder = gui.addFolder('edge.style');
optionFolder.add(options, 'lineWidth', 0, 20);
optionFolder.add(options, 'opacity', 0, 1);
optionFolder.addColor(options, 'stroke');
// startArrow
optionFolder.add(options, 'startArrow').onChange((v) => {
startArrowSize.show(v);
startArrowType.show(v);
});
const startArrowSize = optionFolder.add(options, 'startArrowSize', 0, 20).hide();
const startArrowType = optionFolder
.add(options, 'startArrowType', ['triangle', 'circle', 'diamond', 'vee', 'rect', 'triangleRect', 'simple'])
.hide();
// endArrow
optionFolder.add(options, 'endArrow').onChange((v) => {
endArrowSize.show(v);
endArrowType.show(v);
});
const endArrowSize = optionFolder.add(options, 'endArrowSize', 0, 20).hide();
const endArrowType = optionFolder
.add(options, 'endArrowType', ['triangle', 'circle', 'diamond', 'vee', 'rect', 'triangleRect', 'simple'])
.hide();
// label
optionFolder.add(options, 'label').onChange((v) => {
[labelAutoRotate, labelMaxWidth, labelOffsetX, labelOffsetY, labelPadding, labelPlacement, labelText].forEach(
(i) => i.show(v),
);
});
const labelAutoRotate = optionFolder.add(options, 'labelAutoRotate');
const labelMaxWidth = optionFolder.add(options, 'labelMaxWidth', ['80%', '20px', '200%']);
const labelOffsetX = optionFolder.add(options, 'labelOffsetX', 0, 50);
const labelOffsetY = optionFolder.add(options, 'labelOffsetY', 0, 50);
const labelPadding = optionFolder.add(options, 'labelPadding', 0, 20);
const labelPlacement = optionFolder.add(options, 'labelPlacement', ['start', 'center', 'end', 0.2, 0.8]);
const labelText = optionFolder.add(options, 'labelText');
const labelBackground = optionFolder.add(options, 'labelBackground').onChange((v) => {
[
labelBackgroundFill,
labelBackgroundStroke,
labelBackgroundLineDash,
labelBackgroundLineWidth,
labelBackgroundOpacity,
labelBackgroundRadius,
].forEach((i) => i.show(v));
});
const labelBackgroundFill = optionFolder.addColor(options, 'labelBackgroundFill').hide();
const labelBackgroundStroke = optionFolder.addColor(options, 'labelBackgroundStroke').hide();
const labelBackgroundLineDash = optionFolder.add(options, 'labelBackgroundLineDash', 0, 10).hide();
const labelBackgroundLineWidth = optionFolder.add(options, 'labelBackgroundLineWidth', 0, 10).hide();
const labelBackgroundOpacity = optionFolder.add(options, 'labelBackgroundOpacity', 0, 1).hide();
const labelBackgroundRadius = optionFolder.add(options, 'labelBackgroundRadius', 0, 30).hide();
const halo = optionFolder.add(options, 'halo').onChange((v) => {
[haloStrokeOpacity, haloLineDash, haloLineWidth].forEach((i) => i.show(v));
});
const haloStrokeOpacity = optionFolder.addColor(options, 'haloStrokeOpacity', 0, 1).hide();
const haloLineDash = optionFolder.add(options, 'haloLineDash', 0, 10).hide();
const haloLineWidth = optionFolder.add(options, 'haloLineWidth', 0, 10).hide();
optionFolder.onChange(({ property, value }) => {
graph.updateEdgeData([{ id: 'edge1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,46 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [{ id: 'node1' }, { id: 'node2' }, { id: 'node3' }, { id: 'node4' }, { id: 'node5' }, { id: 'node6' }],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'node1', target: 'node4' },
{ source: 'node1', target: 'node5' },
{ source: 'node1', target: 'node6' },
],
},
node: {
style: { port: true, ports: [{ placement: 'left' }, { placement: 'right' }] },
},
edge: { type: 'cubic-horizontal' },
behaviors: ['drag-canvas', 'drag-element'],
plugins: ['grid-line'],
layout: {
type: 'antv-dagre',
begin: [100, 50],
rankdir: 'LR',
nodesep: 15,
ranksep: 100,
},
},
{ width: 600, height: 400 },
(gui, graph) => {
gui.add({ type: 'cubic-horizontal' }, 'type').disable();
const options = {
curveOffset: 20,
curvePosition: 0.5,
};
const optionFolder = gui.addFolder('cubic-horizontal.style');
optionFolder.add(options, 'curveOffset', 0, 100);
optionFolder.add(options, 'curvePosition', 0, 1);
optionFolder.onChange(({ property, value }) => {
graph.updateEdgeData((prev) => prev.map((edge) => ({ ...edge, style: { [property]: value } })));
graph.render();
});
},
);
```

View File

@ -0,0 +1,46 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [{ id: 'node1' }, { id: 'node2' }, { id: 'node3' }, { id: 'node4' }, { id: 'node5' }, { id: 'node6' }],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'node1', target: 'node4' },
{ source: 'node1', target: 'node5' },
{ source: 'node1', target: 'node6' },
],
},
node: {
style: { port: true, ports: [{ placement: 'bottom' }, { placement: 'top' }] },
},
edge: { type: 'cubic-vertical' },
behaviors: ['drag-canvas', 'drag-element'],
plugins: ['grid-line'],
layout: {
type: 'antv-dagre',
begin: [100, 50],
rankdir: 'TB',
nodesep: 25,
ranksep: 80,
},
},
{ width: 600, height: 300 },
(gui, graph) => {
gui.add({ type: 'cubic-vertical' }, 'type').disable();
const options = {
curveOffset: 20,
curvePosition: 0.5,
};
const optionFolder = gui.addFolder('cubic-vertical.style');
optionFolder.add(options, 'curveOffset', 0, 100);
optionFolder.add(options, 'curvePosition', 0, 1);
optionFolder.onChange(({ property, value }) => {
graph.updateEdgeData((prev) => prev.map((edge) => ({ ...edge, style: { [property]: value } })));
graph.render();
});
},
);
```

View File

@ -0,0 +1,33 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{ id: 'node1', style: { x: 150, y: 150 } },
{ id: 'node2', style: { x: 350, y: 150 } },
],
edges: [{ id: 'edge1', source: 'node1', target: 'node2' }],
},
edge: { type: 'cubic' },
behaviors: ['drag-canvas', 'drag-element'],
plugins: ['grid-line'],
},
{ width: 600, height: 300 },
(gui, graph) => {
gui.add({ type: 'cubic' }, 'type').disable();
const options = {
curveOffset: 20,
curvePosition: 0.5,
};
const optionFolder = gui.addFolder('cubic.style');
optionFolder.add(options, 'curveOffset', 0, 100, 1);
optionFolder.add(options, 'curvePosition', 0, 1, 0.1);
optionFolder.onChange(({ property, value }) => {
graph.updateEdgeData([{ id: 'edge1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,19 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{ id: 'node1', style: { x: 150, y: 150 } },
{ id: 'node2', style: { x: 350, y: 150 } },
],
edges: [{ id: 'edge1', source: 'node1', target: 'node2' }],
},
behaviors: ['drag-canvas', 'drag-element'],
plugins: ['grid-line'],
},
{ width: 600, height: 300 },
(gui, graph) => {
gui.add({ type: 'line' }, 'type').disable();
},
);
```

View File

@ -0,0 +1,82 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{
id: 'node1',
style: { x: 150, y: 150 },
},
{
id: 'node2',
style: {
x: 400,
y: 150,
labelText: 'Drag Me!',
labelPadding: [1, 5],
labelBackground: true,
labelBackgroundRadius: 10,
labelBackgroundFill: '#99add1',
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
type: 'polyline',
style: {
router: true,
},
},
],
},
behaviors: ['drag-canvas', 'drag-element'],
plugins: ['grid-line'],
},
{ width: 600, height: 300 },
(gui, graph) => {
gui.add({ type: 'polyline' }, 'type').disable();
let index = 3;
const options = {
radius: 0,
router: true,
random: () => {
const x = Math.floor(Math.random() * 600);
const y = Math.floor(Math.random() * 300);
graph.addNodeData([
{
id: `node-${index}`,
style: {
size: 5,
fill: '#D580FF',
x,
y,
},
},
]);
index++;
graph.updateEdgeData((prev) => {
const targetEdgeData = prev.find((edge) => edge.id === 'edge1');
const controlPoints = [...(targetEdgeData.style.controlPoints || [])];
controlPoints.push([x, y]);
return [{ ...targetEdgeData, style: { ...targetEdgeData.style, controlPoints } }];
});
graph.render();
},
};
const optionFolder = gui.addFolder('polyline.style');
optionFolder.add(options, 'radius', 0, 100, 1);
optionFolder.add(options, 'router');
optionFolder.add(options, 'random').name('Add random node as control points');
optionFolder.onChange(({ property, value }) => {
if (property === 'random') return;
graph.updateEdgeData([{ id: 'edge1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,33 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{ id: 'node1', style: { x: 150, y: 150 } },
{ id: 'node2', style: { x: 350, y: 150 } },
],
edges: [{ id: 'edge1', source: 'node1', target: 'node2' }],
},
edge: { type: 'quadratic' },
behaviors: ['drag-canvas', 'drag-element'],
plugins: ['grid-line'],
},
{ width: 600, height: 300 },
(gui, graph) => {
gui.add({ type: 'quadratic' }, 'type').disable();
const options = {
curveOffset: 30,
curvePosition: 0.5,
};
const optionFolder = gui.addFolder('quadratic.style');
optionFolder.add(options, 'curveOffset', 0, 100);
optionFolder.add(options, 'curvePosition', 0, 1);
optionFolder.onChange(({ property, value }) => {
graph.updateEdgeData([{ id: 'edge1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,249 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{
id: 'node1',
style: {
x: 300,
y: 150,
size: 40,
label: false,
labelText: 'node',
icon: false,
iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
donuts: [30, 30, 20, 20],
donutPalette: ['#1783FF', '#00C9C9', '#F08F56', '#D580FF'],
badge: false,
badges: [{ text: 'Important' }],
port: false,
ports: [
{ key: 'left', placement: [0, 0.5] },
{ key: 'right', placement: [1, 0.5] },
],
portFill: '#00C9C9',
portR: 3,
portStroke: '#00C9C9',
},
},
],
},
plugins: ['grid-line'],
},
{ width: 600, height: 600 },
(gui, graph) => {
const global = { type: 'circle' };
gui
.add(global, 'type', ['circle', 'diamond', 'donut', 'ellipse', 'hexagon', 'image', 'rect', 'star', 'triangle'])
.onChange((v) => {
graph.updateNodeData([{ id: 'node1', type: v }]);
graph.render();
});
const options = {
x: 300,
y: 150,
fill: '#1783FF',
fillOpacity: 1,
lineWidth: 0,
'size[0]': 40,
'size[1]': 40,
stroke: '#000000',
strokeOpacity: 1,
label: false,
labelFill: '000000d9',
labelMaxWidth: '200%',
labelPadding: 0,
labelPlacement: 'bottom',
labelText: 'node',
labelWordWrap: false,
labelOpacity: 1,
labelBackground: false,
labelBackgroundFill: '#fff',
labelBackgroundLineDash: 0,
labelBackgroundLineWidth: 0,
labelBackgroundOpacity: 0.5,
labelBackgroundRadius: 0,
labelBackgroundStroke: '#fff',
halo: false,
haloLineDash: 0,
haloLineWidth: 12,
haloStrokeOpacity: 0.25,
icon: false,
iconFill: '#fff',
iconFontSize: 16,
iconOpacity: 1,
iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
iconText: '',
iconWidth: 20,
iconHeight: 20,
badge: false,
badgeFill: '000000d9',
badgeMaxWidth: '200%',
badgeOpacity: 1,
badgePadding: 0,
badgePlacement: 'top',
badgeText: 'Important',
badgeWordWrap: false,
badgeBackground: true,
badgeBackgroundFill: '#fff',
badgeBackgroundLineDash: 0,
badgeBackgroundLineWidth: 0,
badgeBackgroundOpacity: 0.5,
badgeBackgroundRadius: 0,
badgeBackgroundStroke: '#fff',
port: false,
portFill: '#00C9C9',
portR: 3,
portStroke: '#00C9C9',
};
const optionFolder = gui.addFolder('node.style');
optionFolder.add(options, 'x', 0, 600, 1);
optionFolder.add(options, 'y', 0, 300, 1);
optionFolder.add(options, 'size[0]', 0, 100).name('width(size[0])');
optionFolder.add(options, 'size[1]', 0, 100).name('height(size[1])');
optionFolder.add(options, 'lineWidth', 0, 20);
optionFolder.addColor(options, 'fill');
optionFolder.add(options, 'fillOpacity', 0, 1);
optionFolder.addColor(options, 'stroke');
optionFolder.add(options, 'strokeOpacity', 0, 1);
optionFolder.add(options, 'label').onChange((v) => {
[labelFill, labelMaxWidth, labelWordWrap, labelPadding, labelPlacement, labelText, labelOpacity].forEach((i) =>
i.show(v),
);
});
const labelFill = optionFolder.addColor(options, 'labelFill').hide();
const labelMaxWidth = optionFolder.add(options, 'labelMaxWidth', ['200%', '20px', '80%']).hide();
const labelWordWrap = optionFolder.add(options, 'labelWordWrap').hide();
const labelPadding = optionFolder.add(options, 'labelPadding', 0, 20).hide();
const labelPlacement = optionFolder
.add(options, 'labelPlacement', [
'left',
'right',
'top',
'bottom',
'left-top',
'left-bottom',
'right-top',
'right-bottom',
'top-left',
'top-right',
'bottom-left',
'bottom-right',
'center',
])
.hide();
const labelText = optionFolder.add(options, 'labelText').hide();
const labelOpacity = optionFolder.add(options, 'labelOpacity', 0, 1).hide();
const labelBackground = optionFolder.add(options, 'labelBackground').onChange((v) => {
[
labelBackgroundFill,
labelBackgroundStroke,
labelBackgroundLineDash,
labelBackgroundLineWidth,
labelBackgroundOpacity,
labelBackgroundRadius,
].forEach((i) => i.show(v));
});
const labelBackgroundFill = optionFolder.addColor(options, 'labelBackgroundFill').hide();
const labelBackgroundStroke = optionFolder.addColor(options, 'labelBackgroundStroke').hide();
const labelBackgroundLineDash = optionFolder.add(options, 'labelBackgroundLineDash', 0, 10).hide();
const labelBackgroundLineWidth = optionFolder.add(options, 'labelBackgroundLineWidth', 0, 10).hide();
const labelBackgroundOpacity = optionFolder.add(options, 'labelBackgroundOpacity', 0, 1).hide();
const labelBackgroundRadius = optionFolder.add(options, 'labelBackgroundRadius', 0, 30).hide();
const halo = optionFolder.add(options, 'halo').onChange((v) => {
[haloStrokeOpacity, haloLineDash, haloLineWidth].forEach((i) => i.show(v));
});
const haloStrokeOpacity = optionFolder.add(options, 'haloStrokeOpacity', 0, 1).hide();
const haloLineDash = optionFolder.add(options, 'haloLineDash', 0, 10).hide();
const haloLineWidth = optionFolder.add(options, 'haloLineWidth', 0, 10).hide();
const icon = optionFolder.add(options, 'icon').onChange((v) => {
[iconSrc, iconText, iconFill, iconFontSize, iconOpacity, iconWidth, iconHeight].forEach((i) => i.show(v));
});
const iconSrc = optionFolder.add(options, 'iconSrc').hide();
const iconText = optionFolder.add(options, 'iconText').hide();
const iconFill = optionFolder.addColor(options, 'iconFill').hide();
const iconFontSize = optionFolder.add(options, 'iconFontSize', 12, 20, 1).hide();
const iconOpacity = optionFolder.add(options, 'iconOpacity', 0, 1).hide();
const iconWidth = optionFolder.add(options, 'iconWidth', 0, 100, 1).hide();
const iconHeight = optionFolder.add(options, 'iconHeight', 0, 100, 1).hide();
const badge = optionFolder.add(options, 'badge').onChange((v) => {
[badgeFill, badgeMaxWidth, badgeWordWrap, badgePadding, badgePlacement, badgeText, badgeOpacity].forEach((i) =>
i.show(v),
);
});
const badgeFill = optionFolder.addColor(options, 'badgeFill').hide();
const badgeMaxWidth = optionFolder.add(options, 'badgeMaxWidth', ['200%', '20px', '80%']).hide();
const badgeWordWrap = optionFolder.add(options, 'badgeWordWrap').hide();
const badgePadding = optionFolder.add(options, 'badgePadding', 0, 20).hide();
const badgePlacement = optionFolder
.add(options, 'badgePlacement', [
'left',
'right',
'top',
'bottom',
'left-top',
'left-bottom',
'right-top',
'right-bottom',
'top-left',
'top-right',
'bottom-left',
'bottom-right',
])
.hide();
const badgeText = optionFolder.add(options, 'badgeText').hide();
const badgeOpacity = optionFolder.add(options, 'badgeOpacity', 0, 1).hide();
const badgeBackground = optionFolder.add(options, 'badgeBackground').onChange((v) => {
[
badgeBackgroundFill,
badgeBackgroundStroke,
badgeBackgroundLineDash,
badgeBackgroundLineWidth,
badgeBackgroundOpacity,
badgeBackgroundRadius,
].forEach((i) => i.show(v));
});
const badgeBackgroundFill = optionFolder.addColor(options, 'badgeBackgroundFill').hide();
const badgeBackgroundStroke = optionFolder.addColor(options, 'badgeBackgroundStroke').hide();
const badgeBackgroundLineDash = optionFolder.add(options, 'badgeBackgroundLineDash', 0, 10).hide();
const badgeBackgroundLineWidth = optionFolder.add(options, 'badgeBackgroundLineWidth', 0, 10).hide();
const badgeBackgroundOpacity = optionFolder.add(options, 'badgeBackgroundOpacity', 0, 1).hide();
const badgeBackgroundRadius = optionFolder.add(options, 'badgeBackgroundRadius', 0, 30).hide();
const port = optionFolder.add(options, 'port').onChange((v) => {
[portR, portFill, portStroke].forEach((i) => i.show(v));
});
const portR = optionFolder.add(options, 'portR', 0, 20, 1).hide();
const portFill = optionFolder.addColor(options, 'portFill').hide();
const portStroke = optionFolder.addColor(options, 'portStroke').hide();
optionFolder.onChange(({ property, value, object }) => {
let updateStyle = { [property]: value };
if (['size[0]', 'size[1]'].includes(property)) {
updateStyle.size = [object['size[0]'], object['size[1]']];
} else if (['badgePlacement', 'badgeText'].includes(property)) {
const nodeData = graph.getNodeData('node1').badges;
updateStyle.badges = [{ text: object.badgeText, placement: object.badgePlacement }];
}
graph.updateNodeData([{ id: 'node1', style: updateStyle }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,22 @@
```js | ob { pin: false }
createGraph(
{
data: { nodes: [{ id: 'node1', style: { x: 300, y: 110, size: 40 } }] },
node: { type: 'circle' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'circle' }, 'type').disable();
const options = { size: 40 };
const optionFolder = gui.addFolder('circle.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,22 @@
```js | ob { pin: false }
createGraph(
{
data: { nodes: [{ id: 'node1', style: { x: 300, y: 110, size: 40 } }] },
node: { type: 'diamond' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'diamond' }, 'type').disable();
const options = { size: 40 };
const optionFolder = gui.addFolder('diamond.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,43 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{
id: 'node1',
style: {
x: 300,
y: 110,
fill: 'transparent',
size: 60,
donuts: [30, 30, 20, 20],
donutPalette: ['#1783FF', '#00C9C9', '#F08F56', '#D580FF'],
},
},
],
},
node: { type: 'donut' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'donut' }, 'type').disable();
const options = {
size: 60,
innerR: 50,
donutPalette: ['#1783FF', '#00C9C9', '#F08F56', '#D580FF'],
};
const optionFolder = gui.addFolder('donut.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.add(options, 'innerR', 0, 100, 1).name('innerR(%)');
optionFolder.add(options, 'donutPalette', ['spectral', 'tableau', ['#1783FF', '#00C9C9', '#F08F56', '#D580FF']]);
optionFolder.onChange(({ property, value }) => {
if (property === 'innerR') value = value + '%';
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,24 @@
```js | ob { pin: false }
createGraph(
{
data: { nodes: [{ id: 'node1', style: { x: 300, y: 110 } }] },
node: { type: 'ellipse' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'ellipse' }, 'type').disable();
const options = { 'size[0]': 80, 'size[1]': 40 };
const optionFolder = gui.addFolder('ellipse.style');
optionFolder.add(options, 'size[0]', 0, 100, 1);
optionFolder.add(options, 'size[1]', 0, 100, 1);
optionFolder.onChange(({ object }) => {
graph.updateNodeData([{ id: 'node1', style: { size: [object['size[0]'], object['size[1]']] } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,26 @@
```js | ob { pin: false }
createGraph(
{
data: { nodes: [{ id: 'node1', style: { x: 300, y: 110, size: 40 } }] },
node: { type: 'hexagon' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'hexagon' }, 'type').disable();
const options = {
size: 40,
outerR: 0,
};
const optionFolder = gui.addFolder('hexagon.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.add(options, 'outerR', 0, 100);
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,48 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{
id: 'node1',
style: {
x: 300,
y: 110,
size: [120, 40],
innerHTML: `
<div style="width: 100%; height: 100%; background: #7863FF; display: flex; justify-content: center; align-items: center;">
<span style="color: #fff; font-size: 12px;">
HTML Node
</span>
</div>`,
},
},
],
},
node: { type: 'html' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'html' }, 'type').disable();
const options = {
size: 50,
innerHTML: `
<div style="width: 100%; height: 100%; background: #7863FF; display: flex; justify-content: center; align-items: center;">
<span style="color: #fff; font-size: 20px;">
'HTML Node'
</span>
</div>`,
};
const optionFolder = gui.addFolder('html.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.add(options, 'innerHTML');
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,37 @@
```js | ob { pin: false }
createGraph(
{
data: {
nodes: [
{
id: 'node1',
style: {
x: 300,
y: 110,
src: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ',
},
},
],
},
node: { type: 'image' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'image' }, 'type').disable();
const options = {
size: 50,
src: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ',
};
const optionFolder = gui.addFolder('image.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.add(options, 'src');
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,24 @@
```js | ob { pin: false }
createGraph(
{
data: { nodes: [{ id: 'node1', style: { x: 300, y: 110 } }] },
node: { type: 'rect' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'rect' }, 'type').disable();
const options = { 'size[0]': 48, 'size[1]': 24 };
const optionFolder = gui.addFolder('rect.style');
optionFolder.add(options, 'size[0]', 0, 100, 1);
optionFolder.add(options, 'size[1]', 0, 100, 1);
optionFolder.onChange(({ object }) => {
graph.updateNodeData([{ id: 'node1', style: { size: [object['size[0]'], object['size[1]']] } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,26 @@
```js | ob { pin: false }
createGraph(
{
data: { nodes: [{ id: 'node1', style: { x: 300, y: 110, size: 40 } }] },
node: { type: 'star' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'star' }, 'type').disable();
const options = {
size: 40,
innerR: 0,
};
const optionFolder = gui.addFolder('star.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.add(options, 'innerR', 0, 100);
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -0,0 +1,26 @@
```js | ob { pin: false }
createGraph(
{
data: { nodes: [{ id: 'node1', style: { x: 300, y: 110 } }] },
node: { type: 'triangle' },
plugins: ['grid-line'],
},
{ width: 600, height: 220 },
(gui, graph) => {
gui.add({ type: 'triangle' }, 'type').disable();
const options = {
size: 40,
direction: 'up',
};
const optionFolder = gui.addFolder('triangle.style');
optionFolder.add(options, 'size', 0, 100, 1);
optionFolder.add(options, 'direction', ['up', 'left', 'right', 'down']);
optionFolder.onChange(({ property, value }) => {
graph.updateNodeData([{ id: 'node1', style: { [property]: value } }]);
graph.render();
});
},
);
```

View File

@ -22,7 +22,7 @@ const graph = new Graph({
type: 'donut', // 👈🏻 Node shape type.
size: 80,
fill: '#DB9D0D',
innerRadius: 0.5,
innerR: 0.5,
donuts: (v, index) => {
if (index === 0) return [1, 2, 3];

View File

@ -1,8 +1,34 @@
/**
* @file Generate API Markdown documentation from the API Document Model files
*/
import { MarkdownAction } from '../src/MarkdownAction';
import { ApiModel } from '@microsoft/api-extractor-model';
import { FileSystem } from '@rushstack/node-core-library';
import * as path from 'path';
import { MarkdownDocumenter } from '../src/MarkdownDocumenter';
(function runApiDocumenter() {
new MarkdownAction().onExecute();
const inputFolder = 'support/api';
const outputFolder = 'docs/api';
(async function runApiDocumenter() {
const apiModel: ApiModel = new ApiModel();
if (!FileSystem.exists(inputFolder)) {
throw new Error('The input folder does not exist: ' + inputFolder);
}
FileSystem.ensureFolder(outputFolder);
for (const filename of FileSystem.readFolderItemNames(inputFolder)) {
if (filename.match(/\.api\.json$/i)) {
console.log(`Reading ${filename}`);
const filenamePath: string = path.join(inputFolder, filename);
apiModel.loadPackage(filenamePath);
}
}
const markdownDocumenter: MarkdownDocumenter = new MarkdownDocumenter({
apiModel,
outputFolder,
});
await markdownDocumenter.generateFiles();
})();

View File

@ -1,112 +0,0 @@
import type { ApiItem, IResolveDeclarationReferenceResult } from '@microsoft/api-extractor-model';
import { ApiDocumentedItem, ApiItemContainerMixin, ApiModel } from '@microsoft/api-extractor-model';
import type * as tsdoc from '@microsoft/tsdoc';
import { FileSystem } from '@rushstack/node-core-library';
import * as path from 'path';
import { MarkdownDocumenter } from './MarkdownDocumenter';
import { inputFolder, outputFolder } from './setting';
interface IBuildApiModelResult {
apiModel: ApiModel;
inputFolder: string;
outputFolder: string;
}
export class MarkdownAction {
public async onExecute(): Promise<void> {
const { apiModel, outputFolder } = this.buildApiModel();
const markdownDocumenter: MarkdownDocumenter = new MarkdownDocumenter({
apiModel,
outputFolder,
});
await markdownDocumenter.generateFiles();
}
/**
* Builds the ApiModel by loading all .api.json files from the input folder.
* @returns The ApiModel and the input/output folders.
*/
protected buildApiModel(): IBuildApiModelResult {
const apiModel: ApiModel = new ApiModel();
if (!FileSystem.exists(inputFolder)) {
throw new Error('The input folder does not exist: ' + inputFolder);
}
FileSystem.ensureFolder(outputFolder);
for (const filename of FileSystem.readFolderItemNames(inputFolder)) {
if (filename.match(/\.api\.json$/i)) {
console.log(`Reading ${filename}`);
const filenamePath: string = path.join(inputFolder, filename);
apiModel.loadPackage(filenamePath);
}
}
this._applyInheritDoc(apiModel, apiModel);
return { apiModel, inputFolder, outputFolder };
}
// TODO: This is a temporary workaround. The long term plan is for API Extractor's DocCommentEnhancer
// to apply all @inheritDoc tags before the .api.json file is written.
// See DocCommentEnhancer._applyInheritDoc() for more info.
private _applyInheritDoc(apiItem: ApiItem, apiModel: ApiModel): void {
if (apiItem instanceof ApiDocumentedItem) {
if (apiItem.tsdocComment) {
const inheritDocTag: tsdoc.DocInheritDocTag | undefined = apiItem.tsdocComment.inheritDocTag;
if (inheritDocTag && inheritDocTag.declarationReference) {
// Attempt to resolve the declaration reference
const result: IResolveDeclarationReferenceResult = apiModel.resolveDeclarationReference(
inheritDocTag.declarationReference,
apiItem,
);
if (result.errorMessage) {
console.log(`Warning: Unresolved @inheritDoc tag for ${apiItem.displayName}: ` + result.errorMessage);
} else {
if (
result.resolvedApiItem instanceof ApiDocumentedItem &&
result.resolvedApiItem.tsdocComment &&
result.resolvedApiItem !== apiItem
) {
this._copyInheritedDocs(apiItem.tsdocComment, result.resolvedApiItem.tsdocComment);
}
}
}
}
}
// Recurse members
if (ApiItemContainerMixin.isBaseClassOf(apiItem)) {
for (const member of apiItem.members) {
this._applyInheritDoc(member, apiModel);
}
}
}
/**
* Copy the content from `sourceDocComment` to `targetDocComment`.
* This code is borrowed from DocCommentEnhancer as a temporary workaround.
* @param targetDocComment - The target doc comment to copy into
* @param sourceDocComment - The source doc comment to copy from
* @internal
*/
private _copyInheritedDocs(targetDocComment: tsdoc.DocComment, sourceDocComment: tsdoc.DocComment): void {
targetDocComment.summarySection = sourceDocComment.summarySection;
targetDocComment.remarksBlock = sourceDocComment.remarksBlock;
targetDocComment.params.clear();
for (const param of sourceDocComment.params) {
targetDocComment.params.add(param);
}
for (const typeParam of sourceDocComment.typeParams) {
targetDocComment.typeParams.add(typeParam);
}
targetDocComment.returnsBlock = sourceDocComment.returnsBlock;
targetDocComment.inheritDocTag = undefined;
}
}

View File

@ -50,8 +50,9 @@ import { FileSystem, NewlineKind, PackageName } from '@rushstack/node-core-libra
import { camelCase, isBoolean, kebabCase, upperFirst } from 'lodash';
import * as path from 'path';
import prettier from 'prettier';
import { Keyword, LocaleLanguage, getApiCategoryIntl, getHelperIntl, getKeywordIntl } from './constants';
import { intl } from './constants';
import { links } from './constants/link';
import { Keyword, LocaleLanguage, LocaleType } from './constants/locales/enum';
import { CustomMarkdownEmitter } from './markdown/CustomMarkdownEmitter';
import { CustomDocNodes } from './nodes/CustomDocNodeKind';
import { DocContainer } from './nodes/DocContainer';
@ -65,7 +66,6 @@ import { DocTableCell } from './nodes/DocTableCell';
import { DocTableRow } from './nodes/DocTableRow';
import { DocText } from './nodes/DocText';
import { DocUnorderedList } from './nodes/DocUnorderedList';
import { outputFolder, referenceFoldername } from './setting';
import { Utilities } from './utils/Utilities';
import {
ICustomExcerptToken,
@ -73,11 +73,17 @@ import {
liftPrefixExcerptTokens,
parseExcerptTokens,
} from './utils/excerpt-token';
import { initGitignore, syncToGitignore } from './utils/gitignore';
import { getBlockTagByName } from './utils/parser';
const referenceFoldername = 'reference';
const supportedApiItems = [ApiItemKind.Interface, ApiItemKind.Enum, ApiItemKind.Class, ApiItemKind.TypeAlias];
const hyperlinkReferences = ['BaseNodeStyleProps'];
// 需要跳过解析的复杂类型 | Complex types that need to be skipped for parsing
export const interfacesToSkipParsing = ['BaseNodeStyleProps', 'BaseEdgeStyleProps', 'BaseComboStyleProps'];
const typesToSkipParsing = ['CallableValue'];
/**
* A page and its associated API items.
@ -130,7 +136,7 @@ export class MarkdownDocumenter {
}
public async generateFiles() {
this._initGitignore();
initGitignore(this._outputFolder);
const collectedData = this._initPageData(this._apiModel);
@ -466,10 +472,11 @@ export class MarkdownDocumenter {
const isBase = pageData.name.startsWith('Base');
if (apiClass && !isBase) {
this._writeSummarySection(output, apiClass);
this._writeRemarksSection(output, apiClass);
}
this._assertDemo(output, pageData);
if (apiInterface) {
this._writeElementOptions(output, apiInterface, pageData, isBase);
}
@ -482,31 +489,26 @@ export class MarkdownDocumenter {
/**
* Special for Element options, which needs to handle `Prefix`
*/
private _writeElementOptions(
output: DocSection,
apiInterface: ApiInterface,
pageData: IPageData,
includeExcerptTokens: boolean,
) {
private _writeElementOptions(output: DocSection, apiInterface: ApiInterface, pageData: IPageData, isBase: boolean) {
const configuration: TSDocConfiguration = this._tsdocConfiguration;
if (!includeExcerptTokens) {
if (!isBase) {
const elementType = pageData.group.split('/')[1].slice(0, -1);
const baseStyleFileName = upperFirst(camelCase(`base ${elementType}`));
output.appendNode(
new DocContainer({ configuration, status: 'info', title: getHelperIntl('remarks', this.locale) }, [
new DocParagraph({ configuration }, [
new DocPlainText({
configuration,
text:
getHelperIntl('basePropsStyleHelper', this.locale) + `(./${baseStyleFileName}.${this._getLang()}.md)`,
}),
]),
new DocParagraph({ configuration }, [
new DocText({
configuration,
text:
'> ' +
intl(LocaleType.HELPER, 'basePropsStyleHelper', this.locale) +
` [${baseStyleFileName}](./${baseStyleFileName}.${this._getLang()}.md)`,
}),
]),
);
}
this._writeOptions(output, apiInterface, { showTitle: false, includeExcerptTokens });
this._writeOptions(output, apiInterface, { showTitle: false, includeExcerptTokens: true });
}
private _getLinkFromExcerptToken(excerptToken: ICustomExcerptToken): string | undefined {
@ -569,7 +571,7 @@ export class MarkdownDocumenter {
const filterTokens = (tokens: ICustomExcerptToken[]): ICustomExcerptToken[] => {
return tokens
.filter((token) => token.type !== 'Prefix')
.filter((token) => token.type !== 'Prefix' && !interfacesToSkipParsing.includes(token.text))
.map((token) => ({
...token,
children: token.children ? filterTokens(token.children) : [],
@ -653,13 +655,6 @@ export class MarkdownDocumenter {
private _writeExcerptTokens(output: DocSection, customExcerptTokens: ICustomExcerptToken[]) {
const configuration: TSDocConfiguration = this._tsdocConfiguration;
customExcerptTokens.forEach((customExcerptToken) => {
if (hyperlinkReferences.includes(customExcerptToken.text)) {
delete customExcerptToken.children;
delete customExcerptToken.interface;
}
});
for (const customExcerptToken of customExcerptTokens) {
const textNodes: DocPlainText[] = [];
@ -704,7 +699,7 @@ export class MarkdownDocumenter {
const helper = (key: string) =>
linkMd +
this._intl(Keyword.LEFT_PARENTHESIS) +
getHelperIntl(key, this.locale) +
intl(LocaleType.HELPER, key, this.locale) +
' ' +
fieldString +
' ' +
@ -740,7 +735,7 @@ export class MarkdownDocumenter {
new DocParagraph({ configuration }, [
new DocPlainText({
configuration,
text: getHelperIntl('advancedPropsHelper', this.locale) + this._intl(Keyword.COLON),
text: intl(LocaleType.HELPER, 'advancedPropsHelper', this.locale) + this._intl(Keyword.COLON),
}),
...textNodes
.map((node) => [node, new DocPlainText({ configuration, text: ', ' })])
@ -755,7 +750,9 @@ export class MarkdownDocumenter {
new DocParagraph({ configuration }, [
new DocPlainText({
configuration,
text: getHelperIntl('prefixHelper', this.locale) + `(../../reference/g6.prefix.${this._getLang()}.md)`,
text:
intl(LocaleType.HELPER, 'prefixHelper', this.locale) +
`(../../reference/g6.prefix.${this._getLang()}.md)`,
}),
]),
);
@ -763,7 +760,7 @@ export class MarkdownDocumenter {
if (content.length > 0) {
output.appendNode(
new DocContainer({ configuration, status: 'info', title: getHelperIntl('remarks', this.locale) }, content),
new DocContainer({ configuration, status: 'info', title: this._intl(Keyword.REMARKS) }, content),
);
}
@ -836,7 +833,7 @@ export class MarkdownDocumenter {
Object.entries(groupMembers).forEach(([category, apiMembers]) => {
if (category !== 'undeclared' && showSubTitle) {
const title = getApiCategoryIntl(category, this.locale);
const title = intl(LocaleType.API_CATEGORY, category, this.locale);
output.appendNode(new DocHeading({ configuration, title, level: 1 }));
}
if (apiMembers.length > 0) {
@ -959,38 +956,7 @@ export class MarkdownDocumenter {
convertLineEndings: NewlineKind.CrLf,
});
this._syncToGitignore(filename);
}
private _syncToGitignore(filename: string) {
const gitignoreUrl = outputFolder + '/.gitignore';
const relativeFilename = filename.replace(`${outputFolder}/`, '');
if (FileSystem.exists(gitignoreUrl)) {
const gitignoreContent = FileSystem.readFile(gitignoreUrl);
if (relativeFilename.includes(referenceFoldername)) {
const hasReference = gitignoreContent.includes(referenceFoldername);
if (!hasReference) FileSystem.appendToFile(gitignoreUrl, `\n${referenceFoldername}`);
return;
}
if (!gitignoreContent.includes(relativeFilename)) {
FileSystem.appendToFile(gitignoreUrl, `\n${relativeFilename}`);
}
}
}
private _initGitignore() {
const splitLine = '# auto-generated by api-documenter';
const gitignoreUrl = outputFolder + '/.gitignore';
if (FileSystem.exists(gitignoreUrl)) {
let gitignoreContent = FileSystem.readFile(gitignoreUrl);
const index = gitignoreContent.indexOf(splitLine);
if (index !== -1) gitignoreContent = gitignoreContent.substring(0, index);
gitignoreContent += splitLine;
FileSystem.writeFile(gitignoreUrl, gitignoreContent);
} else {
FileSystem.writeFile(gitignoreUrl, splitLine);
}
syncToGitignore(this._outputFolder, filename);
}
private _writeHeritageTypes(output: DocSection, apiItem: ApiDeclaredItem): void {
@ -1828,15 +1794,15 @@ export class MarkdownDocumenter {
}
}
private _parseTypeAliasTokens(excerptTokens: ExcerptToken[]): ExcerptToken[] {
const tokens = excerptTokens.flatMap((token) => {
private _parseTypeAliasTokens(apiTypeAlias: ApiTypeAlias): ExcerptToken[] {
const tokens = apiTypeAlias.excerptTokens.slice(1, -1).flatMap((token) => {
if (token.kind === ExcerptTokenKind.Reference && token.canonicalReference) {
const apiItemResult: IResolveDeclarationReferenceResult = this._apiModel.resolveDeclarationReference(
token.canonicalReference,
undefined,
);
if (apiItemResult.resolvedApiItem instanceof ApiTypeAlias) {
return this._parseTypeAliasTokens(apiItemResult.resolvedApiItem.excerptTokens.slice(1, -1));
return this._parseTypeAliasTokens(apiItemResult.resolvedApiItem);
}
}
return token;
@ -1860,13 +1826,16 @@ export class MarkdownDocumenter {
);
if (apiItemResult.resolvedApiItem) {
if (apiItemResult.resolvedApiItem.kind === ApiItemKind.TypeAlias && this.referenceLevel > 0) {
const excerptTokens = (apiItemResult.resolvedApiItem as ApiTypeAlias).excerptTokens.slice(1, -1);
const typeAliasTokens = this._parseTypeAliasTokens(excerptTokens);
if (
apiItemResult.resolvedApiItem.kind === ApiItemKind.TypeAlias &&
this.referenceLevel > 0 &&
!typesToSkipParsing.includes(apiItemResult.resolvedApiItem.displayName)
) {
const typeAliasTokens = this._parseTypeAliasTokens(apiItemResult.resolvedApiItem as ApiTypeAlias);
// If the type alias is a simple single-line type alias, then render it as plain text
// Otherwise, render it as a hyperlink
if (typeAliasTokens.every((token) => !token.text.includes('\n') && !token.text.includes('\r'))) {
return docNodeContainer.appendNode(
docNodeContainer.appendNode(
new DocPlainText({
configuration,
text: typeAliasTokens
@ -1877,6 +1846,7 @@ export class MarkdownDocumenter {
.join(' | '),
}),
);
return;
}
}
@ -2177,7 +2147,11 @@ export class MarkdownDocumenter {
private _appendPageTitle(output: DocSection, key: string, order?: number): void {
const configuration: TSDocConfiguration = this._tsdocConfiguration;
output.appendNode(new DocPageTitle({ configuration, key, locale: this.locale, order }));
const en = intl(LocaleType.PAGE_NAME, key, LocaleLanguage.EN);
const zh = intl(LocaleType.PAGE_NAME, key, LocaleLanguage.ZH);
const title = this.locale === LocaleLanguage.ZH && en !== zh ? `${en} ${zh}` : en;
output.appendNode(new DocPageTitle({ configuration, title, order }));
}
/**
@ -2374,7 +2348,7 @@ export class MarkdownDocumenter {
}
private _intl(keyword: Keyword) {
return getKeywordIntl(keyword, this.locale);
return intl(LocaleType.KEYWORD, keyword, this.locale);
}
private _getLang() {

View File

@ -0,0 +1,17 @@
{
"option": ["Option", "图配置项"],
"render": ["Render", "渲染"],
"data": ["Data", "数据"],
"instance": ["Instance", "图实例"],
"canvas": ["Canvas", "画布"],
"viewport": ["Viewport", "视口"],
"element": ["Element", "元素"],
"animation": ["Animation", "动画"],
"layout": ["Layout", "布局"],
"behavior": ["Behavior", "交互"],
"event": ["Event", "事件"],
"theme": ["Theme", "主题"],
"transform": ["Transform", "数据转换"],
"plugin": ["plugin", "插件"],
"exportImage": ["Export Image", "导出图片"]
}

View File

@ -1,27 +0,0 @@
import { getIntl } from './common';
const apiCategoryNames: Record<string, string[]> = {
option: ['Option', '图配置项'],
render: ['Render', '渲染'],
data: ['Data', '数据'],
instance: ['Instance', '图实例'],
canvas: ['Canvas', '画布'],
viewport: ['Viewport', '视口'],
element: ['Element', '元素'], //包括元素配置项设置 API 和元素操纵 API
animation: ['Animation', '动画'],
layout: ['Layout', '布局'],
behavior: ['Behavior', '交互'],
event: ['Event', '事件'],
theme: ['Theme', '主题'],
transform: ['Transform', '数据转换'],
plugin: ['plugin', '插件'],
exportImage: ['Export Image', '导出图片'],
};
/**
* API
* @param categoryKey key
* @param locale
* @returns
*/
export const getApiCategoryIntl = getIntl(apiCategoryNames);

View File

@ -1,11 +0,0 @@
export enum LocaleLanguage {
EN = 'en-US',
ZH = 'zh-CN',
}
export const getIntl = (obj: Record<string, string[]>) => {
return (key: string, language: LocaleLanguage) => {
const [en, zh] = obj[key];
return language === LocaleLanguage.EN ? en : zh;
};
};

View File

@ -0,0 +1,5 @@
{
"node": ["Node", "节点"],
"edge": ["Edge", "边"],
"combo": ["Combo", "组合"]
}

View File

@ -0,0 +1,61 @@
export enum LocaleType {
API_CATEGORY = 'api-category',
ELEMENT = 'element',
HELPER = 'helper',
KEYWORD = 'keyword',
PAGE_NAME = 'page-name',
}
export enum LocaleLanguage {
EN = 'en-US',
ZH = 'zh-CN',
}
export enum Keyword {
ABSTRACT_CLASS = 'abstract-class',
ABSTRACT_CLASSES = 'abstract-classes',
CLASS = 'class',
CLASSES = 'classes',
COLON = 'colon',
COMMA = 'comma',
CONSTRUCTOR = 'constructor',
CONSTRUCTORS = 'constructors',
DECORATOR = 'decorator',
DEFAULT_VALUE = 'defaultValue',
DESCRIPTION = 'description',
ENUMERATION = 'enumeration',
ENUMERATION_MEMBERS = 'enumeration-members',
ENUMERATIONS = 'enumerations',
EVENTS = 'events',
EXAMPLE = 'example',
EXCEPTIONS = 'exceptions',
EXTENDS = 'extends',
FUNCTION = 'function',
FUNCTIONS = 'functions',
IMPLEMENTS = 'implements',
INTERFACE = 'interface',
INTERFACES = 'interfaces',
LEFT_PARENTHESIS = 'leftParenthesis',
METHOD = 'method',
METHODS = 'methods',
MODIFIERS = 'modifiers',
NAMESPACE = 'namespace',
NAMESPACES = 'namespaces',
OPTIONAL = 'optional',
OPTIONS = 'options',
PACKAGE = 'package',
PACKAGES = 'packages',
PARAMETER = 'parameter',
PROPERTIES = 'properties',
PROPERTY = 'property',
REFERENCES = 'references',
REMARKS = 'remarks',
RETURNS = 'returns',
RIGHT_PARENTHESIS = 'rightParenthesis',
TYPE = 'type',
TYPE_ALIAS = 'type-alias',
TYPE_ALIASES = 'type-aliases',
VARIABLE = 'variable',
VARIABLES = 'variables',
VIEW_PARAMETERS = 'view-parameters',
}

View File

@ -0,0 +1,13 @@
{
"prefixHelper": ["More about `Prefix` generic usage, see [Prefix]", "关于 `Prefix` 泛型的使用信息,见 [Prefix]"],
"basePropsStyleHelper": [
"If the element has its specific properties, we will list them below. For all generic style attributes, see",
"如果元素有其特定的属性,我们将在下面列出。对于所有的通用样式属性,见"
],
"advancedPropsHelper": [
"Except for the properties explicitly listed below, other supported properties are seen",
"除了下面显式列出的属性,其他支持属性见"
],
"includes": ["Includes", "其中的"],
"excludes": ["Excludes", "除了"]
}

View File

@ -1,15 +0,0 @@
import { getIntl } from './index';
export const helpers = {
prefixHelper: ['More about `Prefix` generic usage, see [Prefix]', '关于 `Prefix` 泛型的使用信息,见 [Prefix]'],
basePropsStyleHelper: ['More about base style configuration, please check [here]', '了解通用样式配置,请点击[这里]'],
advancedPropsHelper: [
'Except for the properties explicitly listed below, other supported properties are seen',
'除了下面显式列出的属性,其他支持属性见',
],
includes: ['Includes', '其中的'],
excludes: ['Excludes', '除了'],
remarks: ['Remarks', '说明'],
};
export const getHelperIntl = getIntl(helpers);

View File

@ -1,5 +1,18 @@
export * from './api-category';
export * from './common';
export * from './helper';
export * from './keywords';
export * from './page';
import { FileSystem } from '@rushstack/node-core-library';
import * as path from 'path';
import type { LocaleType } from './enum';
import { LocaleLanguage } from './enum';
const localeMap = new Map<LocaleType, Record<string, string[]>>();
export const intl = (type: LocaleType, key: string, lang: LocaleLanguage): string => {
if (!localeMap.has(type)) {
const filePath = path.join(__dirname, `${type}.json`);
if (!FileSystem.exists(filePath)) {
throw new Error(`The locale file does not exist: ${filePath}`);
}
localeMap.set(type, JSON.parse(FileSystem.readFile(filePath)));
}
const [en, zh] = localeMap.get(type)?.[key] || [key, key];
return lang === LocaleLanguage.EN ? en : zh;
};

View File

@ -0,0 +1,48 @@
{
"abstract-class": ["Abstract Class", "抽象类"],
"abstract-classes": ["Abstract Classes", "抽象类"],
"class": ["Class", "类"],
"classes": ["Classes", "类"],
"colon": [": ", ""],
"comma": [", ", ""],
"constructor": ["Constructor", "构造函数"],
"constructors": ["Constructors", "构造函数"],
"decorator": ["Decorator", "装饰器"],
"defaultValue": ["Default Value", "默认值"],
"description": ["Description", "描述"],
"enumeration-members": ["Enumeration Members", "枚举成员"],
"enumeration": ["Enumeration", "枚举"],
"enumerations": ["Enumerations", "枚举"],
"events": ["Events", "事件"],
"example": ["Example", "示例"],
"exceptions": ["Exceptions", "异常"],
"extends": ["Extends", "继承自"],
"function": ["Function", "函数"],
"functions": ["Functions", "函数"],
"implements": ["Implements", "实现自"],
"interface": ["Interface", "接口"],
"interfaces": ["Interfaces", "接口"],
"leftParenthesis": ["(", ""],
"method": ["Method", "方法"],
"methods": ["Methods", "方法"],
"modifiers": ["Modifiers", "修饰符"],
"namespace": ["Namespace", "命名空间"],
"namespaces": ["Namespaces", "命名空间"],
"optional": ["Optional", "可选"],
"options": ["Options", "配置项"],
"package": ["Package", "包"],
"packages": ["Packages", "包"],
"parameter": ["Parameter", "参数"],
"properties": ["Properties", "属性"],
"property": ["Property", "属性"],
"references": ["References", "引用"],
"remarks": ["Remarks", "附注"],
"returns": ["Returns", "返回值"],
"rightParenthesis": [")", ""],
"type": ["Type", "类型"],
"type-alias": ["Type Alias", "类型别名"],
"type-aliases": ["Type Aliases", "类型别名"],
"variable": ["Variable", "变量"],
"variables": ["Variables", "变量"],
"view-parameters": ["View Parameters", "相关参数"]
}

View File

@ -1,101 +0,0 @@
import { getIntl } from './index';
export enum Keyword {
ABSTRACT_CLASS = 'abstract-class',
ABSTRACT_CLASSES = 'abstract-classes',
CLASS = 'class',
CLASSES = 'classes',
COLON = 'colon',
COMMA = 'comma',
CONSTRUCTOR = 'constructor',
CONSTRUCTORS = 'constructors',
DECORATOR = 'decorator',
DEFAULT_VALUE = 'defaultValue',
DESCRIPTION = 'description',
ENUMERATION = 'enumeration',
ENUMERATION_MEMBERS = 'enumeration-members',
ENUMERATIONS = 'enumerations',
EVENTS = 'events',
EXAMPLE = 'example',
EXCEPTIONS = 'exceptions',
EXTENDS = 'extends',
FUNCTION = 'function',
FUNCTIONS = 'functions',
IMPLEMENTS = 'implements',
INTERFACE = 'interface',
INTERFACES = 'interfaces',
LEFT_PARENTHESIS = 'leftParenthesis',
METHOD = 'method',
METHODS = 'methods',
MODIFIERS = 'modifiers',
NAMESPACE = 'namespace',
NAMESPACES = 'namespaces',
OPTIONAL = 'optional',
OPTIONS = 'options',
PACKAGE = 'package',
PACKAGES = 'packages',
PARAMETER = 'parameter',
PROPERTIES = 'properties',
PROPERTY = 'property',
REFERENCES = 'references',
REMARKS = 'remarks',
RETURNS = 'returns',
RIGHT_PARENTHESIS = 'rightParenthesis',
TYPE = 'type',
TYPE_ALIAS = 'type-alias',
TYPE_ALIASES = 'type-aliases',
VARIABLE = 'variable',
VARIABLES = 'variables',
VIEW_PARAMETERS = 'view-parameters',
}
export const KeywordMapping: Record<Keyword, string[]> = {
[Keyword.ABSTRACT_CLASS]: ['Abstract Class', '抽象类'],
[Keyword.ABSTRACT_CLASSES]: ['Abstract Classes', '抽象类'],
[Keyword.CLASS]: ['Class', '类'],
[Keyword.CLASSES]: ['Classes', '类'],
[Keyword.COLON]: [': ', ''],
[Keyword.COMMA]: [', ', ''],
[Keyword.CONSTRUCTOR]: ['Constructor', '构造函数'],
[Keyword.CONSTRUCTORS]: ['Constructors', '构造函数'],
[Keyword.DECORATOR]: ['Decorator', '装饰器'],
[Keyword.DEFAULT_VALUE]: ['Default Value', '默认值'],
[Keyword.DESCRIPTION]: ['Description', '描述'],
[Keyword.ENUMERATION_MEMBERS]: ['Enumeration Members', '枚举成员'],
[Keyword.ENUMERATION]: ['Enumeration', '枚举'],
[Keyword.ENUMERATIONS]: ['Enumerations', '枚举'],
[Keyword.EVENTS]: ['Events', '事件'],
[Keyword.EXAMPLE]: ['Example', '示例'],
[Keyword.EXCEPTIONS]: ['Exceptions', '异常'],
[Keyword.EXTENDS]: ['Extends', '继承自'],
[Keyword.FUNCTION]: ['Function', '函数'],
[Keyword.FUNCTIONS]: ['Functions', '函数'],
[Keyword.IMPLEMENTS]: ['Implements', '实现自'],
[Keyword.INTERFACE]: ['Interface', '接口'],
[Keyword.INTERFACES]: ['Interfaces', '接口'],
[Keyword.LEFT_PARENTHESIS]: ['(', ''],
[Keyword.METHOD]: ['Method', '方法'],
[Keyword.METHODS]: ['Methods', '方法'],
[Keyword.MODIFIERS]: ['Modifiers', '修饰符'],
[Keyword.NAMESPACE]: ['Namespace', '命名空间'],
[Keyword.NAMESPACES]: ['Namespaces', '命名空间'],
[Keyword.OPTIONAL]: ['Optional', '可选'],
[Keyword.OPTIONS]: ['Options', '配置项'],
[Keyword.PACKAGE]: ['Package', '包'],
[Keyword.PACKAGES]: ['Packages', '包'],
[Keyword.PARAMETER]: ['Parameter', '参数'],
[Keyword.PROPERTIES]: ['Properties', '属性'],
[Keyword.PROPERTY]: ['Property', '属性'],
[Keyword.REFERENCES]: ['References', '引用'],
[Keyword.REMARKS]: ['Remarks', '备注'],
[Keyword.RETURNS]: ['Returns', '返回值'],
[Keyword.RIGHT_PARENTHESIS]: [')', ''],
[Keyword.TYPE_ALIAS]: ['Type Alias', '类型别名'],
[Keyword.TYPE_ALIASES]: ['Type Aliases', '类型别名'],
[Keyword.TYPE]: ['Type', '类型'],
[Keyword.VARIABLE]: ['Variable', '变量'],
[Keyword.VARIABLES]: ['Variables', '变量'],
[Keyword.VIEW_PARAMETERS]: ['View Parameters', '相关参数'],
};
export const getKeywordIntl = getIntl(KeywordMapping);

View File

@ -0,0 +1,66 @@
{
"GraphOptions": ["Options", "配置项"],
"GraphMethods": ["API", "方法"],
"GraphProperties": ["Properties", "属性"],
"ElementMethods": ["API", "方法"],
"BaseNode": ["BaseNode", "节点通用样式"],
"Circle": ["Circle", "圆形"],
"Diamond": ["Diamond", "菱形"],
"Donut": ["Donut", "甜甜圈"],
"Ellipse": ["Ellipse", "椭圆形"],
"Hexagon": ["Hexagon", "六边形"],
"Html": ["Html", "HTML"],
"Image": ["Image", "图片"],
"Rect": ["Rect", "矩形"],
"Star": ["Star", "五角形"],
"Triangle": ["Triangle", "三角形"],
"BaseEdge": ["BaseEdge", "边通用样式"],
"Cubic": ["Cubic", "三次贝塞尔曲线"],
"CubicHorizontal": ["CubicHorizontal", "水平三次贝塞尔曲线"],
"CubicVertical": ["CubicVertical", "垂直三次贝塞尔曲线"],
"Line": ["Line", "直线"],
"Polyline": ["Polyline", "折线"],
"Quadratic": ["Quadratic", "二次贝塞尔曲线"],
"BaseCombo": ["BaseCombo", "组合基础样式"],
"CircleCombo": ["Circle", "圆形"],
"RectCombo": ["Rect", "矩形"],
"AntvDagreLayout": ["AntvDagre", "布局"],
"CircularLayout": ["Circular", "环形布局"],
"ComboCombinedLayout": ["ComboCombined", "复合布局"],
"ConcentricLayout": ["Concentric", "同心圆布局"],
"D3Force3DLayout": ["D3Force3D", "3D 力导向布局"],
"D3ForceLayout": ["D3Force", "力导向布局"],
"DagreLayout": ["Dagre", "层次布局"],
"ForceAtlas2Layout": ["ForceAtlas2", "力导向布局"],
"ForceLayout": ["Force", "力导向布局"],
"FruchtermanLayout": ["Fruchterman", "力导向布局"],
"GridLayout": ["Grid", "网格布局"],
"MdsLayout": ["Mds", "高维数据降维布局"],
"RadialLayout": ["Radial", "镭射布局"],
"RandomLayout": ["Random", "随机布局"],
"BaseBehavior": ["BaseBehavior", "基础交互"],
"BrushSelect": ["BrushSelect", "框选"],
"ClickElement": ["ClickElement", "点击选中"],
"CollapseExpand": ["CollapseExpand", "展开折叠Combo"],
"CreateEdge": ["CreateEdge", "创建边"],
"DragCanvas": ["DragCanvas", "拖拽画布"],
"DragElement": ["DragElement", "拖拽元素"],
"DragElementForce": ["DragElementForce", "力导向拖拽元素"],
"FocusElement": ["FocusElement", "聚焦元素"],
"HoverElement": ["HoverElement", "悬停激活"],
"LassoSelect": ["LassoSelect", "套索选择"],
"ScrollCanvas": ["ScrollCanvas", "滚动画布"],
"ZoomCanvas": ["ZoomCanvas", "缩放画布"],
"BubbleSets": ["BubbleSets", "气泡集"],
"CameraSetting": ["CameraSetting", "相机设置"],
"ContextMenu": ["ContextMenu", "右键菜单"],
"GridLine": ["GridLine", "网格线"],
"History": ["History", "历史记录"],
"Hull": ["Hull", "轮廓包围"],
"Legend": ["Legend", "图例"],
"Toolbar": ["Toolbar", "工具栏"],
"Tooltip": ["Tooltip", "提示框"],
"Watermark": ["Watermark", "水印"],
"Timebar": ["Timebar", "时间条"],
"Contextmenu": ["Contextmenu", "上下文菜单"]
}

View File

@ -1,85 +0,0 @@
import { getIntl } from './common';
export const PageTitle: Record<string, string[]> = {
// graph
GraphOptions: ['Options', '配置项'],
GraphMethods: ['API', '方法'],
GraphProperties: ['Properties', '属性'],
// element
ElementMethods: ['API', '方法'],
// element/node
BaseNode: ['BaseNode', '节点通用样式属性'],
Circle: ['Circle', '圆形'],
Diamond: ['Diamond', '菱形'],
Donut: ['Donut', '甜甜圈'],
Ellipse: ['Ellipse', '椭圆形'],
Hexagon: ['Hexagon', '六边形'],
Html: ['Html', 'HTML'],
Image: ['Image', '图片'],
Rect: ['Rect', '矩形'],
Star: ['Star', '五角形'],
Triangle: ['Triangle', '三角形'],
// element/edge
BaseEdge: ['BaseEdge', '边通用样式属性'],
Cubic: ['Cubic', '三次贝塞尔曲线'],
CubicHorizontal: ['CubicHorizontal', '水平三次贝塞尔曲线'],
CubicVertical: ['CubicVertical', '垂直三次贝塞尔曲线'],
Line: ['Line', '直线'],
Polyline: ['Polyline', '折线'],
Quadratic: ['Quadratic', '二次贝塞尔曲线'],
// element/combo
BaseCombo: ['BaseCombo', '组合基础样式属性'],
CircleCombo: ['Circle', '圆形'],
RectCombo: ['Rect', '矩形'],
// layout
AntvDagreLayout: ['AntvDagre', '布局'],
CircularLayout: ['Circular', '环形布局'],
ComboCombinedLayout: ['ComboCombined', '复合布局'],
ConcentricLayout: ['Concentric', '同心圆布局'],
D3Force3DLayout: ['D3Force3D', '3D 力导向布局'],
D3ForceLayout: ['D3Force', '力导向布局'],
DagreLayout: ['Dagre', '层次布局'],
ForceAtlas2Layout: ['ForceAtlas2', '力导向布局'],
ForceLayout: ['Force', '力导向布局'],
FruchtermanLayout: ['Fruchterman', '力导向布局'],
GridLayout: ['Grid', '网格布局'],
MdsLayout: ['Mds', '高维数据降维布局'],
RadialLayout: ['Radial', '镭射布局'],
RandomLayout: ['Random', '随机布局'],
// behaviors
BaseBehavior: ['BaseBehavior', '基础交互'],
BrushSelect: ['BrushSelect', '框选'],
ClickElement: ['ClickElement', '点击选中'],
CollapseExpand: ['CollapseExpand', '展开折叠Combo'],
CreateEdge: ['CreateEdge', '创建边'],
DragCanvas: ['DragCanvas', '拖拽画布'],
DragElement: ['DragElement', '拖拽元素'],
DragElementForce: ['DragElementForce', '力导向拖拽元素'],
FocusElement: ['FocusElement', '聚焦元素'],
HoverElement: ['HoverElement', '悬停激活'],
LassoSelect: ['LassoSelect', '套索选择'],
ScrollCanvas: ['ScrollCanvas', '滚动画布'],
ZoomCanvas: ['ZoomCanvas', '缩放画布'],
// plugins
BubbleSets: ['BubbleSets', '气泡集'],
CameraSetting: ['CameraSetting', '相机设置'],
ContextMenu: ['ContextMenu', '右键菜单'],
GridLine: ['GridLine', '网格线'],
History: ['History', '历史记录'],
Hull: ['Hull', '轮廓包围'],
Legend: ['Legend', '图例'],
Toolbar: ['Toolbar', '工具栏'],
Tooltip: ['Tooltip', '提示框'],
Watermark: ['Watermark', '水印'],
Timebar: ['Timebar', '时间条'],
Contextmenu: ['Contextmenu', '上下文菜单'],
};
// 节点、边、Combo 的中英文对照
export const ElementLocale: Record<string, string[]> = {
node: ['Node', '节点'],
edge: ['Edge', '边'],
combo: ['Combo', '组合'],
};
export const getElementIntl = getIntl(ElementLocale);

View File

@ -1,14 +1,12 @@
import type { IDocNodeParameters } from '@microsoft/tsdoc';
import { DocNode } from '@microsoft/tsdoc';
import { LocaleLanguage, PageTitle } from '../constants/locales';
import { CustomDocNodeKind } from './CustomDocNodeKind';
/**
* Constructor parameters for {@link DocPageTitle}.
*/
export interface IDocPageTitleParameters extends IDocNodeParameters {
key: string;
locale: string;
title: string;
order?: number;
}
@ -27,10 +25,8 @@ export class DocPageTitle extends DocNode {
public constructor(parameters: IDocPageTitleParameters) {
super(parameters);
const { key, locale, order } = parameters;
const arr = PageTitle[key];
this.title = arr ? (locale === LocaleLanguage.EN ? arr[0] : arr.join(' ')) : key;
this.order = order;
this.title = parameters.title;
this.order = parameters.order;
}
/** @override */

View File

@ -1,3 +0,0 @@
export const inputFolder = 'support/api';
export const outputFolder = 'docs/api';
export const referenceFoldername = 'reference';

View File

@ -1,5 +1,6 @@
import { ApiInterface, ApiModel, ExcerptTokenKind, type ExcerptToken } from '@microsoft/api-extractor-model';
import { camelCase } from 'lodash';
import { interfacesToSkipParsing } from '../MarkdownDocumenter';
export type BaseExcerptToken = {
text: string;
@ -94,6 +95,8 @@ export const liftPrefixExcerptTokens = (items: ICustomExcerptToken[]): ICustomEx
let topLevelPrefixes: ICustomExcerptToken[] = [];
for (const item of items) {
if (interfacesToSkipParsing.includes(item.text)) continue;
if (item.type === 'Prefix') {
const newItem = { ...item, prefix: getAccessorExcerptTokensPrefixes(item) };
topLevelPrefixes.push(newItem);

View File

@ -0,0 +1,30 @@
import { FileSystem } from '@rushstack/node-core-library';
import * as path from 'path';
/**
* Get the path to the .gitignore file
* @param outputFolder - The output folder
* @returns The path to the .gitignore file
*/
function getGitignorePath(outputFolder: string): string {
return path.join(outputFolder, '.gitignore');
}
/**
* Initialize the .gitignore file
* @param outputFolder - The output folder
*/
export function initGitignore(outputFolder: string) {
const gitPath = getGitignorePath(outputFolder);
FileSystem.writeFile(gitPath, '');
}
/**
* Sync the filename to the .gitignore file
* @param outputFolder - The output folder
* @param filename - The filename to sync
*/
export function syncToGitignore(outputFolder: string, filename: string) {
const gitPath = getGitignorePath(outputFolder);
FileSystem.appendToFile(gitPath, `\n${filename.replace(outputFolder, '')}`);
}