kumiki 0.1.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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +256 -0
  4. data/lib/kumiki/animation/animated_state.rb +83 -0
  5. data/lib/kumiki/animation/easing.rb +62 -0
  6. data/lib/kumiki/animation/value_tween.rb +69 -0
  7. data/lib/kumiki/app.rb +381 -0
  8. data/lib/kumiki/box.rb +40 -0
  9. data/lib/kumiki/chart/area_chart.rb +308 -0
  10. data/lib/kumiki/chart/bar_chart.rb +291 -0
  11. data/lib/kumiki/chart/base_chart.rb +213 -0
  12. data/lib/kumiki/chart/chart_helpers.rb +74 -0
  13. data/lib/kumiki/chart/gauge_chart.rb +174 -0
  14. data/lib/kumiki/chart/heatmap_chart.rb +223 -0
  15. data/lib/kumiki/chart/line_chart.rb +292 -0
  16. data/lib/kumiki/chart/pie_chart.rb +222 -0
  17. data/lib/kumiki/chart/scales.rb +79 -0
  18. data/lib/kumiki/chart/scatter_chart.rb +306 -0
  19. data/lib/kumiki/chart/stacked_bar_chart.rb +279 -0
  20. data/lib/kumiki/column.rb +351 -0
  21. data/lib/kumiki/core.rb +2511 -0
  22. data/lib/kumiki/dsl.rb +408 -0
  23. data/lib/kumiki/frame_ranma.rb +570 -0
  24. data/lib/kumiki/markdown/ast.rb +127 -0
  25. data/lib/kumiki/markdown/mermaid/layout.rb +389 -0
  26. data/lib/kumiki/markdown/mermaid/models.rb +235 -0
  27. data/lib/kumiki/markdown/mermaid/parser.rb +522 -0
  28. data/lib/kumiki/markdown/mermaid/renderer.rb +339 -0
  29. data/lib/kumiki/markdown/parser.rb +808 -0
  30. data/lib/kumiki/markdown/renderer.rb +642 -0
  31. data/lib/kumiki/markdown/theme.rb +168 -0
  32. data/lib/kumiki/render_node.rb +262 -0
  33. data/lib/kumiki/row.rb +288 -0
  34. data/lib/kumiki/spacer.rb +20 -0
  35. data/lib/kumiki/style.rb +799 -0
  36. data/lib/kumiki/theme.rb +567 -0
  37. data/lib/kumiki/themes/material.rb +40 -0
  38. data/lib/kumiki/themes/tokyo_night.rb +11 -0
  39. data/lib/kumiki/version.rb +5 -0
  40. data/lib/kumiki/widgets/button.rb +105 -0
  41. data/lib/kumiki/widgets/calendar.rb +1028 -0
  42. data/lib/kumiki/widgets/checkbox.rb +119 -0
  43. data/lib/kumiki/widgets/container.rb +111 -0
  44. data/lib/kumiki/widgets/data_table.rb +670 -0
  45. data/lib/kumiki/widgets/divider.rb +31 -0
  46. data/lib/kumiki/widgets/image.rb +105 -0
  47. data/lib/kumiki/widgets/input.rb +485 -0
  48. data/lib/kumiki/widgets/markdown.rb +58 -0
  49. data/lib/kumiki/widgets/modal.rb +165 -0
  50. data/lib/kumiki/widgets/multiline_input.rb +970 -0
  51. data/lib/kumiki/widgets/multiline_text.rb +180 -0
  52. data/lib/kumiki/widgets/net_image.rb +100 -0
  53. data/lib/kumiki/widgets/progress_bar.rb +72 -0
  54. data/lib/kumiki/widgets/radio_buttons.rb +93 -0
  55. data/lib/kumiki/widgets/slider.rb +135 -0
  56. data/lib/kumiki/widgets/switch.rb +84 -0
  57. data/lib/kumiki/widgets/tabs.rb +175 -0
  58. data/lib/kumiki/widgets/text.rb +120 -0
  59. data/lib/kumiki/widgets/tree.rb +434 -0
  60. data/lib/kumiki/widgets/webview.rb +87 -0
  61. data/lib/kumiki.rb +130 -0
  62. metadata +113 -0
@@ -0,0 +1,389 @@
1
+ module Kumiki
2
+ # Mermaid flowchart layout - BFS layered graph layout
3
+ # Assigns x, y, width, height to all nodes and subgraphs
4
+
5
+ class MermaidLayout
6
+ def layout(diagram, max_width, padding)
7
+ if diagram.nodes.length == 0
8
+ return
9
+ end
10
+
11
+ assign_layers(diagram)
12
+ calculate_node_sizes(diagram)
13
+
14
+ horizontal = diagram.direction == MERMAID_DIR_LR || diagram.direction == MERMAID_DIR_RL
15
+ if horizontal
16
+ position_horizontal(diagram, padding)
17
+ else
18
+ position_vertical(diagram, padding, max_width)
19
+ end
20
+
21
+ position_subgraphs(diagram)
22
+ end
23
+
24
+ def assign_layers(diagram)
25
+ # Simple BFS layering using node.layer field
26
+ # First, set all layers to -1 (unvisited)
27
+ i = 0
28
+ while i < diagram.nodes.length
29
+ diagram.nodes[i].layer = -1
30
+ i = i + 1
31
+ end
32
+
33
+ # Find root nodes (not a target of any edge)
34
+ i = 0
35
+ while i < diagram.nodes.length
36
+ n = diagram.nodes[i]
37
+ is_root = true
38
+ ei = 0
39
+ while ei < diagram.edges.length
40
+ if diagram.edges[ei].target == n.id
41
+ is_root = false
42
+ break
43
+ end
44
+ ei = ei + 1
45
+ end
46
+ if is_root
47
+ n.layer = 0
48
+ end
49
+ i = i + 1
50
+ end
51
+
52
+ # If no roots, set first node as root
53
+ has_root = false
54
+ i = 0
55
+ while i < diagram.nodes.length
56
+ if diagram.nodes[i].layer == 0
57
+ has_root = true
58
+ break
59
+ end
60
+ i = i + 1
61
+ end
62
+ if !has_root && diagram.nodes.length > 0
63
+ diagram.nodes[0].layer = 0
64
+ end
65
+
66
+ # BFS: propagate layers from roots to children
67
+ # Only assign to unvisited nodes (layer < 0) to handle cycles gracefully
68
+ changed = true
69
+ max_iters = diagram.nodes.length
70
+ iter = 0
71
+ while changed && iter < max_iters
72
+ changed = false
73
+ ei = 0
74
+ while ei < diagram.edges.length
75
+ edge = diagram.edges[ei]
76
+ src = diagram.get_node(edge.source)
77
+ tgt = diagram.get_node(edge.target)
78
+ if src && tgt && src.layer >= 0 && tgt.layer < 0
79
+ tgt.layer = src.layer + 1
80
+ changed = true
81
+ end
82
+ ei = ei + 1
83
+ end
84
+ iter = iter + 1
85
+ end
86
+
87
+ # Set any remaining unvisited nodes to layer 0
88
+ i = 0
89
+ while i < diagram.nodes.length
90
+ if diagram.nodes[i].layer < 0
91
+ diagram.nodes[i].layer = 0
92
+ end
93
+ i = i + 1
94
+ end
95
+ end
96
+
97
+ def calculate_node_sizes(diagram)
98
+ char_width = 8.0
99
+ min_width = 100.0
100
+ node_height = 40.0
101
+
102
+ i = 0
103
+ while i < diagram.nodes.length
104
+ n = diagram.nodes[i]
105
+ label_w = n.label.length * char_width + 24.0
106
+ if label_w < min_width
107
+ label_w = min_width
108
+ end
109
+ n.width = label_w
110
+ n.height = node_height
111
+
112
+ if n.shape == MERMAID_SHAPE_CIRCLE
113
+ max_dim = n.width
114
+ if n.height > max_dim
115
+ max_dim = n.height
116
+ end
117
+ n.width = max_dim
118
+ n.height = max_dim
119
+ elsif n.shape == MERMAID_SHAPE_DIAMOND
120
+ n.width = n.width + 20.0
121
+ n.height = n.width * 0.6
122
+ end
123
+ i = i + 1
124
+ end
125
+ end
126
+
127
+ def max_layer(diagram)
128
+ mx = 0
129
+ i = 0
130
+ while i < diagram.nodes.length
131
+ if diagram.nodes[i].layer > mx
132
+ mx = diagram.nodes[i].layer
133
+ end
134
+ i = i + 1
135
+ end
136
+ mx
137
+ end
138
+
139
+ def position_vertical(diagram, padding, max_width)
140
+ h_spacing = 40.0
141
+ v_spacing = 60.0
142
+ ml = max_layer(diagram)
143
+ reverse = diagram.direction == MERMAID_DIR_BT
144
+
145
+ # First pass: find max total width across all layers
146
+ max_w = find_max_layer_width(diagram, ml, h_spacing)
147
+ if max_w < 1.0
148
+ max_w = max_width
149
+ end
150
+
151
+ # Second pass: position nodes
152
+ y_offset = padding
153
+ layer = 0
154
+ while layer <= ml
155
+ actual = layer
156
+ if reverse
157
+ actual = ml - layer
158
+ end
159
+ position_layer_vertical(diagram, actual, y_offset, padding, max_w, h_spacing)
160
+ tallest = layer_max_height(diagram, actual)
161
+ y_offset = y_offset + tallest + v_spacing
162
+ layer = layer + 1
163
+ end
164
+ end
165
+
166
+ def find_max_layer_width(diagram, ml, spacing)
167
+ max_w = 0.0
168
+ layer = 0
169
+ while layer <= ml
170
+ w = compute_layer_width(diagram, layer, spacing)
171
+ if w > max_w
172
+ max_w = w
173
+ end
174
+ layer = layer + 1
175
+ end
176
+ max_w
177
+ end
178
+
179
+ def compute_layer_width(diagram, layer, spacing)
180
+ total = 0.0
181
+ count = 0
182
+ i = 0
183
+ while i < diagram.nodes.length
184
+ if diagram.nodes[i].layer == layer
185
+ total = total + diagram.nodes[i].width
186
+ count = count + 1
187
+ end
188
+ i = i + 1
189
+ end
190
+ if count > 1
191
+ total = total + (count - 1) * spacing
192
+ end
193
+ total
194
+ end
195
+
196
+ def position_layer_vertical(diagram, layer, y, padding, max_w, spacing)
197
+ lw = compute_layer_width(diagram, layer, spacing)
198
+ start_x = padding + (max_w - lw) / 2.0
199
+ x = start_x
200
+ i = 0
201
+ while i < diagram.nodes.length
202
+ n = diagram.nodes[i]
203
+ if n.layer == layer
204
+ n.x = x
205
+ n.y = y
206
+ x = x + n.width + spacing
207
+ end
208
+ i = i + 1
209
+ end
210
+ end
211
+
212
+ def layer_max_height(diagram, layer)
213
+ mx = 0.0
214
+ i = 0
215
+ while i < diagram.nodes.length
216
+ if diagram.nodes[i].layer == layer && diagram.nodes[i].height > mx
217
+ mx = diagram.nodes[i].height
218
+ end
219
+ i = i + 1
220
+ end
221
+ mx
222
+ end
223
+
224
+ def position_horizontal(diagram, padding)
225
+ h_spacing = 40.0
226
+ v_spacing = 60.0
227
+ ml = max_layer(diagram)
228
+ reverse = diagram.direction == MERMAID_DIR_RL
229
+
230
+ # Find max total height across all layers
231
+ max_h = find_max_layer_height(diagram, ml, h_spacing)
232
+
233
+ # Position nodes
234
+ x_offset = padding
235
+ layer = 0
236
+ while layer <= ml
237
+ actual = layer
238
+ if reverse
239
+ actual = ml - layer
240
+ end
241
+ position_layer_horizontal(diagram, actual, x_offset, padding, max_h, h_spacing)
242
+ widest = layer_max_width(diagram, actual)
243
+ x_offset = x_offset + widest + v_spacing
244
+ layer = layer + 1
245
+ end
246
+ end
247
+
248
+ def find_max_layer_height(diagram, ml, spacing)
249
+ max_h = 0.0
250
+ layer = 0
251
+ while layer <= ml
252
+ h = compute_layer_height(diagram, layer, spacing)
253
+ if h > max_h
254
+ max_h = h
255
+ end
256
+ layer = layer + 1
257
+ end
258
+ max_h
259
+ end
260
+
261
+ def compute_layer_height(diagram, layer, spacing)
262
+ total = 0.0
263
+ count = 0
264
+ i = 0
265
+ while i < diagram.nodes.length
266
+ if diagram.nodes[i].layer == layer
267
+ total = total + diagram.nodes[i].height
268
+ count = count + 1
269
+ end
270
+ i = i + 1
271
+ end
272
+ if count > 1
273
+ total = total + (count - 1) * spacing
274
+ end
275
+ total
276
+ end
277
+
278
+ def position_layer_horizontal(diagram, layer, x, padding, max_h, spacing)
279
+ lh = compute_layer_height(diagram, layer, spacing)
280
+ start_y = padding + (max_h - lh) / 2.0
281
+ y = start_y
282
+ i = 0
283
+ while i < diagram.nodes.length
284
+ n = diagram.nodes[i]
285
+ if n.layer == layer
286
+ n.x = x
287
+ n.y = y
288
+ y = y + n.height + spacing
289
+ end
290
+ i = i + 1
291
+ end
292
+ end
293
+
294
+ def layer_max_width(diagram, layer)
295
+ mx = 0.0
296
+ i = 0
297
+ while i < diagram.nodes.length
298
+ if diagram.nodes[i].layer == layer && diagram.nodes[i].width > mx
299
+ mx = diagram.nodes[i].width
300
+ end
301
+ i = i + 1
302
+ end
303
+ mx
304
+ end
305
+
306
+ def position_subgraphs(diagram)
307
+ sg_padding = 15.0
308
+ title_height = 20.0
309
+
310
+ si = 0
311
+ while si < diagram.subgraphs.length
312
+ sg = diagram.subgraphs[si]
313
+ if sg.node_ids.length > 0
314
+ compute_subgraph_bounds(diagram, sg, sg_padding, title_height)
315
+ end
316
+ si = si + 1
317
+ end
318
+ end
319
+
320
+ def compute_subgraph_bounds(diagram, sg, sg_padding, title_height)
321
+ min_x = 99999.0
322
+ min_y = 99999.0
323
+ max_x = 0.0
324
+ max_y = 0.0
325
+
326
+ ni = 0
327
+ while ni < sg.node_ids.length
328
+ node = diagram.get_node(sg.node_ids[ni])
329
+ if node
330
+ if node.x < min_x
331
+ min_x = node.x
332
+ end
333
+ if node.y < min_y
334
+ min_y = node.y
335
+ end
336
+ right = node.x + node.width
337
+ if right > max_x
338
+ max_x = right
339
+ end
340
+ bottom = node.y + node.height
341
+ if bottom > max_y
342
+ max_y = bottom
343
+ end
344
+ end
345
+ ni = ni + 1
346
+ end
347
+
348
+ sg.x = min_x - sg_padding
349
+ sg.y = min_y - sg_padding - title_height
350
+ sg.width = (max_x - min_x) + sg_padding * 2.0
351
+ sg.height = (max_y - min_y) + sg_padding * 2.0 + title_height
352
+ end
353
+
354
+ def calculate_height(diagram)
355
+ max_y = 0.0
356
+ i = 0
357
+ while i < diagram.nodes.length
358
+ bottom = diagram.nodes[i].y + diagram.nodes[i].height
359
+ if bottom > max_y
360
+ max_y = bottom
361
+ end
362
+ i = i + 1
363
+ end
364
+ i = 0
365
+ while i < diagram.subgraphs.length
366
+ bottom = diagram.subgraphs[i].y + diagram.subgraphs[i].height
367
+ if bottom > max_y
368
+ max_y = bottom
369
+ end
370
+ i = i + 1
371
+ end
372
+ max_y + 20.0
373
+ end
374
+
375
+ def calculate_width(diagram)
376
+ max_x = 0.0
377
+ i = 0
378
+ while i < diagram.nodes.length
379
+ right = diagram.nodes[i].x + diagram.nodes[i].width
380
+ if right > max_x
381
+ max_x = right
382
+ end
383
+ i = i + 1
384
+ end
385
+ max_x + 20.0
386
+ end
387
+ end
388
+
389
+ end
@@ -0,0 +1,235 @@
1
+ module Kumiki
2
+ # Mermaid diagram data models
3
+ # Direction, shape, line type, arrow type constants and node/edge/subgraph classes
4
+
5
+ # Direction constants
6
+ MERMAID_DIR_TB = 0
7
+ MERMAID_DIR_BT = 1
8
+ MERMAID_DIR_LR = 2
9
+ MERMAID_DIR_RL = 3
10
+
11
+ # Node shape constants
12
+ MERMAID_SHAPE_RECT = 0
13
+ MERMAID_SHAPE_ROUNDED = 1
14
+ MERMAID_SHAPE_STADIUM = 2
15
+ MERMAID_SHAPE_CIRCLE = 3
16
+ MERMAID_SHAPE_DIAMOND = 4
17
+ MERMAID_SHAPE_HEXAGON = 5
18
+ MERMAID_SHAPE_SUBROUTINE = 6
19
+
20
+ # Line type constants
21
+ MERMAID_LINE_SOLID = 0
22
+ MERMAID_LINE_DASHED = 1
23
+ MERMAID_LINE_THICK = 2
24
+
25
+ # Arrow type constants
26
+ MERMAID_ARROW_ARROW = 0
27
+ MERMAID_ARROW_OPEN = 1
28
+ MERMAID_ARROW_CIRCLE = 2
29
+ MERMAID_ARROW_CROSS = 3
30
+
31
+ class MermaidNode
32
+ def initialize(id, label, shape)
33
+ @id = id
34
+ @label = label
35
+ @shape = shape
36
+ @x = 0.0
37
+ @y = 0.0
38
+ @width = 0.0
39
+ @height = 0.0
40
+ @layer = 0
41
+ end
42
+
43
+ def id
44
+ @id
45
+ end
46
+
47
+ def label
48
+ @label
49
+ end
50
+
51
+ def label=(v)
52
+ @label = v
53
+ end
54
+
55
+ def shape
56
+ @shape
57
+ end
58
+
59
+ def shape=(v)
60
+ @shape = v
61
+ end
62
+
63
+ def x
64
+ @x
65
+ end
66
+
67
+ def x=(v)
68
+ @x = v
69
+ end
70
+
71
+ def y
72
+ @y
73
+ end
74
+
75
+ def y=(v)
76
+ @y = v
77
+ end
78
+
79
+ def width
80
+ @width
81
+ end
82
+
83
+ def width=(v)
84
+ @width = v
85
+ end
86
+
87
+ def height
88
+ @height
89
+ end
90
+
91
+ def height=(v)
92
+ @height = v
93
+ end
94
+
95
+ def layer
96
+ @layer
97
+ end
98
+
99
+ def layer=(v)
100
+ @layer = v
101
+ end
102
+ end
103
+
104
+ class MermaidEdge
105
+ def initialize(source, target, label, line_type, arrow_type)
106
+ @source = source
107
+ @target = target
108
+ @label = label
109
+ @line_type = line_type
110
+ @arrow_type = arrow_type
111
+ end
112
+
113
+ def source
114
+ @source
115
+ end
116
+
117
+ def target
118
+ @target
119
+ end
120
+
121
+ def label
122
+ @label
123
+ end
124
+
125
+ def line_type
126
+ @line_type
127
+ end
128
+
129
+ def arrow_type
130
+ @arrow_type
131
+ end
132
+ end
133
+
134
+ class MermaidSubgraph
135
+ def initialize(id, title)
136
+ @id = id
137
+ @title = title
138
+ @node_ids = []
139
+ @x = 0.0
140
+ @y = 0.0
141
+ @width = 0.0
142
+ @height = 0.0
143
+ end
144
+
145
+ def id
146
+ @id
147
+ end
148
+
149
+ def title
150
+ @title
151
+ end
152
+
153
+ def node_ids
154
+ @node_ids
155
+ end
156
+
157
+ def x
158
+ @x
159
+ end
160
+
161
+ def x=(v)
162
+ @x = v
163
+ end
164
+
165
+ def y
166
+ @y
167
+ end
168
+
169
+ def y=(v)
170
+ @y = v
171
+ end
172
+
173
+ def width
174
+ @width
175
+ end
176
+
177
+ def width=(v)
178
+ @width = v
179
+ end
180
+
181
+ def height
182
+ @height
183
+ end
184
+
185
+ def height=(v)
186
+ @height = v
187
+ end
188
+ end
189
+
190
+ class MermaidDiagram
191
+ def initialize(direction)
192
+ @direction = direction
193
+ @nodes = []
194
+ @edges = []
195
+ @subgraphs = []
196
+ @node_map = {}
197
+ end
198
+
199
+ def direction
200
+ @direction
201
+ end
202
+
203
+ def nodes
204
+ @nodes
205
+ end
206
+
207
+ def edges
208
+ @edges
209
+ end
210
+
211
+ def subgraphs
212
+ @subgraphs
213
+ end
214
+
215
+ def add_node(node)
216
+ if !@node_map[node.id]
217
+ @nodes.push(node)
218
+ @node_map[node.id] = node
219
+ end
220
+ end
221
+
222
+ def get_node(id)
223
+ @node_map[id]
224
+ end
225
+
226
+ def add_edge(edge)
227
+ @edges.push(edge)
228
+ end
229
+
230
+ def add_subgraph(sg)
231
+ @subgraphs.push(sg)
232
+ end
233
+ end
234
+
235
+ end