wads 0.1.3 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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