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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +42 -0
- data/data/sample_graph.csv +11 -0
- data/lib/wads/app.rb +53 -320
- data/lib/wads/data_structures.rb +248 -18
- data/lib/wads/textinput.rb +20 -14
- data/lib/wads/version.rb +1 -1
- data/lib/wads/widgets.rb +2165 -216
- data/lib/wads.rb +1 -1
- data/media/CircleAlpha.png +0 -0
- data/media/CircleAqua.png +0 -0
- data/media/CircleBlue.png +0 -0
- data/media/CircleGray.png +0 -0
- data/media/CircleGreen.png +0 -0
- data/media/CirclePurple.png +0 -0
- data/media/CircleRed.png +0 -0
- data/media/CircleWhite.png +0 -0
- data/media/CircleYellow.png +0 -0
- data/media/GreenLight.png +0 -0
- data/media/RedLight.png +0 -0
- data/media/SampleGraph.png +0 -0
- data/media/StocksSample.png +0 -0
- data/media/WadsScreenshot.png +0 -0
- data/media/YellowLight.png +0 -0
- data/run-graph +3 -0
- data/run-star-wars +3 -0
- data/run-stocks +3 -0
- data/run-theme-test +3 -0
- data/samples/basic_gosu_with_graph_widget.rb +66 -0
- data/samples/gosu_bouncing_ball.rb +34 -0
- data/samples/gosu_hello_world.rb +16 -0
- data/samples/gosu_reaction_time.rb +121 -0
- data/samples/graph.rb +72 -0
- data/samples/star_wars.rb +112 -0
- data/samples/stocks.rb +126 -0
- data/samples/theme_test.rb +256 -0
- data/samples/wads_reaction_time.rb +117 -0
- metadata +31 -5
- data/run-sample-app +0 -3
- data/sample_app.rb +0 -64
data/lib/wads/data_structures.rb
CHANGED
@@ -4,6 +4,7 @@ module Wads
|
|
4
4
|
|
5
5
|
SPACER = " "
|
6
6
|
VALUE_WIDTH = 10
|
7
|
+
COLOR_TAG = "color"
|
7
8
|
|
8
9
|
DEG_0 = 0
|
9
10
|
DEG_45 = Math::PI * 0.25
|
@@ -24,6 +25,9 @@ module Wads
|
|
24
25
|
DEG_292_5 = DEG_270 + DEG_22_5
|
25
26
|
DEG_337_5 = DEG_315 + DEG_22_5
|
26
27
|
|
28
|
+
#
|
29
|
+
# A convenience data structure to store multiple, named sets of key/value pairs
|
30
|
+
#
|
27
31
|
class HashOfHashes
|
28
32
|
attr_accessor :data
|
29
33
|
|
@@ -31,6 +35,9 @@ module Wads
|
|
31
35
|
@data = {}
|
32
36
|
end
|
33
37
|
|
38
|
+
#
|
39
|
+
# Store the value y based on the key x for the named data set
|
40
|
+
#
|
34
41
|
def set(data_set_name, x, y)
|
35
42
|
data_set = @data[data_set_name]
|
36
43
|
if data_set.nil?
|
@@ -40,6 +47,9 @@ module Wads
|
|
40
47
|
data_set[x] = y
|
41
48
|
end
|
42
49
|
|
50
|
+
#
|
51
|
+
# Retrieve the value for the given key x in the named data set
|
52
|
+
#
|
43
53
|
def get(data_set_name, x)
|
44
54
|
data_set = @data[data_set_name]
|
45
55
|
if data_set.nil?
|
@@ -48,6 +58,9 @@ module Wads
|
|
48
58
|
data_set[x]
|
49
59
|
end
|
50
60
|
|
61
|
+
#
|
62
|
+
# Get the list of keys for the named data set
|
63
|
+
#
|
51
64
|
def keys(data_set_name)
|
52
65
|
data_set = @data[data_set_name]
|
53
66
|
if data_set.nil?
|
@@ -57,6 +70,11 @@ module Wads
|
|
57
70
|
end
|
58
71
|
end
|
59
72
|
|
73
|
+
#
|
74
|
+
# Stats allows you to maintain sets of data values, identified by a key,
|
75
|
+
# or data set name. You can then use Stats methods to get the count, average,
|
76
|
+
# sum, or percentiles for these keys.
|
77
|
+
#
|
60
78
|
class Stats
|
61
79
|
attr_accessor :name
|
62
80
|
attr_accessor :data
|
@@ -250,6 +268,20 @@ module Wads
|
|
250
268
|
end
|
251
269
|
end
|
252
270
|
|
271
|
+
#
|
272
|
+
# A node in a graph data structure. Nodes can be used independently, and you
|
273
|
+
# connect them to other nodes, or you can use an overarching Graph instance
|
274
|
+
# to help manage them. Nodes can carry arbitrary metadata in the tags map.
|
275
|
+
# The children are either other nodes, or an Edge instance which can be used
|
276
|
+
# to add information about the connection. For example, in a map graph use
|
277
|
+
# case, the edge may contain information about the distance between the two
|
278
|
+
# nodes. In other applications, metadata about the edges, or connections,
|
279
|
+
# may not be necessary. This class, and the Graph data structure, support
|
280
|
+
# children in either form. Each child connection is a one-directional
|
281
|
+
# connection. The backlinks are stored and managed internally so that we can
|
282
|
+
# easily navigate between nodes of the graph. Nodes themselves have a name
|
283
|
+
# an an optional value.
|
284
|
+
#
|
253
285
|
class Node
|
254
286
|
attr_accessor :name
|
255
287
|
attr_accessor :value
|
@@ -257,6 +289,7 @@ module Wads
|
|
257
289
|
attr_accessor :outputs
|
258
290
|
attr_accessor :visited
|
259
291
|
attr_accessor :tags
|
292
|
+
attr_accessor :depth
|
260
293
|
|
261
294
|
def id
|
262
295
|
# id is an alias for name
|
@@ -270,7 +303,20 @@ module Wads
|
|
270
303
|
@outputs = []
|
271
304
|
@visited = false
|
272
305
|
@tags = tags
|
306
|
+
@depth = 1
|
273
307
|
end
|
308
|
+
|
309
|
+
def add(name, value = nil, tags = {})
|
310
|
+
add_output_node(Node.new(name, value, tags))
|
311
|
+
end
|
312
|
+
|
313
|
+
def children
|
314
|
+
@outputs
|
315
|
+
end
|
316
|
+
|
317
|
+
def number_of_links
|
318
|
+
@outputs.size + @backlinks.size
|
319
|
+
end
|
274
320
|
|
275
321
|
def add_child(name, value)
|
276
322
|
add_output(name, value)
|
@@ -363,21 +409,48 @@ module Wads
|
|
363
409
|
end
|
364
410
|
end
|
365
411
|
|
366
|
-
def
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
412
|
+
def bfs(max_depth, &block)
|
413
|
+
node_queue = [self]
|
414
|
+
@depth = 1
|
415
|
+
until node_queue.empty?
|
416
|
+
node = node_queue.shift
|
417
|
+
yield node
|
418
|
+
node.visited = true
|
419
|
+
if node.depth < max_depth
|
420
|
+
# Get the set of all outputs and backlinks
|
421
|
+
h = {}
|
422
|
+
node.outputs.each do |n|
|
423
|
+
if n.is_a? Edge
|
424
|
+
n = n.destination
|
425
|
+
end
|
426
|
+
h[n.name] = n
|
427
|
+
end
|
428
|
+
node.backlinks.each do |n|
|
429
|
+
h[n.name] = n
|
430
|
+
end
|
431
|
+
|
432
|
+
h.values.each do |n|
|
433
|
+
if n.visited
|
434
|
+
# ignore, don't process again
|
435
|
+
else
|
436
|
+
n.visited = true
|
437
|
+
n.depth = node.depth + 1
|
438
|
+
node_queue << n
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
374
442
|
end
|
375
|
-
|
376
|
-
|
377
|
-
|
443
|
+
end
|
444
|
+
|
445
|
+
def to_display
|
446
|
+
"Node #{@name}: #{value} inputs: #{@backlinks.size} outputs: #{@outputs.size}"
|
378
447
|
end
|
379
448
|
end
|
380
449
|
|
450
|
+
#
|
451
|
+
# An Edge is a connection between nodes that stores additional information
|
452
|
+
# as arbitrary tags, or name/value pairs.
|
453
|
+
#
|
381
454
|
class Edge
|
382
455
|
attr_accessor :destination
|
383
456
|
attr_accessor :tags
|
@@ -396,13 +469,30 @@ module Wads
|
|
396
469
|
end
|
397
470
|
end
|
398
471
|
|
472
|
+
#
|
473
|
+
# A Graph helps manage nodes by providing high level methods to
|
474
|
+
# add or connect nodes to the graph. It also maintains a list of
|
475
|
+
# nodes and supports having multiple root nodes, i.e. nodes with
|
476
|
+
# no incoming connections.
|
477
|
+
# This class also supports constructing the graph from data stored
|
478
|
+
# in a file.
|
479
|
+
#
|
399
480
|
class Graph
|
400
481
|
attr_accessor :node_list
|
401
482
|
attr_accessor :node_map
|
402
483
|
|
403
|
-
def initialize
|
484
|
+
def initialize(root_node = nil)
|
404
485
|
@node_list = []
|
405
486
|
@node_map = {}
|
487
|
+
if root_node
|
488
|
+
if root_node.is_a? Node
|
489
|
+
root_node.visit do |n|
|
490
|
+
add_node(n)
|
491
|
+
end
|
492
|
+
elsif root_node.is_a? String
|
493
|
+
read_graph_from_file(root_node)
|
494
|
+
end
|
495
|
+
end
|
406
496
|
end
|
407
497
|
|
408
498
|
def add(name, value = nil, tags = {})
|
@@ -450,6 +540,37 @@ module Wads
|
|
450
540
|
source.add_output_edge(target, tags)
|
451
541
|
end
|
452
542
|
|
543
|
+
def node_with_most_connections
|
544
|
+
max_node = nil
|
545
|
+
max = -1
|
546
|
+
@node_list.each do |node|
|
547
|
+
num_links = node.number_of_links
|
548
|
+
if num_links > max
|
549
|
+
max = num_links
|
550
|
+
max_node = node
|
551
|
+
end
|
552
|
+
end
|
553
|
+
max_node
|
554
|
+
end
|
555
|
+
|
556
|
+
def get_number_of_connections_range
|
557
|
+
# Find the min and max
|
558
|
+
min = 1000
|
559
|
+
max = 0
|
560
|
+
@node_list.each do |node|
|
561
|
+
num_links = node.number_of_links
|
562
|
+
if num_links < min
|
563
|
+
min = num_links
|
564
|
+
end
|
565
|
+
if num_links > max
|
566
|
+
max = num_links
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
# Then create the scale
|
571
|
+
DataRange.new(min - 0.1, max + 0.1)
|
572
|
+
end
|
573
|
+
|
453
574
|
def find_node(name)
|
454
575
|
@node_map[name]
|
455
576
|
end
|
@@ -461,6 +582,7 @@ module Wads
|
|
461
582
|
def reset_visited
|
462
583
|
@node_list.each do |node|
|
463
584
|
node.visited = false
|
585
|
+
node.depth = 0
|
464
586
|
end
|
465
587
|
end
|
466
588
|
|
@@ -496,7 +618,7 @@ module Wads
|
|
496
618
|
false
|
497
619
|
end
|
498
620
|
|
499
|
-
def traverse_and_collect_nodes(node, max_depth, current_depth = 1)
|
621
|
+
def traverse_and_collect_nodes(node, max_depth = 0, current_depth = 1)
|
500
622
|
if max_depth > 0
|
501
623
|
if current_depth > max_depth
|
502
624
|
return {}
|
@@ -504,21 +626,25 @@ module Wads
|
|
504
626
|
end
|
505
627
|
map = {}
|
506
628
|
if node.visited
|
629
|
+
if current_depth < node.depth
|
630
|
+
node.depth = current_depth
|
631
|
+
end
|
507
632
|
return {}
|
508
633
|
else
|
509
634
|
map[node.name] = node
|
635
|
+
node.depth = current_depth
|
510
636
|
node.visited = true
|
511
637
|
end
|
512
|
-
node.
|
638
|
+
node.outputs.each do |child|
|
639
|
+
if child.is_a? Edge
|
640
|
+
child = child.destination
|
641
|
+
end
|
513
642
|
map_from_child = traverse_and_collect_nodes(child, max_depth, current_depth + 1)
|
514
643
|
map_from_child.each do |key, value|
|
515
644
|
map[key] = value
|
516
645
|
end
|
517
646
|
end
|
518
|
-
node.
|
519
|
-
if child.is_a? Edge
|
520
|
-
child = child.destination
|
521
|
-
end
|
647
|
+
node.backlinks.each do |child|
|
522
648
|
map_from_child = traverse_and_collect_nodes(child, max_depth, current_depth + 1)
|
523
649
|
map_from_child.each do |key, value|
|
524
650
|
map[key] = value
|
@@ -526,8 +652,76 @@ module Wads
|
|
526
652
|
end
|
527
653
|
map
|
528
654
|
end
|
655
|
+
|
656
|
+
def process_tag_string(tags, tag_string)
|
657
|
+
parts = tag_string.partition("=")
|
658
|
+
tag_name = parts[0]
|
659
|
+
tag_value = parts[2]
|
660
|
+
if tag_name == COLOR_TAG
|
661
|
+
begin
|
662
|
+
value = eval(tag_value)
|
663
|
+
puts "Adding tag #{tag_name} = #{value}"
|
664
|
+
tags[tag_name] = value
|
665
|
+
rescue => e
|
666
|
+
puts "Ignoring tag #{tag_name} = #{tag_value}"
|
667
|
+
end
|
668
|
+
else
|
669
|
+
puts "Adding tag #{tag_name} = #{tag_value}"
|
670
|
+
tags[tag_name] = tag_value
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
# The format is a csv file as follows:
|
675
|
+
# N,name,value --> nodes
|
676
|
+
# C,source,destination --> connections (also called edges)
|
677
|
+
#
|
678
|
+
# Optionally, each line type can be followed by comma-separated tag=value
|
679
|
+
def read_graph_from_file(filename)
|
680
|
+
puts "Read graph data from file #{filename}"
|
681
|
+
File.readlines(filename).each do |line|
|
682
|
+
line = line.chomp # remove the carriage return
|
683
|
+
values = line.split(",")
|
684
|
+
type = values[0]
|
685
|
+
tags = {}
|
686
|
+
if type == "N" or type == "n"
|
687
|
+
name = values[1]
|
688
|
+
if values.size > 2
|
689
|
+
value = values[2]
|
690
|
+
# The second position can be a tag or the node value
|
691
|
+
if value.include? "="
|
692
|
+
process_tag_string(tags, value)
|
693
|
+
value = nil
|
694
|
+
end
|
695
|
+
else
|
696
|
+
value = nil
|
697
|
+
end
|
698
|
+
if values.size > 3
|
699
|
+
values[3..-1].each do |tag_string|
|
700
|
+
process_tag_string(tags, tag_string)
|
701
|
+
end
|
702
|
+
end
|
703
|
+
add(name, value, tags)
|
704
|
+
elsif type == "E" or type == "e" or type == "L" or type == "l" or type == "C" or type == "c"
|
705
|
+
source_name = values[1]
|
706
|
+
destination_name = values[2]
|
707
|
+
if values.size > 3
|
708
|
+
values[3..-1].each do |tag_string|
|
709
|
+
process_tag_string(tags, tag_string)
|
710
|
+
end
|
711
|
+
end
|
712
|
+
connect(source_name, destination_name, tags)
|
713
|
+
else
|
714
|
+
puts "Ignoring line: #{line}"
|
715
|
+
end
|
716
|
+
end
|
717
|
+
end
|
529
718
|
end
|
530
719
|
|
720
|
+
#
|
721
|
+
# An internally used data structure that facilitates walking from the leaf nodes
|
722
|
+
# up to the top of the graph, such that a node is only visited once all of its
|
723
|
+
# descendants have been visited.
|
724
|
+
#
|
531
725
|
class GraphReverseIterator
|
532
726
|
attr_accessor :output
|
533
727
|
def initialize(graph)
|
@@ -553,6 +747,42 @@ module Wads
|
|
553
747
|
end
|
554
748
|
end
|
555
749
|
|
750
|
+
#
|
751
|
+
# A single dimension range, going from min to max.
|
752
|
+
# This class has helper methods to create bins within the given range.
|
753
|
+
#
|
754
|
+
class DataRange
|
755
|
+
attr_accessor :min
|
756
|
+
attr_accessor :max
|
757
|
+
attr_accessor :range
|
758
|
+
|
759
|
+
def initialize(min, max)
|
760
|
+
if min < max
|
761
|
+
@min = min
|
762
|
+
@max = max
|
763
|
+
else
|
764
|
+
@min = max
|
765
|
+
@max = min
|
766
|
+
end
|
767
|
+
@range = @max - @min
|
768
|
+
end
|
769
|
+
|
770
|
+
def bin_max_values(number_of_bins)
|
771
|
+
bin_size = @range / number_of_bins.to_f
|
772
|
+
bins = []
|
773
|
+
bin_start_value = @min
|
774
|
+
number_of_bins.times do
|
775
|
+
bin_start_value = bin_start_value + bin_size
|
776
|
+
bins << bin_start_value
|
777
|
+
end
|
778
|
+
bins
|
779
|
+
end
|
780
|
+
end
|
781
|
+
|
782
|
+
#
|
783
|
+
# A two dimensional range used by Plot to determine the visible area
|
784
|
+
# which can be a subset of the total data range(s)
|
785
|
+
#
|
556
786
|
class VisibleRange
|
557
787
|
attr_accessor :left_x
|
558
788
|
attr_accessor :right_x
|
data/lib/wads/textinput.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'gosu'
|
2
2
|
|
3
|
+
module Wads
|
4
|
+
|
3
5
|
class TextField < Gosu::TextInput
|
4
6
|
# Some constants that define our appearance.
|
5
7
|
INACTIVE_COLOR = 0xcc666666
|
@@ -12,10 +14,12 @@ class TextField < Gosu::TextInput
|
|
12
14
|
attr_accessor :base_z
|
13
15
|
attr_accessor :old_value
|
14
16
|
|
15
|
-
def initialize(
|
17
|
+
def initialize(x, y, original_text, default_width)
|
16
18
|
super()
|
17
19
|
|
18
|
-
@
|
20
|
+
@x = x
|
21
|
+
@y = y
|
22
|
+
@font = WadsConfig.instance.current_theme.font
|
19
23
|
@default_width = default_width
|
20
24
|
@base_z = 0
|
21
25
|
self.text = original_text
|
@@ -25,15 +29,15 @@ class TextField < Gosu::TextInput
|
|
25
29
|
def draw
|
26
30
|
# Depending on whether this is the currently selected input or not, change the
|
27
31
|
# background's color.
|
28
|
-
if
|
32
|
+
if WadsConfig.instance.get_window.text_input == self then
|
29
33
|
background_color = ACTIVE_COLOR
|
30
34
|
else
|
31
35
|
background_color = INACTIVE_COLOR
|
32
36
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
+
WadsConfig.instance.get_window.draw_quad(x - PADDING, y - PADDING, background_color,
|
38
|
+
x + width + PADDING, y - PADDING, background_color,
|
39
|
+
x - PADDING, y + height + PADDING, background_color,
|
40
|
+
x + width + PADDING, y + height + PADDING, background_color, @base_z + 9)
|
37
41
|
|
38
42
|
# Calculate the position of the caret and the selection start.
|
39
43
|
pos_x = x + @font.text_width(self.text[0...self.caret_pos])
|
@@ -41,15 +45,15 @@ class TextField < Gosu::TextInput
|
|
41
45
|
|
42
46
|
# Draw the selection background, if any; if not, sel_x and pos_x will be
|
43
47
|
# the same value, making this quad empty.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
WadsConfig.instance.get_window.draw_quad(sel_x, y, SELECTION_COLOR,
|
49
|
+
pos_x, y, SELECTION_COLOR,
|
50
|
+
sel_x, y + height, SELECTION_COLOR,
|
51
|
+
pos_x, y + height, SELECTION_COLOR, @base_z + 10)
|
48
52
|
|
49
53
|
# Draw the caret; again, only if this is the currently selected field.
|
50
|
-
if
|
51
|
-
|
52
|
-
|
54
|
+
if WadsConfig.instance.get_window.text_input == self then
|
55
|
+
WadsConfig.instance.get_window.draw_line(pos_x, y, CARET_COLOR,
|
56
|
+
pos_x, y + height, CARET_COLOR, @base_z + 10)
|
53
57
|
end
|
54
58
|
|
55
59
|
# Finally, draw the text itself!
|
@@ -130,3 +134,5 @@ class TextField < Gosu::TextInput
|
|
130
134
|
# empty base implementation
|
131
135
|
end
|
132
136
|
end
|
137
|
+
|
138
|
+
end # end wads module
|
data/lib/wads/version.rb
CHANGED