technical_graph 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/DOCUMENTATION.md +14 -4
- data/DOCUMENTATION.textile +68 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/VERSION +1 -1
- data/lib/technical_graph/array.rb +25 -0
- data/lib/technical_graph/data_layer.rb +45 -7
- data/lib/technical_graph/data_layer_processor.rb +4 -1
- data/lib/technical_graph/data_layer_processor_noise_removal.rb +4 -23
- data/lib/technical_graph/data_layer_processor_simple_smoother.rb +9 -3
- data/lib/technical_graph/graph_axis.rb +69 -217
- data/lib/technical_graph/graph_color_library.rb +38 -22
- data/lib/technical_graph/graph_data_processor.rb +13 -1
- data/lib/technical_graph/graph_image_drawer.rb +97 -114
- data/lib/technical_graph/graph_image_drawer_module.rb +72 -0
- data/lib/technical_graph/graph_image_drawer_rasem.rb +185 -0
- data/lib/technical_graph/graph_image_drawer_rmagick.rb +237 -0
- data/lib/technical_graph.rb +23 -8
- data/test/helper.rb +4 -0
- data/test/test_technical_autocolor.rb +2 -2
- data/test/test_technical_axis_enlarge.rb +2 -3
- data/test/test_technical_graph.rb +4 -3
- data/test/test_technical_graph_axis.rb +2 -2
- data/test/test_technical_multilayer.rb +2 -2
- data/test/test_technical_rasem.rb +22 -0
- data/test/test_technical_readme.rb +39 -18
- data/test/test_technical_simple_graph.rb +2 -2
- data/test/test_technical_smoother.rb +2 -2
- data/test/test_technical_smoother_adv.rb +15 -5
- metadata +32 -14
data/DOCUMENTATION.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Options Hash
|
4
4
|
|
5
|
+
options[:drawer_class] = :rasem (default) or :rmagick
|
6
|
+
|
5
7
|
Default ranges:
|
6
8
|
|
7
9
|
options[:x_min]
|
@@ -38,6 +40,13 @@ Labels has truncate string to define precision. Default it is "%.2f".
|
|
38
40
|
|
39
41
|
options[:truncate_string] = "%.2f"
|
40
42
|
|
43
|
+
Draw axis labels, and labels for zero axis
|
44
|
+
|
45
|
+
options[:axis_value_and_param_labels] = true
|
46
|
+
options[:axis_zero_labels] = true
|
47
|
+
|
48
|
+
|
49
|
+
|
41
50
|
Graph image size:
|
42
51
|
|
43
52
|
options[:width]
|
@@ -49,13 +58,13 @@ Possible #RRGGBB or color names (ex. 'white').
|
|
49
58
|
|
50
59
|
options[:background_color] - background color of image
|
51
60
|
options[:background_hatch_color] - background hatch color
|
52
|
-
options[:axis_color] - color of axis
|
61
|
+
options[:axis_color] - color of axis, default #000000
|
53
62
|
|
54
63
|
Anti-aliasing:
|
55
64
|
|
56
|
-
options[:
|
57
|
-
options[:layers_antialias] - use anti-aliasing for data layers, default false, can be override using layer option
|
58
|
-
options[:font_antialias] - use anti-aliasing for all fonts, default false
|
65
|
+
options[:antialias] - draw axis using antialias
|
66
|
+
# options[:layers_antialias] - use anti-aliasing for data layers, default false, can be override using layer option
|
67
|
+
# options[:font_antialias] - use anti-aliasing for all fonts, default false
|
59
68
|
|
60
69
|
Font size:
|
61
70
|
|
@@ -91,3 +100,4 @@ Layer options Hash
|
|
91
100
|
layer_options[:noise_removal] - enable removal of noises/peaks, default false
|
92
101
|
layer_options[:noise_removal_level] - tolerance level, higher - less points will be removes, default 3
|
93
102
|
layer_options[:noise_removal_window_size] - how many near values check for determining what is noise, default 10
|
103
|
+
layer_options[:perform_parameter_uniq] - it takes some time and rarely usable, so it is turned off by default
|
data/DOCUMENTATION.textile
CHANGED
@@ -359,9 +359,76 @@ file_name = 'samples/readme/08b_truncate_string.png'
|
|
359
359
|
!https://github.com/akwiatkowski/technical_graph/raw/master/samples/readme/08b_truncate_string.png((08b) displaying float numbers)!
|
360
360
|
|
361
361
|
|
362
|
+
|
363
|
+
|
364
|
+
|
365
|
+
|
366
|
+
h2. Graph image size
|
367
|
+
|
368
|
+
p. It would be very silly if this library had hard coded image size. You can change it using options[:width] and options[:height].
|
369
|
+
|
370
|
+
<pre>
|
371
|
+
<code>
|
372
|
+
@simple_data_array = [
|
373
|
+
{ :x => 0, :y => 0 },
|
374
|
+
{ :x => 1, :y => 1 },
|
375
|
+
{ :x => 2, :y => 2 },
|
376
|
+
{ :x => 3, :y => 2 },
|
377
|
+
{ :x => 4, :y => 1 },
|
378
|
+
{ :x => 5, :y => 0 },
|
379
|
+
]
|
380
|
+
|
381
|
+
@tg = TechnicalGraph.new(
|
382
|
+
{
|
383
|
+
:width => 600,
|
384
|
+
:height => 300
|
385
|
+
})
|
386
|
+
@tg.add_layer(@simple_data_array)
|
387
|
+
@tg.render
|
388
|
+
file_name = 'samples/readme/09_image_size.png'
|
389
|
+
@tg.image_drawer.save_to_file(file_name)
|
390
|
+
</code>
|
391
|
+
</pre>
|
392
|
+
|
393
|
+
!https://github.com/akwiatkowski/technical_graph/raw/master/samples/readme/09_image_size.png((09) image size)!
|
394
|
+
|
395
|
+
|
396
|
+
|
397
|
+
h2. Colours
|
398
|
+
|
399
|
+
p. If you think you have better artistic taste feel free to change colours used in graph :)
|
400
|
+
|
401
|
+
<pre>
|
402
|
+
<code>
|
403
|
+
@simple_data_array = [
|
404
|
+
{ :x => 0, :y => 0 },
|
405
|
+
{ :x => 1, :y => 1 },
|
406
|
+
{ :x => 2, :y => 2 },
|
407
|
+
{ :x => 3, :y => 2 },
|
408
|
+
{ :x => 4, :y => 1 },
|
409
|
+
{ :x => 5, :y => 0 },
|
410
|
+
]
|
411
|
+
|
412
|
+
@tg = TechnicalGraph.new(
|
413
|
+
{
|
414
|
+
:width => 600,
|
415
|
+
:height => 300
|
416
|
+
})
|
417
|
+
@tg.add_layer(@simple_data_array, @layer_params)
|
418
|
+
@tg.render
|
419
|
+
file_name = 'samples/readme/09_image_size.png'
|
420
|
+
@tg.image_drawer.save_to_file(file_name)
|
421
|
+
</code>
|
422
|
+
</pre>
|
423
|
+
|
424
|
+
!https://github.com/akwiatkowski/technical_graph/raw/master/samples/readme/09_image_size.png((09) image size)!
|
425
|
+
|
426
|
+
|
427
|
+
|
428
|
+
|
429
|
+
|
362
430
|
h2. TODO
|
363
431
|
|
364
|
-
# Graph image size
|
365
432
|
# Graph colors: background, hatch (option to turn it off?), axis
|
366
433
|
# Anti-aliasing: image size comparison, layer antialiases
|
367
434
|
# Font sizes
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
@@ -11,4 +11,29 @@ class Array
|
|
11
11
|
def float_mean
|
12
12
|
float_sum / size
|
13
13
|
end
|
14
|
+
|
15
|
+
# Create partial array and fill with border values if needed
|
16
|
+
def clone_partial_w_fill(_from, _to)
|
17
|
+
part_array = Array.new
|
18
|
+
# border = false
|
19
|
+
|
20
|
+
(_from.._to).each do |current_i|
|
21
|
+
# outside ranges
|
22
|
+
if current_i < 0
|
23
|
+
part_array << self.first
|
24
|
+
# border = true
|
25
|
+
next
|
26
|
+
end
|
27
|
+
|
28
|
+
if self.size <= current_i
|
29
|
+
part_array << self.last
|
30
|
+
# border = true
|
31
|
+
next
|
32
|
+
end
|
33
|
+
|
34
|
+
part_array << self[current_i]
|
35
|
+
end
|
36
|
+
|
37
|
+
return part_array
|
38
|
+
end
|
14
39
|
end
|
@@ -10,7 +10,23 @@ require 'technical_graph/data_layer_processor'
|
|
10
10
|
|
11
11
|
class DataLayer
|
12
12
|
|
13
|
-
|
13
|
+
# Use global logger for technical_graph or create new
|
14
|
+
def logger
|
15
|
+
return @logger if not @logger.nil?
|
16
|
+
|
17
|
+
if not @technical_graph.nil?
|
18
|
+
@logger = @technical_graph.logger
|
19
|
+
else
|
20
|
+
@logger = Logger.new(STDOUT)
|
21
|
+
end
|
22
|
+
|
23
|
+
@logger
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(d = [], options = { }, technical_graph = nil)
|
27
|
+
# used for accessing logger
|
28
|
+
@technical_graph = technical_graph
|
29
|
+
|
14
30
|
@data_params = options
|
15
31
|
|
16
32
|
@data_params[:color] ||= GraphColorLibrary.instance.get_color
|
@@ -42,10 +58,14 @@ class DataLayer
|
|
42
58
|
if data_array.kind_of? Array
|
43
59
|
# append as DataPoint
|
44
60
|
# convert to DataPoints, which has more specialized methods
|
61
|
+
|
62
|
+
t = Time.now
|
45
63
|
data_array.each do |d|
|
46
64
|
@data << DataPoint.new(d)
|
47
65
|
end
|
48
|
-
|
66
|
+
logger.debug "appending data, size #{data_array.size}"
|
67
|
+
logger.debug " TIME COST #{Time.now - t}"
|
68
|
+
|
49
69
|
# sort, clean bad records
|
50
70
|
process_data_internal
|
51
71
|
|
@@ -72,8 +92,11 @@ class DataLayer
|
|
72
92
|
|
73
93
|
# Run external processor (smoothing, ...)
|
74
94
|
def process!
|
95
|
+
t = Time.now
|
75
96
|
@processed_data = @data.clone
|
76
97
|
@processed_data = @processor.process
|
98
|
+
logger.debug "processed data using external processor"
|
99
|
+
logger.debug " TIME COST #{Time.now - t}"
|
77
100
|
end
|
78
101
|
|
79
102
|
# Additional parameters
|
@@ -84,10 +107,6 @@ class DataLayer
|
|
84
107
|
return @data_params[:color]
|
85
108
|
end
|
86
109
|
|
87
|
-
def antialias
|
88
|
-
return @data_params[:antialias]
|
89
|
-
end
|
90
|
-
|
91
110
|
def label
|
92
111
|
return @data_params[:label]
|
93
112
|
end
|
@@ -116,6 +135,10 @@ class DataLayer
|
|
116
135
|
return @data_params[:simple_smoother_x]
|
117
136
|
end
|
118
137
|
|
138
|
+
def perform_parameter_uniq
|
139
|
+
return @data_params[:perform_parameter_uniq] == true
|
140
|
+
end
|
141
|
+
|
119
142
|
# Clear data
|
120
143
|
def clear_data
|
121
144
|
@data = Array.new
|
@@ -123,12 +146,24 @@ class DataLayer
|
|
123
146
|
|
124
147
|
# Clean and process data used for drawing current data layer
|
125
148
|
def process_data_internal
|
149
|
+
t = Time.now
|
150
|
+
|
126
151
|
# delete duplicates
|
127
|
-
|
152
|
+
if perform_parameter_uniq
|
153
|
+
@data = @data.inject([]) { |result, d| result << d unless result.select { |r| r.x == d.x }.size > 0; result }
|
154
|
+
end
|
155
|
+
|
156
|
+
logger.debug "internal processor - deleting duplicates"
|
157
|
+
logger.debug " TIME COST #{Time.now - t}"
|
158
|
+
t = Time.now
|
128
159
|
|
129
160
|
@data.delete_if { |d| d.x.nil? or d.y.nil? }
|
130
161
|
@data.sort! { |d, e| d.x <=> e.x }
|
131
162
|
|
163
|
+
logger.debug "internal processor - deleting nils and sorting"
|
164
|
+
logger.debug " TIME COST #{Time.now - t}"
|
165
|
+
t = Time.now
|
166
|
+
|
132
167
|
# default X values, if data is not empty
|
133
168
|
if @data.size > 0
|
134
169
|
@data_params[:x_min] = @data.first.x || @options[:default_x_min]
|
@@ -139,6 +174,9 @@ class DataLayer
|
|
139
174
|
@data_params[:y_min] = y_sort.first.y || @options[:default_y_min]
|
140
175
|
@data_params[:y_max] = y_sort.last.y || @options[:@default_y_max]
|
141
176
|
end
|
177
|
+
|
178
|
+
logger.debug "internal processor - setting min and max"
|
179
|
+
logger.debug " TIME COST #{Time.now - t}"
|
142
180
|
end
|
143
181
|
|
144
182
|
def x_min
|
@@ -9,6 +9,10 @@ class DataLayerProcessor
|
|
9
9
|
include DataLayerProcessorSimpleSmoother
|
10
10
|
include DataLayerProcessorNoiseRemoval
|
11
11
|
|
12
|
+
def logger
|
13
|
+
@data_layer.logger
|
14
|
+
end
|
15
|
+
|
12
16
|
def initialize(data_layer)
|
13
17
|
@data_layer = data_layer
|
14
18
|
simple_smoother_initialize(data_params)
|
@@ -33,7 +37,6 @@ class DataLayerProcessor
|
|
33
37
|
simple_smoother_initialize(data_params)
|
34
38
|
noise_removal_initialize(data_params)
|
35
39
|
|
36
|
-
# TODO add in options array to choose order of these methods
|
37
40
|
noise_removal_process
|
38
41
|
simple_smoother_process
|
39
42
|
|
@@ -23,7 +23,7 @@ module DataLayerProcessorNoiseRemoval
|
|
23
23
|
|
24
24
|
@noises_removed_count = 0
|
25
25
|
|
26
|
-
|
26
|
+
logger.debug "Noise removal started"
|
27
27
|
|
28
28
|
(0...data.size).each do |i|
|
29
29
|
if not noise?(i)
|
@@ -33,7 +33,8 @@ module DataLayerProcessorNoiseRemoval
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
logger.debug "Noise removal completed, removed #{@noises_removed_count}"
|
37
|
+
logger.debug " TIME COST #{Time.now - t}"
|
37
38
|
|
38
39
|
@data = new_data
|
39
40
|
return new_data
|
@@ -52,27 +53,7 @@ module DataLayerProcessorNoiseRemoval
|
|
52
53
|
i_from = noise_removal_window_from(i)
|
53
54
|
i_to = noise_removal_window_to(i)
|
54
55
|
|
55
|
-
|
56
|
-
part_array = Array.new
|
57
|
-
border = false
|
58
|
-
|
59
|
-
(i_from..i_to).each do |current_i|
|
60
|
-
# outside ranges
|
61
|
-
if current_i < 0
|
62
|
-
part_array << data.first
|
63
|
-
border = true
|
64
|
-
next
|
65
|
-
end
|
66
|
-
|
67
|
-
if data.size <= current_i
|
68
|
-
part_array << data.last
|
69
|
-
border = true
|
70
|
-
next
|
71
|
-
end
|
72
|
-
|
73
|
-
part_array << data[current_i]
|
74
|
-
|
75
|
-
end
|
56
|
+
part_array = data.clone_partial_w_fill(i_from, i_to)
|
76
57
|
y_mean = part_array.collect { |p| p.y }.float_mean
|
77
58
|
|
78
59
|
# another algorithm
|
@@ -72,7 +72,8 @@ module DataLayerProcessorSimpleSmoother
|
|
72
72
|
|
73
73
|
# pre-processing, distance
|
74
74
|
if simple_smoother_x == true
|
75
|
-
|
75
|
+
logger.debug "X axis distance smoothing enabled"
|
76
|
+
t = Time.now
|
76
77
|
|
77
78
|
(0...old_data.size).each do |i|
|
78
79
|
new_data << DataPoint.xy(old_data[i].x, process_part(old_data, i, false))
|
@@ -80,15 +81,20 @@ module DataLayerProcessorSimpleSmoother
|
|
80
81
|
|
81
82
|
old_data = new_data
|
82
83
|
new_data = Array.new
|
84
|
+
|
85
|
+
logger.debug "X axis distance smoothing completed"
|
86
|
+
logger.debug " TIME COST #{Time.now - t}"
|
83
87
|
end
|
84
88
|
|
85
|
-
|
89
|
+
logger.debug "Y axis distance smoothing"
|
90
|
+
t = Time.now
|
86
91
|
|
87
92
|
(0...old_data.size).each do |i|
|
88
93
|
new_data << DataPoint.xy(old_data[i].x, process_part(old_data, i))
|
89
94
|
end
|
90
95
|
|
91
|
-
|
96
|
+
logger.debug "Y axis Smoothing completed, simple_smoother_level #{simple_smoother_level}, data size #{old_data.size}"
|
97
|
+
logger.debug " TIME COST #{Time.now - t}"
|
92
98
|
|
93
99
|
@data = new_data
|
94
100
|
return new_data
|