dsh-model-everything 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en-US.md +12 -0
- package/README.md +4 -0
- package/lib/client.js +2437 -216
- package/lib/render/channels.js +243 -0
- package/lib/render/collapse.js +12 -20
- package/lib/render/components.js +99 -0
- package/lib/render/coords.js +288 -0
- package/lib/render/cycle-break.js +147 -0
- package/lib/render/dummy.js +43 -0
- package/lib/render/from-mermaid.js +34 -10
- package/lib/render/from-plantuml.js +318 -46
- package/lib/render/layer.js +91 -0
- package/lib/render/layout.js +27 -11
- package/lib/render/network-simplex.js +272 -0
- package/lib/render/order.js +122 -0
- package/lib/render/paint.js +764 -67
- package/lib/render/place.js +393 -121
- package/lib/render/route.js +225 -0
- package/lib/render/svg.js +143 -21
- package/lib/render/symbol.js +108 -0
- package/lib/render/text.js +74 -0
- package/lib/render/wrap.js +87 -0
- package/lib/types/render/channels.d.ts +63 -0
- package/lib/types/render/components.d.ts +57 -0
- package/lib/types/render/coords.d.ts +64 -0
- package/lib/types/render/cycle-break.d.ts +45 -0
- package/lib/types/render/dummy.d.ts +32 -0
- package/lib/types/render/from-plantuml.d.ts +12 -6
- package/lib/types/render/layer.d.ts +28 -0
- package/lib/types/render/layout.d.ts +43 -11
- package/lib/types/render/model.d.ts +17 -0
- package/lib/types/render/network-simplex.d.ts +54 -0
- package/lib/types/render/order.d.ts +27 -0
- package/lib/types/render/paint.d.ts +42 -9
- package/lib/types/render/place.d.ts +35 -19
- package/lib/types/render/route.d.ts +77 -0
- package/lib/types/render/svg.d.ts +1 -1
- package/lib/types/render/symbol.d.ts +73 -0
- package/lib/types/render/text.d.ts +38 -0
- package/lib/types/render/wrap.d.ts +27 -0
- package/package.json +148 -148
- package/skills/model-everything-diagram/SKILL.md +84 -82
- package/src/render/channels.ts +309 -0
- package/src/render/collapse.ts +150 -155
- package/src/render/components.ts +126 -0
- package/src/render/coords.ts +328 -0
- package/src/render/cycle-break.ts +164 -0
- package/src/render/dummy.ts +65 -0
- package/src/render/from-mermaid.ts +261 -236
- package/src/render/from-plantuml.ts +469 -178
- package/src/render/layer.ts +98 -0
- package/src/render/layout.ts +101 -62
- package/src/render/model.ts +86 -67
- package/src/render/network-simplex.ts +290 -0
- package/src/render/order.ts +139 -0
- package/src/render/paint.ts +1006 -290
- package/src/render/place.ts +527 -234
- package/src/render/route.ts +269 -0
- package/src/render/svg.ts +270 -127
- package/src/render/symbol.ts +137 -0
- package/src/render/text.ts +86 -0
- package/src/render/wrap.ts +103 -0
|
@@ -5,21 +5,28 @@
|
|
|
5
5
|
* sidebar tab - so nothing here knows about pixel size. It emits a content-sized canvas and lets the
|
|
6
6
|
* container scale it: the same numbers serve a 360px card and a 1200px whiteboard.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* The drawing is placed by the layered pipeline (`place.ts`), which also says which lane a long edge reserved.
|
|
9
|
+
* Edges are drawn from where the boxes ended up, in two ways only: a long edge follows its lane as one polyline,
|
|
10
|
+
* and everything else goes through the orthogonal router. Which side of a box an edge leaves by is read off the
|
|
11
|
+
* route, not picked by comparing the boxes' centres: a route between two rows arrives vertically, so the endpoint
|
|
12
|
+
* is snapped onto the top or bottom perimeter in the direction the route approaches. Where along that side it sits
|
|
13
|
+
* follows the symbol: a rectangle spreads its ports along the side, a cylinder's top port sits on its cap curve, and a
|
|
14
|
+
* diamond or a circle has four legal points and no side to spread along at all.
|
|
9
15
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* whose members float somewhere else on the page is not showing containment at all.
|
|
16
|
+
* A symbol with discrete points gives each of its edges a point of its own: the point whose outward normal faces the
|
|
17
|
+
* box at the other end is the one that edge takes, and an edge only shares a point when all four are spoken for. A
|
|
18
|
+
* node with a run on its side keeps doing what it always did, because there the ports are places on the run and not
|
|
19
|
+
* points at all. Sharing is the fallback, not the rule: two labelled branches of a decision leave by two vertices.
|
|
15
20
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
21
|
+
* An edge's name is drawn on the edge, not beside it: it is anchored at the midpoint of the edge's longest straight
|
|
22
|
+
* run, over a rectangle in the page colour that masks the line behind the text, and it slides along that run when
|
|
23
|
+
* something is in the way. Labels are placed after every edge is drawn, because what a label has to avoid is the
|
|
24
|
+
* whole picture.
|
|
18
25
|
*
|
|
19
26
|
* Pure: a diagram in, shapes out, every decision assertable.
|
|
20
27
|
*/
|
|
21
28
|
import { type ColourRole, type Palette, type PaletteOptions } from './palette.js';
|
|
22
|
-
import { type Diagram, type DiagramNode } from './model.js';
|
|
29
|
+
import { type Diagram, type DiagramNode, type EdgeArrow, type NodeShape } from './model.js';
|
|
23
30
|
export interface Point {
|
|
24
31
|
x: number;
|
|
25
32
|
y: number;
|
|
@@ -30,7 +37,11 @@ export interface BoxShape {
|
|
|
30
37
|
y: number;
|
|
31
38
|
width: number;
|
|
32
39
|
height: number;
|
|
40
|
+
/** The lines to draw, already wrapped; `label` keeps the whole text for anything that reads the shape. */
|
|
33
41
|
label: string;
|
|
42
|
+
lines: string[];
|
|
43
|
+
/** The symbol to draw. Undefined means a rectangle. */
|
|
44
|
+
shape?: NodeShape;
|
|
34
45
|
colour: ColourRole;
|
|
35
46
|
initial: boolean;
|
|
36
47
|
}
|
|
@@ -41,17 +52,25 @@ export interface LinkShape {
|
|
|
41
52
|
x2: number;
|
|
42
53
|
y2: number;
|
|
43
54
|
label: string;
|
|
55
|
+
/** The centre the label is drawn on, and therefore a point on the edge itself. */
|
|
44
56
|
labelX: number;
|
|
45
57
|
labelY: number;
|
|
46
58
|
colour: ColourRole;
|
|
59
|
+
/** A dashed line, when the notation distinguished one. */
|
|
60
|
+
dashed?: boolean;
|
|
61
|
+
/** The arrow the source edge stated. Undefined means the plain arrow every edge has been drawn with. */
|
|
62
|
+
arrow?: EdgeArrow;
|
|
47
63
|
}
|
|
48
64
|
export interface PathShape {
|
|
49
65
|
kind: 'path';
|
|
50
66
|
points: Point[];
|
|
51
67
|
label: string;
|
|
68
|
+
/** The centre the label is drawn on, and therefore a point on the edge itself. */
|
|
52
69
|
labelX: number;
|
|
53
70
|
labelY: number;
|
|
54
71
|
colour: ColourRole;
|
|
72
|
+
dashed?: boolean;
|
|
73
|
+
arrow?: EdgeArrow;
|
|
55
74
|
}
|
|
56
75
|
/** A boundary around several states: a package, a layer, a subgraph. */
|
|
57
76
|
export interface GroupShape {
|
|
@@ -85,8 +104,22 @@ export interface Canvas {
|
|
|
85
104
|
height: number;
|
|
86
105
|
shapes: Shape[];
|
|
87
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* How far apart two edges that join the same pair are drawn.
|
|
109
|
+
*
|
|
110
|
+
* Wider than the least two ports may sit: two statements about one pair have to read as two lines, and a gap the
|
|
111
|
+
* port packer would allow is a gap a reader sees as one thick line. Exported so a test can name the pattern.
|
|
112
|
+
*/
|
|
113
|
+
export declare const MULTI_EDGE_SPACING = 20;
|
|
88
114
|
/** Which colour a box earns from its role. One mapping, used by every container. */
|
|
89
115
|
export declare function roleOfNode(node: DiagramNode): ColourRole;
|
|
116
|
+
/**
|
|
117
|
+
* How tall the mask an edge label is drawn over is.
|
|
118
|
+
*
|
|
119
|
+
* Exported because two modules must agree on it: the painter reserves the rectangle when it looks for a clear
|
|
120
|
+
* place, and the writer draws the rectangle that was reserved. A label with two sizes is a label half masked.
|
|
121
|
+
*/
|
|
122
|
+
export declare const LABEL_HEIGHT = 12;
|
|
90
123
|
export declare function paintWith(diagram: Diagram, overrides?: Partial<Palette>, options?: PaletteOptions): {
|
|
91
124
|
canvas: Canvas;
|
|
92
125
|
colours: Palette;
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where the boxes go.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The drawing is placed by the layered pipeline, in the order the mature implementations run it: cycle breaking turns
|
|
5
|
+
* the graph into a DAG, layering gives every box a row, dummy nodes give every long edge a lane, ordering reduces the
|
|
6
|
+
* crossings, and coordinate assignment decides the x of each lane and box. This module composes those stages; it does
|
|
7
|
+
* not decide any of them itself. Each stage is a pure module next door, so it can be asserted without a canvas.
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Two decompositions sit around the pipeline. A level - the top of the drawing, or the inside of a boundary - is
|
|
10
|
+
* split into weakly connected components, each laid out on its own and then packed onto the page, so a wide
|
|
11
|
+
* subgraph cannot squeeze an unrelated one. A boundary is a node in its parent's layer whose size is whatever its
|
|
12
|
+
* contents need, and its inside is laid out by the same pipeline; that is how nesting is drawn.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* line.
|
|
14
|
+
* What comes out is geometry only: a box per node, a rectangle per boundary, and - for a long edge - the lane its
|
|
15
|
+
* dummies reserved, so the painter can draw one polyline through it instead of searching around the boxes.
|
|
14
16
|
*/
|
|
15
|
-
import type
|
|
17
|
+
import { type Measured as TextBox } from './text.js';
|
|
18
|
+
import type { Diagram, DiagramNode } from './model.js';
|
|
16
19
|
/** Geometry of one placed box, before anything is drawn. */
|
|
17
20
|
export type Box = {
|
|
18
21
|
x: number;
|
|
@@ -20,20 +23,33 @@ export type Box = {
|
|
|
20
23
|
width: number;
|
|
21
24
|
height: number;
|
|
22
25
|
};
|
|
23
|
-
/**
|
|
26
|
+
/** A point in the drawing's own coordinates. */
|
|
27
|
+
export interface Point {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
}
|
|
31
|
+
/** The lane a long edge's dummies reserved, in layout order: from the layout source down to the layout target. */
|
|
32
|
+
export interface Lane {
|
|
33
|
+
/** One point per gap the edge crosses, at the lane's own x: it keeps its own order, and the painter walks it. */
|
|
34
|
+
points: Point[];
|
|
35
|
+
/** True when the layout runs opposite to the source's arrow, so the painter walks the lane backwards. */
|
|
36
|
+
reversed: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** What a placement returns: every box, every drawn boundary, every reserved lane, and how wide the content is. */
|
|
24
39
|
export interface Placement {
|
|
25
40
|
boxes: Map<string, Box>;
|
|
26
41
|
groupRects: Map<string, Box>;
|
|
27
42
|
width: number;
|
|
43
|
+
lanes: Map<number, Lane>;
|
|
44
|
+
/** The height of the one horizontal run an edge between two adjacent rows needs. */
|
|
45
|
+
runs: Map<number, number>;
|
|
46
|
+
/** Edges whose two ends ended up in different wrapped columns: they are read across, not down. */
|
|
47
|
+
across: Set<number>;
|
|
28
48
|
}
|
|
29
|
-
/** Geometry constants
|
|
49
|
+
/** Geometry constants in one place: a container with different proportions changes these, not the rules. */
|
|
30
50
|
export declare const MARGIN = 20;
|
|
31
|
-
/**
|
|
32
|
-
export declare
|
|
33
|
-
/**
|
|
34
|
-
* Place every box of a diagram.
|
|
35
|
-
*
|
|
36
|
-
* The choice of placement is a property of the source, not of the caller: a drawing that declares boundaries is
|
|
37
|
-
* read as containers and gets the tree; one that does not is read as a machine and gets the ranks.
|
|
38
|
-
*/
|
|
51
|
+
/** The text a node will draw, measured from its label - the id is what the source called it, not what it says. */
|
|
52
|
+
export declare function textOf(node: DiagramNode): TextBox;
|
|
39
53
|
export declare function place(diagram: Diagram, top: number): Placement;
|
|
54
|
+
/** The aspect ratio of a placement, for the tests that check a drawing is a page and not a strip. */
|
|
55
|
+
export declare function placementAspect(placement: Placement): number;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orthogonal edge routing around boxes.
|
|
3
|
+
*
|
|
4
|
+
* A straight line between two boxes is right until a third box sits between them; then it is a line drawn through a
|
|
5
|
+
* component, which reads as a connection that is not there. Graphviz solves this by routing along a visibility
|
|
6
|
+
* graph and then fitting splines; ELK, yFiles and mxGraph build a *sparse* grid from the boxes' own edges and run
|
|
7
|
+
* A* over it with a bend penalty. Sparse is the whole trick: a pixel grid is O(pixels²), while the candidate lines
|
|
8
|
+
* are the coordinate of every box edge plus a margin - O(boxes) per axis.
|
|
9
|
+
*
|
|
10
|
+
* So this module does exactly that, in the two steps those routers use:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Candidate lines.** Every box contributes four: one lane just outside each side. Together with the anchor
|
|
13
|
+
* coordinates they form the grid a route may travel on.
|
|
14
|
+
* 2. **A* with bends.** Moving along a candidate line costs its length; changing direction costs extra, because a
|
|
15
|
+
* route that zig-zags to save three points is harder to follow than one that goes around.
|
|
16
|
+
*
|
|
17
|
+
* A route is only computed for an edge that needs it: the cheap straight segment is tried first, and routing is the
|
|
18
|
+
* fallback. That keeps the common case - a box directly above another - at two points and no search.
|
|
19
|
+
*
|
|
20
|
+
* The layered painter mostly does not ask: an edge between two rows and an edge with a lane are both built from the
|
|
21
|
+
* channel stage's answer, because a route searched on its own cannot know which lanes the other edges already hold.
|
|
22
|
+
* What is left for this router is the drawing the layers do not describe - an edge across two wrapped columns, an
|
|
23
|
+
* edge one of whose ends is a boundary - and the fallback when a channel route would meet a box.
|
|
24
|
+
*/
|
|
25
|
+
import type { Point } from './paint.js';
|
|
26
|
+
/** A rectangle an edge may not cross. */
|
|
27
|
+
export interface Obstacle {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}
|
|
33
|
+
/** The four sides a route may leave a box from. */
|
|
34
|
+
export type Side = 'top' | 'bottom' | 'left' | 'right';
|
|
35
|
+
/** The length of the straight run an edge takes before it is allowed to turn. */
|
|
36
|
+
export declare const ROUTE_STUB = 16;
|
|
37
|
+
/** The outward normal of a side: the direction an edge leaves along. */
|
|
38
|
+
export declare function normalOf(side: Side): Point;
|
|
39
|
+
/** A segment crossing a box at all, with a little slack so a route does not shave a corner. */
|
|
40
|
+
export declare function crossesBox(x1: number, y1: number, x2: number, y2: number, box: Obstacle): boolean;
|
|
41
|
+
/** The point on a side of a box, in the middle of that side. */
|
|
42
|
+
export declare function anchor(box: Obstacle, side: Side): Point;
|
|
43
|
+
/**
|
|
44
|
+
* Which side of `from` an edge leaves by, and which side of `to` it arrives at.
|
|
45
|
+
*
|
|
46
|
+
* The side is a property of the drawing, not of the distance between two centres. A layered drawing puts its boxes
|
|
47
|
+
* in horizontal rows, and an edge between two rows approaches vertically: out of the bottom, into the top, and the
|
|
48
|
+
* other way round for an edge drawn against the flow. Only two boxes in the same row are joined sideways, and then
|
|
49
|
+
* the direction of travel decides which side.
|
|
50
|
+
*
|
|
51
|
+
* This replaces the old rule, which compared the two centres' dx and dy and picked the larger axis - so an edge
|
|
52
|
+
* between two rows left through a box's side as soon as the target was far enough sideways, which is the hook a
|
|
53
|
+
* top-down drawing is read as a defect.
|
|
54
|
+
*/
|
|
55
|
+
export declare function approachSides(from: Obstacle, to: Obstacle): {
|
|
56
|
+
exit: Side;
|
|
57
|
+
entry: Side;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Route from one box to another, going around any box in the way.
|
|
61
|
+
*
|
|
62
|
+
* Returns the polyline to draw. When nothing blocks the straight segment, or when every candidate is blocked (a
|
|
63
|
+
* single box that fills the drawing), the straight line is returned and the caller keeps a two-point shape - an
|
|
64
|
+
* unroutable edge is still an edge, and drawing it straight is more honest than dropping it.
|
|
65
|
+
*/
|
|
66
|
+
export interface Ports {
|
|
67
|
+
exit: Point;
|
|
68
|
+
entry: Point;
|
|
69
|
+
exitSide: Side;
|
|
70
|
+
entrySide: Side;
|
|
71
|
+
}
|
|
72
|
+
export declare function routeEdge(from: Obstacle, to: Obstacle, obstacles: Obstacle[], anchors?: Ports): {
|
|
73
|
+
points: Point[];
|
|
74
|
+
routed: boolean;
|
|
75
|
+
};
|
|
76
|
+
/** Drop the points in the middle of a straight run: three collinear points are one segment. */
|
|
77
|
+
export declare function simplify(points: Point[]): Point[];
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Pure, and the output is asserted as text: escaping, geometry, arrowheads, wrapped captions.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type Canvas } from './paint.js';
|
|
15
15
|
import type { Palette } from './palette.js';
|
|
16
16
|
export declare function toSvg(canvas: Canvas, colours: Palette, label?: string): {
|
|
17
17
|
markup: string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The geometry of a node's symbol, shared by the module that draws it and the module that attaches edges to it.
|
|
3
|
+
*
|
|
4
|
+
* A box is a rectangle, but a symbol is not: the diamond's side midpoints are its vertices, a circle has no side at
|
|
5
|
+
* all, and a cylinder's top is an arc. Any rule that decides where an edge may meet a node therefore needs the
|
|
6
|
+
* symbol's own points - and the module that draws the symbol needs the same numbers, or the anchor and the outline
|
|
7
|
+
* disagree and an edge ends in the air.
|
|
8
|
+
*
|
|
9
|
+
* So the arithmetic lives here once: the painter reads a port from it, the writer draws the shape from it, and a
|
|
10
|
+
* change to a symbol's outline moves both. Pure data and pure functions, no SVG markup.
|
|
11
|
+
*/
|
|
12
|
+
import type { Box, Point } from './place.js';
|
|
13
|
+
import type { NodeShape } from './model.js';
|
|
14
|
+
/** A cubic Bezier, as the writer draws a curve and the painter reads a point from it. */
|
|
15
|
+
export interface Cubic {
|
|
16
|
+
from: Point;
|
|
17
|
+
c1: Point;
|
|
18
|
+
c2: Point;
|
|
19
|
+
to: Point;
|
|
20
|
+
}
|
|
21
|
+
/** Where the two caps of a cylinder start and end, and how deep each one is. */
|
|
22
|
+
export interface CylinderCaps {
|
|
23
|
+
/** How far the cap reaches into the box: the writer's `min(10, h / 3)`. */
|
|
24
|
+
cap: number;
|
|
25
|
+
/** The top cap, drawn from its left shoulder to its right shoulder. */
|
|
26
|
+
top: Cubic;
|
|
27
|
+
/** The bottom cap, drawn from its right shoulder to its left shoulder. */
|
|
28
|
+
bottom: Cubic;
|
|
29
|
+
}
|
|
30
|
+
/** A point on a cubic Bezier at parameter `t`, which is what makes "the top of the cap" a shared fact. */
|
|
31
|
+
export declare function cubicPoint(curve: Cubic, t: number): Point;
|
|
32
|
+
/**
|
|
33
|
+
* A diamond's four points, in drawing order: top, right, bottom, left.
|
|
34
|
+
*
|
|
35
|
+
* These are the only four places an edge may meet a diamond: each side of the box meets the symbol at exactly that
|
|
36
|
+
* point, and every other point on the side is in the air inside or outside the outline.
|
|
37
|
+
*/
|
|
38
|
+
export declare function diamondPoints(box: Box): [Point, Point, Point, Point];
|
|
39
|
+
/**
|
|
40
|
+
* Does this symbol meet each of its four sides at exactly one point?
|
|
41
|
+
*
|
|
42
|
+
* A diamond's side midpoints are its four vertices, and an ellipse's are its four cardinal points: both offer one legal
|
|
43
|
+
* port per side and no run to spread along. Every other symbol offers a run on at least one side - a rectangle along
|
|
44
|
+
* its whole side, a hexagon along the flat between its cut corners, a stadium between its end caps, a cylinder along
|
|
45
|
+
* its straight flank - so a port there has a place to sit rather than a point to take.
|
|
46
|
+
*
|
|
47
|
+
* This is the distinction the painter needs: a node with one legal point per side can give its edges a point each,
|
|
48
|
+
* and a node with a run can only give them places along it. The two points the discrete shapes offer are the same
|
|
49
|
+
* four points of the box, which is why one answer serves both.
|
|
50
|
+
*/
|
|
51
|
+
export declare function hasDiscretePorts(shape: NodeShape | undefined): boolean;
|
|
52
|
+
/** How much of each end of a hexagon the corner cuts away. */
|
|
53
|
+
export declare function hexagonCut(width: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* A hexagon's six points, in drawing order: top-left, top-right, right, bottom-right, bottom-left, left.
|
|
56
|
+
*
|
|
57
|
+
* The top and bottom are flat runs between the first two and the last two points; the left and right sides are the
|
|
58
|
+
* single points at index 5 and 2, because the corner cut removed everything else.
|
|
59
|
+
*/
|
|
60
|
+
export declare function hexagonPoints(box: Box): [Point, Point, Point, Point, Point, Point];
|
|
61
|
+
/** The radius of a stadium's end caps: half its height, unless the box is narrower than that. */
|
|
62
|
+
export declare function stadiumRadius(box: Box): number;
|
|
63
|
+
/**
|
|
64
|
+
* A cylinder's two caps.
|
|
65
|
+
*
|
|
66
|
+
* The writer draws the top cap from the left shoulder to the right shoulder with the box's own top edge as the
|
|
67
|
+
* control line, and the bottom cap mirrored. The cap curve's apex - the point the painter attaches a top or bottom
|
|
68
|
+
* port to - is therefore a property of this curve, not a constant: it sits a quarter of the cap's depth inside the
|
|
69
|
+
* box, not `cap` inside it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function cylinderCaps(box: Box): CylinderCaps;
|
|
72
|
+
/** The apex of a cylinder's top or bottom cap: the only point on that side an edge meets the outline at. */
|
|
73
|
+
export declare function cylinderApex(box: Box, side: 'top' | 'bottom'): Point;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text as the layout sees it: measured, broken into lines, and turned into a box.
|
|
3
|
+
*
|
|
4
|
+
* Mermaid and Graphviz both measure a label in the platform's own text engine and then lay out around that
|
|
5
|
+
* measurement - a node's size is its text plus padding, and a label too long for its maximum width is wrapped
|
|
6
|
+
* before anything is placed. There is no DOM here, so the measurement is an estimate over a per-character width
|
|
7
|
+
* table; what matters is that the *order* is the same: text first, geometry second. A renderer that fixes the box
|
|
8
|
+
* and pours text into it cannot help overflowing.
|
|
9
|
+
*
|
|
10
|
+
* Explicit breaks (`<br>`, `<br/>`, `<br />`, a real newline) are honoured as written, because a source that put
|
|
11
|
+
* one there wanted that line to end.
|
|
12
|
+
*/
|
|
13
|
+
/** A line longer than this wraps on a space, or breaks in the middle when it has none. */
|
|
14
|
+
export declare const MAX_LINE_CHARS = 30;
|
|
15
|
+
/** Height of one line of box text, and the breathing room around the block. */
|
|
16
|
+
export declare const LINE_HEIGHT = 15;
|
|
17
|
+
export declare const TEXT_PADDING = 12;
|
|
18
|
+
/** The smallest and largest box a label may produce. */
|
|
19
|
+
export declare const MIN_NODE_WIDTH = 88;
|
|
20
|
+
export declare const MAX_NODE_WIDTH = 260;
|
|
21
|
+
/** The lines a label asks for, before wrapping: an explicit break is a line end, not a space. */
|
|
22
|
+
export declare function explicitLines(label: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Wrap one line to the maximum width.
|
|
25
|
+
*
|
|
26
|
+
* A word longer than the limit is broken rather than allowed to overflow: a single token with no spaces is the
|
|
27
|
+
* common case in identifiers, and letting it run off the box is the defect this function exists to prevent.
|
|
28
|
+
*/
|
|
29
|
+
export declare function wrapLine(line: string, limit?: number): string[];
|
|
30
|
+
export interface Measured {
|
|
31
|
+
lines: string[];
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
}
|
|
35
|
+
/** The text block a label produces: its lines, and the box it needs. */
|
|
36
|
+
export declare function measure(label: string): Measured;
|
|
37
|
+
/** Rough width of a single-line piece of text, for placing an edge label without a box. */
|
|
38
|
+
export declare function textWidth(text: string): number;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapping: turning one endless column into a page.
|
|
3
|
+
*
|
|
4
|
+
* A chain of twelve boxes has twelve layers, and layering alone draws it as a single column taller than anything a
|
|
5
|
+
* reader can take in - a strip, not a drawing. Graphviz and ELK both answer this with a wrapping pass: cut the layer
|
|
6
|
+
* sequence into bands and stand the bands side by side, so reading order becomes "down the page, then the next
|
|
7
|
+
* column", the way text wraps.
|
|
8
|
+
*
|
|
9
|
+
* The cut is chosen, not guessed: each candidate number of bands is scored by how close the packed page gets to the
|
|
10
|
+
* aspect ratio a page and a landscape screen share, and the best one wins. A drawing that is already close to that
|
|
11
|
+
* shape is left alone - wrapping exists for the strip, not for every tall drawing.
|
|
12
|
+
*
|
|
13
|
+
* Pure: layer sizes in, band ranges and offsets out. The caller applies them.
|
|
14
|
+
*/
|
|
15
|
+
export interface BandRange {
|
|
16
|
+
/** The first and last layer the band holds, inclusive. */
|
|
17
|
+
first: number;
|
|
18
|
+
last: number;
|
|
19
|
+
/** How far right the band stands, relative to the first one. */
|
|
20
|
+
offsetX: number;
|
|
21
|
+
}
|
|
22
|
+
export interface BandPlan {
|
|
23
|
+
bands: BandRange[];
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function planBands(layerWidth: number[], layerHeight: number[], gap: number, aspect?: number): BandPlan;
|