wads 0.1.3 → 0.2.3

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,6 +303,7 @@ module Wads
270
303
  @outputs = []
271
304
  @visited = false
272
305
  @tags = tags
306
+ @depth = 1
273
307
  end
274
308
 
275
309
  def add(name, value = nil, tags = {})
@@ -279,6 +313,10 @@ module Wads
279
313
  def children
280
314
  @outputs
281
315
  end
316
+
317
+ def number_of_links
318
+ @outputs.size + @backlinks.size
319
+ end
282
320
 
283
321
  def add_child(name, value)
284
322
  add_output(name, value)
@@ -371,21 +409,48 @@ module Wads
371
409
  end
372
410
  end
373
411
 
374
- def to_display
375
- "#{@name}: #{value} inputs: #{@backlinks.size} outputs: #{@outputs.size}"
376
- end
377
-
378
- def full_display
379
- puts to_display
380
- @backlinks.each do |i|
381
- 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
382
442
  end
383
- #@outputs.each do |o|
384
- # puts "Output: #{o.name}"
385
- #end
443
+ end
444
+
445
+ def to_display
446
+ "Node #{@name}: #{value} inputs: #{@backlinks.size} outputs: #{@outputs.size}"
386
447
  end
387
448
  end
388
449
 
450
+ #
451
+ # An Edge is a connection between nodes that stores additional information
452
+ # as arbitrary tags, or name/value pairs.
453
+ #
389
454
  class Edge
390
455
  attr_accessor :destination
391
456
  attr_accessor :tags
@@ -404,6 +469,14 @@ module Wads
404
469
  end
405
470
  end
406
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
+ #
407
480
  class Graph
408
481
  attr_accessor :node_list
409
482
  attr_accessor :node_map
@@ -412,8 +485,12 @@ module Wads
412
485
  @node_list = []
413
486
  @node_map = {}
414
487
  if root_node
415
- root_node.visit do |n|
416
- add_node(n)
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)
417
494
  end
418
495
  end
419
496
  end
@@ -463,6 +540,37 @@ module Wads
463
540
  source.add_output_edge(target, tags)
464
541
  end
465
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
+
466
574
  def find_node(name)
467
575
  @node_map[name]
468
576
  end
@@ -474,6 +582,7 @@ module Wads
474
582
  def reset_visited
475
583
  @node_list.each do |node|
476
584
  node.visited = false
585
+ node.depth = 0
477
586
  end
478
587
  end
479
588
 
@@ -509,7 +618,7 @@ module Wads
509
618
  false
510
619
  end
511
620
 
512
- def traverse_and_collect_nodes(node, max_depth, current_depth = 1)
621
+ def traverse_and_collect_nodes(node, max_depth = 0, current_depth = 1)
513
622
  if max_depth > 0
514
623
  if current_depth > max_depth
515
624
  return {}
@@ -517,21 +626,25 @@ module Wads
517
626
  end
518
627
  map = {}
519
628
  if node.visited
629
+ if current_depth < node.depth
630
+ node.depth = current_depth
631
+ end
520
632
  return {}
521
633
  else
522
634
  map[node.name] = node
635
+ node.depth = current_depth
523
636
  node.visited = true
524
637
  end
525
- node.backlinks.each do |child|
638
+ node.outputs.each do |child|
639
+ if child.is_a? Edge
640
+ child = child.destination
641
+ end
526
642
  map_from_child = traverse_and_collect_nodes(child, max_depth, current_depth + 1)
527
643
  map_from_child.each do |key, value|
528
644
  map[key] = value
529
645
  end
530
646
  end
531
- node.outputs.each do |child|
532
- if child.is_a? Edge
533
- child = child.destination
534
- end
647
+ node.backlinks.each do |child|
535
648
  map_from_child = traverse_and_collect_nodes(child, max_depth, current_depth + 1)
536
649
  map_from_child.each do |key, value|
537
650
  map[key] = value
@@ -539,8 +652,76 @@ module Wads
539
652
  end
540
653
  map
541
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
542
718
  end
543
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
+ #
544
725
  class GraphReverseIterator
545
726
  attr_accessor :output
546
727
  def initialize(graph)
@@ -566,6 +747,42 @@ module Wads
566
747
  end
567
748
  end
568
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
+ #
569
786
  class VisibleRange
570
787
  attr_accessor :left_x
571
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.3"
2
+ VERSION = "0.2.3"
3
3
  end