wads 0.1.0 → 0.2.0

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: ced90f1073417f101cc5688139f63c6437814a7bf96249b54344ad3a72f5a0fc
4
- data.tar.gz: '08fafccb767210d6f3653ee9ab6c33ca01962b4bd71503c54a3afcca83efcd9f'
3
+ metadata.gz: a1cf7ab7c4229f57f2bb4691819c5a4282a3fd0f7703e5caa6c93e49aeb35053
4
+ data.tar.gz: a1f0e971db1ea5064e44fef39c31767125f844b8ab3ef87eeaf09e3e92f27ad1
5
5
  SHA512:
6
- metadata.gz: 2daa8f10e3cabab4c8824f5f070b5b9f5b124b5c90910b6bd9d0a75c8775e8ed1285ed4cb2a019a08320f27c735065789a2401e60b2098731d6e7d915073868d
7
- data.tar.gz: 235bd6b70e43ef6f8714ea6406598a49747f5846495193d5193d089acf09b30ef80ff5dbf91733c6eb6e0e08468f34b192735262900b31b89d4caf71b41a1648
6
+ metadata.gz: 315790b3c8fe99525c3ad78eaa90eecd17fadd09a2a3e16a13ba82cb61ea5279472c57ada6e88021e9c6649236bb598aad0a46a1954928a1c37dc8dbf8b45b5e
7
+ data.tar.gz: 89de4f997dcef2e1b9e25062af443d6994d7ee4070d89dab21f06cb492ed3d106f837a9c3bdf85d7ba7e6522d30a039694982d939d49dd41b7da30c2637299ea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
- ## [Unreleased]
2
-
3
1
  ## [0.1.0] - 2021-08-23
4
2
 
5
3
  - Initial release
4
+
5
+ ## [0.1.1] - 2021-08-29
6
+
7
+ - Added graph data structure and corresponding display widgets
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,8 +32,56 @@ 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.
40
+
41
+ ![alt Screenshot](https://github.com/dbroemme/ruby-wads/blob/main/media/StocksSample.png?raw=true)
42
+
43
+
44
+ You can also see the graph capabilities by running the Star Wars analysis example.
45
+ This uses a data set that captures all character interactions within Episode 4.
46
+
47
+ ```
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.
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)
83
+
84
+ ## References
85
+
86
+ The Star Wars data set is courtesy of:
87
+ Gabasova, E. (2016). Star Wars social network. DOI: https://doi.org/10.5281/zenodo.1411479.
@@ -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
@@ -0,0 +1,336 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "name": "C-3PO",
5
+ "value": 42,
6
+ "colour": "#FFD700"
7
+ },
8
+ {
9
+ "name": "LUKE",
10
+ "value": 88,
11
+ "colour": "#3881e5"
12
+ },
13
+ {
14
+ "name": "DARTH VADER",
15
+ "value": 25,
16
+ "colour": "#000000"
17
+ },
18
+ {
19
+ "name": "CAMIE",
20
+ "value": 3,
21
+ "colour": "#808080"
22
+ },
23
+ {
24
+ "name": "BIGGS",
25
+ "value": 18,
26
+ "colour": "#808080"
27
+ },
28
+ {
29
+ "name": "LEIA",
30
+ "value": 25,
31
+ "colour": "#DCDCDC"
32
+ },
33
+ {
34
+ "name": "BERU",
35
+ "value": 5,
36
+ "colour": "#808080"
37
+ },
38
+ {
39
+ "name": "OWEN",
40
+ "value": 6,
41
+ "colour": "#808080"
42
+ },
43
+ {
44
+ "name": "OBI-WAN",
45
+ "value": 22,
46
+ "colour": "#48D1CC"
47
+ },
48
+ {
49
+ "name": "MOTTI",
50
+ "value": 3,
51
+ "colour": "#808080"
52
+ },
53
+ {
54
+ "name": "TARKIN",
55
+ "value": 12,
56
+ "colour": "#808080"
57
+ },
58
+ {
59
+ "name": "HAN",
60
+ "value": 44,
61
+ "colour": "#ff9400"
62
+ },
63
+ {
64
+ "name": "GREEDO",
65
+ "value": 2,
66
+ "colour": "#808080"
67
+ },
68
+ {
69
+ "name": "JABBA",
70
+ "value": 2,
71
+ "colour": "#808080"
72
+ },
73
+ {
74
+ "name": "DODONNA",
75
+ "value": 3,
76
+ "colour": "#808080"
77
+ },
78
+ {
79
+ "name": "GOLD LEADER",
80
+ "value": 14,
81
+ "colour": "#808080"
82
+ },
83
+ {
84
+ "name": "WEDGE",
85
+ "value": 15,
86
+ "colour": "#808080"
87
+ },
88
+ {
89
+ "name": "RED LEADER",
90
+ "value": 33,
91
+ "colour": "#808080"
92
+ },
93
+ {
94
+ "name": "RED TEN",
95
+ "value": 8,
96
+ "colour": "#808080"
97
+ },
98
+ {
99
+ "name": "GOLD FIVE",
100
+ "value": 8,
101
+ "colour": "#808080"
102
+ }
103
+ ],
104
+ "links": [
105
+ {
106
+ "source": 3,
107
+ "target": 1,
108
+ "value": 2
109
+ },
110
+ {
111
+ "source": 4,
112
+ "target": 3,
113
+ "value": 2
114
+ },
115
+ {
116
+ "source": 4,
117
+ "target": 1,
118
+ "value": 4
119
+ },
120
+ {
121
+ "source": 2,
122
+ "target": 5,
123
+ "value": 1
124
+ },
125
+ {
126
+ "source": 6,
127
+ "target": 1,
128
+ "value": 3
129
+ },
130
+ {
131
+ "source": 6,
132
+ "target": 7,
133
+ "value": 3
134
+ },
135
+ {
136
+ "source": 6,
137
+ "target": 0,
138
+ "value": 2
139
+ },
140
+ {
141
+ "source": 1,
142
+ "target": 7,
143
+ "value": 3
144
+ },
145
+ {
146
+ "source": 0,
147
+ "target": 1,
148
+ "value": 18
149
+ },
150
+ {
151
+ "source": 0,
152
+ "target": 7,
153
+ "value": 2
154
+ },
155
+ {
156
+ "source": 0,
157
+ "target": 5,
158
+ "value": 6
159
+ },
160
+ {
161
+ "source": 5,
162
+ "target": 1,
163
+ "value": 17
164
+ },
165
+ {
166
+ "source": 6,
167
+ "target": 5,
168
+ "value": 1
169
+ },
170
+ {
171
+ "source": 1,
172
+ "target": 8,
173
+ "value": 19
174
+ },
175
+ {
176
+ "source": 0,
177
+ "target": 8,
178
+ "value": 6
179
+ },
180
+ {
181
+ "source": 5,
182
+ "target": 8,
183
+ "value": 1
184
+ },
185
+ {
186
+ "source": 9,
187
+ "target": 10,
188
+ "value": 2
189
+ },
190
+ {
191
+ "source": 2,
192
+ "target": 9,
193
+ "value": 1
194
+ },
195
+ {
196
+ "source": 2,
197
+ "target": 10,
198
+ "value": 7
199
+ },
200
+ {
201
+ "source": 11,
202
+ "target": 8,
203
+ "value": 9
204
+ },
205
+ {
206
+ "source": 11,
207
+ "target": 1,
208
+ "value": 26
209
+ },
210
+ {
211
+ "source": 12,
212
+ "target": 11,
213
+ "value": 1
214
+ },
215
+ {
216
+ "source": 11,
217
+ "target": 13,
218
+ "value": 1
219
+ },
220
+ {
221
+ "source": 0,
222
+ "target": 11,
223
+ "value": 6
224
+ },
225
+ {
226
+ "source": 5,
227
+ "target": 9,
228
+ "value": 1
229
+ },
230
+ {
231
+ "source": 5,
232
+ "target": 10,
233
+ "value": 1
234
+ },
235
+ {
236
+ "source": 11,
237
+ "target": 5,
238
+ "value": 13
239
+ },
240
+ {
241
+ "source": 2,
242
+ "target": 8,
243
+ "value": 1
244
+ },
245
+ {
246
+ "source": 14,
247
+ "target": 15,
248
+ "value": 1
249
+ },
250
+ {
251
+ "source": 14,
252
+ "target": 16,
253
+ "value": 1
254
+ },
255
+ {
256
+ "source": 14,
257
+ "target": 1,
258
+ "value": 1
259
+ },
260
+ {
261
+ "source": 15,
262
+ "target": 16,
263
+ "value": 1
264
+ },
265
+ {
266
+ "source": 15,
267
+ "target": 1,
268
+ "value": 1
269
+ },
270
+ {
271
+ "source": 1,
272
+ "target": 16,
273
+ "value": 2
274
+ },
275
+ {
276
+ "source": 4,
277
+ "target": 5,
278
+ "value": 1
279
+ },
280
+ {
281
+ "source": 5,
282
+ "target": 17,
283
+ "value": 1
284
+ },
285
+ {
286
+ "source": 1,
287
+ "target": 17,
288
+ "value": 3
289
+ },
290
+ {
291
+ "source": 4,
292
+ "target": 17,
293
+ "value": 3
294
+ },
295
+ {
296
+ "source": 4,
297
+ "target": 0,
298
+ "value": 1
299
+ },
300
+ {
301
+ "source": 0,
302
+ "target": 17,
303
+ "value": 1
304
+ },
305
+ {
306
+ "source": 17,
307
+ "target": 16,
308
+ "value": 3
309
+ },
310
+ {
311
+ "source": 15,
312
+ "target": 17,
313
+ "value": 1
314
+ },
315
+ {
316
+ "source": 4,
317
+ "target": 16,
318
+ "value": 2
319
+ },
320
+ {
321
+ "source": 17,
322
+ "target": 18,
323
+ "value": 1
324
+ },
325
+ {
326
+ "source": 4,
327
+ "target": 15,
328
+ "value": 1
329
+ },
330
+ {
331
+ "source": 1,
332
+ "target": 18,
333
+ "value": 1
334
+ }
335
+ ]
336
+ }
data/lib/wads/app.rb CHANGED
@@ -1,199 +1,57 @@
1
1
  require 'gosu'
2
- require_relative 'data_structures'
3
2
  require_relative 'widgets'
4
- require_relative 'version'
5
3
 
6
- include Wads
7
-
8
- class WadsSampleApp < Gosu::Window
9
-
10
- STOCKS_DATA_FILE = "./data/NASDAQ.csv"
11
- LOTTERY_DATA_FILE = "./data/Pick4_12_21_2020.txt"
12
-
13
- def initialize
14
- super(800, 600)
15
- self.caption = "Wads Sample App"
16
- @font = Gosu::Font.new(32)
17
- @title_font = Gosu::Font.new(38)
18
- @version_font = Gosu::Font.new(22)
19
- @small_font = Gosu::Font.new(22)
20
- @banner_image = Gosu::Image.new("./media/Banner.png")
21
- @display_widget = nil
22
- 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
23
20
 
24
- def parse_opts_and_run
25
- # Make help the default output if no args are specified
26
- if ARGV.length == 0
27
- 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
28
27
  end
29
28
 
30
- opts = SampleAppCommand.new.parse.run
31
- if opts[:stocks]
32
- stats = process_stock_data
33
- if opts[:gui]
34
- @display_widget = SampleStocksDisplay.new(@small_font, stats)
35
- show
36
- else
37
- stats.report(Date::DAYNAMES[1..5])
38
- end
39
-
40
- elsif opts[:lottery]
41
- process_lottery_data
42
- else
43
- puts " "
44
- puts "Select one of the following sample analysis options"
45
- puts "-s Run sample stocks analysis"
46
- puts "-l Run sample analysis of lottery numbers"
47
- puts " "
48
- exit
29
+ def get_display
30
+ @main_widget
49
31
  end
50
32
 
51
- end
52
-
53
- def update
54
- # TODO
55
- end
33
+ def update
34
+ @main_widget.update(@update_count, mouse_x, mouse_y)
35
+ @update_count = @update_count + 1
36
+ end
56
37
 
57
- def draw
58
- draw_banner
59
- @display_widget.draw
60
- end
61
-
62
- def draw_banner
63
- @banner_image.draw(1,1,1,0.9,0.9)
64
- @title_font.draw_text("Wads Sample App", 10, 20, 2, 1, 1, Gosu::Color::WHITE)
65
- @version_font.draw_text("Version #{Wads::VERSION}", 13, 54, 2, 1, 1, Gosu::Color::WHITE)
66
- end
67
-
68
- def button_down id
69
- close if id == Gosu::KbEscape
70
- # Delegate button events to the primary display widget
71
- result = @display_widget.button_down id, mouse_x, mouse_y
72
- if result.close_widget
73
- close
38
+ def draw
39
+ @main_widget.draw
74
40
  end
75
- end
76
-
77
- def process_stock_data
78
- # The data file comes from https://finance.yahoo.com
79
- # The format of this file is as follows:
80
- #
81
- # Date,Open,High,Low,Close,Adj Close,Volume
82
- # 2000-01-03,4186.189941,4192.189941,3989.709961,4131.149902,4131.149902,1510070000
83
- # 2000-01-04,4020.000000,4073.250000,3898.229980,3901.689941,3901.689941,1511840000
84
- # ...
85
- # 2020-12-30,12906.509766,12924.929688,12857.759766,12870.000000,12870.000000,5292210000
86
- # 2020-12-31,12877.089844,12902.070313,12821.230469,12888.280273,12888.280273,4771390000
87
-
88
- stats = Stats.new("NASDAQ")
89
- previous_close = nil
90
-
91
- puts "Read the data file #{STOCKS_DATA_FILE}"
92
- File.readlines(STOCKS_DATA_FILE).each do |line|
93
- line = line.chomp # remove the carriage return
94
-
95
- # Ignore header and any empty lines, process numeric data lines
96
- if line.length > 0 and line[0].match(/[0-9]/)
97
- values = line.split(",")
98
- date = Date.strptime(values[0], "%Y-%m-%d")
99
- weekday = Date::DAYNAMES[date.wday]
100
-
101
- open_value = values[1].to_f
102
- close_value = values[4].to_f
103
41
 
104
- if previous_close.nil?
105
- # Just use the first day to set the baseline
106
- previous_close = close_value
107
- else
108
- change_from_previous_close = close_value - previous_close
109
- change_intraday = close_value - open_value
110
- change_overnight = open_value - previous_close
111
-
112
- change_percent = change_from_previous_close / previous_close
113
-
114
- stats.add(weekday, change_percent)
115
- stats.add("#{weekday} prev close", change_from_previous_close)
116
- stats.add("#{weekday} intraday", change_intraday)
117
- stats.add("#{weekday} overnight", change_overnight)
118
-
119
- 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
120
49
  end
121
50
  end
122
51
  end
123
-
124
- stats
125
- end
126
-
127
- def process_lottery_data
128
- puts "The lottery example has not been implemented yet"
129
- puts "however the data is available in the data directory,"
130
- puts "and the same pattern used in the stocks example can"
131
- puts "be applied here."
132
- end
133
- end
134
-
135
- class SampleStocksDisplay < Widget
136
- attr_accessor :stats
137
-
138
- def initialize(font, stats)
139
- super(10, 100, COLOR_HEADER_BRIGHT_BLUE)
140
- set_dimensions(780, 500)
141
- set_font(font)
142
- add_child(Document.new(sample_content, x + 5, y + 5, @width, @height, @font))
143
- @exit_button = Button.new("Exit", 380, bottom_edge - 30, @font)
144
- add_child(@exit_button)
145
-
146
- @stats = stats
147
- @data_table = SingleSelectTable.new(@x + 5, @y + 100, # top left corner
148
- 770, 200, # width, height
149
- ["Day", "Min", "Avg", "StdDev", "Max", "p10", "p90"], # column headers
150
- @font, COLOR_WHITE) # font and text color
151
- @data_table.selected_color = COLOR_LIGHT_GRAY
152
- Date::DAYNAMES[1..5].each do |day|
153
- min = format_percent(@stats.min(day))
154
- avg = format_percent(@stats.average(day))
155
- std = format_percent(@stats.std_dev(day))
156
- max = format_percent(@stats.max(day))
157
- p10 = format_percent(@stats.percentile(day, 0.1))
158
- p90 = format_percent(@stats.percentile(day, 0.90))
159
- @data_table.add_row([day, min, avg, std, max, p10, p90], COLOR_HEADER_BRIGHT_BLUE)
160
- end
161
- add_child(@data_table)
162
- @selection_text = nil
163
- end
164
-
165
- def format_percent(val)
166
- "#{(val * 100).round(3)}%"
167
- end
168
-
169
- def sample_content
170
- <<~HEREDOC
171
- This sample stock analysis uses NASDAQ data from https://finance.yahoo.com looking
172
- at closing data through the years 2000 to 2020. The percent gain or loss is broken
173
- down per day, as shown in the table below.
174
- HEREDOC
175
- end
176
-
177
- def render
178
- if @selection_text
179
- @selection_text.draw
52
+
53
+ def button_up id
54
+ @main_widget.button_up id, mouse_x, mouse_y
180
55
  end
181
56
  end
182
-
183
- def button_down id, mouse_x, mouse_y
184
- if id == Gosu::MsLeft
185
- if @exit_button.contains_click(mouse_x, mouse_y)
186
- return WidgetResult.new(true)
187
- elsif @data_table.contains_click(mouse_x, mouse_y)
188
- val = @data_table.set_selected_row(mouse_y, 0)
189
- if val.nil?
190
- # nothing to do
191
- else
192
- @selection_text = Text.new("You selected #{val}, a great day!",
193
- x + 5, y + 400, @font)
194
- end
195
- end
196
- end
197
- WidgetResult.new(false)
198
- end
199
- end
57
+ end