wads 0.1.0 → 0.1.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: ced90f1073417f101cc5688139f63c6437814a7bf96249b54344ad3a72f5a0fc
4
- data.tar.gz: '08fafccb767210d6f3653ee9ab6c33ca01962b4bd71503c54a3afcca83efcd9f'
3
+ metadata.gz: dffb479b6d7bdc47ff103db3f88b1acece5d0ec672361070aec604eb25aa6e9b
4
+ data.tar.gz: b9fd53c1345bc7f5ab18cd0b5ab2eb74cc0b3276cd538cdae6dad414523f9529
5
5
  SHA512:
6
- metadata.gz: 2daa8f10e3cabab4c8824f5f070b5b9f5b124b5c90910b6bd9d0a75c8775e8ed1285ed4cb2a019a08320f27c735065789a2401e60b2098731d6e7d915073868d
7
- data.tar.gz: 235bd6b70e43ef6f8714ea6406598a49747f5846495193d5193d089acf09b30ef80ff5dbf91733c6eb6e0e08468f34b192735262900b31b89d4caf71b41a1648
6
+ metadata.gz: dbeb4a1dd4f5918d047954b6336219e3dd9429db3de755f56f12f34cf60c4e187d92a967a3cbde88ad02dc35952257da492729f1da762f8b49035775462b4b1b
7
+ data.tar.gz: 6c28345cfc8658cd25c1243469ff0fa308aa8278597b48e42f9cdd71b1a4d7e285e3df989d19504ea2f3137a36e593ee6a46116a360d4ed92d627c2b9b334619
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
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
+
data/README.md CHANGED
@@ -25,3 +25,15 @@ cd ruby-wads
25
25
 
26
26
  This will run the sample NASDAQ stocks analysis that features the use of the
27
27
  Stats class, and a table Gosu widget used to display the results of the analysis.
28
+
29
+ You can also see the graph capabilities by running the Star Wars analysis example.
30
+ This uses a data set that captures all character interactions within Episode 4.
31
+
32
+ ```
33
+ ./run-sample-app -j -g
34
+ ```
35
+
36
+ ## References
37
+
38
+ The Star Wars data set is courtesy of:
39
+ Gabasova, E. (2016). Star Wars social network. DOI: https://doi.org/10.5281/zenodo.1411479.
@@ -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,4 +1,5 @@
1
1
  require 'gosu'
2
+ require 'json'
2
3
  require_relative 'data_structures'
3
4
  require_relative 'widgets'
4
5
  require_relative 'version'
@@ -9,6 +10,7 @@ class WadsSampleApp < Gosu::Window
9
10
 
10
11
  STOCKS_DATA_FILE = "./data/NASDAQ.csv"
11
12
  LOTTERY_DATA_FILE = "./data/Pick4_12_21_2020.txt"
13
+ STAR_WARS_DATA_FILE = "./data/starwars-episode-4-interactions.json"
12
14
 
13
15
  def initialize
14
16
  super(800, 600)
@@ -19,6 +21,7 @@ class WadsSampleApp < Gosu::Window
19
21
  @small_font = Gosu::Font.new(22)
20
22
  @banner_image = Gosu::Image.new("./media/Banner.png")
21
23
  @display_widget = nil
24
+ @update_count = 0
22
25
  end
23
26
 
24
27
  def parse_opts_and_run
@@ -36,13 +39,18 @@ class WadsSampleApp < Gosu::Window
36
39
  else
37
40
  stats.report(Date::DAYNAMES[1..5])
38
41
  end
39
-
42
+
43
+ elsif opts[:jedi]
44
+ graph = process_star_wars_data
45
+ @display_widget = SampleStarWarsDisplay.new(@small_font, graph)
46
+ show
40
47
  elsif opts[:lottery]
41
48
  process_lottery_data
42
49
  else
43
50
  puts " "
44
51
  puts "Select one of the following sample analysis options"
45
52
  puts "-s Run sample stocks analysis"
53
+ puts "-j Run sample Star Wars character analysis"
46
54
  puts "-l Run sample analysis of lottery numbers"
47
55
  puts " "
48
56
  exit
@@ -51,7 +59,8 @@ class WadsSampleApp < Gosu::Window
51
59
  end
52
60
 
53
61
  def update
54
- # TODO
62
+ @display_widget.update(@update_count, mouse_x, mouse_y)
63
+ @update_count = @update_count + 1
55
64
  end
56
65
 
57
66
  def draw
@@ -74,6 +83,10 @@ class WadsSampleApp < Gosu::Window
74
83
  end
75
84
  end
76
85
 
86
+ def button_up id
87
+ @display_widget.button_up id, mouse_x, mouse_y
88
+ end
89
+
77
90
  def process_stock_data
78
91
  # The data file comes from https://finance.yahoo.com
79
92
  # The format of this file is as follows:
@@ -130,6 +143,41 @@ class WadsSampleApp < Gosu::Window
130
143
  puts "and the same pattern used in the stocks example can"
131
144
  puts "be applied here."
132
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
133
181
  end
134
182
 
135
183
  class SampleStocksDisplay < Widget
@@ -196,4 +244,72 @@ class SampleStocksDisplay < Widget
196
244
  end
197
245
  WidgetResult.new(false)
198
246
  end
247
+ 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
199
315
  end
@@ -4,7 +4,25 @@ module Wads
4
4
 
5
5
  SPACER = " "
6
6
  VALUE_WIDTH = 10
7
- NODE_UNKNOWN = "undefined"
7
+
8
+ DEG_0 = 0
9
+ DEG_45 = Math::PI * 0.25
10
+ DEG_90 = Math::PI * 0.5
11
+ DEG_135 = Math::PI * 0.75
12
+ DEG_180 = Math::PI
13
+ DEG_225 = Math::PI * 1.25
14
+ DEG_270 = Math::PI * 1.5
15
+ DEG_315 = Math::PI * 1.75
16
+ DEG_360 = Math::PI * 2
17
+
18
+ DEG_22_5 = Math::PI * 0.125
19
+ DEG_67_5 = DEG_45 + DEG_22_5
20
+ DEG_112_5 = DEG_90 + DEG_22_5
21
+ DEG_157_5 = DEG_135 + DEG_22_5
22
+ DEG_202_5 = DEG_180 + DEG_22_5
23
+ DEG_247_5 = DEG_225 + DEG_22_5
24
+ DEG_292_5 = DEG_270 + DEG_22_5
25
+ DEG_337_5 = DEG_315 + DEG_22_5
8
26
 
9
27
  class HashOfHashes
10
28
  attr_accessor :data
@@ -225,27 +243,198 @@ module Wads
225
243
  end
226
244
 
227
245
  class Node
228
- attr_accessor :x
229
- attr_accessor :y
230
246
  attr_accessor :name
231
- attr_accessor :type
232
- attr_accessor :inputs
247
+ attr_accessor :value
248
+ attr_accessor :backlinks
233
249
  attr_accessor :outputs
234
250
  attr_accessor :visited
251
+ attr_accessor :tags
235
252
 
236
- def initialize(name, type = NODE_UNKNOWN)
237
- @name = name
238
- @type = type
239
- @inputs = []
253
+ def initialize(name, value = nil, tags = {})
254
+ @name = name
255
+ @value = value
256
+ @backlinks = []
240
257
  @outputs = []
241
258
  @visited = false
259
+ @tags = tags
260
+ end
261
+
262
+ def add_child(name, value)
263
+ add_output(name, value)
242
264
  end
243
265
 
244
- #
245
- # TODO Visitor pattern and solution for detecting cyclic graphs
246
- #
247
- # when you visit, reset all the visited flags
248
- # set it to true when you visit the node
249
- # first check though if visited already true, if so, you have a cycle
266
+ def add_output(name, value)
267
+ child_node = Node.new(name, value)
268
+ add_output_node(child_node)
269
+ end
270
+
271
+ def add_output_node(child_node)
272
+ child_node.backlinks << self
273
+ @outputs << child_node
274
+ child_node
275
+ end
276
+
277
+ def add_output_edge(destination, tags = {})
278
+ edge = Edge.new(destination, tags)
279
+ destination.backlinks << self
280
+ @outputs << edge
281
+ edge
282
+ end
283
+
284
+ def add_tag(key, value)
285
+ @tags[key] = value
286
+ end
287
+
288
+ def get_tag(key)
289
+ @tags[key]
290
+ end
291
+
292
+ def find_node(search_name)
293
+ if @name == search_name
294
+ return self
295
+ end
296
+ found_node_in_child = nil
297
+
298
+ @outputs.each do |child|
299
+ if child.is_a? Edge
300
+ child = child.destination
301
+ end
302
+ found_node_in_child = child.find_node(search_name)
303
+ if found_node_in_child
304
+ return found_node_in_child
305
+ end
306
+ end
307
+ nil
308
+ end
309
+
310
+ def visit(&block)
311
+ node_queue = [self]
312
+ until node_queue.empty?
313
+ node = node_queue.shift
314
+ yield node
315
+ node.outputs.each do |c|
316
+ if child.is_a? Edge
317
+ child = child.destination
318
+ end
319
+ node_queue << c
320
+ end
321
+ end
322
+ end
323
+
324
+ def to_display
325
+ "#{@name}: #{value} inputs: #{@backlinks.size} outputs: #{@outputs.size}"
326
+ end
327
+
328
+ def full_display
329
+ puts to_display
330
+ @backlinks.each do |i|
331
+ puts "Input: #{i.name}"
332
+ end
333
+ #@outputs.each do |o|
334
+ # puts "Output: #{o.name}"
335
+ #end
336
+ end
337
+ end
338
+
339
+ class Edge
340
+ attr_accessor :destination
341
+ attr_accessor :tags
342
+
343
+ def initialize(destination, tags = {})
344
+ @destination = destination
345
+ @tags = tags
346
+ end
347
+
348
+ def add_tag(key, value)
349
+ @tags[key] = value
350
+ end
351
+
352
+ def get_tag(key)
353
+ @tags[key]
354
+ end
355
+ end
356
+
357
+ class Graph
358
+ attr_accessor :node_list
359
+ attr_accessor :node_map
360
+
361
+ def initialize
362
+ @node_list = []
363
+ @node_map = {}
364
+ end
365
+
366
+ def add_node(node)
367
+ @node_list << node
368
+ @node_map[node.name] = node
369
+ end
370
+
371
+ def add_edge(source, target, tags)
372
+ if source.is_a? String
373
+ source = find_node(source)
374
+ end
375
+ if target.is_a? String
376
+ target = find_node(target)
377
+ end
378
+ end
379
+
380
+ def find_node(name)
381
+ @node_map[name]
382
+ end
383
+
384
+ def node_by_index(index)
385
+ @node_list[index]
386
+ end
387
+
388
+ def reset_visited
389
+ @node_list.each do |node|
390
+ node.visited = false
391
+ end
392
+ end
393
+
394
+ def root_nodes
395
+ list = []
396
+ @node_list.each do |node|
397
+ if node.backlinks.empty?
398
+ list << node
399
+ end
400
+ end
401
+ list
402
+ end
403
+
404
+ def is_cycle(node)
405
+ reset_visited
406
+ node.visit do |n|
407
+ if n.visited
408
+ return true
409
+ else
410
+ n.visited = true
411
+ end
412
+ end
413
+ false
414
+ end
415
+
416
+ def fan_out(node, max_depth, current_depth = 1)
417
+ if current_depth > max_depth
418
+ return {}
419
+ end
420
+ map = {}
421
+ map[node.name] = node
422
+ node.backlinks.each do |child|
423
+ map_from_child = fan_out(child, max_depth, current_depth + 1)
424
+ map_from_child.each do |key, value|
425
+ map[key] = value
426
+ end
427
+ end
428
+ node.outputs.each do |child|
429
+ if child.is_a? Edge
430
+ child = child.destination
431
+ end
432
+ map_from_child = fan_out(child, max_depth, current_depth + 1)
433
+ map_from_child.each do |key, value|
434
+ map[key] = value
435
+ end
436
+ end
437
+ map
438
+ end
250
439
  end
251
440
  end
data/lib/wads/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Wads
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/wads/widgets.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require_relative 'data_structures'
2
+
1
3
  module Wads
2
4
  COLOR_PEACH = Gosu::Color.argb(0xffe6b0aa)
3
5
  COLOR_LIGHT_PURPLE = Gosu::Color.argb(0xffd7bde2)
@@ -28,8 +30,8 @@ module Wads
28
30
 
29
31
  Z_ORDER_BACKGROUND = 2
30
32
  Z_ORDER_WIDGET_BORDER = 3
31
- Z_ORDER_GRAPHIC_ELEMENTS = 4
32
- Z_ORDER_SELECTION_BACKGROUND = 5
33
+ Z_ORDER_SELECTION_BACKGROUND = 4
34
+ Z_ORDER_GRAPHIC_ELEMENTS = 5
33
35
  Z_ORDER_PLOT_POINTS = 6
34
36
  Z_ORDER_OVERLAY_BACKGROUND = 7
35
37
  Z_ORDER_OVERLAY_ELEMENTS = 8
@@ -94,6 +96,10 @@ module Wads
94
96
  @x + ((right_edge - @x) / 2)
95
97
  end
96
98
 
99
+ def center_y
100
+ @y + ((bottom_edge - @y) / 2)
101
+ end
102
+
97
103
  def draw
98
104
  if @visible
99
105
  render
@@ -109,8 +115,13 @@ module Wads
109
115
  end
110
116
  end
111
117
 
112
- def draw_background
113
- Gosu::draw_rect(@x + 1, @y + 1, @width - 1, @height - 1, @background_color, Z_ORDER_BACKGROUND)
118
+ def draw_background(z_order = Z_ORDER_BACKGROUND)
119
+ Gosu::draw_rect(@x + 1, @y + 1, @width - 3, @height - 3, @background_color, z_order)
120
+ end
121
+
122
+ def draw_shadow(color, z_order = Z_ORDER_WIDGET_BORDER)
123
+ Gosu::draw_line @x - 1, @y - 1, color, right_edge - 1, @y - 1, color, z_order
124
+ Gosu::draw_line @x - 1, @y - 1, color, @x - 1, bottom_edge - 1, color, z_order
114
125
  end
115
126
 
116
127
  def render
@@ -119,19 +130,35 @@ module Wads
119
130
  # render is for specific drawing done by the widget
120
131
  end
121
132
 
122
- def draw_border(color = nil)
133
+ def draw_border(color = nil, zorder = Z_ORDER_WIDGET_BORDER)
123
134
  if color.nil?
124
- color = @color
135
+ if @border_color
136
+ color = @border_color
137
+ else
138
+ color = @color
139
+ end
125
140
  end
126
- Gosu::draw_line @x, @y, color, right_edge, @y, color, Z_ORDER_WIDGET_BORDER
127
- Gosu::draw_line @x, @y, color, @x, bottom_edge, color, Z_ORDER_WIDGET_BORDER
128
- Gosu::draw_line @x,bottom_edge, color, right_edge, bottom_edge, color, Z_ORDER_WIDGET_BORDER
129
- Gosu::draw_line right_edge, @y, color, right_edge, bottom_edge, color, Z_ORDER_WIDGET_BORDER
141
+ Gosu::draw_line @x, @y, color, right_edge, @y, color, zorder
142
+ Gosu::draw_line @x, @y, color, @x, bottom_edge, color, zorder
143
+ Gosu::draw_line @x,bottom_edge, color, right_edge, bottom_edge, color, zorder
144
+ Gosu::draw_line right_edge, @y, color, right_edge, bottom_edge, color, zorder
130
145
  end
131
146
 
132
147
  def contains_click(mouse_x, mouse_y)
133
148
  mouse_x >= @x and mouse_x <= right_edge and mouse_y >= @y and mouse_y <= bottom_edge
134
149
  end
150
+
151
+ def update update_count, mouse_x, mouse_y
152
+ # empty base implementation
153
+ end
154
+
155
+ def button_down id, mouse_x, mouse_y
156
+ # empty base implementation
157
+ end
158
+
159
+ def button_up id, mouse_x, mouse_y
160
+ # empty base implementation
161
+ end
135
162
  end
136
163
 
137
164
  class Text < Widget
@@ -204,7 +231,7 @@ module Wads
204
231
  end
205
232
 
206
233
  def render
207
- draw_border(COLOR_WHITE)
234
+ draw_border(@color)
208
235
  text_x = center_x - (@text_pixel_width / 2)
209
236
  @font.draw_text(@label, text_x, @y, Z_ORDER_TEXT, 1, 1, @text_color)
210
237
  end
@@ -456,7 +483,7 @@ module Wads
456
483
  end
457
484
 
458
485
  def scroll_down
459
- if @current_row < @data_rows.size - 1
486
+ if @current_row + @max_visible_rows < @data_rows.size
460
487
  @current_row = @current_row + @max_visible_rows
461
488
  end
462
489
  end
@@ -546,7 +573,13 @@ module Wads
546
573
  def set_selected_row(mouse_y, column_number)
547
574
  row_number = determine_row_number(mouse_y)
548
575
  if not row_number.nil?
549
- @selected_row = @current_row + row_number
576
+ new_selected_row = @current_row + row_number
577
+ if @selected_row
578
+ if @selected_row == new_selected_row
579
+ return nil # You can't select the same row already selected
580
+ end
581
+ end
582
+ @selected_row = new_selected_row
550
583
  @data_rows[@selected_row][column_number]
551
584
  end
552
585
  end
@@ -702,8 +735,6 @@ module Wads
702
735
  data_set.derive_values(@visible_range, @data_set_hash)
703
736
  data_set.data_points.each do |point|
704
737
  if is_on_screen(point)
705
- #puts "Adding render point at x #{point.x}, #{Time.at(point.x)}"
706
- #puts "Visible range: #{Time.at(@visible_range.left_x)} #{Time.at(@visible_range.right_x)}"
707
738
  data_set.add_rendered_point PlotPoint.new(draw_x(point.x), draw_y(point.y), data_set.color, data_set.data_point_size)
708
739
  end
709
740
  end
@@ -824,4 +855,143 @@ module Wads
824
855
  [get_x_data_val(mouse_x), get_y_data_val(mouse_y)]
825
856
  end
826
857
  end
858
+
859
+ class NodeWidget < Button
860
+ attr_accessor :data_node
861
+
862
+ def initialize(node, x, y, font, border_color = COLOR_DARK_GRAY, text_color = COLOR_HEADER_BRIGHT_BLUE)
863
+ super(node.name, x, y, font, nil, border_color, text_color)
864
+ set_background(COLOR_BLACK)
865
+ @data_node = node
866
+ end
867
+
868
+ def render
869
+ super
870
+ draw_background(Z_ORDER_OVERLAY_BACKGROUND)
871
+ draw_shadow(COLOR_GRAY)
872
+ end
873
+ end
874
+
875
+ class GraphWidget < Widget
876
+ attr_accessor :graph
877
+ attr_accessor :center_node
878
+ attr_accessor :depth
879
+ attr_accessor :selected_node
880
+ attr_accessor :selected_node_x_offset
881
+ attr_accessor :selected_node_y_offset
882
+
883
+ def initialize(x, y, width, height, font, color, graph)
884
+ super x, y, color
885
+ set_font(font)
886
+ set_dimensions(width, height)
887
+ set_border(color)
888
+ @graph = graph
889
+ end
890
+
891
+ def update update_count, mouse_x, mouse_y
892
+ if contains_click(mouse_x, mouse_y) and @selected_node
893
+ @selected_node.x = mouse_x - @selected_node_x_offset
894
+ @selected_node.y = mouse_y - @selected_node_y_offset
895
+ end
896
+ end
897
+
898
+ def button_down id, mouse_x, mouse_y
899
+ if id == Gosu::MsLeft
900
+ # check to see if any node was selected
901
+ if @rendered_nodes
902
+ @rendered_nodes.values.each do |rn|
903
+ if rn.contains_click(mouse_x, mouse_y)
904
+ @selected_node = rn
905
+ @selected_node_x_offset = mouse_x - rn.x
906
+ @selected_node_y_offset = mouse_y - rn.y
907
+ end
908
+ end
909
+ end
910
+ end
911
+ WidgetResult.new(false)
912
+ end
913
+
914
+ def button_up id, mouse_x, mouse_y
915
+ if id == Gosu::MsLeft
916
+ if @selected_node
917
+ @selected_node = nil
918
+ end
919
+ end
920
+ end
921
+
922
+ def set_display(center_node, max_depth)
923
+ # Determine the list of nodes to draw
924
+ @depth = depth
925
+ @visible_data_nodes = @graph.fan_out(center_node, max_depth)
926
+
927
+ # Convert the data nodes to rendered nodes
928
+ # Start by putting the center node in the center, then draw others around it
929
+ @rendered_nodes = {}
930
+ @rendered_nodes[center_node.name] = NodeWidget.new(center_node, center_x, center_y, @font,
931
+ center_node.get_tag("color"), center_node.get_tag("color"))
932
+
933
+ # Spread out the other nodes around the center node
934
+ # going in a circle
935
+ number_of_visible_nodes = @visible_data_nodes.size
936
+ radians_between_nodes = DEG_360 / number_of_visible_nodes.to_f
937
+ current_radians = 0.05
938
+
939
+ @visible_data_nodes.each do |node_name, data_node|
940
+ if node_name == center_node.name
941
+ # skip, we already got this one
942
+ else
943
+ # Use radians to spread the other nodes around the center node
944
+ # For now, we will randomly place them
945
+ node_x = center_x + ((80 + rand(200)) * Math.cos(current_radians))
946
+ node_y = center_y - ((40 + rand(100)) * Math.sin(current_radians))
947
+ if node_x < @x
948
+ node_x = @x + 1
949
+ elsif node_x > right_edge - 20
950
+ node_x = right_edge - 20
951
+ end
952
+ if node_y < @y
953
+ node_y = @y + 1
954
+ elsif node_y > bottom_edge - 26
955
+ node_y = bottom_edge - 26
956
+ end
957
+ current_radians = current_radians + radians_between_nodes
958
+
959
+ # Note we can link between data nodes and rendered nodes using the node name
960
+ # We have a map of each
961
+ @rendered_nodes[data_node.name] = NodeWidget.new(
962
+ data_node,
963
+ node_x,
964
+ node_y,
965
+ @font,
966
+ data_node.get_tag("color"),
967
+ data_node.get_tag("color"))
968
+ end
969
+ end
970
+ end
971
+
972
+ def render
973
+ if @rendered_nodes
974
+ @rendered_nodes.values.each do |vn|
975
+ vn.draw
976
+ end
977
+ # Draw the connections between nodes
978
+ @visible_data_nodes.values.each do |data_node|
979
+ data_node.outputs.each do |connected_data_node|
980
+ if connected_data_node.is_a? Edge
981
+ connected_data_node = connected_data_node.destination
982
+ end
983
+ rendered_node = @rendered_nodes[data_node.name]
984
+ connected_rendered_node = @rendered_nodes[connected_data_node.name]
985
+ if connected_rendered_node.nil?
986
+ # Don't draw if it is not currently visible
987
+ else
988
+ Gosu::draw_line rendered_node.center_x, rendered_node.center_y, rendered_node.color,
989
+ connected_rendered_node.center_x, connected_rendered_node.center_y, connected_rendered_node.color,
990
+ Z_ORDER_GRAPHIC_ELEMENTS
991
+ end
992
+ end
993
+ end
994
+ end
995
+ end
996
+ end
827
997
  end
data/sample_app.rb CHANGED
@@ -10,8 +10,8 @@ class SampleAppCommand
10
10
  command ""
11
11
 
12
12
  example <<~EOS
13
- Run the sample app to analyze stock market data
14
- $ ./run-sample-app
13
+ Run the sample app to analyze stock market data in gui mode
14
+ $ ./run-sample-app -s -g
15
15
  EOS
16
16
 
17
17
  end
@@ -34,6 +34,12 @@ class SampleAppCommand
34
34
  desc "Run sample analysis of lottery numbers"
35
35
  end
36
36
 
37
+ flag :jedi do
38
+ short "-j"
39
+ long "--jedi"
40
+ desc "Run sample analysis of interactions between Star Wars"
41
+ end
42
+
37
43
  flag :gui do
38
44
  short "-g"
39
45
  long "--gui"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wads
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - dbroemme
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-26 00:00:00.000000000 Z
11
+ date: 2021-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gosu
@@ -69,6 +69,7 @@ files:
69
69
  - bin/setup
70
70
  - data/NASDAQ.csv
71
71
  - data/Pick4_12_21_2020.txt
72
+ - data/starwars-episode-4-interactions.json
72
73
  - lib/wads.rb
73
74
  - lib/wads/app.rb
74
75
  - lib/wads/data_structures.rb