wads 0.1.2 → 0.2.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2652d96c92be67ffd117365e2eb1ed93c21fd6f6c83cae037c6761f3f70754d5
4
- data.tar.gz: e1680952ab2bdd333bc518ae7d97bca0a8bfc4033e2b0b9f8bf062e689ee22e9
3
+ metadata.gz: 51b79bd0bfafd0b02a32240f3410d4d5fcfdef5938f1a9fe23e4d89914f778d0
4
+ data.tar.gz: 8ab8279ec6ed32b2c64b5f637d9c6db13e5fd7f157d24ea6c8d59ee4818649d5
5
5
  SHA512:
6
- metadata.gz: 2e07cd62b59ca7d4e73176a94f3396f37f2981b6d448ebdd9490f9b6d5c0a69e0cd531805fed3e5512435b2d9a669521b1a047e0d022dd9ee0fb1ffb9ddd1941
7
- data.tar.gz: 9189a546fe8c0f74fa0e18abbca8de17eacd70b63b7092e09374b4315f832ed359f62add905a3ce9c82641728b569846fc44f04007b2e14bcdb28e0608f35e3d
6
+ metadata.gz: 9f0e2d5d176468c106c290845767d27dc520bcd5009e4d30d4caa5c0e4bb93c4dfe861084660d0dd6ddd2f401f3fe274fc3816e1febc5a64808b4291199ef1a8
7
+ data.tar.gz: 14120137ff1e6a7398b3ae1a858127bfd37a072df1485777c36aab4888e3e7fd412a8f43e949efbdd766c24d51d23d0de93b82ae92cc88b2f6a32aa5c9a33619
data/CHANGELOG.md CHANGED
@@ -10,3 +10,10 @@
10
10
 
11
11
  - Fixed bugs with data structures. Added convenience methods to manage child widgets
12
12
 
13
+ ## [0.1.3] - 2021-09-30
14
+
15
+ - Improvements to graph display including user ability to change focus node
16
+
17
+ ## [0.2.0] - 2021-10-05
18
+
19
+ - Breaking change in order to refactor, including standardizing order of widget constructor args
data/README.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  A number of Ruby Gosu-based (W)idgets (a)nd (D)ata (S)tructures for your applications.
4
4
 
5
+ The user guide and examples can be found at https://www.rubydoesitagain.com
6
+
7
+ Wads (Widgets and Data Structures) is intended to help you quickly build native Ruby applications using the Gosu gem.
8
+ GUI widgets, such as tables and buttons, are provided to simplify building graphical applications.
9
+ Widgets in wads can be used independently within your Gosu app, or you can use wads as a holistic GUI framework.
10
+ Wads includes basic features found in many GUI toolkits including The use of themes and a layout manager.
11
+
12
+ One of the motivations for creating wads is to making data analysis, games, and productivity applications using Ruby easier to do.
13
+ Data structures are included that can be used for statistical analysis or graph-based applications.
14
+ The samples provided illustrate both use cases (Stocks - stats) and Star Wars character interactions (graphs).
15
+
5
16
  ![alt Screenshot](https://github.com/dbroemme/ruby-wads/blob/main/media/WadsScreenshot.png?raw=true)
6
17
  ## Installation
7
18
 
@@ -27,6 +38,9 @@ cd ruby-wads
27
38
  This will run the sample NASDAQ stocks analysis that features the use of the
28
39
  Stats class, and a table Gosu widget used to display the results of the analysis.
29
40
 
41
+ ![alt Screenshot](https://github.com/dbroemme/ruby-wads/blob/main/media/StocksSample.png?raw=true)
42
+
43
+
30
44
  You can also see the graph capabilities by running the Star Wars analysis example.
31
45
  This uses a data set that captures all character interactions within Episode 4.
32
46
 
@@ -38,6 +52,34 @@ There is also a sample Graph display using the following command.
38
52
  ```
39
53
  ./run-sample-app -g
40
54
  ```
55
+ Note that you can construct graphs in one of two ways. The first approach uses Nodes directly.
56
+ ```
57
+ root = Node.new("a")
58
+ b = root.add("b")
59
+ b.add("d")
60
+ b.add("e").add("f")
61
+ root.add("c")
62
+ Graph.new(root)
63
+ ```
64
+ The second approach uses an overarching Graph object.
65
+ ```
66
+ g = Graph.new
67
+ g.add("a")
68
+ g.add("b")
69
+ g.add("c")
70
+ g.add("d")
71
+ g.add("e")
72
+ g.add("f")
73
+ g.connect("a", "b")
74
+ g.connect("a", "c")
75
+ g.connect("b", "d")
76
+ g.connect("b", "e")
77
+ g.connect("e", "f")
78
+ ```
79
+
80
+ Currently, the Graph object is used by the Graph Widget for display. For the code above, this appears by default as follows:
81
+
82
+ ![alt Screenshot](https://github.com/dbroemme/ruby-wads/blob/main/media/SampleGraph.png?raw=true)
41
83
 
42
84
  ## References
43
85
 
@@ -0,0 +1,11 @@
1
+ N,A,color=COLOR_YELLOW
2
+ N,B,color=COLOR_GREEN
3
+ N,C,color=COLOR_BLUE
4
+ N,D,color=COLOR_RED
5
+ N,E,color=COLOR_PURPLE
6
+ N,F,color=COLOR_GRAY
7
+ C,A,B
8
+ C,A,C
9
+ C,B,D
10
+ C,B,E
11
+ C,E,F
data/lib/wads/app.rb CHANGED
@@ -1,340 +1,73 @@
1
1
  require 'gosu'
2
- require 'json'
3
- require_relative 'data_structures'
4
2
  require_relative 'widgets'
5
- require_relative 'version'
6
3
 
7
- include Wads
8
-
9
- class WadsSampleApp < Gosu::Window
10
-
11
- STOCKS_DATA_FILE = "./data/NASDAQ.csv"
12
- LOTTERY_DATA_FILE = "./data/Pick4_12_21_2020.txt"
13
- STAR_WARS_DATA_FILE = "./data/starwars-episode-4-interactions.json"
14
-
15
- def initialize
16
- super(800, 600)
17
- self.caption = "Wads Sample App"
18
- @font = Gosu::Font.new(32)
19
- @title_font = Gosu::Font.new(38)
20
- @version_font = Gosu::Font.new(22)
21
- @small_font = Gosu::Font.new(22)
22
- @banner_image = Gosu::Image.new("./media/Banner.png")
23
- @display_widget = nil
24
- @update_count = 0
25
- end
4
+ module Wads
5
+ #
6
+ # The WadsApp class provides a simple starting point to quickly build a native
7
+ # Ruby application using Gosu as an underlying library. It provides all the necessary
8
+ # hooks to get started. All you need to do is supply the parent Wads widget using
9
+ # the set_display(widget) method. See one of the Wads samples for example usage.
10
+ #
11
+ class WadsApp < Gosu::Window
12
+ def initialize(width, height, caption, widget)
13
+ super(width, height)
14
+ self.caption = caption
15
+ @update_count = 0
16
+ WadsConfig.instance.set_window(self)
17
+ set_display(widget)
18
+ WadsConfig.instance.set_log_level("info")
19
+ @registered_hold_down_buttons = []
20
+ end
26
21
 
27
- def parse_opts_and_run
28
- # Make help the default output if no args are specified
29
- if ARGV.length == 0
30
- ARGV[0] = "-h"
22
+ #
23
+ # This method must be invoked with any Wads::Widget instance. It then handles
24
+ # delegating all events and drawing all child widgets.
25
+ #
26
+ def set_display(widget)
27
+ @main_widget = widget
31
28
  end
32
29
 
33
- opts = SampleAppCommand.new.parse.run
34
- if opts[:stocks]
35
- stats = process_stock_data
36
- if opts[:text]
37
- stats.report(Date::DAYNAMES[1..5])
38
- else
39
- @display_widget = SampleStocksDisplay.new(@small_font, stats)
40
- show
41
- end
42
-
43
- elsif opts[:jedi]
44
- graph = process_star_wars_data
45
- @display_widget = SampleStarWarsDisplay.new(@small_font, graph)
46
- show
47
-
48
- elsif opts[:lottery]
49
- process_lottery_data
50
-
51
- elsif opts[:graph]
52
- graph = create_test_graph
53
- if not opts[:text]
54
- @display_widget = SampleGraphDisplay.new(@small_font, graph)
55
- show
56
- end
57
- else
58
- puts " "
59
- puts "Select one of the following sample analysis options"
60
- puts "-s Run sample stocks analysis"
61
- puts "-j Run sample Star Wars character analysis"
62
- puts "-g Run sample graph display"
63
- puts " "
64
- exit
30
+ def get_display
31
+ @main_widget
65
32
  end
66
33
 
67
- end
68
-
69
- def update
70
- @display_widget.update(@update_count, mouse_x, mouse_y)
71
- @update_count = @update_count + 1
72
- end
73
-
74
- def draw
75
- draw_banner
76
- @display_widget.draw
77
- end
78
-
79
- def draw_banner
80
- @banner_image.draw(1,1,1,0.9,0.9)
81
- @title_font.draw_text("Wads Sample App", 10, 20, 2, 1, 1, Gosu::Color::WHITE)
82
- @version_font.draw_text("Version #{Wads::VERSION}", 13, 54, 2, 1, 1, Gosu::Color::WHITE)
83
- end
84
-
85
- def button_down id
86
- close if id == Gosu::KbEscape
87
- # Delegate button events to the primary display widget
88
- result = @display_widget.button_down id, mouse_x, mouse_y
89
- if not result.nil? and result.is_a? WidgetResult
90
- if result.close_widget
91
- close
92
- end
34
+ # Register a key (identified by the Gosu id) to check if it is being held down.
35
+ # If so, the handle_key_held_down callback will be invoked on widgets
36
+ # For example, register_hold_down_key(Gosu::KbLeft)
37
+ def register_hold_down_key(id)
38
+ @registered_hold_down_buttons << id
93
39
  end
94
- end
95
-
96
- def button_up id
97
- @display_widget.button_up id, mouse_x, mouse_y
98
- end
99
-
100
- def process_stock_data
101
- # The data file comes from https://finance.yahoo.com
102
- # The format of this file is as follows:
103
- #
104
- # Date,Open,High,Low,Close,Adj Close,Volume
105
- # 2000-01-03,4186.189941,4192.189941,3989.709961,4131.149902,4131.149902,1510070000
106
- # 2000-01-04,4020.000000,4073.250000,3898.229980,3901.689941,3901.689941,1511840000
107
- # ...
108
- # 2020-12-30,12906.509766,12924.929688,12857.759766,12870.000000,12870.000000,5292210000
109
- # 2020-12-31,12877.089844,12902.070313,12821.230469,12888.280273,12888.280273,4771390000
110
-
111
- stats = Stats.new("NASDAQ")
112
- previous_close = nil
113
-
114
- puts "Read the data file #{STOCKS_DATA_FILE}"
115
- File.readlines(STOCKS_DATA_FILE).each do |line|
116
- line = line.chomp # remove the carriage return
117
40
 
118
- # Ignore header and any empty lines, process numeric data lines
119
- if line.length > 0 and line[0].match(/[0-9]/)
120
- values = line.split(",")
121
- date = Date.strptime(values[0], "%Y-%m-%d")
122
- weekday = Date::DAYNAMES[date.wday]
41
+ def update
42
+ @main_widget.update(@update_count, mouse_x, mouse_y)
123
43
 
124
- open_value = values[1].to_f
125
- close_value = values[4].to_f
126
-
127
- if previous_close.nil?
128
- # Just use the first day to set the baseline
129
- previous_close = close_value
130
- else
131
- change_from_previous_close = close_value - previous_close
132
- change_intraday = close_value - open_value
133
- change_overnight = open_value - previous_close
134
-
135
- change_percent = change_from_previous_close / previous_close
136
-
137
- stats.add(weekday, change_percent)
138
- stats.add("#{weekday} prev close", change_from_previous_close)
139
- stats.add("#{weekday} intraday", change_intraday)
140
- stats.add("#{weekday} overnight", change_overnight)
141
-
142
- previous_close = close_value
44
+ # Look for keys that are held down and delegate those events
45
+ @registered_hold_down_buttons.each do |id|
46
+ if button_down?(id)
47
+ @main_widget.handle_key_held_down id, mouse_x, mouse_y
143
48
  end
144
49
  end
145
- end
146
50
 
147
- stats
148
- end
149
-
150
- def process_lottery_data
151
- puts "The lottery example has not been implemented yet"
152
- puts "however the data is available in the data directory,"
153
- puts "and the same pattern used in the stocks example can"
154
- puts "be applied here."
155
- end
156
-
157
- def process_star_wars_data
158
- star_wars_json = File.read(STAR_WARS_DATA_FILE)
159
- data_hash = JSON.parse(star_wars_json)
160
- characters = data_hash['nodes']
161
- interactions = data_hash['links']
162
-
163
- # The interactions in the data set reference the characters by their
164
- # zero based index, so we keep a reference in our graph by index.
165
- # The character's value is the number of scenes in which they appear.
166
- graph = Graph.new
167
- characters.each do |character|
168
- node_tags = {}
169
- node_color_str = character['colour']
170
- # This is a bit of a hack, but our background is black so black text
171
- # will not show up. Change this to white
172
- if node_color_str == "#000000"
173
- node_color_str = "#FFFFFF"
174
- end
175
- # Convert hex string (ex. "#EE00AA") into int hex representation
176
- # understood by Gosu color (ex. 0xFFEE00AA)
177
- node_color = "0xFF#{node_color_str[1..-1]}".to_i(16)
178
- node_tags['color'] = node_color
179
- graph.add_node(Node.new(character['name'], character['value'], node_tags))
180
- end
181
- interactions.each do |interaction|
182
- character_one = graph.node_by_index(interaction['source'])
183
- character_two = graph.node_by_index(interaction['target'])
184
- number_of_scenes_together = interaction['value']
185
- edge_tags = {}
186
- edge_tags["scenes"] = number_of_scenes_together
187
- graph.add_edge(character_one, character_two, edge_tags)
188
- end
189
- graph
190
- end
191
-
192
- def create_test_graph
193
- g = Graph.new
194
- g.add("a")
195
- g.add("b")
196
- g.add("c")
197
- g.add("d")
198
- g.add("e")
199
- g.add("f")
200
- g.connect("a", "b")
201
- g.connect("a", "c")
202
- g.connect("b", "d")
203
- g.connect("b", "e")
204
- g.connect("e", "f")
205
- g
206
- end
207
- end
208
-
209
- class SampleStocksDisplay < Widget
210
- attr_accessor :stats
211
-
212
- def initialize(font, stats)
213
- super(10, 100, COLOR_HEADER_BRIGHT_BLUE)
214
- set_dimensions(780, 500)
215
- set_font(font)
216
- add_document(sample_content, 5, 5, @width, @height)
217
- add_button("Exit", 380, @height - 30) do
218
- WidgetResult.new(true)
219
- end
220
-
221
- @stats = stats
222
- @data_table = add_single_select_table(5, 100, # top left corner
223
- 770, 200, # width, height
224
- ["Day", "Min", "Avg", "StdDev", "Max", "p10", "p90"], # column headers
225
- COLOR_WHITE) # font and text color
226
- @data_table.selected_color = COLOR_LIGHT_GRAY
227
- Date::DAYNAMES[1..5].each do |day|
228
- min = format_percent(@stats.min(day))
229
- avg = format_percent(@stats.average(day))
230
- std = format_percent(@stats.std_dev(day))
231
- max = format_percent(@stats.max(day))
232
- p10 = format_percent(@stats.percentile(day, 0.1))
233
- p90 = format_percent(@stats.percentile(day, 0.90))
234
- @data_table.add_row([day, min, avg, std, max, p10, p90], COLOR_HEADER_BRIGHT_BLUE)
235
- end
236
-
237
- @selection_text = nil
238
- end
239
-
240
- def format_percent(val)
241
- "#{(val * 100).round(3)}%"
242
- end
243
-
244
- def sample_content
245
- <<~HEREDOC
246
- This sample stock analysis uses NASDAQ data from https://finance.yahoo.com looking
247
- at closing data through the years 2000 to 2020. The percent gain or loss is broken
248
- down per day, as shown in the table below.
249
- HEREDOC
250
- end
251
-
252
- def render
253
- if @selection_text
254
- @selection_text.draw
255
- end
256
- end
257
-
258
- def handle_mouse_down mouse_x, mouse_y
259
- if @data_table.contains_click(mouse_x, mouse_y)
260
- val = @data_table.set_selected_row(mouse_y, 0)
261
- if not val.nil?
262
- @selection_text = Text.new("You selected #{val}, a great day!",
263
- x + 5, y + 400, @font)
264
- end
51
+ @update_count = @update_count + 1
52
+ end
53
+
54
+ def draw
55
+ @main_widget.draw
265
56
  end
266
- nil
267
- end
268
- end
269
57
 
270
- class SampleStarWarsDisplay < Widget
271
- attr_accessor :graph
272
-
273
- def initialize(font, graph)
274
- super(10, 100, COLOR_HEADER_BRIGHT_BLUE)
275
- set_dimensions(780, 500)
276
- set_font(font)
277
- @graph = graph
278
-
279
- add_document(sample_content, 5, 5, @width, @height)
280
- add_button("Exit", 370, @height - 30) do
281
- WidgetResult.new(true)
58
+ def button_down id
59
+ close if id == Gosu::KbEscape
60
+ # Delegate button events to the primary display widget
61
+ result = @main_widget.button_down id, mouse_x, mouse_y
62
+ if not result.nil? and result.is_a? WidgetResult
63
+ if result.close_widget
64
+ close
65
+ end
66
+ end
282
67
  end
283
-
284
- @data_table = add_single_select_table(5, 70, # top left corner
285
- 770, 180, # width, height
286
- ["Character", "Number of Scenes"], # column headers
287
- COLOR_WHITE, # font and text color
288
- 5) # max visible rows
289
- @data_table.selected_color = COLOR_LIGHT_GRAY
290
-
291
- @graph.node_list.each do |character|
292
- @data_table.add_row([character.name, character.value], character.get_tag("color"))
68
+
69
+ def button_up id
70
+ @main_widget.button_up id, mouse_x, mouse_y
293
71
  end
294
- @graph_display = add_graph_display(5, 260, 770, 200, @graph)
295
72
  end
296
-
297
- def sample_content
298
- <<~HEREDOC
299
- This sample analysis shows the interactions between characters in the Star Wars
300
- Episode 4: A New Hope. Click on a character to see more detail.
301
- HEREDOC
302
- end
303
-
304
- def handle_key_press id, mouse_x, mouse_y
305
- if id == Gosu::KbUp
306
- @data_table.scroll_up
307
- elsif id == Gosu::KbDown
308
- @data_table.scroll_down
309
- end
310
- WidgetResult.new(false)
311
- end
312
-
313
- def handle_mouse_down mouse_x, mouse_y
314
- if @data_table.contains_click(mouse_x, mouse_y)
315
- val = @data_table.set_selected_row(mouse_y, 0)
316
- if not val.nil?
317
- node = @graph.find_node(val)
318
- @graph_display.set_center_node(node, 2)
319
- end
320
- end
321
- nil
322
- end
323
73
  end
324
-
325
- class SampleGraphDisplay < Widget
326
- attr_accessor :graph
327
-
328
- def initialize(font, graph)
329
- super(10, 100, COLOR_CYAN)
330
- set_dimensions(780, 500)
331
- set_font(font)
332
- @graph = graph
333
- exit_button = add_button("Exit", 370, @height - 30) do
334
- WidgetResult.new(true)
335
- end
336
- exit_button.text_color = COLOR_CYAN
337
- @graph_display = add_graph_display(5, 60, 770, 400, @graph)
338
- @graph_display.set_tree_display
339
- end
340
- end