@statewalker/fsm-charts 0.0.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1 -0
  4. package/dist/StateChart-DNGQd1XG.d.ts +53 -0
  5. package/dist/chunk-3GGM3ZWL.js +2685 -0
  6. package/dist/chunk-NXWOFQMO.js +10 -0
  7. package/dist/chunk-TPNWHU7Y.js +437 -0
  8. package/dist/index-layout.d.ts +96 -0
  9. package/dist/index-layout.js +2 -0
  10. package/dist/index-svg.d.ts +25 -0
  11. package/dist/index-svg.js +2 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +3 -0
  14. package/package.json +60 -0
  15. package/src/FsmStateConfig.ts +5 -0
  16. package/src/dagre/acyclic.d.ts +2 -0
  17. package/src/dagre/acyclic.js +60 -0
  18. package/src/dagre/add-border-segments.d.ts +1 -0
  19. package/src/dagre/add-border-segments.js +36 -0
  20. package/src/dagre/coordinate-system.d.ts +2 -0
  21. package/src/dagre/coordinate-system.js +75 -0
  22. package/src/dagre/data/list.d.ts +12 -0
  23. package/src/dagre/data/list.js +55 -0
  24. package/src/dagre/debug.d.ts +2 -0
  25. package/src/dagre/debug.js +32 -0
  26. package/src/dagre/greedy-fas.d.ts +1 -0
  27. package/src/dagre/greedy-fas.js +126 -0
  28. package/src/dagre/index.d.ts +5 -0
  29. package/src/dagre/index.js +6 -0
  30. package/src/dagre/layout.d.ts +1 -0
  31. package/src/dagre/layout.js +466 -0
  32. package/src/dagre/nesting-graph.d.ts +2 -0
  33. package/src/dagre/nesting-graph.js +137 -0
  34. package/src/dagre/normalize.d.ts +2 -0
  35. package/src/dagre/normalize.js +91 -0
  36. package/src/dagre/order/add-subgraph-constraints.d.ts +1 -0
  37. package/src/dagre/order/add-subgraph-constraints.js +53 -0
  38. package/src/dagre/order/barycenter.d.ts +15 -0
  39. package/src/dagre/order/barycenter.js +31 -0
  40. package/src/dagre/order/build-layer-graph.d.ts +2 -0
  41. package/src/dagre/order/build-layer-graph.js +76 -0
  42. package/src/dagre/order/cross-count.d.ts +1 -0
  43. package/src/dagre/order/cross-count.js +82 -0
  44. package/src/dagre/order/index.d.ts +1 -0
  45. package/src/dagre/order/index.js +77 -0
  46. package/src/dagre/order/init-order.d.ts +1 -0
  47. package/src/dagre/order/init-order.js +44 -0
  48. package/src/dagre/order/resolve-conflicts.d.ts +1 -0
  49. package/src/dagre/order/resolve-conflicts.js +128 -0
  50. package/src/dagre/order/sort-subgraph.d.ts +8 -0
  51. package/src/dagre/order/sort-subgraph.js +78 -0
  52. package/src/dagre/order/sort.d.ts +6 -0
  53. package/src/dagre/order/sort.js +59 -0
  54. package/src/dagre/parent-dummy-chains.d.ts +1 -0
  55. package/src/dagre/parent-dummy-chains.js +86 -0
  56. package/src/dagre/position/bk.d.ts +31 -0
  57. package/src/dagre/position/bk.js +423 -0
  58. package/src/dagre/position/index.d.ts +1 -0
  59. package/src/dagre/position/index.js +31 -0
  60. package/src/dagre/rank/feasible-tree.d.ts +2 -0
  61. package/src/dagre/rank/feasible-tree.js +87 -0
  62. package/src/dagre/rank/index.d.ts +1 -0
  63. package/src/dagre/rank/index.js +52 -0
  64. package/src/dagre/rank/network-simplex.d.ts +16 -0
  65. package/src/dagre/rank/network-simplex.js +236 -0
  66. package/src/dagre/rank/util.d.ts +2 -0
  67. package/src/dagre/rank/util.js +63 -0
  68. package/src/dagre/util.d.ts +27 -0
  69. package/src/dagre/util.js +250 -0
  70. package/src/graphlib/alg/components.d.ts +1 -0
  71. package/src/graphlib/alg/components.js +27 -0
  72. package/src/graphlib/alg/dfs.d.ts +1 -0
  73. package/src/graphlib/alg/dfs.js +46 -0
  74. package/src/graphlib/alg/dijkstra-all.d.ts +1 -0
  75. package/src/graphlib/alg/dijkstra-all.js +14 -0
  76. package/src/graphlib/alg/dijkstra.d.ts +1 -0
  77. package/src/graphlib/alg/dijkstra.js +65 -0
  78. package/src/graphlib/alg/find-cycles.d.ts +1 -0
  79. package/src/graphlib/alg/find-cycles.js +10 -0
  80. package/src/graphlib/alg/floyd-warshall.d.ts +1 -0
  81. package/src/graphlib/alg/floyd-warshall.js +55 -0
  82. package/src/graphlib/alg/index.d.ts +24 -0
  83. package/src/graphlib/alg/index.js +25 -0
  84. package/src/graphlib/alg/is-acyclic.d.ts +1 -0
  85. package/src/graphlib/alg/is-acyclic.js +15 -0
  86. package/src/graphlib/alg/postorder.d.ts +1 -0
  87. package/src/graphlib/alg/postorder.js +7 -0
  88. package/src/graphlib/alg/preorder.d.ts +1 -0
  89. package/src/graphlib/alg/preorder.js +7 -0
  90. package/src/graphlib/alg/prim.d.ts +2 -0
  91. package/src/graphlib/alg/prim.js +52 -0
  92. package/src/graphlib/alg/tarjan.d.ts +1 -0
  93. package/src/graphlib/alg/tarjan.js +47 -0
  94. package/src/graphlib/alg/topsort.d.ts +5 -0
  95. package/src/graphlib/alg/topsort.js +36 -0
  96. package/src/graphlib/data/priority-queue.d.ts +59 -0
  97. package/src/graphlib/data/priority-queue.js +152 -0
  98. package/src/graphlib/graph.d.ts +55 -0
  99. package/src/graphlib/graph.js +510 -0
  100. package/src/graphlib/index.d.ts +3 -0
  101. package/src/graphlib/index.js +7 -0
  102. package/src/graphlib/json.d.ts +16 -0
  103. package/src/graphlib/json.js +63 -0
  104. package/src/index-layout.ts +1 -0
  105. package/src/index-svg.ts +1 -0
  106. package/src/index.ts +2 -0
  107. package/src/layout/Dimensions.ts +8 -0
  108. package/src/layout/GraphParamsProvider.ts +17 -0
  109. package/src/layout/Padding.ts +10 -0
  110. package/src/layout/Position.ts +4 -0
  111. package/src/layout/StateChart.ts +8 -0
  112. package/src/layout/StateGraphEdge.ts +14 -0
  113. package/src/layout/StateGraphNode.ts +12 -0
  114. package/src/layout/Transition.ts +5 -0
  115. package/src/layout/TransitionsGraph.ts +13 -0
  116. package/src/layout/buildCharts.ts +74 -0
  117. package/src/layout/buildFlatCharts.ts +142 -0
  118. package/src/layout/getGraphParamsProvider.ts +63 -0
  119. package/src/layout/getLabelDimensions.ts +59 -0
  120. package/src/layout/index.ts +15 -0
  121. package/src/lodash-es/_deleteme.js +291 -0
  122. package/src/lodash-es/index.ts +9 -0
  123. package/src/lodash-es/types.ts +35 -0
  124. package/src/svg/buildStatechartCss.ts +120 -0
  125. package/src/svg/buildStatechartSvg.ts +256 -0
  126. package/src/svg/buildStatechartsPanel.ts +38 -0
  127. package/src/svg/index.ts +3 -0
  128. package/src/utils/BasisCurve.ts +89 -0
  129. package/src/utils/getPadding.ts +13 -0
  130. package/src/utils/serializeAttrs.ts +28 -0
  131. package/src/utils/serializeStyle.ts +11 -0
  132. package/src/utils/toKebabCase.ts +18 -0
@@ -0,0 +1,256 @@
1
+ import type {
2
+ Dimensions,
3
+ Padding,
4
+ Position,
5
+ TransitionsGraph,
6
+ } from "../layout/index.ts";
7
+ import { getPadding } from "../utils/getPadding.ts";
8
+ import { serializeAttrs } from "../utils/serializeAttrs.ts";
9
+ import { BasisCurve } from "../utils/BasisCurve.ts";
10
+
11
+ function getBoxParams({
12
+ x,
13
+ y,
14
+ width,
15
+ height,
16
+ padding = 0,
17
+ }: Position & Dimensions & { padding?: Padding }) {
18
+ const [top, right, bottom, left] = getPadding(padding);
19
+ const h = Math.ceil(height - top - bottom);
20
+ return {
21
+ x: x + left,
22
+ y: y - bottom - h,
23
+ width: Math.ceil(width - left - right),
24
+ height: h,
25
+ };
26
+ }
27
+
28
+ export function buildStatechartSvg({
29
+ graph: { width, height, nodes, edges },
30
+ newId,
31
+ stateFontSize = 14,
32
+ transitionFontSize = 12,
33
+ println,
34
+ initialStateKey = "<initial>",
35
+ finalStateKey = "<final>",
36
+ }: {
37
+ graph: TransitionsGraph;
38
+ newId: (prefix: string) => string;
39
+ println: (str: string) => void;
40
+ stateFontSize?: number;
41
+ transitionFontSize?: number;
42
+ initialStateKey?: string;
43
+ finalStateKey?: string;
44
+ }) {
45
+ width = Math.ceil(width);
46
+ height = Math.ceil(height);
47
+ println(
48
+ `<svg ${serializeAttrs({
49
+ class: "statechart",
50
+ width,
51
+ height,
52
+ viewbox: `0 0 ${width} ${height}`,
53
+ xmlns: "http://www.w3.org/2000/svg",
54
+ })}>`
55
+ );
56
+
57
+ /*
58
+ ---------------------------------------------------
59
+ Transitions visualization
60
+
61
+ CSS Classes:
62
+ .transitions .transition-marker
63
+ .transitions .transition .transition-line
64
+ .transitions .transition .transition-box
65
+ .transitions .transition .transition-label
66
+ ---------------------------------------------------
67
+ */
68
+ println(`<g class="transitions">`);
69
+
70
+ const markerAttrs = serializeAttrs({
71
+ refX: 19,
72
+ refY: 7,
73
+ markerWidth: 20,
74
+ markerHeight: 14,
75
+ orient: "auto",
76
+ class: "transition-marker",
77
+ stroke: "none",
78
+ fill: "currentColor",
79
+ });
80
+
81
+ for (const edge of edges) {
82
+ const { id, points, event: text } = edge;
83
+ println(` <g class="transition" data-transition-id="${id}">`);
84
+ const markerId = newId("marker");
85
+
86
+ println(` <defs>`);
87
+ println(` <marker id="${markerId}" ${markerAttrs}>`);
88
+ println(` <path d="M 19,7 L9,13 L14,7 L9,1 Z"></path>`);
89
+ println(` </marker>`);
90
+ println(` </defs>`);
91
+
92
+ const path: string[] = [];
93
+ const round = (...n: number[]) => n.map(Math.round);
94
+ const base = new BasisCurve({
95
+ bezierCurveTo(
96
+ x0: number,
97
+ y0: number,
98
+ x1: number,
99
+ y1: number,
100
+ x2: number,
101
+ y2: number
102
+ ) {
103
+ path.push("C" + round(x0, y0, x1, y1, x2, y2).join(","));
104
+ },
105
+ moveTo(x: number, y: number) {
106
+ path.push("M" + round(x, y).join(","));
107
+ },
108
+ lineTo(x: number, y: number) {
109
+ path.push("L" + round(x, y).join(","));
110
+ },
111
+ closePath() {
112
+ path.push("Z");
113
+ },
114
+ });
115
+ base.lineStart();
116
+ for (const point of points) {
117
+ base.point(point.x, point.y);
118
+ }
119
+ base.lineEnd();
120
+
121
+ const serializedAttrs = serializeAttrs({
122
+ class: "transition-line",
123
+ "transition-id": id,
124
+ fill: "none",
125
+ stroke: "currentColor",
126
+ strokeWidth: "1px",
127
+ d: path.join(""),
128
+ "marker-end": `url(#${markerId})`,
129
+ });
130
+ println(` <path ${serializedAttrs} />`);
131
+
132
+ if (text) {
133
+ const { x, y, width, height } = getBoxParams({
134
+ ...edge,
135
+ padding: 0,
136
+ });
137
+
138
+ const rectAttrs = serializeAttrs({
139
+ x,
140
+ y: y - transitionFontSize,
141
+ width,
142
+ height,
143
+ rx: Math.ceil(transitionFontSize / 2),
144
+ ry: Math.ceil(transitionFontSize / 2),
145
+ fill: "rgba(255,255,255,0.7)",
146
+ stroke: "none",
147
+ strokeWidth: "1",
148
+ class: "transition-box",
149
+ });
150
+ println(` <rect ${rectAttrs} />`);
151
+
152
+ const textAttrs = serializeAttrs({
153
+ class: "transition-label",
154
+ "text-anchor": "middle",
155
+ fill: "currentColor",
156
+ x: Math.round(x + width / 2),
157
+ y: Math.round(y + height / 2 - transitionFontSize / 2),
158
+ });
159
+ println(` <text ${textAttrs}>${text}</text>`);
160
+ }
161
+ println(` </g>`);
162
+ }
163
+ println("</g>");
164
+
165
+ /*
166
+ ---------------------------------------------------
167
+ States visualization
168
+
169
+ CSS Classes:
170
+ .states .state
171
+ .states .state .state-box
172
+ .states .state .state-initial
173
+ .states .state .state-final
174
+ .states .state .state-final.inner
175
+ .states .state .state-label
176
+ ---------------------------------------------------
177
+ */
178
+
179
+ const shift = stateFontSize / 4;
180
+ const initialStateRadius = 6;
181
+ const finalStateRadius = initialStateRadius / 2;
182
+
183
+ println(`<g class="states">`);
184
+ for (const node of nodes) {
185
+ const { key, id, state: text } = node;
186
+ const { x, y, width, height } = getBoxParams({ ...node, padding: shift });
187
+
188
+ println(` <g class="state" data-state-id="${id}">`);
189
+ if (key === initialStateKey || key === finalStateKey) {
190
+ const cx = Math.round(x + width / 2);
191
+ const cy = Math.round(y + height / 2);
192
+ if (key === initialStateKey) {
193
+ const serializedAttrs = serializeAttrs({
194
+ class: "state-initial",
195
+ cx,
196
+ cy,
197
+ r: initialStateRadius,
198
+ fill: "none",
199
+ stroke: "currentColor",
200
+ strokeWidth: "2",
201
+ });
202
+ println(` <circle ${serializedAttrs} />`);
203
+ } else {
204
+ let serializedAttrs = serializeAttrs({
205
+ class: "state-final",
206
+ cx,
207
+ cy,
208
+ r: initialStateRadius,
209
+ fill: "none",
210
+ stroke: "currentColor",
211
+ strokeWidth: 2,
212
+ });
213
+ println(` <circle ${serializedAttrs} />`);
214
+
215
+ serializedAttrs = serializeAttrs({
216
+ class: "state-final inner",
217
+ cx,
218
+ cy,
219
+ r: finalStateRadius,
220
+ fill: "currentColor",
221
+ stroke: "none",
222
+ strokeWidth: 0,
223
+ });
224
+ println(` <circle ${serializedAttrs} />`);
225
+ }
226
+ } else {
227
+ const rectAttrs = serializeAttrs({
228
+ x,
229
+ y,
230
+ width,
231
+ height,
232
+ rx: Math.ceil(stateFontSize / 2),
233
+ ry: Math.ceil(stateFontSize / 2),
234
+ fill: "none",
235
+ stroke: "currentColor",
236
+ strokeWidth: "2",
237
+ class: "state-box",
238
+ });
239
+ println(` <rect ${rectAttrs} />`);
240
+
241
+ const textAttrs = serializeAttrs({
242
+ class: "state-label",
243
+ "text-anchor": "middle",
244
+ fill: "currentColor",
245
+ x: Math.round(x + width / 2),
246
+ y: Math.round(y + height / 2 + stateFontSize / 4),
247
+ "data-state-id": id,
248
+ });
249
+ println(` <text ${textAttrs}>${text}</text>`);
250
+ }
251
+ println(" </g>");
252
+ }
253
+ println("</g>");
254
+
255
+ println("</svg>");
256
+ }
@@ -0,0 +1,38 @@
1
+ import { buildStatechartSvg } from "./buildStatechartSvg.ts";
2
+ import type { StateChart } from "../layout/StateChart.ts";
3
+
4
+ export function buildStatechartsPanel({
5
+ statechart,
6
+ newId,
7
+ println,
8
+ initialStateKey = "<initial>",
9
+ finalStateKey = "<final>",
10
+ }: {
11
+ statechart: StateChart;
12
+ newId: (prefix: string) => string;
13
+ println: (str: string) => void;
14
+ initialStateKey?: string;
15
+ finalStateKey?: string;
16
+ }) : void {
17
+ render(statechart, println);
18
+
19
+ function render(statechart: StateChart, println: (str: string) => void) {
20
+ println(`<details class="state-details" data-state-id="${statechart.id}">`);
21
+ println(` <summary class="state-label">${statechart.key}</summary>`);
22
+ println(` <div class="state-description"><div class="state-chart-container">`);
23
+ const printChildren = (str: string) => println(` ${str}`);
24
+ buildStatechartSvg({
25
+ graph: statechart.graph,
26
+ newId,
27
+ println: printChildren,
28
+ initialStateKey,
29
+ finalStateKey,
30
+ });
31
+ println(` </div>`);
32
+ for (const child of statechart.children || []) {
33
+ render(child, printChildren);
34
+ }
35
+ println(` </div>`);
36
+ println(`</details>`);
37
+ }
38
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./buildStatechartCss.ts";
2
+ export * from "./buildStatechartsPanel.ts";
3
+ export * from "./buildStatechartSvg.ts";
@@ -0,0 +1,89 @@
1
+ // This file contains the implementation of the BasisCurve class
2
+ // copied from d3-shape/src/curve/basis.ts (Mike Bostock, MIT License)
3
+
4
+ export type DrawContext = {
5
+ bezierCurveTo(
6
+ x0: number,
7
+ y0: number,
8
+ x1: number,
9
+ y1: number,
10
+ x2: number,
11
+ y2: number
12
+ ): void;
13
+ moveTo(x: number, y: number): void;
14
+ lineTo(x: number, y: number): void;
15
+ closePath(): void;
16
+ };
17
+
18
+ export class BasisCurve {
19
+ _context: DrawContext;
20
+ _x0: number = 0;
21
+ _y0: number = 0;
22
+ _x1: number = 0;
23
+ _y1: number = 0;
24
+ _point: number = 0;
25
+ _line: number = 0;
26
+ constructor(context: DrawContext) {
27
+ this._context = context;
28
+ }
29
+ $point(x: number, y: number) {
30
+ this._context.bezierCurveTo(
31
+ (2 * this._x0 + this._x1) / 3,
32
+ (2 * this._y0 + this._y1) / 3,
33
+ (this._x0 + 2 * this._x1) / 3,
34
+ (this._y0 + 2 * this._y1) / 3,
35
+ (this._x0 + 4 * this._x1 + x) / 6,
36
+ (this._y0 + 4 * this._y1 + y) / 6
37
+ );
38
+ }
39
+
40
+ areaStart() {
41
+ this._line = 0;
42
+ }
43
+
44
+ areaEnd() {
45
+ this._line = NaN;
46
+ }
47
+
48
+ lineStart() {
49
+ this._x0 = this._x1 = this._y0 = this._y1 = NaN;
50
+ this._point = 0;
51
+ }
52
+
53
+ lineEnd() {
54
+ switch (this._point) {
55
+ case 3:
56
+ this.$point(this._x1, this._y1); // falls through
57
+ case 2:
58
+ this._context.lineTo(this._x1, this._y1);
59
+ break;
60
+ }
61
+ if (this._line || (this._line !== 0 && this._point === 1))
62
+ this._context.closePath();
63
+ this._line = 1 - this._line;
64
+ }
65
+
66
+ point(x: number, y: number) {
67
+ (x = +x), (y = +y);
68
+ switch (this._point) {
69
+ case 0:
70
+ this._point = 1;
71
+ this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
72
+ break;
73
+ case 1:
74
+ this._point = 2;
75
+ break;
76
+ case 2:
77
+ this._point = 3;
78
+ this._context.lineTo(
79
+ (5 * this._x0 + this._x1) / 6,
80
+ (5 * this._y0 + this._y1) / 6
81
+ ); // falls through
82
+ default:
83
+ this.$point(x, y);
84
+ break;
85
+ }
86
+ (this._x0 = this._x1), (this._x1 = x);
87
+ (this._y0 = this._y1), (this._y1 = y);
88
+ }
89
+ }
@@ -0,0 +1,13 @@
1
+ import { Padding } from "../layout/Padding.ts";
2
+
3
+ /**
4
+ * Expands a padding value to a 4-element array: [top, right, bottom, left].
5
+ * @param padding padding value to expand
6
+ * @returns a 4-element array representing the padding
7
+ */
8
+ export function getPadding(padding: Padding): [number, number, number, number] {
9
+ if (typeof padding === "number") return [padding, padding, padding, padding];
10
+ if (padding.length === 2)
11
+ return [padding[0], padding[1], padding[0], padding[1]];
12
+ return padding;
13
+ }
@@ -0,0 +1,28 @@
1
+ export function serializeAttrs(
2
+ attrs: Record<string, string | number | undefined> = {},
3
+ {
4
+ prefix = "",
5
+ transform = false,
6
+ }: { prefix: string; transform: boolean } = {}
7
+ ) {
8
+ const result: string[] = [];
9
+ for (const [key, value] of Object.entries(attrs || {}).sort(([a], [b]) =>
10
+ a > b ? 1 : -1
11
+ )) {
12
+ if (value === undefined || value === null || value === "") continue;
13
+ result.push(
14
+ `${prefix}${escapeValue(key)}${
15
+ "=" +
16
+ JSON.stringify(escapeValue(transform ? toKebabCase(value) : value))
17
+ }`
18
+ );
19
+ }
20
+ return result.join(" ");
21
+
22
+ function escapeValue(value: string | number | undefined): string {
23
+ return String(value)
24
+ .replaceAll(/</gim, "&lt;")
25
+ .replaceAll(/>/gim, "&gt;")
26
+ .replaceAll(/"/gim, "&qout;");
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ import { toKebabCase } from "./toKebabCase.ts";
2
+
3
+ export function serializeStyle(
4
+ style: Record<string, string>,
5
+ separator = "; "
6
+ ) {
7
+ const str = Object.entries(style)
8
+ .map(([key, value]) => `${toKebabCase(key)}: ${value}`)
9
+ .join(separator);
10
+ return str ? str + separator : "";
11
+ }
@@ -0,0 +1,18 @@
1
+ export function toKebabCase(str: string): string {
2
+ // Remove spaces:
3
+ str = str.trim().replace(/\s+/gim, "-");
4
+ // Transform to kebab:
5
+ str = str.replace(
6
+ /([^A-Z]?)([A-Z])/g,
7
+ (_, a, b) => a.toLowerCase() + "-" + b.toLowerCase()
8
+ );
9
+ return str;
10
+
11
+ // return (
12
+ // str.match(
13
+ // /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g
14
+ // ) || []
15
+ // )
16
+ // .join("-")
17
+ // .toLowerCase();
18
+ }