wads 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dffb479b6d7bdc47ff103db3f88b1acece5d0ec672361070aec604eb25aa6e9b
4
- data.tar.gz: b9fd53c1345bc7f5ab18cd0b5ab2eb74cc0b3276cd538cdae6dad414523f9529
3
+ metadata.gz: abb19e4e8651f417126dac1b5f6d684d366924f13596e220395fb2c5106b8912
4
+ data.tar.gz: e8e414d75ac5941bf934888ada51241a74728f186a6df60247f13499601e37cb
5
5
  SHA512:
6
- metadata.gz: dbeb4a1dd4f5918d047954b6336219e3dd9429db3de755f56f12f34cf60c4e187d92a967a3cbde88ad02dc35952257da492729f1da762f8b49035775462b4b1b
7
- data.tar.gz: 6c28345cfc8658cd25c1243469ff0fa308aa8278597b48e42f9cdd71b1a4d7e285e3df989d19504ea2f3137a36e593ee6a46116a360d4ed92d627c2b9b334619
6
+ metadata.gz: 94bf5046023232e7c4f9b46f7e8032c7c3ebd97ca9711a3423a3e20d42c23b1e3b39d3b8cd73dec4be8bc51c00bd685b41901fde8250576fffcba602cdf36b4b
7
+ data.tar.gz: 3c34a400278a5e2db1afb161bad83ac81375084f40c5928b63bdb6ddfc3446cbed7f7bdead0307c919612f515aedacc2ed7e02b61c5b3cf7b85766739772bcd3
data/CHANGELOG.md CHANGED
@@ -6,3 +6,14 @@
6
6
 
7
7
  - Added graph data structure and corresponding display widgets
8
8
 
9
+ ## [0.1.2] - 2021-08-30
10
+
11
+ - Fixed bugs with data structures. Added convenience methods to manage child widgets
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,18 @@
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
+
16
+ ![alt Screenshot](https://github.com/dbroemme/ruby-wads/blob/main/media/WadsScreenshot.png?raw=true)
5
17
  ## Installation
6
18
 
7
19
  Add this line to your application’s Gemfile:
@@ -9,7 +21,7 @@ Add this line to your application’s Gemfile:
9
21
  ```
10
22
  gem 'wads'
11
23
  ```
12
- And the run bundle install.
24
+ And then run bundle install.
13
25
 
14
26
  ## Sample Application
15
27
 
@@ -20,18 +32,54 @@ git clone https://github.com/dbroemme/ruby-wads.git
20
32
  cd ruby-wads
21
33
  ./bin/setup
22
34
  ./run
23
- ./run-sample-app -s -g
35
+ ./run-sample-app -s
24
36
  ```
25
37
 
26
38
  This will run the sample NASDAQ stocks analysis that features the use of the
27
39
  Stats class, and a table Gosu widget used to display the results of the analysis.
28
40
 
41
+ ![alt Screenshot](https://github.com/dbroemme/ruby-wads/blob/main/media/StocksSample.png?raw=true)
42
+
43
+
29
44
  You can also see the graph capabilities by running the Star Wars analysis example.
30
45
  This uses a data set that captures all character interactions within Episode 4.
31
46
 
32
47
  ```
33
- ./run-sample-app -j -g
48
+ ./run-sample-app -j
49
+ ```
50
+
51
+ There is also a sample Graph display using the following command.
52
+ ```
53
+ ./run-sample-app -g
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.
34
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)
35
83
 
36
84
  ## References
37
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,315 +1,57 @@
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
+ end
26
20
 
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"
21
+ #
22
+ # This method must be invoked with any Wads::Widget instance. It then handles
23
+ # delegating all events and drawing all child widgets.
24
+ #
25
+ def set_display(widget)
26
+ @main_widget = widget
31
27
  end
32
28
 
33
- opts = SampleAppCommand.new.parse.run
34
- if opts[:stocks]
35
- stats = process_stock_data
36
- if opts[:gui]
37
- @display_widget = SampleStocksDisplay.new(@small_font, stats)
38
- show
39
- else
40
- stats.report(Date::DAYNAMES[1..5])
41
- end
42
-
43
- elsif opts[:jedi]
44
- graph = process_star_wars_data
45
- @display_widget = SampleStarWarsDisplay.new(@small_font, graph)
46
- show
47
- elsif opts[:lottery]
48
- process_lottery_data
49
- else
50
- puts " "
51
- puts "Select one of the following sample analysis options"
52
- puts "-s Run sample stocks analysis"
53
- puts "-j Run sample Star Wars character analysis"
54
- puts "-l Run sample analysis of lottery numbers"
55
- puts " "
56
- exit
29
+ def get_display
30
+ @main_widget
57
31
  end
58
32
 
59
- end
60
-
61
- def update
62
- @display_widget.update(@update_count, mouse_x, mouse_y)
63
- @update_count = @update_count + 1
64
- end
33
+ def update
34
+ @main_widget.update(@update_count, mouse_x, mouse_y)
35
+ @update_count = @update_count + 1
36
+ end
65
37
 
66
- def draw
67
- draw_banner
68
- @display_widget.draw
69
- end
70
-
71
- def draw_banner
72
- @banner_image.draw(1,1,1,0.9,0.9)
73
- @title_font.draw_text("Wads Sample App", 10, 20, 2, 1, 1, Gosu::Color::WHITE)
74
- @version_font.draw_text("Version #{Wads::VERSION}", 13, 54, 2, 1, 1, Gosu::Color::WHITE)
75
- end
76
-
77
- def button_down id
78
- close if id == Gosu::KbEscape
79
- # Delegate button events to the primary display widget
80
- result = @display_widget.button_down id, mouse_x, mouse_y
81
- if result.close_widget
82
- close
38
+ def draw
39
+ @main_widget.draw
83
40
  end
84
- end
85
-
86
- def button_up id
87
- @display_widget.button_up id, mouse_x, mouse_y
88
- end
89
-
90
- def process_stock_data
91
- # The data file comes from https://finance.yahoo.com
92
- # The format of this file is as follows:
93
- #
94
- # Date,Open,High,Low,Close,Adj Close,Volume
95
- # 2000-01-03,4186.189941,4192.189941,3989.709961,4131.149902,4131.149902,1510070000
96
- # 2000-01-04,4020.000000,4073.250000,3898.229980,3901.689941,3901.689941,1511840000
97
- # ...
98
- # 2020-12-30,12906.509766,12924.929688,12857.759766,12870.000000,12870.000000,5292210000
99
- # 2020-12-31,12877.089844,12902.070313,12821.230469,12888.280273,12888.280273,4771390000
100
-
101
- stats = Stats.new("NASDAQ")
102
- previous_close = nil
103
-
104
- puts "Read the data file #{STOCKS_DATA_FILE}"
105
- File.readlines(STOCKS_DATA_FILE).each do |line|
106
- line = line.chomp # remove the carriage return
107
-
108
- # Ignore header and any empty lines, process numeric data lines
109
- if line.length > 0 and line[0].match(/[0-9]/)
110
- values = line.split(",")
111
- date = Date.strptime(values[0], "%Y-%m-%d")
112
- weekday = Date::DAYNAMES[date.wday]
113
-
114
- open_value = values[1].to_f
115
- close_value = values[4].to_f
116
-
117
- if previous_close.nil?
118
- # Just use the first day to set the baseline
119
- previous_close = close_value
120
- else
121
- change_from_previous_close = close_value - previous_close
122
- change_intraday = close_value - open_value
123
- change_overnight = open_value - previous_close
124
-
125
- change_percent = change_from_previous_close / previous_close
126
41
 
127
- stats.add(weekday, change_percent)
128
- stats.add("#{weekday} prev close", change_from_previous_close)
129
- stats.add("#{weekday} intraday", change_intraday)
130
- stats.add("#{weekday} overnight", change_overnight)
131
-
132
- previous_close = close_value
42
+ def button_down id
43
+ close if id == Gosu::KbEscape
44
+ # Delegate button events to the primary display widget
45
+ result = @main_widget.button_down id, mouse_x, mouse_y
46
+ if not result.nil? and result.is_a? WidgetResult
47
+ if result.close_widget
48
+ close
133
49
  end
134
50
  end
135
51
  end
136
-
137
- stats
138
- end
139
-
140
- def process_lottery_data
141
- puts "The lottery example has not been implemented yet"
142
- puts "however the data is available in the data directory,"
143
- puts "and the same pattern used in the stocks example can"
144
- puts "be applied here."
145
- end
146
-
147
- def process_star_wars_data
148
- star_wars_json = File.read(STAR_WARS_DATA_FILE)
149
- data_hash = JSON.parse(star_wars_json)
150
- characters = data_hash['nodes']
151
- interactions = data_hash['links']
152
-
153
- # The interactions in the data set reference the characters by their
154
- # zero based index, so we keep a reference in our graph by index.
155
- # The character's value is the number of scenes in which they appear.
156
- graph = Graph.new
157
- characters.each do |character|
158
- node_tags = {}
159
- node_color_str = character['colour']
160
- # This is a bit of a hack, but our background is black so black text
161
- # will not show up. Change this to white
162
- if node_color_str == "#000000"
163
- node_color_str = "#FFFFFF"
164
- end
165
- # Convert hex string (ex. "#EE00AA") into int hex representation
166
- # understood by Gosu color (ex. 0xFFEE00AA)
167
- node_color = "0xFF#{node_color_str[1..-1]}".to_i(16)
168
- node_tags['color'] = node_color
169
- graph.add_node(Node.new(character['name'], character['value'], node_tags))
170
- end
171
- interactions.each do |interaction|
172
- character_one = graph.node_by_index(interaction['source'])
173
- character_two = graph.node_by_index(interaction['target'])
174
- number_of_scenes_together = interaction['value']
175
- edge_tags = {}
176
- edge_tags["scenes"] = number_of_scenes_together
177
- character_one.add_output_edge(character_two, edge_tags)
178
- end
179
- graph
180
- end
181
- end
182
-
183
- class SampleStocksDisplay < Widget
184
- attr_accessor :stats
185
-
186
- def initialize(font, stats)
187
- super(10, 100, COLOR_HEADER_BRIGHT_BLUE)
188
- set_dimensions(780, 500)
189
- set_font(font)
190
- add_child(Document.new(sample_content, x + 5, y + 5, @width, @height, @font))
191
- @exit_button = Button.new("Exit", 380, bottom_edge - 30, @font)
192
- add_child(@exit_button)
193
-
194
- @stats = stats
195
- @data_table = SingleSelectTable.new(@x + 5, @y + 100, # top left corner
196
- 770, 200, # width, height
197
- ["Day", "Min", "Avg", "StdDev", "Max", "p10", "p90"], # column headers
198
- @font, COLOR_WHITE) # font and text color
199
- @data_table.selected_color = COLOR_LIGHT_GRAY
200
- Date::DAYNAMES[1..5].each do |day|
201
- min = format_percent(@stats.min(day))
202
- avg = format_percent(@stats.average(day))
203
- std = format_percent(@stats.std_dev(day))
204
- max = format_percent(@stats.max(day))
205
- p10 = format_percent(@stats.percentile(day, 0.1))
206
- p90 = format_percent(@stats.percentile(day, 0.90))
207
- @data_table.add_row([day, min, avg, std, max, p10, p90], COLOR_HEADER_BRIGHT_BLUE)
208
- end
209
- add_child(@data_table)
210
- @selection_text = nil
211
- end
212
-
213
- def format_percent(val)
214
- "#{(val * 100).round(3)}%"
215
- end
216
-
217
- def sample_content
218
- <<~HEREDOC
219
- This sample stock analysis uses NASDAQ data from https://finance.yahoo.com looking
220
- at closing data through the years 2000 to 2020. The percent gain or loss is broken
221
- down per day, as shown in the table below.
222
- HEREDOC
223
- end
224
-
225
- def render
226
- if @selection_text
227
- @selection_text.draw
52
+
53
+ def button_up id
54
+ @main_widget.button_up id, mouse_x, mouse_y
228
55
  end
229
56
  end
230
-
231
- def button_down id, mouse_x, mouse_y
232
- if id == Gosu::MsLeft
233
- if @exit_button.contains_click(mouse_x, mouse_y)
234
- return WidgetResult.new(true)
235
- elsif @data_table.contains_click(mouse_x, mouse_y)
236
- val = @data_table.set_selected_row(mouse_y, 0)
237
- if val.nil?
238
- # nothing to do
239
- else
240
- @selection_text = Text.new("You selected #{val}, a great day!",
241
- x + 5, y + 400, @font)
242
- end
243
- end
244
- end
245
- WidgetResult.new(false)
246
- end
247
57
  end
248
-
249
- class SampleStarWarsDisplay < Widget
250
- attr_accessor :graph
251
-
252
- def initialize(font, graph)
253
- super(10, 100, COLOR_HEADER_BRIGHT_BLUE)
254
- set_dimensions(780, 500)
255
- set_font(font)
256
- add_child(Document.new(sample_content, x + 5, y + 5, @width, @height, @font))
257
- @exit_button = Button.new("Exit", 380, bottom_edge - 30, @font)
258
- add_child(@exit_button)
259
-
260
- @graph = graph
261
- @data_table = SingleSelectTable.new(@x + 5, @y + 70, # top left corner
262
- 770, 180, # width, height
263
- ["Character", "Number of Scenes"], # column headers
264
- @font, COLOR_WHITE, # font and text color
265
- 5) # max visible rows
266
- @data_table.selected_color = COLOR_LIGHT_GRAY
267
- @graph.node_list.each do |character|
268
- @data_table.add_row([character.name, character.value], character.get_tag("color"))
269
- end
270
- add_child(@data_table)
271
- @graph_display = GraphWidget.new(x + 5, y + 260, 770, 200, @font, @color, @graph)
272
- end
273
-
274
- def sample_content
275
- <<~HEREDOC
276
- This sample analysis shows the interactions between characters in the Star Wars
277
- Episode 4: A New Hope. Click on a character to see more detail.
278
- HEREDOC
279
- end
280
-
281
- def render
282
- @graph_display.draw
283
- end
284
-
285
- def button_down id, mouse_x, mouse_y
286
- if id == Gosu::MsLeft
287
- if @exit_button.contains_click(mouse_x, mouse_y)
288
- return WidgetResult.new(true)
289
- elsif @data_table.contains_click(mouse_x, mouse_y)
290
- val = @data_table.set_selected_row(mouse_y, 0)
291
- if val.nil?
292
- # nothing to do
293
- else
294
- node = @graph.find_node(val)
295
- @graph_display.set_display(node, 2)
296
- end
297
- elsif @graph_display.contains_click(mouse_x, mouse_y)
298
- @graph_display.button_down id, mouse_x, mouse_y
299
- end
300
- elsif id == Gosu::KbUp
301
- @data_table.scroll_up
302
- elsif id == Gosu::KbDown
303
- @data_table.scroll_down
304
- end
305
- WidgetResult.new(false)
306
- end
307
-
308
- def button_up id, mouse_x, mouse_y
309
- @graph_display.button_up id, mouse_x, mouse_y
310
- end
311
-
312
- def update update_count, mouse_x, mouse_y
313
- @graph_display.update update_count, mouse_x, mouse_y
314
- end
315
- end