technical_graph 0.3.0 → 0.3.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.
- 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
|
|