nicevg 0.20.1 → 0.21.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
@@ -38,8 +38,8 @@ import { box, circle } from "nicevg/style/simple";
38
38
 
39
39
  const image = render({
40
40
  nodes: [
41
- { id: "user", class: "circle", x: 20, y: 20, width: 60, height: 60, label: "User" },
42
- { id: "order", class: "box", x: 200, y: 22, width: 120, height: 56, label: "Order" },
41
+ { id: "user", class: "circle", x: 20, y: 20, label: "User", fill: "#fef3c7" },
42
+ { id: "order", class: "box", x: 200, y: 30, label: "Order", fill: "#dbeafe" },
43
43
  ],
44
44
  edges: [{ from: "user", to: "order", label: "place" }],
45
45
  style: { box, circle },
@@ -49,7 +49,7 @@ await image.write("diagram.svg");
49
49
 
50
50
  ### Styling
51
51
 
52
- A style is a function that draws its nodes as SVG, with `svg` for the markup and `text` for labels measured the way nicevg measures them.
52
+ A style is a function that draws its nodes as SVG.
53
53
 
54
54
  ```ts
55
55
  import { render, svg, text } from "nicevg";
@@ -115,4 +115,4 @@ Fixes the diagram, nudges its nodes into a cleaner layout and returns an `Svg`.
115
115
  | `text(...).at({ left \| cx \| right, top \| cy \| bottom })` | Places the text so that point of its box sits at those coordinates. |
116
116
  | `` svg`...`.at(x, y) `` | The fragment with its origin moved to `x`, `y`. |
117
117
 
118
- `nicevg/style/simple` has `box` and `circle`, white shapes outlined in black with the label centred, growing to fit their label: `import { box, circle } from "nicevg/style/simple"`, then `style: { box, circle }`. Their nodes take `width`, `height`, `label` (lines split at `\n`) and `fontSize`; a circle sits in the middle of its `width` and `height`.
118
+ `nicevg/style/simple` has `box` and `circle`, white shapes outlined in black with the label centred, growing to fit their label: `import { box, circle } from "nicevg/style/simple"`, then `style: { box, circle }`. Their nodes take `label` (lines split at `\n`), `fontSize`, and optionally `width` and `height`; without them a node is just big enough for its label, and a circle sits in the middle of its `width` and `height`. `fill`, `stroke`, `strokeWidth` and `strokeDasharray` paint the shape, as the SVG attributes of those names do.
package/dist/index.d.ts CHANGED
@@ -53,8 +53,8 @@ export interface SketchConnector {
53
53
  * It lies around what the style draws, or the rect or circle the style marks
54
54
  * `data-bounds="true"`, and rendering moves it but never resizes it. Its
55
55
  * other fields, such as `label`, are for the style to use: `box` and
56
- * `circle` from `nicevg/style/simple` take `width`, `height`, `label` and
57
- * `fontSize`.
56
+ * `circle` from `nicevg/style/simple` take `label`, `fontSize` and, when
57
+ * the label alone should not size them, `width` and `height`.
58
58
  */
59
59
  export interface SketchNode {
60
60
  id: string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Boxes and circles drawn the way nicevg draws a sketch's rect and circle
3
- * nodes: white, outlined in black, the label centred in lines. A label
3
+ * nodes: white, outlined in black unless painted, the label centred in lines. A label
4
4
  * too big for its node grows it as fixing grows those, so a diagram
5
5
  * drawn with these styles arranges as the same diagram without them.
6
6
  *
@@ -8,19 +8,24 @@
8
8
  * import { render } from "nicevg";
9
9
  * import { box, circle } from "nicevg/style/simple";
10
10
  *
11
- * render({ nodes: [{ id: "a", class: "box", x: 20, y: 20, width: 120, height: 56, label: "Order" }], style: { box, circle } });
11
+ * render({ nodes: [{ id: "a", class: "box", x: 20, y: 20, label: "Order" }], style: { box, circle } });
12
12
  * ```
13
13
  */
14
14
  import { type SvgFragment } from "../draw.ts";
15
- /** A node a simple style draws: the box it takes, and its text. */
15
+ /** A node a simple style draws: the box it takes, just big enough for its label when not given, and its text. */
16
16
  export interface SimpleNode {
17
- width: number;
18
- height: number;
17
+ width?: number;
18
+ height?: number;
19
19
  /** Its text, lines split at `\n`, centred. */
20
20
  label?: string;
21
21
  fontSize?: number;
22
+ /** The shape's paint, as the SVG attributes of the same names: white, outlined 1px in black, unless given. */
23
+ fill?: string;
24
+ stroke?: string;
25
+ strokeWidth?: number;
26
+ strokeDasharray?: string;
22
27
  }
23
28
  /** A box, grown to keep its label 12px inside it. */
24
- export declare function box({ width, height, label: content, fontSize }: SimpleNode): SvgFragment;
29
+ export declare function box({ label: content, fontSize, ...node }: SimpleNode): SvgFragment;
25
30
  /** A circle in the middle of the box it is given, grown to keep its label 12px inside it. */
26
- export declare function circle({ width, height, label: content, fontSize }: SimpleNode): SvgFragment;
31
+ export declare function circle({ label: content, fontSize, ...node }: SimpleNode): SvgFragment;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Boxes and circles drawn the way nicevg draws a sketch's rect and circle
3
- * nodes: white, outlined in black, the label centred in lines. A label
3
+ * nodes: white, outlined in black unless painted, the label centred in lines. A label
4
4
  * too big for its node grows it as fixing grows those, so a diagram
5
5
  * drawn with these styles arranges as the same diagram without them.
6
6
  *
@@ -8,10 +8,14 @@
8
8
  * import { render } from "nicevg";
9
9
  * import { box, circle } from "nicevg/style/simple";
10
10
  *
11
- * render({ nodes: [{ id: "a", class: "box", x: 20, y: 20, width: 120, height: 56, label: "Order" }], style: { box, circle } });
11
+ * render({ nodes: [{ id: "a", class: "box", x: 20, y: 20, label: "Order" }], style: { box, circle } });
12
12
  * ```
13
13
  */
14
14
  import { WIDE, svg } from "../draw.js";
15
+ /** The shape's paint attributes. */
16
+ function paint({ fill = "white", stroke = "black", strokeWidth, strokeDasharray }) {
17
+ return svg `fill="${fill}" stroke="${stroke}"${strokeWidth === undefined ? "" : svg ` stroke-width="${strokeWidth}"`}${strokeDasharray === undefined ? "" : svg ` stroke-dasharray="${strokeDasharray}"`}`;
18
+ }
15
19
  const LINE_HEIGHT = 1.2;
16
20
  /** How far a label keeps inside its node. */
17
21
  const PADDING = 12;
@@ -49,24 +53,36 @@ function label(content, cx, cy, size) {
49
53
  bounds: { x: left, y: top, width: right - left, height: bottom - top },
50
54
  };
51
55
  }
56
+ /** How far a label centred on a point reaches from it across and down. */
57
+ function reach(content, size) {
58
+ if (content === undefined) {
59
+ return [0, 0];
60
+ }
61
+ const { x, y, width, height } = label(content, 0, 0, size).bounds;
62
+ return [Math.max(-x, x + width), Math.max(-y, y + height)];
63
+ }
52
64
  /** A box, grown to keep its label 12px inside it. */
53
- export function box({ width, height, label: content, fontSize = 14 }) {
65
+ export function box({ label: content, fontSize = 14, ...node }) {
66
+ const [dx, dy] = reach(content, fontSize);
67
+ const { width = 2 * (dx + PADDING), height = 2 * (dy + PADDING) } = node;
54
68
  if (content === undefined) {
55
- return svg `<rect data-bounds="true" x="0" y="0" width="${width}" height="${height}" fill="white" stroke="black"/>`;
69
+ return svg `<rect data-bounds="true" x="0" y="0" width="${width}" height="${height}" ${paint(node)}/>`;
56
70
  }
57
71
  const text = label(content, width / 2, height / 2, fontSize);
58
72
  const left = Math.min(0, text.bounds.x - PADDING);
59
73
  const top = Math.min(0, text.bounds.y - PADDING);
60
74
  const right = Math.max(width, text.bounds.x + text.bounds.width + PADDING);
61
75
  const bottom = Math.max(height, text.bounds.y + text.bounds.height + PADDING);
62
- return svg `<rect data-bounds="true" x="${left}" y="${top}" width="${right - left}" height="${bottom - top}" fill="white" stroke="black"/>${text.markup}`;
76
+ return svg `<rect data-bounds="true" x="${left}" y="${top}" width="${right - left}" height="${bottom - top}" ${paint(node)}/>${text.markup}`;
63
77
  }
64
78
  /** A circle in the middle of the box it is given, grown to keep its label 12px inside it. */
65
- export function circle({ width, height, label: content, fontSize = 14 }) {
79
+ export function circle({ label: content, fontSize = 14, ...node }) {
80
+ const diameter = 2 * Math.ceil(Math.hypot(...reach(content, fontSize)) + PADDING);
81
+ const { width = diameter, height = diameter } = node;
66
82
  const [cx, cy] = [width / 2, height / 2];
67
83
  let radius = Math.min(width, height) / 2;
68
84
  if (content === undefined) {
69
- return svg `<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" fill="white" stroke="black"/>`;
85
+ return svg `<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" ${paint(node)}/>`;
70
86
  }
71
87
  const text = label(content, cx, cy, fontSize);
72
88
  const dx = Math.max(Math.abs(text.bounds.x - cx), Math.abs(text.bounds.x + text.bounds.width - cx));
@@ -75,6 +91,6 @@ export function circle({ width, height, label: content, fontSize = 14 }) {
75
91
  if (needed > radius) {
76
92
  radius = Math.ceil(needed);
77
93
  }
78
- return svg `<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" fill="white" stroke="black"/>${text.markup}`;
94
+ return svg `<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" ${paint(node)}/>${text.markup}`;
79
95
  }
80
96
  //# sourceMappingURL=simple.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"simple.js","sourceRoot":"","sources":["../../src/style/simple.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAoB,IAAI,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAWzD,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,6CAA6C;AAC7C,MAAM,OAAO,GAAG,EAAE,CAAC;AAEnB,kCAAkC;AAClC,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhH,kGAAkG;AAClG,SAAS,SAAS,CAAC,IAAY,EAAE,IAAY;IAC3C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;AACnD,CAAC;AASD,iGAAiG;AACjG,SAAS,KAAK,CAAC,OAAe,EAAE,EAAU,EAAE,EAAU,EAAE,IAAY;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IAC/E,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,WAAW,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACtC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACpC,OAAO,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAE,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC;IAC/F,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,OAAO;QACL,MAAM,EAAE,GAAG,CAAA,yCAAyC,IAAI,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAA,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAE,KAAK,IAAI,UAAU,CAAC,SAAS;QACzJ,MAAM,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,EAAE;KACvE,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,GAAG,EAAE,EAAc;IAC9E,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAA,+CAA+C,KAAK,aAAa,MAAM,iCAAiC,CAAC;IACrH,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;IAC9E,OAAO,GAAG,CAAA,+BAA+B,IAAI,QAAQ,GAAG,YAAY,KAAK,GAAG,IAAI,aAAa,MAAM,GAAG,GAAG,kCAAkC,IAAI,CAAC,MAAM,EAAE,CAAC;AAC3J,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,GAAG,EAAE,EAAc;IACjF,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;IACzC,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAA,kCAAkC,EAAE,SAAS,EAAE,QAAQ,MAAM,iCAAiC,CAAC;IAC3G,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;IACpG,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC;IACrG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC;IAC5C,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC;QACpB,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAA,kCAAkC,EAAE,SAAS,EAAE,QAAQ,MAAM,kCAAkC,IAAI,CAAC,MAAM,EAAE,CAAC;AACzH,CAAC"}
1
+ {"version":3,"file":"simple.js","sourceRoot":"","sources":["../../src/style/simple.ts"],"names":[],"mappings":"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"}
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nicevg",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "description": "Deterministic checks and repairs for diagrams, given as shapes or SVG, on Bun and Node through native FFI.",
5
5
  "keywords": [
6
6
  "diagram",
package/src/index.ts CHANGED
@@ -62,8 +62,8 @@ export interface SketchConnector {
62
62
  * It lies around what the style draws, or the rect or circle the style marks
63
63
  * `data-bounds="true"`, and rendering moves it but never resizes it. Its
64
64
  * other fields, such as `label`, are for the style to use: `box` and
65
- * `circle` from `nicevg/style/simple` take `width`, `height`, `label` and
66
- * `fontSize`.
65
+ * `circle` from `nicevg/style/simple` take `label`, `fontSize` and, when
66
+ * the label alone should not size them, `width` and `height`.
67
67
  */
68
68
  export interface SketchNode {
69
69
  id: string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Boxes and circles drawn the way nicevg draws a sketch's rect and circle
3
- * nodes: white, outlined in black, the label centred in lines. A label
3
+ * nodes: white, outlined in black unless painted, the label centred in lines. A label
4
4
  * too big for its node grows it as fixing grows those, so a diagram
5
5
  * drawn with these styles arranges as the same diagram without them.
6
6
  *
@@ -8,19 +8,29 @@
8
8
  * import { render } from "nicevg";
9
9
  * import { box, circle } from "nicevg/style/simple";
10
10
  *
11
- * render({ nodes: [{ id: "a", class: "box", x: 20, y: 20, width: 120, height: 56, label: "Order" }], style: { box, circle } });
11
+ * render({ nodes: [{ id: "a", class: "box", x: 20, y: 20, label: "Order" }], style: { box, circle } });
12
12
  * ```
13
13
  */
14
14
 
15
15
  import { type SvgFragment, WIDE, svg } from "../draw.ts";
16
16
 
17
- /** A node a simple style draws: the box it takes, and its text. */
17
+ /** A node a simple style draws: the box it takes, just big enough for its label when not given, and its text. */
18
18
  export interface SimpleNode {
19
- width: number;
20
- height: number;
19
+ width?: number;
20
+ height?: number;
21
21
  /** Its text, lines split at `\n`, centred. */
22
22
  label?: string;
23
23
  fontSize?: number;
24
+ /** The shape's paint, as the SVG attributes of the same names: white, outlined 1px in black, unless given. */
25
+ fill?: string;
26
+ stroke?: string;
27
+ strokeWidth?: number;
28
+ strokeDasharray?: string;
29
+ }
30
+
31
+ /** The shape's paint attributes. */
32
+ function paint({ fill = "white", stroke = "black", strokeWidth, strokeDasharray }: SimpleNode): SvgFragment {
33
+ return svg`fill="${fill}" stroke="${stroke}"${strokeWidth === undefined ? "" : svg` stroke-width="${strokeWidth}"`}${strokeDasharray === undefined ? "" : svg` stroke-dasharray="${strokeDasharray}"`}`;
24
34
  }
25
35
 
26
36
  const LINE_HEIGHT = 1.2;
@@ -70,25 +80,38 @@ function label(content: string, cx: number, cy: number, size: number): { markup:
70
80
  };
71
81
  }
72
82
 
83
+ /** How far a label centred on a point reaches from it across and down. */
84
+ function reach(content: string | undefined, size: number): [number, number] {
85
+ if (content === undefined) {
86
+ return [0, 0];
87
+ }
88
+ const { x, y, width, height } = label(content, 0, 0, size).bounds;
89
+ return [Math.max(-x, x + width), Math.max(-y, y + height)];
90
+ }
91
+
73
92
  /** A box, grown to keep its label 12px inside it. */
74
- export function box({ width, height, label: content, fontSize = 14 }: SimpleNode): SvgFragment {
93
+ export function box({ label: content, fontSize = 14, ...node }: SimpleNode): SvgFragment {
94
+ const [dx, dy] = reach(content, fontSize);
95
+ const { width = 2 * (dx + PADDING), height = 2 * (dy + PADDING) } = node;
75
96
  if (content === undefined) {
76
- return svg`<rect data-bounds="true" x="0" y="0" width="${width}" height="${height}" fill="white" stroke="black"/>`;
97
+ return svg`<rect data-bounds="true" x="0" y="0" width="${width}" height="${height}" ${paint(node)}/>`;
77
98
  }
78
99
  const text = label(content, width / 2, height / 2, fontSize);
79
100
  const left = Math.min(0, text.bounds.x - PADDING);
80
101
  const top = Math.min(0, text.bounds.y - PADDING);
81
102
  const right = Math.max(width, text.bounds.x + text.bounds.width + PADDING);
82
103
  const bottom = Math.max(height, text.bounds.y + text.bounds.height + PADDING);
83
- return svg`<rect data-bounds="true" x="${left}" y="${top}" width="${right - left}" height="${bottom - top}" fill="white" stroke="black"/>${text.markup}`;
104
+ return svg`<rect data-bounds="true" x="${left}" y="${top}" width="${right - left}" height="${bottom - top}" ${paint(node)}/>${text.markup}`;
84
105
  }
85
106
 
86
107
  /** A circle in the middle of the box it is given, grown to keep its label 12px inside it. */
87
- export function circle({ width, height, label: content, fontSize = 14 }: SimpleNode): SvgFragment {
108
+ export function circle({ label: content, fontSize = 14, ...node }: SimpleNode): SvgFragment {
109
+ const diameter = 2 * Math.ceil(Math.hypot(...reach(content, fontSize)) + PADDING);
110
+ const { width = diameter, height = diameter } = node;
88
111
  const [cx, cy] = [width / 2, height / 2];
89
112
  let radius = Math.min(width, height) / 2;
90
113
  if (content === undefined) {
91
- return svg`<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" fill="white" stroke="black"/>`;
114
+ return svg`<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" ${paint(node)}/>`;
92
115
  }
93
116
  const text = label(content, cx, cy, fontSize);
94
117
  const dx = Math.max(Math.abs(text.bounds.x - cx), Math.abs(text.bounds.x + text.bounds.width - cx));
@@ -97,5 +120,5 @@ export function circle({ width, height, label: content, fontSize = 14 }: SimpleN
97
120
  if (needed > radius) {
98
121
  radius = Math.ceil(needed);
99
122
  }
100
- return svg`<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" fill="white" stroke="black"/>${text.markup}`;
123
+ return svg`<circle data-bounds="true" cx="${cx}" cy="${cy}" r="${radius}" ${paint(node)}/>${text.markup}`;
101
124
  }