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,291 @@
1
+ module Kumiki
2
+ # BarChart - grouped bar chart widget
3
+ # Supports multiple series, hover highlight, value labels
4
+
5
+ class BarChart < BaseChart
6
+ def initialize(categories, series_data, series_names)
7
+ super()
8
+ @categories = categories
9
+ @series_data = series_data
10
+ @series_names = series_names
11
+ @show_values = false
12
+ @bar_radius = 2.0
13
+ @data_min = 0.0
14
+ @data_max = 0.0
15
+ # Computed per-frame
16
+ @y_scale = nil
17
+ @y_ticks = nil
18
+ @band = nil
19
+ @bar_w = 2.0
20
+ @bar_gap = 2.0
21
+ @baseline_y = 0.0
22
+ @num_series = 0
23
+ @num_cats = 0
24
+ @y_range_min = 0.0
25
+ @y_range_max = 1.0
26
+ @chart_px = 0.0
27
+ @chart_py = 0.0
28
+ @chart_pw = 0.0
29
+ @chart_ph = 0.0
30
+ compute_data_range
31
+ end
32
+
33
+ def show_values(v)
34
+ @show_values = v
35
+ self
36
+ end
37
+
38
+ def bar_radius(r)
39
+ @bar_radius = r
40
+ self
41
+ end
42
+
43
+ def set_data(categories, series_data, series_names)
44
+ @categories = categories
45
+ @series_data = series_data
46
+ @series_names = series_names
47
+ compute_data_range
48
+ mark_dirty
49
+ update
50
+ end
51
+
52
+ def render_chart(painter, px, py, pw, ph)
53
+ return if @categories.length == 0
54
+ return if @series_data.length == 0
55
+ @chart_px = px
56
+ @chart_py = py
57
+ @chart_pw = pw
58
+ @chart_ph = ph
59
+ setup_chart_counts
60
+ setup_chart_ticks
61
+ setup_chart_y_scale
62
+ setup_chart_band
63
+ setup_chart_bar_width
64
+ setup_chart_baseline
65
+ draw_y_axis(painter, px, py, pw, ph, @y_ticks, @y_scale)
66
+ draw_x_axis_line(painter, px, py, pw, ph)
67
+ draw_cat_labels(painter)
68
+ draw_all_bars(painter)
69
+ draw_bar_legend(painter, px, py)
70
+ end
71
+
72
+ def setup_chart_counts
73
+ @num_series = @series_data.length
74
+ @num_cats = @categories.length
75
+ end
76
+
77
+ def setup_chart_ticks
78
+ @y_ticks = compute_ticks(@data_min, @data_max, 5)
79
+ @y_range_min = @data_min
80
+ @y_range_max = @data_max
81
+ if @y_ticks.length > 0
82
+ @y_range_min = @y_ticks[0]
83
+ @y_range_max = @y_ticks[@y_ticks.length - 1]
84
+ end
85
+ end
86
+
87
+ def setup_chart_y_scale
88
+ bottom = @chart_py + @chart_ph
89
+ @y_scale = LinearScale.new(@y_range_min, @y_range_max, bottom, @chart_py)
90
+ end
91
+
92
+ def setup_chart_band
93
+ right = @chart_px + @chart_pw
94
+ nc = @num_cats * 1.0
95
+ @band = BandScale.new(nc, @chart_px, right, 12.0)
96
+ end
97
+
98
+ def setup_chart_bar_width
99
+ @bar_gap = 2.0
100
+ group_w = @band.band_width
101
+ ns = @num_series * 1.0
102
+ ns_minus_1 = ns - 1.0
103
+ @bar_w = (group_w - @bar_gap * ns_minus_1) / ns
104
+ if @bar_w < 2.0
105
+ @bar_w = 2.0
106
+ end
107
+ end
108
+
109
+ def setup_chart_baseline
110
+ @baseline_y = @y_scale.map(0.0)
111
+ if @baseline_y < @chart_py
112
+ @baseline_y = @chart_py
113
+ end
114
+ bmax = @chart_py + @chart_ph
115
+ if @baseline_y > bmax
116
+ @baseline_y = bmax
117
+ end
118
+ end
119
+
120
+ def draw_cat_labels(painter)
121
+ lc = Kumiki.theme.text_secondary
122
+ i = 0
123
+ while i < @num_cats
124
+ draw_one_cat_label(painter, i, lc)
125
+ i = i + 1
126
+ end
127
+ end
128
+
129
+ def draw_one_cat_label(painter, i, lc)
130
+ i_f = i * 1.0
131
+ bx = @band.map(i_f)
132
+ label = @categories[i]
133
+ lw = painter.measure_text_width(label, Kumiki.theme.font_family, 11.0)
134
+ ascent = painter.get_text_ascent(Kumiki.theme.font_family, 11.0)
135
+ label_x = bx + @band.band_width / 2.0 - lw / 2.0
136
+ label_y = @chart_py + @chart_ph + 14.0 + ascent
137
+ painter.draw_text(label, label_x, label_y, Kumiki.theme.font_family, 11.0, lc)
138
+ end
139
+
140
+ def draw_all_bars(painter)
141
+ si = 0
142
+ while si < @num_series
143
+ ci = 0
144
+ while ci < @num_cats
145
+ draw_one_bar(painter, si, ci)
146
+ ci = ci + 1
147
+ end
148
+ si = si + 1
149
+ end
150
+ end
151
+
152
+ def draw_one_bar(painter, si, ci)
153
+ val = @series_data[si][ci]
154
+ bx = compute_bar_x(ci, si)
155
+ bar_top = @y_scale.map(val)
156
+ bar_h = @baseline_y - bar_top
157
+ if bar_h < 0.0
158
+ bar_top = @baseline_y
159
+ bar_h = 0.0 - bar_h
160
+ end
161
+ c = get_bar_color(painter, si, ci)
162
+ painter.fill_round_rect(bx, bar_top, @bar_w, bar_h, @bar_radius, c)
163
+ if @show_values
164
+ draw_bar_value(painter, val, bx, bar_top)
165
+ end
166
+ end
167
+
168
+ def compute_bar_x(ci, si)
169
+ ci_f = ci * 1.0
170
+ si_f = si * 1.0
171
+ @band.map(ci_f) + si_f * (@bar_w + @bar_gap)
172
+ end
173
+
174
+ def get_bar_color(painter, si, ci)
175
+ c = series_color(si)
176
+ if @hover_index == ci
177
+ if @hover_series == si
178
+ c = painter.lighten_color(c, 0.3)
179
+ end
180
+ end
181
+ c
182
+ end
183
+
184
+ def draw_bar_value(painter, val, bx, bar_top)
185
+ vl = format_axis_value(painter, val)
186
+ vw = painter.measure_text_width(vl, Kumiki.theme.font_family, 10.0)
187
+ vx = bx + @bar_w / 2.0 - vw / 2.0
188
+ vy = bar_top - 4.0
189
+ painter.draw_text(vl, vx, vy, Kumiki.theme.font_family, 10.0, Kumiki.theme.text_secondary)
190
+ end
191
+
192
+ def draw_bar_legend(painter, px, py)
193
+ if @show_legend
194
+ if @series_names.length > 1
195
+ colors = []
196
+ si = 0
197
+ while si < @series_names.length
198
+ colors << series_color(si)
199
+ si = si + 1
200
+ end
201
+ draw_legend(painter, @series_names, colors, px + 8.0, py - 20.0)
202
+ end
203
+ end
204
+ end
205
+
206
+ def update_hover
207
+ return if @band == nil
208
+ mx = @mouse_x
209
+ my = @mouse_y
210
+ px = plot_x
211
+ py = plot_y
212
+ pw = plot_w
213
+ ph = plot_h
214
+ if mx < px
215
+ @hover_index = -1
216
+ @hover_series = -1
217
+ return
218
+ end
219
+ if mx > px + pw
220
+ @hover_index = -1
221
+ @hover_series = -1
222
+ return
223
+ end
224
+ if my < py
225
+ @hover_index = -1
226
+ @hover_series = -1
227
+ return
228
+ end
229
+ if my > py + ph
230
+ @hover_index = -1
231
+ @hover_series = -1
232
+ return
233
+ end
234
+ find_bar_hover(mx)
235
+ end
236
+
237
+ def find_bar_hover(mx)
238
+ @hover_index = -1
239
+ @hover_series = -1
240
+ ci = 0
241
+ while ci < @num_cats
242
+ si = 0
243
+ while si < @num_series
244
+ bx = compute_bar_x(ci, si)
245
+ if mx >= bx
246
+ if mx <= bx + @bar_w
247
+ @hover_index = ci
248
+ @hover_series = si
249
+ return
250
+ end
251
+ end
252
+ si = si + 1
253
+ end
254
+ ci = ci + 1
255
+ end
256
+ end
257
+
258
+ private
259
+
260
+ def compute_data_range
261
+ @data_min = 0.0
262
+ @data_max = 1.0
263
+ si = 0
264
+ while si < @series_data.length
265
+ ci = 0
266
+ while ci < @series_data[si].length
267
+ v = @series_data[si][ci]
268
+ if v > @data_max
269
+ @data_max = v
270
+ end
271
+ if v < @data_min
272
+ @data_min = v
273
+ end
274
+ ci = ci + 1
275
+ end
276
+ si = si + 1
277
+ end
278
+ range = @data_max - @data_min
279
+ if range > 0.0
280
+ @data_max = @data_max + range * 0.1
281
+ else
282
+ @data_max = @data_min + 1.0
283
+ end
284
+ end
285
+ end
286
+
287
+ def BarChart(categories, series_data, series_names)
288
+ BarChart.new(categories, series_data, series_names)
289
+ end
290
+
291
+ end
@@ -0,0 +1,213 @@
1
+ module Kumiki
2
+ # BaseChart - abstract base class for all chart widgets
3
+ # Provides margin management, title, legend, and hover detection
4
+
5
+ # Chart margin constants
6
+ CHART_MARGIN_TOP = 40.0
7
+ CHART_MARGIN_RIGHT = 20.0
8
+ CHART_MARGIN_BOTTOM = 50.0
9
+ CHART_MARGIN_LEFT = 60.0
10
+
11
+ # Default series colors (8-color palette, Tokyo Night inspired)
12
+ def series_color(index)
13
+ i = index % 8
14
+ c = 0
15
+ if i == 0
16
+ c = 4288807671
17
+ elsif i == 1
18
+ c = 4288452202
19
+ elsif i == 2
20
+ c = 4294604430
21
+ elsif i == 3
22
+ c = 4292886376
23
+ elsif i == 4
24
+ c = 4290530039
25
+ elsif i == 5
26
+ c = 4285791434
27
+ elsif i == 6
28
+ c = 4294934372
29
+ else
30
+ c = 4286361599
31
+ end
32
+ c
33
+ end
34
+
35
+ class BaseChart < Widget
36
+ def initialize
37
+ super()
38
+ @margin_top = CHART_MARGIN_TOP
39
+ @margin_right = CHART_MARGIN_RIGHT
40
+ @margin_bottom = CHART_MARGIN_BOTTOM
41
+ @margin_left = CHART_MARGIN_LEFT
42
+ @title_text = nil
43
+ @show_legend = true
44
+ @hover_index = -1
45
+ @hover_series = -1
46
+ @mouse_x = -1.0
47
+ @mouse_y = -1.0
48
+ @painter = nil
49
+ @width_policy = EXPANDING
50
+ @height_policy = EXPANDING
51
+ end
52
+
53
+ def title(t)
54
+ @title_text = t
55
+ self
56
+ end
57
+
58
+ def legend(show)
59
+ @show_legend = show
60
+ self
61
+ end
62
+
63
+ def margins(top, right, bottom, left)
64
+ @margin_top = top
65
+ @margin_right = right
66
+ @margin_bottom = bottom
67
+ @margin_left = left
68
+ self
69
+ end
70
+
71
+ def plot_x
72
+ @margin_left
73
+ end
74
+
75
+ def plot_y
76
+ @margin_top
77
+ end
78
+
79
+ def plot_w
80
+ w = @width - @margin_left - @margin_right
81
+ if w < 0.0
82
+ w = 0.0
83
+ end
84
+ w
85
+ end
86
+
87
+ def plot_h
88
+ h = @height - @margin_top - @margin_bottom
89
+ if h < 0.0
90
+ h = 0.0
91
+ end
92
+ h
93
+ end
94
+
95
+ def redraw(painter, completely)
96
+ @painter = painter
97
+ bg = Kumiki.theme.bg_primary
98
+ painter.fill_rect(0.0, 0.0, @width, @height, bg)
99
+ draw_title(painter)
100
+ render_chart(painter, plot_x, plot_y, plot_w, plot_h)
101
+ end
102
+
103
+ def draw_title(painter)
104
+ return if @title_text == nil
105
+ tw = painter.measure_text_width(@title_text, Kumiki.theme.font_family, 16.0)
106
+ tx = (@width - tw) / 2.0
107
+ ascent = painter.get_text_ascent(Kumiki.theme.font_family, 16.0)
108
+ tc = Kumiki.theme.text_primary
109
+ painter.draw_text(@title_text, tx, 8.0 + ascent, Kumiki.theme.font_family, 16.0, tc)
110
+ end
111
+
112
+ def render_chart(painter, px, py, pw, ph)
113
+ end
114
+
115
+ # Draw one legend item
116
+ def draw_legend_item(painter, label, color, cx, y)
117
+ painter.fill_rect(cx, y, 12.0, 12.0, color)
118
+ lx = cx + 16.0
119
+ ascent = painter.get_text_ascent(Kumiki.theme.font_family, 11.0)
120
+ tc = Kumiki.theme.text_secondary
121
+ painter.draw_text(label, lx, y + ascent, Kumiki.theme.font_family, 11.0, tc)
122
+ lw = painter.measure_text_width(label, Kumiki.theme.font_family, 11.0)
123
+ lx + lw + 16.0
124
+ end
125
+
126
+ # Draw legend items
127
+ def draw_legend(painter, labels, colors, x, y)
128
+ return if labels.length == 0
129
+ cx = x
130
+ i = 0
131
+ while i < labels.length
132
+ c = colors[i % colors.length]
133
+ cx = draw_legend_item(painter, labels[i], c, cx, y)
134
+ i = i + 1
135
+ end
136
+ end
137
+
138
+ # Draw one Y-axis tick (grid line + tick mark)
139
+ def draw_y_tick(painter, px, pw, yy, grid_color, border_color)
140
+ painter.draw_line(px, yy, px + pw, yy, grid_color, 1.0)
141
+ painter.draw_line(px - 4.0, yy, px, yy, border_color, 1.0)
142
+ end
143
+
144
+ # Draw one Y-axis tick label
145
+ def draw_y_tick_label(painter, px, yy, label, label_color)
146
+ lw = painter.measure_text_width(label, Kumiki.theme.font_family, 11.0)
147
+ ascent = painter.get_text_ascent(Kumiki.theme.font_family, 11.0)
148
+ lx = px - lw - 6.0
149
+ ly = yy + ascent / 2.0
150
+ painter.draw_text(label, lx, ly, Kumiki.theme.font_family, 11.0, label_color)
151
+ end
152
+
153
+ # Draw Y-axis with tick marks and grid lines
154
+ def draw_y_axis(painter, px, py, pw, ph, ticks, scale)
155
+ bc = Kumiki.theme.border
156
+ painter.draw_line(px, py, px, py + ph, bc, 1.0)
157
+ gc = painter.with_alpha(bc, 60)
158
+ lc = Kumiki.theme.text_secondary
159
+ i = 0
160
+ while i < ticks.length
161
+ yy = scale.map(ticks[i])
162
+ draw_y_tick(painter, px, pw, yy, gc, bc)
163
+ label = format_axis_value(painter, ticks[i])
164
+ draw_y_tick_label(painter, px, yy, label, lc)
165
+ i = i + 1
166
+ end
167
+ end
168
+
169
+ # Draw X-axis line
170
+ def draw_x_axis_line(painter, px, py, pw, ph)
171
+ bc = Kumiki.theme.border
172
+ painter.draw_line(px, py + ph, px + pw, py + ph, bc, 1.0)
173
+ end
174
+
175
+ # Mouse tracking for hover
176
+ def cursor_pos(ev)
177
+ @mouse_x = ev.pos.x
178
+ @mouse_y = ev.pos.y
179
+ old_hi = @hover_index
180
+ old_hs = @hover_series
181
+ update_hover
182
+ if @hover_index != old_hi
183
+ mark_dirty
184
+ update
185
+ elsif @hover_series != old_hs
186
+ mark_dirty
187
+ update
188
+ end
189
+ end
190
+
191
+ def mouse_out
192
+ changed = false
193
+ if @hover_index != -1
194
+ @hover_index = -1
195
+ changed = true
196
+ end
197
+ if @hover_series != -1
198
+ @hover_series = -1
199
+ changed = true
200
+ end
201
+ if changed
202
+ @mouse_x = -1.0
203
+ @mouse_y = -1.0
204
+ mark_dirty
205
+ update
206
+ end
207
+ end
208
+
209
+ def update_hover
210
+ end
211
+ end
212
+
213
+ end
@@ -0,0 +1,74 @@
1
+ module Kumiki
2
+ # Chart helper functions
3
+
4
+ # Compute nice axis tick values
5
+ def compute_ticks(min_val, max_val, target_count)
6
+ ticks = []
7
+ range = max_val - min_val
8
+ if range <= 0.0
9
+ ticks << min_val
10
+ return ticks
11
+ end
12
+ rough_step = range / target_count
13
+ nice_step = pick_nice_step(rough_step)
14
+ start = compute_tick_start(min_val, nice_step)
15
+ v = start
16
+ while v <= max_val + nice_step * 0.001
17
+ if v >= min_val - nice_step * 0.001
18
+ ticks << v
19
+ end
20
+ v = v + nice_step
21
+ end
22
+ ticks
23
+ end
24
+
25
+ def pick_nice_step(rough_step)
26
+ if rough_step >= 500.0
27
+ return 500.0
28
+ end
29
+ if rough_step >= 200.0
30
+ return 200.0
31
+ end
32
+ if rough_step >= 100.0
33
+ return 100.0
34
+ end
35
+ if rough_step >= 50.0
36
+ return 50.0
37
+ end
38
+ if rough_step >= 20.0
39
+ return 20.0
40
+ end
41
+ if rough_step >= 10.0
42
+ return 10.0
43
+ end
44
+ if rough_step >= 5.0
45
+ return 5.0
46
+ end
47
+ if rough_step >= 2.0
48
+ return 2.0
49
+ end
50
+ if rough_step >= 1.0
51
+ return 1.0
52
+ end
53
+ if rough_step >= 0.5
54
+ return 0.5
55
+ end
56
+ if rough_step >= 0.2
57
+ return 0.2
58
+ end
59
+ 0.1
60
+ end
61
+
62
+ def compute_tick_start(min_val, nice_step)
63
+ ratio = min_val / nice_step
64
+ int_part = ratio.to_i
65
+ int_f = int_part * 1.0
66
+ int_f * nice_step
67
+ end
68
+
69
+ # Format a number for axis labels
70
+ def format_axis_value(painter, val)
71
+ painter.number_to_string(val)
72
+ end
73
+
74
+ end