wads 0.1.1 → 0.2.1
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 +11 -0
- data/README.md +51 -3
- data/data/sample_graph.csv +11 -0
- data/lib/wads/app.rb +40 -298
- data/lib/wads/data_structures.rb +527 -28
- data/lib/wads/textinput.rb +66 -15
- data/lib/wads/version.rb +1 -1
- data/lib/wads/widgets.rb +2720 -332
- 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/SampleGraph.png +0 -0
- data/media/StocksSample.png +0 -0
- data/media/WadsScreenshot.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/graph.rb +72 -0
- data/samples/star_wars.rb +112 -0
- data/samples/stocks.rb +126 -0
- data/samples/theme_test.rb +256 -0
- metadata +25 -5
- data/run-sample-app +0 -3
- data/sample_app.rb +0 -58
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,24 +35,46 @@ 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
|
-
data_set = @data[
|
42
|
+
data_set = @data[data_set_name]
|
36
43
|
if data_set.nil?
|
37
44
|
data_set = {}
|
38
|
-
@data[
|
45
|
+
@data[data_set_name] = data_set
|
39
46
|
end
|
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
|
-
data_set = @data[
|
54
|
+
data_set = @data[data_set_name]
|
45
55
|
if data_set.nil?
|
46
56
|
return nil
|
47
57
|
end
|
48
58
|
data_set[x]
|
49
59
|
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Get the list of keys for the named data set
|
63
|
+
#
|
64
|
+
def keys(data_set_name)
|
65
|
+
data_set = @data[data_set_name]
|
66
|
+
if data_set.nil?
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
data_set.keys
|
70
|
+
end
|
50
71
|
end
|
51
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
|
+
#
|
52
78
|
class Stats
|
53
79
|
attr_accessor :name
|
54
80
|
attr_accessor :data
|
@@ -242,6 +268,20 @@ module Wads
|
|
242
268
|
end
|
243
269
|
end
|
244
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
|
+
#
|
245
285
|
class Node
|
246
286
|
attr_accessor :name
|
247
287
|
attr_accessor :value
|
@@ -249,6 +289,12 @@ module Wads
|
|
249
289
|
attr_accessor :outputs
|
250
290
|
attr_accessor :visited
|
251
291
|
attr_accessor :tags
|
292
|
+
attr_accessor :depth
|
293
|
+
|
294
|
+
def id
|
295
|
+
# id is an alias for name
|
296
|
+
@name
|
297
|
+
end
|
252
298
|
|
253
299
|
def initialize(name, value = nil, tags = {})
|
254
300
|
@name = name
|
@@ -257,7 +303,20 @@ module Wads
|
|
257
303
|
@outputs = []
|
258
304
|
@visited = false
|
259
305
|
@tags = tags
|
306
|
+
@depth = 1
|
260
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
|
261
320
|
|
262
321
|
def add_child(name, value)
|
263
322
|
add_output(name, value)
|
@@ -281,6 +340,35 @@ module Wads
|
|
281
340
|
edge
|
282
341
|
end
|
283
342
|
|
343
|
+
def remove_output(name)
|
344
|
+
output_to_delete = nil
|
345
|
+
@outputs.each do |output|
|
346
|
+
if output.is_a? Edge
|
347
|
+
output_node = output.destination
|
348
|
+
if output_node.id == name
|
349
|
+
output_to_delete = output
|
350
|
+
end
|
351
|
+
elsif output.id == name
|
352
|
+
output_to_delete = output
|
353
|
+
end
|
354
|
+
end
|
355
|
+
if output_to_delete
|
356
|
+
@outputs.delete(output_to_delete)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def remove_backlink(name)
|
361
|
+
backlink_to_delete = nil
|
362
|
+
@backlinks.each do |backlink|
|
363
|
+
if backlink.id == name
|
364
|
+
backlink_to_delete = backlink
|
365
|
+
end
|
366
|
+
end
|
367
|
+
if backlink_to_delete
|
368
|
+
@backlinks.delete(backlink_to_delete)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
284
372
|
def add_tag(key, value)
|
285
373
|
@tags[key] = value
|
286
374
|
end
|
@@ -313,29 +401,56 @@ module Wads
|
|
313
401
|
node = node_queue.shift
|
314
402
|
yield node
|
315
403
|
node.outputs.each do |c|
|
316
|
-
if
|
317
|
-
|
404
|
+
if c.is_a? Edge
|
405
|
+
c = c.destination
|
318
406
|
end
|
319
407
|
node_queue << c
|
320
408
|
end
|
321
409
|
end
|
322
410
|
end
|
323
411
|
|
324
|
-
def
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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
|
332
442
|
end
|
333
|
-
|
334
|
-
|
335
|
-
|
443
|
+
end
|
444
|
+
|
445
|
+
def to_display
|
446
|
+
"Node #{@name}: #{value} inputs: #{@backlinks.size} outputs: #{@outputs.size}"
|
336
447
|
end
|
337
448
|
end
|
338
449
|
|
450
|
+
#
|
451
|
+
# An Edge is a connection between nodes that stores additional information
|
452
|
+
# as arbitrary tags, or name/value pairs.
|
453
|
+
#
|
339
454
|
class Edge
|
340
455
|
attr_accessor :destination
|
341
456
|
attr_accessor :tags
|
@@ -354,13 +469,60 @@ module Wads
|
|
354
469
|
end
|
355
470
|
end
|
356
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
|
+
#
|
357
480
|
class Graph
|
358
481
|
attr_accessor :node_list
|
359
482
|
attr_accessor :node_map
|
360
483
|
|
361
|
-
def initialize
|
484
|
+
def initialize(root_node = nil)
|
362
485
|
@node_list = []
|
363
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
|
496
|
+
end
|
497
|
+
|
498
|
+
def add(name, value = nil, tags = {})
|
499
|
+
add_node(Node.new(name, value, tags))
|
500
|
+
end
|
501
|
+
|
502
|
+
def connect(source, destination, tags = {})
|
503
|
+
add_edge(source, destination)
|
504
|
+
end
|
505
|
+
|
506
|
+
def delete(node)
|
507
|
+
if node.is_a? String
|
508
|
+
node = find_node(node)
|
509
|
+
end
|
510
|
+
node.backlinks.each do |backlink|
|
511
|
+
backlink.remove_output(node.name)
|
512
|
+
end
|
513
|
+
@node_list.delete(node)
|
514
|
+
@node_map.delete(node.name)
|
515
|
+
end
|
516
|
+
|
517
|
+
def disconnect(source, target)
|
518
|
+
if source.is_a? String
|
519
|
+
source = find_node(source)
|
520
|
+
end
|
521
|
+
if target.is_a? String
|
522
|
+
target = find_node(target)
|
523
|
+
end
|
524
|
+
source.remove_output(target.name)
|
525
|
+
target.remove_backlink(source.name)
|
364
526
|
end
|
365
527
|
|
366
528
|
def add_node(node)
|
@@ -368,13 +530,45 @@ module Wads
|
|
368
530
|
@node_map[node.name] = node
|
369
531
|
end
|
370
532
|
|
371
|
-
def add_edge(source, target, tags)
|
533
|
+
def add_edge(source, target, tags = {})
|
372
534
|
if source.is_a? String
|
373
535
|
source = find_node(source)
|
374
536
|
end
|
375
537
|
if target.is_a? String
|
376
538
|
target = find_node(target)
|
377
539
|
end
|
540
|
+
source.add_output_edge(target, tags)
|
541
|
+
end
|
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)
|
378
572
|
end
|
379
573
|
|
380
574
|
def find_node(name)
|
@@ -388,6 +582,7 @@ module Wads
|
|
388
582
|
def reset_visited
|
389
583
|
@node_list.each do |node|
|
390
584
|
node.visited = false
|
585
|
+
node.depth = 0
|
391
586
|
end
|
392
587
|
end
|
393
588
|
|
@@ -401,6 +596,16 @@ module Wads
|
|
401
596
|
list
|
402
597
|
end
|
403
598
|
|
599
|
+
def leaf_nodes
|
600
|
+
list = []
|
601
|
+
@node_list.each do |node|
|
602
|
+
if node.outputs.empty?
|
603
|
+
list << node
|
604
|
+
end
|
605
|
+
end
|
606
|
+
list
|
607
|
+
end
|
608
|
+
|
404
609
|
def is_cycle(node)
|
405
610
|
reset_visited
|
406
611
|
node.visit do |n|
|
@@ -413,28 +618,322 @@ module Wads
|
|
413
618
|
false
|
414
619
|
end
|
415
620
|
|
416
|
-
def
|
417
|
-
if
|
418
|
-
|
621
|
+
def traverse_and_collect_nodes(node, max_depth = 0, current_depth = 1)
|
622
|
+
if max_depth > 0
|
623
|
+
if current_depth > max_depth
|
624
|
+
return {}
|
625
|
+
end
|
419
626
|
end
|
420
627
|
map = {}
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
map_from_child.each do |key, value|
|
425
|
-
map[key] = value
|
628
|
+
if node.visited
|
629
|
+
if current_depth < node.depth
|
630
|
+
node.depth = current_depth
|
426
631
|
end
|
427
|
-
|
632
|
+
return {}
|
633
|
+
else
|
634
|
+
map[node.name] = node
|
635
|
+
node.depth = current_depth
|
636
|
+
node.visited = true
|
637
|
+
end
|
428
638
|
node.outputs.each do |child|
|
429
639
|
if child.is_a? Edge
|
430
640
|
child = child.destination
|
431
641
|
end
|
432
|
-
map_from_child =
|
642
|
+
map_from_child = traverse_and_collect_nodes(child, max_depth, current_depth + 1)
|
643
|
+
map_from_child.each do |key, value|
|
644
|
+
map[key] = value
|
645
|
+
end
|
646
|
+
end
|
647
|
+
node.backlinks.each do |child|
|
648
|
+
map_from_child = traverse_and_collect_nodes(child, max_depth, current_depth + 1)
|
433
649
|
map_from_child.each do |key, value|
|
434
650
|
map[key] = value
|
435
651
|
end
|
436
652
|
end
|
437
653
|
map
|
438
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
|
439
718
|
end
|
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
|
+
#
|
725
|
+
class GraphReverseIterator
|
726
|
+
attr_accessor :output
|
727
|
+
def initialize(graph)
|
728
|
+
@output = []
|
729
|
+
graph.root_nodes.each do |root|
|
730
|
+
partial_list = process_node(root)
|
731
|
+
@output.push(*partial_list)
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
def process_node(node)
|
736
|
+
list = []
|
737
|
+
node.outputs.each do |child_node|
|
738
|
+
if child_node.is_a? Edge
|
739
|
+
child_node = child_node.destination
|
740
|
+
end
|
741
|
+
child_list = process_node(child_node)
|
742
|
+
list.push(*child_list)
|
743
|
+
end
|
744
|
+
|
745
|
+
list << node
|
746
|
+
list
|
747
|
+
end
|
748
|
+
end
|
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
|
+
#
|
786
|
+
class VisibleRange
|
787
|
+
attr_accessor :left_x
|
788
|
+
attr_accessor :right_x
|
789
|
+
attr_accessor :bottom_y
|
790
|
+
attr_accessor :top_y
|
791
|
+
attr_accessor :x_range
|
792
|
+
attr_accessor :y_range
|
793
|
+
attr_accessor :is_time_based
|
794
|
+
|
795
|
+
def initialize(l, r, b, t, is_time_based = false)
|
796
|
+
if l < r
|
797
|
+
@left_x = l
|
798
|
+
@right_x = r
|
799
|
+
else
|
800
|
+
@left_x = r
|
801
|
+
@right_x = l
|
802
|
+
end
|
803
|
+
if b < t
|
804
|
+
@bottom_y = b
|
805
|
+
@top_y = t
|
806
|
+
else
|
807
|
+
@bottom_y = t
|
808
|
+
@top_y = b
|
809
|
+
end
|
810
|
+
@x_range = @right_x - @left_x
|
811
|
+
@y_range = @top_y - @bottom_y
|
812
|
+
@is_time_based = is_time_based
|
813
|
+
|
814
|
+
@orig_left_x = @left_x
|
815
|
+
@orig_right_x = @right_x
|
816
|
+
@orig_bottom_y = @bottom_y
|
817
|
+
@orig_top_y = @top_y
|
818
|
+
@orig_range_x = @x_range
|
819
|
+
@orig_range_y = @y_range
|
820
|
+
end
|
821
|
+
|
822
|
+
def plus(other_range)
|
823
|
+
l = @left_x < other_range.left_x ? @left_x : other_range.left_x
|
824
|
+
r = @right_x > other_range.right_x ? @right_x : other_range.right_x
|
825
|
+
b = @bottom_y < other_range.bottom_y ? @bottom_y : other_range.bottom_y
|
826
|
+
t = @top_y > other_range.top_y ? @top_y : other_range.top_y
|
827
|
+
VisibleRange.new(l, r, b, t, (@is_time_based or other_range.is_time_based))
|
828
|
+
end
|
829
|
+
|
830
|
+
def x_ten_percent
|
831
|
+
@x_range.to_f / 10
|
832
|
+
end
|
833
|
+
|
834
|
+
def y_ten_percent
|
835
|
+
@y_range.to_f / 10
|
836
|
+
end
|
837
|
+
|
838
|
+
def scale(zoom_level)
|
839
|
+
x_mid_point = @orig_left_x + (@orig_range_x.to_f / 2)
|
840
|
+
x_extension = (@orig_range_x.to_f * zoom_level) / 2
|
841
|
+
@left_x = x_mid_point - x_extension
|
842
|
+
@right_x = x_mid_point + x_extension
|
843
|
+
|
844
|
+
y_mid_point = @orig_bottom_y + (@orig_range_y.to_f / 2)
|
845
|
+
y_extension = (@orig_range_y.to_f * zoom_level) / 2
|
846
|
+
@bottom_y = y_mid_point - y_extension
|
847
|
+
@top_y = y_mid_point + y_extension
|
848
|
+
|
849
|
+
@x_range = @right_x - @left_x
|
850
|
+
@y_range = @top_y - @bottom_y
|
851
|
+
end
|
852
|
+
|
853
|
+
def scroll_up
|
854
|
+
@bottom_y = @bottom_y + y_ten_percent
|
855
|
+
@top_y = @top_y + y_ten_percent
|
856
|
+
@y_range = @top_y - @bottom_y
|
857
|
+
end
|
858
|
+
|
859
|
+
def scroll_down
|
860
|
+
@bottom_y = @bottom_y - y_ten_percent
|
861
|
+
@top_y = @top_y - y_ten_percent
|
862
|
+
@y_range = @top_y - @bottom_y
|
863
|
+
end
|
864
|
+
|
865
|
+
def scroll_right
|
866
|
+
@left_x = @left_x + x_ten_percent
|
867
|
+
@right_x = @right_x + x_ten_percent
|
868
|
+
@x_range = @right_x - @left_x
|
869
|
+
end
|
870
|
+
|
871
|
+
def scroll_left
|
872
|
+
@left_x = @left_x - x_ten_percent
|
873
|
+
@right_x = @right_x - x_ten_percent
|
874
|
+
@x_range = @right_x - @left_x
|
875
|
+
end
|
876
|
+
|
877
|
+
def grid_line_x_values
|
878
|
+
if @cached_grid_line_x_values
|
879
|
+
return @cached_grid_line_x_values
|
880
|
+
end
|
881
|
+
@cached_grid_line_x_values = divide_range_into_values(@x_range, @left_x, @right_x, false)
|
882
|
+
@cached_grid_line_x_values
|
883
|
+
end
|
884
|
+
|
885
|
+
def grid_line_y_values
|
886
|
+
if @cached_grid_line_y_values
|
887
|
+
return @cached_grid_line_y_values
|
888
|
+
end
|
889
|
+
@cached_grid_line_y_values = divide_range_into_values(@y_range, @bottom_y, @top_y, false)
|
890
|
+
@cached_grid_line_y_values
|
891
|
+
end
|
892
|
+
|
893
|
+
def calc_x_values
|
894
|
+
if @cached_calc_x_values
|
895
|
+
return @cached_calc_x_values
|
896
|
+
end
|
897
|
+
@cached_calc_x_values = divide_range_into_values(@x_range, @left_x, @right_x)
|
898
|
+
#puts "The x_axis value to calculate are: #{@cached_calc_x_values}"
|
899
|
+
@cached_calc_x_values
|
900
|
+
end
|
901
|
+
|
902
|
+
def clear_cache
|
903
|
+
@cached_grid_line_x_values = nil
|
904
|
+
@cached_grid_line_y_values = nil
|
905
|
+
@cached_calc_x_values = nil
|
906
|
+
end
|
907
|
+
|
908
|
+
# This method determines what are equidistant points along
|
909
|
+
# the x-axis that we can use to draw gridlines and calculate
|
910
|
+
# derived values from functions
|
911
|
+
def divide_range_into_values(range_size, start_value, end_value, is_derived_values = true)
|
912
|
+
values = []
|
913
|
+
# How big is x-range? What should the step size be?
|
914
|
+
# Generally we want a hundred display points. Let's start there.
|
915
|
+
if range_size < 1.1
|
916
|
+
step_size = is_derived_values ? 0.01 : 0.1
|
917
|
+
elsif range_size < 11
|
918
|
+
step_size = is_derived_values ? 0.1 : 1
|
919
|
+
elsif range_size < 111
|
920
|
+
step_size = is_derived_values ? 1 : 10
|
921
|
+
elsif range_size < 1111
|
922
|
+
step_size = is_derived_values ? 10 : 100
|
923
|
+
elsif range_size < 11111
|
924
|
+
step_size = is_derived_values ? 100 : 1000
|
925
|
+
elsif range_size < 111111
|
926
|
+
step_size = is_derived_values ? 1000 : 10000
|
927
|
+
else
|
928
|
+
step_size = is_derived_values ? 10000 : 100000
|
929
|
+
end
|
930
|
+
grid_x = start_value
|
931
|
+
while grid_x < end_value
|
932
|
+
values << grid_x
|
933
|
+
grid_x = grid_x + step_size
|
934
|
+
end
|
935
|
+
values
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
440
939
|
end
|