@yalehwang/plantuml-js 0.4.0 → 0.5.0

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.md CHANGED
@@ -6,9 +6,28 @@ default, `@viz-js/viz` (WASM Graphviz) as an option.
6
6
 
7
7
  > **Engine status** — `'viz'` is **experimental**. It reaches only a fraction
8
8
  > of `'elk'`'s area utilization and still routes a few edges through unrelated
9
- > nodes on large package diagrams. See [`docs/viz-decision.md`](docs/viz-decision.md)
9
+ > nodes on large package diagrams. See
10
+ > [`docs/viz-decision.md`](https://github.com/yaleh/plantuml-js/blob/author/docs/viz-decision.md)
10
11
  > for the measured comparison. Use `'elk'` (the default) for production diagrams.
11
12
 
13
+ ## What it renders
14
+
15
+ `@yalehwang/plantuml-js` turns a PlantUML class diagram into a styled SVG. Here
16
+ is a minimal diagram through the default `'elk'` engine — three-compartment
17
+ class/interface boxes, a package, and implementation / composition / dependency
18
+ edges:
19
+
20
+ ![Minimal class diagram — package, class boxes, and three edge kinds](https://raw.githubusercontent.com/yaleh/plantuml-js/author/docs/img/quickstart.elk.png)
21
+
22
+ The same pipeline on a real medium-size architecture graph (package grouping,
23
+ orthogonal edge routing, bundled arrowheads):
24
+
25
+ ![Medium architecture diagram](https://raw.githubusercontent.com/yaleh/plantuml-js/author/docs/img/m-architecture.elk.png)
26
+
27
+ See the [gallery](https://github.com/yaleh/plantuml-js/blob/author/docs/gallery.md)
28
+ for every figure's source `.puml`, its SVG output, and the `elk` vs `viz`
29
+ comparison.
30
+
12
31
  ## Install
13
32
 
14
33
  ```bash
package/dist/core.d.ts CHANGED
@@ -46,6 +46,18 @@ export interface RendererOptions {
46
46
  * Default: chosen by entity count (≤40 full, ≤150 compact, else names).
47
47
  */
48
48
  detail?: DetailLevel;
49
+ /**
50
+ * Maximum member rows drawn per entity; surplus members collapse into the
51
+ * existing `… +N more (M members)` row. Applied before layout, so the box is
52
+ * sized for what is drawn. Default: unlimited for `full`, 4 for `compact`.
53
+ */
54
+ memberMaxRows?: number;
55
+ /**
56
+ * Cut each drawn member line to this many characters (ending with `…`).
57
+ * Only the drawn text changes: `PlantUmlMember.raw` keeps the full
58
+ * declaration. Default: unlimited.
59
+ */
60
+ memberMaxChars?: number;
49
61
  /**
50
62
  * `'aggregate'` folds the relations between two packages into one weighted
51
63
  * box-to-box corridor edge (originals kept in its `<title>`); `'raw'` draws
package/dist/core.js CHANGED
@@ -68,7 +68,10 @@ export function createRenderer(options = {}) {
68
68
  }
69
69
  async function renderParsed(parsed, options, engine, renderEngine) {
70
70
  const { model: transformed, hiddenTransitive } = transformForRender(parsed, options);
71
- const model = applyDetail(transformed, options.detail);
71
+ const model = applyDetail(transformed, options.detail, {
72
+ memberMaxRows: options.memberMaxRows,
73
+ memberMaxChars: options.memberMaxChars,
74
+ });
72
75
  const started = performance.now();
73
76
  const layout = await engine.layout(model);
74
77
  const layoutMs = Math.round((performance.now() - started) * 100) / 100;
@@ -105,11 +105,28 @@ export declare const DETAIL_COMPACT_MAX_ENTITIES = 150;
105
105
  /** Member rows kept per entity in `compact` mode. */
106
106
  export declare const COMPACT_MEMBER_ROWS = 4;
107
107
  export declare function autoDetail(entityCount: number): DetailLevel;
108
+ /**
109
+ * Optional caps on the member text a diagram draws. Both are applied by
110
+ * [[applyDetail]] before layout, so an entity is sized for exactly what the
111
+ * renderer emits; neither mutates `PlantUmlMember.raw`.
112
+ */
113
+ export interface MemberDisplayLimits {
114
+ /** Maximum member rows drawn per entity; the rest collapse into the count row. */
115
+ memberMaxRows?: number;
116
+ /** Cut each drawn member line to this many characters, ending with `…`. */
117
+ memberMaxChars?: number;
118
+ }
108
119
  /**
109
120
  * Reduce member rows to the requested detail level *before* layout, so boxes
110
121
  * are sized for what is actually drawn. `undefined` picks by entity count.
122
+ *
123
+ * `limits` caps the drawn rows per entity (`memberMaxRows`, replacing
124
+ * [[COMPACT_MEMBER_ROWS]] for `compact` and also capping `full`) and the width
125
+ * of each drawn line (`memberMaxChars`). Truncation only sets
126
+ * `PlantUmlMember.display`; `raw` keeps the full declaration. Without limits
127
+ * the result is byte-for-byte what it always was.
111
128
  */
112
- export declare function applyDetail(model: PlantUmlModel, detail?: DetailLevel): PlantUmlModel;
129
+ export declare function applyDetail(model: PlantUmlModel, detail?: DetailLevel, limits?: MemberDisplayLimits): PlantUmlModel;
113
130
  /**
114
131
  * Group nodes into rows: within one package (or the top level), nodes whose
115
132
  * vertical extents overlap belong to the same layered-layout rank. Returns
@@ -74,7 +74,7 @@ export function compartmentHeights(entity) {
74
74
  export function estimateEntitySize(entity) {
75
75
  const lines = [
76
76
  displayName(entity),
77
- ...entity.members.map((m) => m.raw),
77
+ ...entity.members.map((m) => m.display ?? m.raw),
78
78
  ];
79
79
  const longest = lines.reduce((max, line) => Math.max(max, measureTextWidth(line, METRICS.fontSize)), 0);
80
80
  const width = snapUp(Math.max(METRICS.minWidth, longest + METRICS.paddingX * 2));
@@ -98,26 +98,48 @@ export function autoDetail(entityCount) {
98
98
  /**
99
99
  * Reduce member rows to the requested detail level *before* layout, so boxes
100
100
  * are sized for what is actually drawn. `undefined` picks by entity count.
101
+ *
102
+ * `limits` caps the drawn rows per entity (`memberMaxRows`, replacing
103
+ * [[COMPACT_MEMBER_ROWS]] for `compact` and also capping `full`) and the width
104
+ * of each drawn line (`memberMaxChars`). Truncation only sets
105
+ * `PlantUmlMember.display`; `raw` keeps the full declaration. Without limits
106
+ * the result is byte-for-byte what it always was.
101
107
  */
102
- export function applyDetail(model, detail) {
108
+ export function applyDetail(model, detail, limits = {}) {
103
109
  const level = detail ?? autoDetail(model.entities.length);
104
- if (level === 'full')
110
+ const { memberMaxRows, memberMaxChars } = limits;
111
+ const hasLimits = memberMaxRows !== undefined || memberMaxChars !== undefined;
112
+ if (level === 'full' && !hasLimits)
105
113
  return model;
114
+ const limitMember = (member) => {
115
+ if (memberMaxChars === undefined || member.raw.length <= memberMaxChars)
116
+ return member;
117
+ return {
118
+ ...member,
119
+ display: `${member.raw.slice(0, Math.max(0, memberMaxChars - 1))}…`,
120
+ };
121
+ };
106
122
  return {
107
123
  ...model,
108
124
  entities: model.entities.map((entity) => {
109
125
  const total = entity.members.length;
110
126
  if (level === 'names')
111
127
  return total === 0 ? entity : { ...entity, members: [] };
112
- if (total <= COMPACT_MEMBER_ROWS)
113
- return entity;
114
- const kept = entity.members.slice(0, COMPACT_MEMBER_ROWS);
128
+ const rowCap = memberMaxRows !== undefined
129
+ ? memberMaxRows
130
+ : level === 'compact'
131
+ ? COMPACT_MEMBER_ROWS
132
+ : total;
133
+ if (total <= rowCap) {
134
+ return hasLimits ? { ...entity, members: entity.members.map(limitMember) } : entity;
135
+ }
136
+ const kept = entity.members.slice(0, rowCap).map(limitMember);
115
137
  const more = {
116
138
  visibility: 'public',
117
139
  name: '…',
118
140
  kind: 'field',
119
141
  modifiers: [],
120
- raw: `… +${total - COMPACT_MEMBER_ROWS} more (${total} members)`,
142
+ raw: `… +${total - rowCap} more (${total} members)`,
121
143
  };
122
144
  return { ...entity, members: [...kept, more] };
123
145
  }),
@@ -20,6 +20,13 @@ export interface PlantUmlMember {
20
20
  optional?: boolean;
21
21
  /** Full authored declaration incl. visibility symbol, for faithful display. */
22
22
  raw: string;
23
+ /**
24
+ * Text actually drawn for this member, when it differs from `raw` (e.g. cut
25
+ * to a display width by `applyDetail`). Layout and rendering read
26
+ * `display ?? raw`; `raw` itself is never modified, so serialization and
27
+ * inspector views keep the full declaration.
28
+ */
29
+ display?: string;
23
30
  }
24
31
  export interface PlantUmlEntity {
25
32
  /** Stable node identity inside the diagram (the alias when `as` is used). */
@@ -166,7 +166,7 @@ function renderEntity(entity, node, theme) {
166
166
  parts.push(rule(opsTop, 'ops'));
167
167
  const titleBaseline = node.y + METRICS.paddingY + METRICS.fontSize + 1;
168
168
  parts.push(textEl(displayName(entity), node.x + node.width / 2, titleBaseline, 'middle', font.size.title, font.weight.title, color.textTitle, theme, fontStyle));
169
- const memberText = (member, idx, top, cls) => textEl(member.raw, node.x + METRICS.paddingX, top + METRICS.lineHeight * idx + METRICS.fontSize + 1, 'start', font.size.member, font.weight.member, color.textMember, theme, ` class="${cls}"`);
169
+ const memberText = (member, idx, top, cls) => textEl(member.display ?? member.raw, node.x + METRICS.paddingX, top + METRICS.lineHeight * idx + METRICS.fontSize + 1, 'start', font.size.member, font.weight.member, color.textMember, theme, ` class="${cls}"`);
170
170
  attrs.forEach((member, i) => {
171
171
  parts.push(memberText(member, i, titleBottom, 'uml-attr'));
172
172
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yalehwang/plantuml-js",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Pure-TypeScript PlantUML class diagram renderer (MIT)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,6 +39,8 @@
39
39
  "typecheck": "tsc -p tsconfig.test.json",
40
40
  "test": "vitest run",
41
41
  "demo:build": "node scripts/build-demo.mjs",
42
+ "docs": "node scripts/render-docs.mjs",
43
+ "docs:check": "node scripts/render-docs.mjs --check",
42
44
  "release": "node scripts/release.mjs",
43
45
  "prepublishOnly": "npm run typecheck && npm run build && node scripts/check-dist-clean.mjs"
44
46
  },