technical_graph 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/DOCUMENTATION.textile +374 -0
- data/README.md +35 -96
- data/VERSION +1 -1
- data/lib/technical_graph/array.rb +14 -0
- data/lib/technical_graph/data_layer.rb +42 -34
- data/lib/technical_graph/data_layer_processor.rb +24 -194
- data/lib/technical_graph/data_layer_processor_noise_removal.rb +90 -0
- data/lib/technical_graph/data_layer_processor_simple_smoother.rb +234 -0
- data/lib/technical_graph/data_point.rb +43 -0
- data/lib/technical_graph/graph_axis.rb +18 -6
- data/lib/technical_graph/graph_image_drawer.rb +8 -6
- data/lib/technical_graph.rb +0 -2
- data/test/test_technical_autocolor.rb +2 -1
- data/test/test_technical_axis_enlarge.rb +2 -1
- data/test/test_technical_graph.rb +20 -10
- data/test/test_technical_graph_axis.rb +12 -6
- data/test/test_technical_multilayer.rb +2 -1
- data/test/test_technical_noise_removal.rb +36 -0
- data/test/test_technical_readme.rb +207 -0
- data/test/test_technical_simple_graph.rb +2 -1
- data/test/test_technical_smoother.rb +18 -18
- data/test/test_technical_smoother_adv.rb +115 -0
- metadata +12 -4
@@ -1,212 +1,42 @@
|
|
1
1
|
#encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
require 'technical_graph/data_layer_processor_simple_smoother'
|
4
|
+
require 'technical_graph/data_layer_processor_noise_removal'
|
4
5
|
|
5
|
-
|
6
|
-
STRATEGIES = {
|
7
|
-
:rectangular => 'generate_vector_rectangular',
|
8
|
-
:gauss => 'generate_vector_gauss'
|
9
|
-
}
|
10
|
-
DEFAULT_STRATEGY = :rectangular
|
11
|
-
|
12
|
-
MIN_LEVEL = 1
|
13
|
-
MAX_LEVEL = 200
|
14
|
-
|
15
|
-
# use 'x' axis for processing also
|
16
|
-
PROCESS_WITH_PARAMETER_DISTANCE = false
|
6
|
+
# Smooth data layer, approximate, ..
|
17
7
|
|
18
|
-
|
19
|
-
|
8
|
+
class DataLayerProcessor
|
9
|
+
include DataLayerProcessorSimpleSmoother
|
10
|
+
include DataLayerProcessorNoiseRemoval
|
20
11
|
|
21
12
|
def initialize(data_layer)
|
22
13
|
@data_layer = data_layer
|
23
|
-
|
24
|
-
|
25
|
-
@vector = Array.new
|
26
|
-
@gauss_coeff = DEFAULT_GAUSS_COEFF
|
14
|
+
simple_smoother_initialize(data_params)
|
15
|
+
noise_removal_initialize(data_params)
|
27
16
|
end
|
28
17
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# Level of approximation
|
33
|
-
def level=(l)
|
34
|
-
@level = l.to_i if l.to_i >= MIN_LEVEL and l.to_i < MAX_LEVEL
|
18
|
+
# Additional layer parameters, processors options
|
19
|
+
def data_params
|
20
|
+
@data_layer.data_params
|
35
21
|
end
|
36
22
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def strategy=(s)
|
41
|
-
method = STRATEGIES[s]
|
42
|
-
@strategy = s unless method.nil?
|
23
|
+
# Data from DataLayer, not raw data
|
24
|
+
def data
|
25
|
+
@data_layer.processed_data
|
43
26
|
end
|
44
27
|
|
45
|
-
attr_reader :strategy
|
46
|
-
|
47
|
-
|
48
|
-
# This vector will be used to process values (Y'es)
|
49
|
-
# Use proper strategy
|
50
|
-
def generate_vector
|
51
|
-
method = STRATEGIES[@strategy]
|
52
|
-
if method.nil?
|
53
|
-
method = STRATEGIES[DEFAULT_STRATEGY]
|
54
|
-
end
|
55
|
-
return self.send(method)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Process values
|
59
28
|
def process
|
60
|
-
|
61
|
-
|
62
|
-
new_data = Array.new
|
63
|
-
|
64
|
-
(0...old_data.size).each do |i|
|
65
|
-
new_data << {
|
66
|
-
:x => old_data[i][:x],
|
67
|
-
:y => process_part(old_data, i)
|
68
|
-
}
|
69
|
-
end
|
70
|
-
|
71
|
-
puts "Smoothing completed, level #{level}, data size #{old_data.size}, time #{Time.now - t}"
|
72
|
-
|
73
|
-
return new_data
|
74
|
-
end
|
75
|
-
|
76
|
-
# Process part (size depends on level)
|
77
|
-
def process_part(old_data, position)
|
78
|
-
# neutral data, used where position is near edge to calculate new value
|
79
|
-
neutral_data = {
|
80
|
-
:x => old_data[position][:x],
|
81
|
-
:y => old_data[position][:y]
|
82
|
-
}
|
83
|
-
part_array = Array.new(level, neutral_data)
|
84
|
-
|
85
|
-
# add data from old_data to part_array
|
86
|
-
offset = (level/2.0).floor
|
87
|
-
# copying data
|
88
|
-
(0...level).each do |l|
|
89
|
-
copy_pos = position + l - offset
|
90
|
-
# if copy_pos is inside data
|
91
|
-
if copy_pos >= 0 and old_data.size > copy_pos
|
92
|
-
part_array[l] = old_data[copy_pos]
|
93
|
-
end
|
94
|
-
end
|
95
|
-
# here we should have part_array and vector
|
96
|
-
# and now do some magic :]
|
97
|
-
|
98
|
-
if PROCESS_WITH_PARAMETER_DISTANCE == false
|
99
|
-
y_sum = 0.0
|
100
|
-
(0...level).each do |l|
|
101
|
-
y_sum += part_array[l][:y] * vector[l]
|
102
|
-
end
|
103
|
-
return y_sum
|
104
|
-
else
|
105
|
-
# TODO bugs!, issues with NaN
|
106
|
-
# waged average using inverted distance
|
107
|
-
_sum = 0.0
|
108
|
-
_wages = 0.0
|
109
|
-
_x_position = old_data[position][:x]
|
110
|
-
|
111
|
-
(0...level).each do |l|
|
112
|
-
_x_distance = (part_array[l][:x] - _x_position).abs
|
113
|
-
_wage = (1.0 / _x_distance)
|
114
|
-
|
115
|
-
unless _wage.nan?
|
116
|
-
_wages += _wage
|
117
|
-
_sum += (part_array[l][:y] * vector[l]) / _x_distance
|
118
|
-
end
|
119
|
-
end
|
120
|
-
y = _sum.to_f / _wages.to_f
|
121
|
-
puts y
|
122
|
-
return y
|
123
|
-
end
|
124
|
-
|
125
|
-
end
|
29
|
+
# before processing old processed data is overwritten by cloned raw data
|
30
|
+
@data = data
|
126
31
|
|
32
|
+
# update params before processing
|
33
|
+
simple_smoother_initialize(data_params)
|
34
|
+
noise_removal_initialize(data_params)
|
127
35
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
@vector << 1.0 / level.to_f
|
134
|
-
end
|
135
|
-
return @vector
|
36
|
+
# TODO add in options array to choose order of these methods
|
37
|
+
noise_removal_process
|
38
|
+
simple_smoother_process
|
39
|
+
|
40
|
+
return @data
|
136
41
|
end
|
137
|
-
|
138
|
-
# This vector will be used to process values (Y'es), linear algorithm
|
139
|
-
def generate_vector_gauss
|
140
|
-
# http://www.techotopia.com/index.php/Ruby_Math_Functions_and_Methods#Ruby_Math_Constants
|
141
|
-
# http://pl.wikipedia.org/wiki/Okno_czasowe
|
142
|
-
|
143
|
-
# calculation
|
144
|
-
count = (level.to_f / 2.0).floor + 1
|
145
|
-
|
146
|
-
v = Array.new
|
147
|
-
# calculated
|
148
|
-
(1..count).each do |i|
|
149
|
-
v << Math::E ** ((-0.5) * (i*gauss_coeff) ** 2)
|
150
|
-
end
|
151
|
-
|
152
|
-
@vector = make_mirror(v, level)
|
153
|
-
|
154
|
-
normalize_vector
|
155
|
-
|
156
|
-
return @vector
|
157
|
-
end
|
158
|
-
|
159
|
-
# Multiply vector to have sum eq. 1.0
|
160
|
-
def normalize_vector
|
161
|
-
s = 0.0
|
162
|
-
@vector.each do |v|
|
163
|
-
s += v
|
164
|
-
end
|
165
|
-
|
166
|
-
new_vector = Array.new
|
167
|
-
|
168
|
-
@vector.each do |v|
|
169
|
-
new_vector << v / s
|
170
|
-
end
|
171
|
-
|
172
|
-
@vector = new_vector
|
173
|
-
|
174
|
-
return @vector
|
175
|
-
end
|
176
|
-
|
177
|
-
# Make mirror array
|
178
|
-
# size = 7 => [ i[3], i[2], i[1], i[0], i[1], i[2], i[3] ]
|
179
|
-
# size = 8 => [ i[3], i[2], i[1], i[0], i[0], i[1], i[2], i[3] ]
|
180
|
-
def make_mirror(input, size)
|
181
|
-
a = Array.new(size, 0.1)
|
182
|
-
if size.even?
|
183
|
-
# two 'first' in central
|
184
|
-
c_left = size/2 - 1
|
185
|
-
c_right = size/2
|
186
|
-
|
187
|
-
a[c_left] = input[0]
|
188
|
-
a[c_right] = input[0]
|
189
|
-
else
|
190
|
-
# there is one 'first'
|
191
|
-
c_left = (size/2.0).floor
|
192
|
-
c_right = (size/2.0).floor
|
193
|
-
|
194
|
-
a[c_left] = input[0]
|
195
|
-
# a[c_right] = input[0]
|
196
|
-
end
|
197
|
-
|
198
|
-
# the rest
|
199
|
-
i = 0
|
200
|
-
while c_left > 0
|
201
|
-
i += 1
|
202
|
-
c_left -= 1
|
203
|
-
c_right += 1
|
204
|
-
|
205
|
-
a[c_left] = input[i]
|
206
|
-
a[c_right] = input[i]
|
207
|
-
end
|
208
|
-
|
209
|
-
return a
|
210
|
-
end
|
211
|
-
|
212
42
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'technical_graph/array'
|
4
|
+
|
5
|
+
module DataLayerProcessorNoiseRemoval
|
6
|
+
DEFAULT_NOISE_REMOVAL_LEVEL = 3
|
7
|
+
DEFAULT_NOISE_REMOVAL_WINDOW_SIZE = 10
|
8
|
+
|
9
|
+
def noise_removal_initialize(options)
|
10
|
+
@noise_removal = options[:noise_removal] == true
|
11
|
+
@noise_removal_level = options[:noise_removal_level] || DEFAULT_NOISE_REMOVAL_LEVEL
|
12
|
+
@noise_removal_window_size = options[:noise_removal_window_size] || DEFAULT_NOISE_REMOVAL_WINDOW_SIZE
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :noise_removal_level, :noise_removal_window_size, :noise_removal
|
16
|
+
|
17
|
+
# Smooth values
|
18
|
+
def noise_removal_process
|
19
|
+
return if noise_removal == false
|
20
|
+
|
21
|
+
t = Time.now
|
22
|
+
new_data = Array.new
|
23
|
+
|
24
|
+
@noises_removed_count = 0
|
25
|
+
|
26
|
+
puts "Noise removal started"
|
27
|
+
|
28
|
+
(0...data.size).each do |i|
|
29
|
+
if not noise?(i)
|
30
|
+
new_data << data[i]
|
31
|
+
else
|
32
|
+
@noises_removed_count += 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "Noise removal completed, removed #{@noises_removed_count}, time #{Time.now - t}"
|
37
|
+
|
38
|
+
@data = new_data
|
39
|
+
return new_data
|
40
|
+
end
|
41
|
+
|
42
|
+
def noise_removal_window_from(i)
|
43
|
+
return i - (noise_removal_window_size.to_f / 2.0).ceil
|
44
|
+
end
|
45
|
+
|
46
|
+
def noise_removal_window_to(i)
|
47
|
+
return i + (noise_removal_window_size.to_f / 2.0).ceil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Check if data at index is noisy
|
51
|
+
def noise?(i)
|
52
|
+
i_from = noise_removal_window_from(i)
|
53
|
+
i_to = noise_removal_window_to(i)
|
54
|
+
|
55
|
+
# create partial array, TODO move it somewhere else
|
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
|
76
|
+
y_mean = part_array.collect { |p| p.y }.mean
|
77
|
+
|
78
|
+
# another algorithm
|
79
|
+
noise_strength = (data[i].y - y_mean).abs / y_mean
|
80
|
+
return noise_strength_enough?(noise_strength)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Some magic here, beware
|
84
|
+
def noise_strength_enough?(noise_strength)
|
85
|
+
threshold_strength = Math.log(@noise_removal_level)
|
86
|
+
return noise_strength > threshold_strength
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
module DataLayerProcessorSimpleSmoother
|
4
|
+
# Smoothing
|
5
|
+
SIMPLE_SMOOTHER_STRATEGIES = {
|
6
|
+
:rectangular => 'generate_vector_rectangular',
|
7
|
+
:gauss => 'generate_vector_gauss'
|
8
|
+
}
|
9
|
+
DEFAULT_SIMPLE_SMOOTHER_STRATEGY = :rectangular
|
10
|
+
|
11
|
+
MIN_SIMPLE_SMOOTHER_LEVEL = 1
|
12
|
+
MAX_SIMPLE_SMOOTHER_LEVEL = 200
|
13
|
+
|
14
|
+
# use 'x' axis for processing also
|
15
|
+
PROCESS_WITH_PARAMETER_DISTANCE = false
|
16
|
+
|
17
|
+
# default Gauss coefficient
|
18
|
+
DEFAULT_GAUSS_COEFF = 0.2
|
19
|
+
|
20
|
+
def simple_smoother_initialize(options)
|
21
|
+
@simple_smoother = options[:simple_smoother] == true
|
22
|
+
@simple_smoother_strategy = options[:simple_smoother_strategy] || DEFAULT_SIMPLE_SMOOTHER_STRATEGY
|
23
|
+
@simple_smoother_level = options[:simple_smoother_level] || MIN_SIMPLE_SMOOTHER_LEVEL
|
24
|
+
@simple_smoother_x = options[:simple_smoother_x] == true
|
25
|
+
|
26
|
+
@vector = Array.new
|
27
|
+
@gauss_coeff = DEFAULT_GAUSS_COEFF
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :vector, :simple_smoother
|
31
|
+
attr_accessor :gauss_coeff
|
32
|
+
|
33
|
+
# Simple_smoother_level of approximation
|
34
|
+
def simple_smoother_level=(l)
|
35
|
+
@simple_smoother_level = l.to_i if l.to_i >= MIN_SIMPLE_SMOOTHER_LEVEL and l.to_i < MAX_SIMPLE_SMOOTHER_LEVEL
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :simple_smoother_level
|
39
|
+
|
40
|
+
# Choose other simple_smoother_strategy from SIMPLE_SMOOTHER_STRATEGIES
|
41
|
+
def simple_smoother_strategy=(s)
|
42
|
+
method = SIMPLE_SMOOTHER_STRATEGIES[s]
|
43
|
+
@simple_smoother_strategy = s unless method.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :simple_smoother_strategy
|
47
|
+
|
48
|
+
# smooth using X distance
|
49
|
+
attr_accessor :simple_smoother_x
|
50
|
+
|
51
|
+
# This vector will be used to process values (Y'es)
|
52
|
+
# Use proper simple_smoother_strategy
|
53
|
+
def generate_vector
|
54
|
+
method = SIMPLE_SMOOTHER_STRATEGIES[@simple_smoother_strategy]
|
55
|
+
if method.nil?
|
56
|
+
method = SIMPLE_SMOOTHER_STRATEGIES[DEFAULT_SIMPLE_SMOOTHER_STRATEGY]
|
57
|
+
end
|
58
|
+
return self.send(method)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Smooth values
|
62
|
+
def simple_smoother_process
|
63
|
+
# not turned on
|
64
|
+
return if simple_smoother == false
|
65
|
+
|
66
|
+
# vector used for smoothing
|
67
|
+
generate_vector
|
68
|
+
|
69
|
+
t = Time.now
|
70
|
+
old_data = @data
|
71
|
+
new_data = Array.new
|
72
|
+
|
73
|
+
# pre-processing, distance
|
74
|
+
if simple_smoother_x == true
|
75
|
+
puts "X axis distance smoothing enabled"
|
76
|
+
|
77
|
+
(0...old_data.size).each do |i|
|
78
|
+
new_data << DataPoint.xy(old_data[i].x, process_part(old_data, i, false))
|
79
|
+
end
|
80
|
+
|
81
|
+
old_data = new_data
|
82
|
+
new_data = Array.new
|
83
|
+
end
|
84
|
+
|
85
|
+
puts "Y axis distance smoothing"
|
86
|
+
|
87
|
+
(0...old_data.size).each do |i|
|
88
|
+
new_data << DataPoint.xy(old_data[i].x, process_part(old_data, i))
|
89
|
+
end
|
90
|
+
|
91
|
+
puts "Smoothing completed, simple_smoother_level #{simple_smoother_level}, data size #{old_data.size}, time #{Time.now - t}"
|
92
|
+
|
93
|
+
@data = new_data
|
94
|
+
return new_data
|
95
|
+
end
|
96
|
+
|
97
|
+
# Process part (size depends on simple_smoother_level)
|
98
|
+
def process_part(old_data, position, y_based = true)
|
99
|
+
# neutral data, used where position is near edge to calculate new value
|
100
|
+
neutral_data = DataPoint.xy(old_data[position].x, old_data[position].y)
|
101
|
+
part_array = Array.new(simple_smoother_level, neutral_data)
|
102
|
+
|
103
|
+
# add data from old_data to part_array
|
104
|
+
offset = (simple_smoother_level/2.0).floor
|
105
|
+
# copying data
|
106
|
+
(0...simple_smoother_level).each do |l|
|
107
|
+
copy_pos = position + l - offset
|
108
|
+
# if copy_pos is inside data
|
109
|
+
if copy_pos >= 0 and old_data.size > copy_pos
|
110
|
+
part_array[l] = old_data[copy_pos]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
# here we should have part_array and vector
|
114
|
+
# and now do some magic :]
|
115
|
+
|
116
|
+
|
117
|
+
if y_based
|
118
|
+
return process_part_only_y(part_array)
|
119
|
+
else
|
120
|
+
return process_part_only_x(part_array, neutral_data)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Process part (size depends on simple_smoother_level), only Y data
|
125
|
+
def process_part_only_y(part_array, neutral_data = nil)
|
126
|
+
y_sum = 0.0
|
127
|
+
(0...simple_smoother_level).each do |l|
|
128
|
+
y_sum += part_array[l].y * vector[l]
|
129
|
+
end
|
130
|
+
return y_sum
|
131
|
+
end
|
132
|
+
|
133
|
+
# Process part (size depends on simple_smoother_level), Y and X data
|
134
|
+
def process_part_only_x(part_array, neutral_data)
|
135
|
+
weights = Array.new
|
136
|
+
w_sum = 0.0
|
137
|
+
(0...simple_smoother_level).each do |l|
|
138
|
+
p = part_array[l]
|
139
|
+
x_distance = p.x_distance(neutral_data)
|
140
|
+
w = (Math::E ** (-1.0 * 0.2 * x_distance)) + 1.0
|
141
|
+
w_sum += w
|
142
|
+
weights << w
|
143
|
+
end
|
144
|
+
|
145
|
+
w_prod = 0.0
|
146
|
+
part_array.each_index { |i| w_prod += part_array[i].y * weights[i].to_f }
|
147
|
+
return w_prod.to_f / w_sum.to_f
|
148
|
+
end
|
149
|
+
|
150
|
+
# This vector will be used to process values (Y'es), linear algorithm
|
151
|
+
def generate_vector_rectangular
|
152
|
+
@vector = Array.new
|
153
|
+
# calculated
|
154
|
+
(1..simple_smoother_level).each do |i|
|
155
|
+
@vector << 1.0 / simple_smoother_level.to_f
|
156
|
+
end
|
157
|
+
return @vector
|
158
|
+
end
|
159
|
+
|
160
|
+
# This vector will be used to process values (Y'es), linear algorithm
|
161
|
+
def generate_vector_gauss
|
162
|
+
# http://www.techotopia.com/index.php/Ruby_Math_Functions_and_Methods#Ruby_Math_Constants
|
163
|
+
# http://pl.wikipedia.org/wiki/Okno_czasowe
|
164
|
+
|
165
|
+
# calculation
|
166
|
+
count = (simple_smoother_level.to_f / 2.0).floor + 1
|
167
|
+
|
168
|
+
v = Array.new
|
169
|
+
# calculated
|
170
|
+
(1..count).each do |i|
|
171
|
+
v << Math::E ** ((-0.5) * (i*gauss_coeff) ** 2)
|
172
|
+
end
|
173
|
+
|
174
|
+
@vector = make_mirror(v, simple_smoother_level)
|
175
|
+
|
176
|
+
normalize_vector
|
177
|
+
|
178
|
+
return @vector
|
179
|
+
end
|
180
|
+
|
181
|
+
# Multiply vector to have sum eq. 1.0
|
182
|
+
def normalize_vector
|
183
|
+
s = 0.0
|
184
|
+
@vector.each do |v|
|
185
|
+
s += v
|
186
|
+
end
|
187
|
+
|
188
|
+
new_vector = Array.new
|
189
|
+
|
190
|
+
@vector.each do |v|
|
191
|
+
new_vector << v / s
|
192
|
+
end
|
193
|
+
|
194
|
+
@vector = new_vector
|
195
|
+
|
196
|
+
return @vector
|
197
|
+
end
|
198
|
+
|
199
|
+
# Make mirror array
|
200
|
+
# size = 7 => [ i[3], i[2], i[1], i[0], i[1], i[2], i[3] ]
|
201
|
+
# size = 8 => [ i[3], i[2], i[1], i[0], i[0], i[1], i[2], i[3] ]
|
202
|
+
def make_mirror(input, size)
|
203
|
+
a = Array.new(size, 0.1)
|
204
|
+
if size.even?
|
205
|
+
# two 'first' in central
|
206
|
+
c_left = size/2 - 1
|
207
|
+
c_right = size/2
|
208
|
+
|
209
|
+
a[c_left] = input[0]
|
210
|
+
a[c_right] = input[0]
|
211
|
+
else
|
212
|
+
# there is one 'first'
|
213
|
+
c_left = (size/2.0).floor
|
214
|
+
c_right = (size/2.0).floor
|
215
|
+
|
216
|
+
a[c_left] = input[0]
|
217
|
+
# a[c_right] = input[0]
|
218
|
+
end
|
219
|
+
|
220
|
+
# the rest
|
221
|
+
i = 0
|
222
|
+
while c_left > 0
|
223
|
+
i += 1
|
224
|
+
c_left -= 1
|
225
|
+
c_right += 1
|
226
|
+
|
227
|
+
a[c_left] = input[i]
|
228
|
+
a[c_right] = input[i]
|
229
|
+
end
|
230
|
+
|
231
|
+
return a
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class DataPoint
|
2
|
+
|
3
|
+
def initialize(h={ :x => 0, :y => 0 })
|
4
|
+
if h.kind_of? Hash
|
5
|
+
@x = h[:x]
|
6
|
+
@y = h[:y]
|
7
|
+
end
|
8
|
+
if h.kind_of? DataPoint
|
9
|
+
@x = h.x
|
10
|
+
@y = h.y
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.xy(_x, _y)
|
15
|
+
DataPoint.new({ :x=>_x, :y=>_y })
|
16
|
+
end
|
17
|
+
|
18
|
+
def x_distance(other_dp)
|
19
|
+
return (self.x - other_dp.x).abs
|
20
|
+
end
|
21
|
+
|
22
|
+
def y_distance(other_dp)
|
23
|
+
return (self.y - other_dp.y).abs
|
24
|
+
end
|
25
|
+
|
26
|
+
def x
|
27
|
+
@x
|
28
|
+
end
|
29
|
+
|
30
|
+
def y
|
31
|
+
@y
|
32
|
+
end
|
33
|
+
|
34
|
+
def x=(_x)
|
35
|
+
@x = _x
|
36
|
+
end
|
37
|
+
|
38
|
+
def y=(_y)
|
39
|
+
@y = _y
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
@@ -42,6 +42,14 @@ class GraphAxis
|
|
42
42
|
options[:y_axis_fixed_interval] == true
|
43
43
|
end
|
44
44
|
|
45
|
+
def y_axis_interval
|
46
|
+
options[:y_axis_interval]
|
47
|
+
end
|
48
|
+
|
49
|
+
def x_axis_interval
|
50
|
+
options[:x_axis_interval]
|
51
|
+
end
|
52
|
+
|
45
53
|
# Where to put axis values
|
46
54
|
def value_axis
|
47
55
|
return calc_axis(data_processor.y_min, data_processor.y_max, options[:y_axis_interval], options[:y_axis_count], y_axis_fixed?)
|
@@ -178,9 +186,10 @@ class GraphAxis
|
|
178
186
|
|
179
187
|
t = Time.now
|
180
188
|
plot_axis_y_line.draw(@image)
|
181
|
-
puts "#{Time.now - t} drawing lines"
|
189
|
+
puts "Y axis time #{Time.now - t}, drawing lines"
|
190
|
+
t = Time.now
|
182
191
|
plot_axis_y_text.draw(@image)
|
183
|
-
puts "#{Time.now - t} drawing text"
|
192
|
+
puts "Y axis time #{Time.now - t}, drawing text"
|
184
193
|
end
|
185
194
|
|
186
195
|
def render_parameters_axis
|
@@ -222,9 +231,10 @@ class GraphAxis
|
|
222
231
|
|
223
232
|
t = Time.now
|
224
233
|
plot_axis_x_line.draw(@image)
|
225
|
-
puts "#{Time.now - t} drawing lines"
|
234
|
+
puts "X axis time #{Time.now - t}, drawing lines"
|
235
|
+
t = Time.now
|
226
236
|
plot_axis_x_text.draw(@image)
|
227
|
-
puts "#{Time.now - t} drawing
|
237
|
+
puts "X axis time #{Time.now - t}, drawing lines"
|
228
238
|
|
229
239
|
end
|
230
240
|
|
@@ -314,7 +324,8 @@ class GraphAxis
|
|
314
324
|
plot_axis_text.pointsize(options[:axis_label_font_size])
|
315
325
|
plot_axis_text.font_family('helvetica')
|
316
326
|
plot_axis_text.font_style(Magick::NormalStyle)
|
317
|
-
plot_axis_text.text_align(Magick::LeftAlign)
|
327
|
+
#plot_axis_text.text_align(Magick::LeftAlign)
|
328
|
+
plot_axis_text.text_align(Magick::CenterAlign)
|
318
329
|
plot_axis_text.text_undercolor(options[:background_color])
|
319
330
|
|
320
331
|
plot_axis_text.text(
|
@@ -332,7 +343,8 @@ class GraphAxis
|
|
332
343
|
plot_axis_text.pointsize(options[:axis_label_font_size])
|
333
344
|
plot_axis_text.font_family('helvetica')
|
334
345
|
plot_axis_text.font_style(Magick::NormalStyle)
|
335
|
-
plot_axis_text.text_align(Magick::LeftAlign)
|
346
|
+
#plot_axis_text.text_align(Magick::LeftAlign)
|
347
|
+
plot_axis_text.text_align(Magick::CenterAlign)
|
336
348
|
plot_axis_text.text_undercolor(options[:background_color])
|
337
349
|
|
338
350
|
plot_axis_text = plot_axis_text.rotate(90)
|
@@ -170,6 +170,8 @@ class GraphImageDrawer
|
|
170
170
|
|
171
171
|
# Render data layer
|
172
172
|
def render_data_layer(l)
|
173
|
+
layer_data = l.processed_data
|
174
|
+
|
173
175
|
layer_line = Magick::Draw.new
|
174
176
|
layer_text = Magick::Draw.new
|
175
177
|
|
@@ -196,21 +198,21 @@ class GraphImageDrawer
|
|
196
198
|
# calculate coords, draw text, and then lines and circles
|
197
199
|
coords = Array.new
|
198
200
|
|
199
|
-
(0...(
|
200
|
-
ax =
|
201
|
+
(0...(layer_data.size - 1)).each do |i|
|
202
|
+
ax = layer_data[i].x
|
201
203
|
ax = calc_bitmap_x(ax).round
|
202
|
-
ay =
|
204
|
+
ay = layer_data[i].y
|
203
205
|
ay = calc_bitmap_y(ay).round
|
204
206
|
|
205
|
-
bx =
|
207
|
+
bx = layer_data[i+1].x
|
206
208
|
bx = calc_bitmap_x(bx).round
|
207
|
-
by =
|
209
|
+
by = layer_data[i+1].y
|
208
210
|
by = calc_bitmap_y(by).round
|
209
211
|
|
210
212
|
coords << {
|
211
213
|
:ax => ax, :ay => ay,
|
212
214
|
:bx => bx, :by => by,
|
213
|
-
:dy =>
|
215
|
+
:dy => layer_data[i].y
|
214
216
|
}
|
215
217
|
end
|
216
218
|
|