its-showtime 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 (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +179 -0
  4. data/bin/showtime +47 -0
  5. data/lib/showtime/app.rb +399 -0
  6. data/lib/showtime/charts.rb +229 -0
  7. data/lib/showtime/component_registry.rb +38 -0
  8. data/lib/showtime/components/Components.md +309 -0
  9. data/lib/showtime/components/alerts.rb +83 -0
  10. data/lib/showtime/components/base.rb +63 -0
  11. data/lib/showtime/components/charts.rb +119 -0
  12. data/lib/showtime/components/data.rb +328 -0
  13. data/lib/showtime/components/inputs.rb +390 -0
  14. data/lib/showtime/components/layout.rb +135 -0
  15. data/lib/showtime/components/media.rb +73 -0
  16. data/lib/showtime/components/sidebar.rb +130 -0
  17. data/lib/showtime/components/text.rb +156 -0
  18. data/lib/showtime/components.rb +18 -0
  19. data/lib/showtime/compute_tracker.rb +21 -0
  20. data/lib/showtime/helpers.rb +53 -0
  21. data/lib/showtime/logger.rb +143 -0
  22. data/lib/showtime/public/.vite/manifest.json +34 -0
  23. data/lib/showtime/public/assets/antd-3aDVoXqG.js +447 -0
  24. data/lib/showtime/public/assets/charts-iowb_sWQ.js +3858 -0
  25. data/lib/showtime/public/assets/index-B2b3lWS5.js +43 -0
  26. data/lib/showtime/public/assets/index-M6NVamDM.css +1 -0
  27. data/lib/showtime/public/assets/react-BE6xecJX.js +32 -0
  28. data/lib/showtime/public/index.html +19 -0
  29. data/lib/showtime/public/letter.png +0 -0
  30. data/lib/showtime/public/logo.png +0 -0
  31. data/lib/showtime/release.rb +108 -0
  32. data/lib/showtime/session.rb +131 -0
  33. data/lib/showtime/version.rb +3 -0
  34. data/lib/showtime/views/index.erb +32 -0
  35. data/lib/showtime.rb +157 -0
  36. metadata +300 -0
@@ -0,0 +1,119 @@
1
+ require_relative "base"
2
+ require "active_support"
3
+ require "showtime/charts"
4
+
5
+ module Showtime
6
+ module Components
7
+ class PlotlyChart < BaseComponent
8
+ attr_reader :spec, :help
9
+
10
+ def initialize(chart_type:, data:, encoding:, colorway: nil, layout: {}, config: {}, key: nil, help: nil)
11
+ super(key: key)
12
+ @help = help
13
+ @spec = build_spec(chart_type, data, encoding, colorway: colorway, layout: layout, config: config)
14
+ end
15
+
16
+ def validate
17
+ @errors ||= []
18
+ end
19
+
20
+ def to_h
21
+ super.merge({
22
+ spec: @spec,
23
+ help: @help,
24
+ })
25
+ end
26
+
27
+ private
28
+
29
+ def build_spec(chart_type, data, encoding, colorway:, layout:, config:)
30
+ Showtime::Charts.figure(
31
+ chart_type: chart_type,
32
+ data: data,
33
+ encoding: encoding,
34
+ colorway: colorway,
35
+ layout: layout,
36
+ config: config,
37
+ )
38
+ rescue Showtime::Charts::ValidationError => e
39
+ @errors = [e.message]
40
+ nil
41
+ end
42
+ end
43
+
44
+ class LineChart < PlotlyChart
45
+ def initialize(data, options = {}, key: nil, help: nil)
46
+ super(chart_type: :line, data: data, encoding: options, key: key, help: help)
47
+ end
48
+ end
49
+
50
+ class BarChart < PlotlyChart
51
+ def initialize(data, options = {}, key: nil, help: nil)
52
+ super(chart_type: :bar, data: data, encoding: options, key: key, help: help)
53
+ end
54
+ end
55
+
56
+ class ColumnChart < PlotlyChart
57
+ def initialize(data, options = {}, key: nil, help: nil)
58
+ super(chart_type: :column, data: data, encoding: options, key: key, help: help)
59
+ end
60
+ end
61
+
62
+ class AreaChart < PlotlyChart
63
+ def initialize(data, options = {}, key: nil, help: nil)
64
+ super(chart_type: :area, data: data, encoding: options, key: key, help: help)
65
+ end
66
+ end
67
+
68
+ class PieChart < PlotlyChart
69
+ def initialize(data, options = { label: :label, value: :value }, key: nil, help: nil)
70
+ enc = (options || {}).transform_keys(&:to_sym)
71
+ super(chart_type: :pie, data: data, encoding: { label: enc[:label] || "label", value: enc[:value] || "value" }, key: key, help: help)
72
+ end
73
+ end
74
+
75
+ class ScatterPlot < PlotlyChart
76
+ def initialize(data, options = {}, key: nil, help: nil)
77
+ super(chart_type: :scatter, data: data, encoding: options, key: key, help: help)
78
+ end
79
+ end
80
+ end
81
+
82
+ module St
83
+ def self.line_chart(data, options = {}, key: nil, help: nil)
84
+ component = Showtime::Components::LineChart.new(data, options, key: key, help: help)
85
+ Showtime::session.add_element(component)
86
+ component.spec
87
+ end
88
+
89
+ def self.bar_chart(data, options = {}, key: nil, help: nil)
90
+ component = Showtime::Components::BarChart.new(data, options, key: key, help: help)
91
+ Showtime::session.add_element(component)
92
+ component.spec
93
+ end
94
+
95
+ def self.column_chart(data, options = {}, key: nil, help: nil)
96
+ component = Showtime::Components::ColumnChart.new(data, options, key: key, help: help)
97
+ Showtime::session.add_element(component)
98
+ component.spec
99
+ end
100
+
101
+ def self.area_chart(data, options = {}, key: nil, help: nil)
102
+ component = Showtime::Components::AreaChart.new(data, options, key: key, help: help)
103
+ Showtime::session.add_element(component)
104
+ component.spec
105
+ end
106
+
107
+ def self.pie_chart(data, options = { label: :label, value: :value }, key: nil, help: nil)
108
+ component = Showtime::Components::PieChart.new(data, options, key: key, help: help)
109
+ Showtime::session.add_element(component)
110
+ component.spec
111
+ end
112
+
113
+ def self.scatter_plot(data, options = {}, key: nil, help: nil)
114
+ component = Showtime::Components::ScatterPlot.new(data, options, key: key, help: help)
115
+ Showtime::session.add_element(component)
116
+ component.spec
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,328 @@
1
+ require_relative 'base'
2
+ require 'logger'
3
+
4
+ module Showtime
5
+ module Components
6
+ class Json < BaseComponent
7
+ attr_reader :data
8
+
9
+ def initialize(data)
10
+ super()
11
+ @data = data
12
+ end
13
+
14
+ def to_h
15
+ super.merge({
16
+ data: @data.is_a?(String) ? @data : JSON.pretty_generate(@data.as_json)
17
+ })
18
+ end
19
+ end
20
+
21
+ class Table < BaseComponent
22
+ attr_reader :data, :page_size, :current_page
23
+
24
+ def initialize(data, page_size: 10, current_page: 1, key: nil)
25
+ super(key: key)
26
+ @data = data
27
+ @page_size = page_size
28
+ @current_page = [current_page, 1].max # Ensure current_page is at least 1
29
+ @logger = Showtime::Logger.logger
30
+
31
+ # Calculate total pages
32
+ total_rows = @data.respond_to?(:height) ? @data.height : (@data.is_a?(Array) ? @data.length : 0)
33
+ total_pages = (total_rows.to_f / @page_size).ceil
34
+ total_pages = [total_pages, 1].max # Ensure at least 1 page
35
+
36
+ # Get pagination state from session with defaults
37
+ @current_page = St.get("#{@key}_page", @current_page)
38
+ @page_size = St.get("#{@key}_page_size", @page_size)
39
+
40
+ # Validate and update current_page
41
+ @current_page = [@current_page, 1].max # Ensure current_page is at least 1
42
+ @current_page = [@current_page, total_pages].min # Ensure current_page doesn't exceed total pages
43
+
44
+ # Update session with validated values
45
+ St.set("#{@key}_page", @current_page)
46
+ St.set("#{@key}_page_size", @page_size)
47
+ end
48
+
49
+ def to_h
50
+ # Handle DataFrame-like objects that respond to columns and height
51
+ if @data.respond_to?(:columns) && @data.respond_to?(:height)
52
+ # Calculate pagination
53
+ total_rows = @data.height
54
+ total_pages = (total_rows.to_f / @page_size).ceil # Calculate total pages
55
+ @current_page = [@current_page, total_pages].min # Ensure current_page does not exceed total pages
56
+ start_idx = [(@current_page - 1) * @page_size, 0].max # Ensure start_idx is not negative
57
+ end_idx = [start_idx + @page_size, total_rows].min
58
+
59
+ # Get paginated data
60
+ length = [end_idx - start_idx, 0].max # Ensure length is not negative
61
+ paginated_df = @data.slice(start_idx, length)
62
+
63
+ # Convert to array of hashes
64
+ rows = []
65
+ columns = @data.columns
66
+
67
+ # Try multiple methods to convert data to array of hashes
68
+ begin
69
+ if paginated_df.respond_to?(:to_h)
70
+ # Try using to_h method
71
+ df_hash = paginated_df.to_h
72
+ rows = []
73
+ paginated_df.height.times do |i|
74
+ row_hash = {}
75
+ columns.each do |col|
76
+ row_hash[col] = df_hash[col][i]
77
+ end
78
+ rows << row_hash
79
+ end
80
+ elsif paginated_df.respond_to?(:rows)
81
+ # Try using rows method
82
+ paginated_df.rows.each do |row|
83
+ row_hash = {}
84
+ columns.each_with_index do |col, i|
85
+ row_hash[col] = row[i]
86
+ end
87
+ rows << row_hash
88
+ end
89
+ elsif paginated_df.respond_to?(:to_a)
90
+ # Try using to_a method
91
+ array_data = paginated_df.to_a
92
+ array_data.each do |row|
93
+ row_hash = {}
94
+ columns.each_with_index do |col, i|
95
+ row_hash[col] = row[i]
96
+ end
97
+ rows << row_hash
98
+ end
99
+ end
100
+ rescue => e
101
+ # If all else fails, return empty rows
102
+ rows = []
103
+ end
104
+
105
+ super.merge({
106
+ data: {
107
+ columns: columns,
108
+ data: rows,
109
+ total_rows: total_rows,
110
+ current_page: @current_page,
111
+ page_size: @page_size
112
+ }
113
+ })
114
+ elsif @data.is_a?(Array) && @data.first.is_a?(Array)
115
+ # Handle array of arrays (matrix-like data)
116
+ total_rows = @data.length
117
+ total_pages = (total_rows.to_f / @page_size).ceil # Calculate total pages
118
+ @current_page = [@current_page, total_pages].min # Ensure current_page does not exceed total pages
119
+ start_idx = [(@current_page - 1) * @page_size, 0].max # Ensure start_idx is not negative
120
+ end_idx = [start_idx + @page_size, total_rows].min
121
+
122
+ paginated_data = @data[start_idx...end_idx]
123
+ columns = @data.first
124
+ data = paginated_data[1..-1] || []
125
+
126
+ super.merge({
127
+ data: {
128
+ columns: columns,
129
+ data: data,
130
+ total_rows: total_rows,
131
+ current_page: @current_page,
132
+ page_size: @page_size
133
+ }
134
+ })
135
+ elsif @data.is_a?(Array) && @data.first.is_a?(Hash)
136
+ # Handle array of hashes
137
+ total_rows = @data.length
138
+ total_pages = (total_rows.to_f / @page_size).ceil # Calculate total pages
139
+ @current_page = [@current_page, total_pages].min # Ensure current_page does not exceed total pages
140
+ start_idx = [(@current_page - 1) * @page_size, 0].max # Ensure start_idx is not negative
141
+ end_idx = [start_idx + @page_size, total_rows].min
142
+
143
+ paginated_data = @data[start_idx...end_idx]
144
+ columns = @data.first.keys
145
+
146
+ super.merge({
147
+ data: {
148
+ columns: columns,
149
+ data: paginated_data,
150
+ total_rows: total_rows,
151
+ current_page: @current_page,
152
+ page_size: @page_size
153
+ }
154
+ })
155
+ else
156
+ super.merge({
157
+ data: @data
158
+ })
159
+ end
160
+ end
161
+ end
162
+
163
+ class DataFrame < BaseComponent
164
+ attr_reader :data, :page_size, :current_page
165
+
166
+ def initialize(data, page_size: 10, current_page: 1, key: nil)
167
+ super(key: key)
168
+ @data = data
169
+ @page_size = page_size
170
+ @current_page = [current_page, 1].max # Ensure current_page is at least 1
171
+ @logger = Showtime::Logger.logger
172
+
173
+ # Calculate total pages
174
+ total_rows = @data.respond_to?(:height) ? @data.height : 0
175
+ total_pages = (total_rows.to_f / @page_size).ceil
176
+ total_pages = [total_pages, 1].max # Ensure at least 1 page
177
+
178
+ # Get pagination state from session with defaults
179
+ @current_page = St.get("#{@key}_page", @current_page)
180
+ @page_size = St.get("#{@key}_page_size", @page_size)
181
+
182
+ # Validate and update current_page
183
+ @current_page = [@current_page, 1].max # Ensure current_page is at least 1
184
+ @current_page = [@current_page, total_pages].min # Ensure current_page doesn't exceed total pages
185
+
186
+ # Update session with validated values
187
+ St.set("#{@key}_page", @current_page)
188
+ St.set("#{@key}_page_size", @page_size)
189
+ end
190
+
191
+ def to_h
192
+ # Handle DataFrame-like objects that respond to columns and height
193
+ if @data.respond_to?(:columns) && @data.respond_to?(:height)
194
+ # Calculate pagination
195
+ total_rows = @data.height
196
+ total_pages = (total_rows.to_f / @page_size).ceil # Calculate total pages
197
+ @current_page = [@current_page, total_pages].min # Ensure current_page does not exceed total pages
198
+ start_idx = [(@current_page - 1) * @page_size, 0].max # Ensure start_idx is not negative
199
+ end_idx = [start_idx + @page_size, total_rows].min
200
+
201
+ # Get paginated data
202
+ length = [end_idx - start_idx, 0].max # Ensure length is not negative
203
+ paginated_df = @data.slice(start_idx, length)
204
+
205
+ # Convert to array of hashes
206
+ rows = []
207
+ columns = @data.columns
208
+
209
+ # Try multiple methods to convert data to array of hashes
210
+ begin
211
+ if paginated_df.respond_to?(:to_h)
212
+ # Try using to_h method
213
+ df_hash = paginated_df.to_h
214
+ rows = []
215
+ paginated_df.height.times do |i|
216
+ row_hash = {}
217
+ columns.each do |col|
218
+ row_hash[col] = df_hash[col][i]
219
+ end
220
+ rows << row_hash
221
+ end
222
+ elsif paginated_df.respond_to?(:rows)
223
+ # Try using rows method
224
+ paginated_df.rows.each do |row|
225
+ row_hash = {}
226
+ columns.each_with_index do |col, i|
227
+ row_hash[col] = row[i]
228
+ end
229
+ rows << row_hash
230
+ end
231
+ elsif paginated_df.respond_to?(:to_a)
232
+ # Try using to_a method
233
+ array_data = paginated_df.to_a
234
+ array_data.each do |row|
235
+ row_hash = {}
236
+ columns.each_with_index do |col, i|
237
+ row_hash[col] = row[i]
238
+ end
239
+ rows << row_hash
240
+ end
241
+ end
242
+ rescue => e
243
+ # If all else fails, return empty rows
244
+ rows = []
245
+ end
246
+
247
+ super.merge({
248
+ data: {
249
+ columns: columns,
250
+ data: rows,
251
+ total_rows: total_rows,
252
+ current_page: @current_page,
253
+ page_size: @page_size,
254
+ height: @data.height,
255
+ width: @data.respond_to?(:width) ? @data.width : columns.length,
256
+ dtypes: @data.columns.map do |col|
257
+ begin
258
+ [col, @data[col].dtype.to_s]
259
+ rescue
260
+ [col, "unknown"]
261
+ end
262
+ end.to_h
263
+ }
264
+ })
265
+ elsif @data.is_a?(Array) && @data.first.is_a?(Array)
266
+ # Handle array of arrays (matrix-like data)
267
+ total_rows = @data.length
268
+ total_pages = (total_rows.to_f / @page_size).ceil # Calculate total pages
269
+ @current_page = [@current_page, total_pages].min # Ensure current_page does not exceed total pages
270
+ start_idx = [(@current_page - 1) * @page_size, 0].max # Ensure start_idx is not negative
271
+ end_idx = [start_idx + @page_size, total_rows].min
272
+
273
+ paginated_data = @data[start_idx...end_idx]
274
+ columns = @data.first
275
+ data = paginated_data[1..-1] || []
276
+
277
+ super.merge({
278
+ data: {
279
+ columns: columns,
280
+ data: data,
281
+ total_rows: total_rows,
282
+ current_page: @current_page,
283
+ page_size: @page_size
284
+ }
285
+ })
286
+ elsif @data.is_a?(Array) && @data.first.is_a?(Hash)
287
+ # Handle array of hashes
288
+ total_rows = @data.length
289
+ total_pages = (total_rows.to_f / @page_size).ceil # Calculate total pages
290
+ @current_page = [@current_page, total_pages].min # Ensure current_page does not exceed total pages
291
+ start_idx = [(@current_page - 1) * @page_size, 0].max # Ensure start_idx is not negative
292
+ end_idx = [start_idx + @page_size, total_rows].min
293
+
294
+ paginated_data = @data[start_idx...end_idx]
295
+ columns = @data.first.keys
296
+
297
+ super.merge({
298
+ data: {
299
+ columns: columns,
300
+ data: paginated_data,
301
+ total_rows: total_rows,
302
+ current_page: @current_page,
303
+ page_size: @page_size
304
+ }
305
+ })
306
+ else
307
+ super.merge({
308
+ data: @data
309
+ })
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ module St
316
+ def self.json(data)
317
+ Showtime::session.add_element(Showtime::Components::Json.new(data))
318
+ end
319
+
320
+ def self.table(data, page_size: 10, current_page: 1, key: nil)
321
+ Showtime::session.add_element(Showtime::Components::Table.new(data, page_size: page_size, current_page: current_page, key: key))
322
+ end
323
+
324
+ def self.dataframe(data, page_size: 10, current_page: 1, key: nil)
325
+ Showtime::session.add_element(Showtime::Components::DataFrame.new(data, page_size: page_size, current_page: current_page, key: key))
326
+ end
327
+ end
328
+ end