wads 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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