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.
@@ -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 to_display
367
- "#{@name}: #{value} inputs: #{@backlinks.size} outputs: #{@outputs.size}"
368
- end
369
-
370
- def full_display
371
- puts to_display
372
- @backlinks.each do |i|
373
- puts "Input: #{i.name}"
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
- #@outputs.each do |o|
376
- # puts "Output: #{o.name}"
377
- #end
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.backlinks.each do |child|
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.outputs.each do |child|
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
@@ -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(window, font, x, y, original_text, default_width)
17
+ def initialize(x, y, original_text, default_width)
16
18
  super()
17
19
 
18
- @window, @font, @x, @y = window, font, x, y
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 @window.text_input == self then
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
- @window.draw_quad(x - PADDING, y - PADDING, background_color,
34
- x + width + PADDING, y - PADDING, background_color,
35
- x - PADDING, y + height + PADDING, background_color,
36
- x + width + PADDING, y + height + PADDING, background_color, @base_z + 9)
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
- @window.draw_quad(sel_x, y, SELECTION_COLOR,
45
- pos_x, y, SELECTION_COLOR,
46
- sel_x, y + height, SELECTION_COLOR,
47
- pos_x, y + height, SELECTION_COLOR, @base_z + 10)
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 @window.text_input == self then
51
- @window.draw_line(pos_x, y, CARET_COLOR,
52
- pos_x, y + height, CARET_COLOR, @base_z + 10)
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
@@ -1,3 +1,3 @@
1
1
  module Wads
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.2"
3
3
  end