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
@@ -21,10 +21,18 @@ class GraphAxis
|
|
21
21
|
@technical_graph.data_processor
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
24
|
+
def image
|
25
25
|
@technical_graph.image_drawer
|
26
26
|
end
|
27
27
|
|
28
|
+
def drawer
|
29
|
+
image.drawer
|
30
|
+
end
|
31
|
+
|
32
|
+
def logger
|
33
|
+
@technical_graph.logger
|
34
|
+
end
|
35
|
+
|
28
36
|
def truncate_string
|
29
37
|
options[:truncate_string]
|
30
38
|
end
|
@@ -62,6 +70,8 @@ class GraphAxis
|
|
62
70
|
|
63
71
|
# Calculate axis using 2 methods
|
64
72
|
def calc_axis(from, to, interval, count, fixed_interval)
|
73
|
+
t = Time.now
|
74
|
+
|
65
75
|
axis = Array.new
|
66
76
|
l = to - from
|
67
77
|
current = from
|
@@ -71,12 +81,16 @@ class GraphAxis
|
|
71
81
|
axis << current
|
72
82
|
current += interval
|
73
83
|
end
|
84
|
+
logger.debug "fixed interval axis calculation from #{from} to #{to} using int. #{interval}"
|
85
|
+
logger.debug " TIME COST #{Time.now - t}"
|
74
86
|
return axis
|
75
87
|
|
76
88
|
else
|
77
89
|
(0...count).each do |i|
|
78
90
|
axis << from + (l.to_f * i.to_f) / count.to_f
|
79
91
|
end
|
92
|
+
logger.debug "fixed count axis calculation from #{from} to #{to} using count #{count}"
|
93
|
+
logger.debug " TIME COST #{Time.now - t}"
|
80
94
|
return axis
|
81
95
|
|
82
96
|
end
|
@@ -85,8 +99,12 @@ class GraphAxis
|
|
85
99
|
# Enlarge image to maintain proper axis density
|
86
100
|
def axis_distance_image_enlarge
|
87
101
|
if options[:axis_density_enlarge_image]
|
102
|
+
t = Time.now
|
88
103
|
x_axis_distance_image_enlarge
|
89
104
|
y_axis_distance_image_enlarge
|
105
|
+
|
106
|
+
logger.debug "axis enlarged"
|
107
|
+
logger.debug " TIME COST #{Time.now - t}"
|
90
108
|
end
|
91
109
|
end
|
92
110
|
|
@@ -97,9 +115,9 @@ class GraphAxis
|
|
97
115
|
return if a.size < 2
|
98
116
|
|
99
117
|
ax = a[0]
|
100
|
-
ax =
|
118
|
+
ax = image.calc_bitmap_y(ax).round
|
101
119
|
bx = a[1]
|
102
|
-
bx =
|
120
|
+
bx = image.calc_bitmap_y(bx).round
|
103
121
|
|
104
122
|
axis_distance = (bx - ax).abs
|
105
123
|
|
@@ -117,9 +135,9 @@ class GraphAxis
|
|
117
135
|
return if a.size < 2
|
118
136
|
|
119
137
|
ay = a[0]
|
120
|
-
ay =
|
138
|
+
ay = image.calc_bitmap_y(ay).round
|
121
139
|
by = a[1]
|
122
|
-
by =
|
140
|
+
by = image.calc_bitmap_y(by).round
|
123
141
|
|
124
142
|
axis_distance = (by - ay).abs
|
125
143
|
|
@@ -134,228 +152,62 @@ class GraphAxis
|
|
134
152
|
def render_on_image(image)
|
135
153
|
@image = image
|
136
154
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
render_values_zero_axis
|
141
|
-
render_parameters_zero_axis
|
142
|
-
|
155
|
+
render_axis
|
156
|
+
render_zero_axis
|
143
157
|
render_axis_labels
|
144
158
|
end
|
145
159
|
|
146
|
-
def
|
147
|
-
options[:
|
148
|
-
end
|
149
|
-
|
150
|
-
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
plot_axis_y_text.pointsize(options[:axis_font_size])
|
166
|
-
plot_axis_y_text.font_family('helvetica')
|
167
|
-
plot_axis_y_text.font_style(Magick::NormalStyle)
|
168
|
-
plot_axis_y_text.text_align(Magick::LeftAlign)
|
169
|
-
plot_axis_y_text.text_undercolor(options[:background_color])
|
170
|
-
|
171
|
-
value_axis.each do |y|
|
172
|
-
by = image_drawer.calc_bitmap_y(y)
|
173
|
-
plot_axis_y_line.line(
|
174
|
-
0, by.round,
|
175
|
-
@image.columns-1, by.round
|
176
|
-
)
|
177
|
-
|
178
|
-
string_label = "#{truncate_string % y}"
|
179
|
-
|
180
|
-
plot_axis_y_text.text(
|
181
|
-
5,
|
182
|
-
by.round + 15,
|
183
|
-
string_label
|
184
|
-
)
|
185
|
-
end
|
186
|
-
|
187
|
-
t = Time.now
|
188
|
-
plot_axis_y_line.draw(@image)
|
189
|
-
puts "Y axis time #{Time.now - t}, drawing lines"
|
190
|
-
t = Time.now
|
191
|
-
plot_axis_y_text.draw(@image)
|
192
|
-
puts "Y axis time #{Time.now - t}, drawing text"
|
193
|
-
end
|
194
|
-
|
195
|
-
def render_parameters_axis
|
196
|
-
|
197
|
-
plot_axis_x_line = Magick::Draw.new
|
198
|
-
plot_axis_x_text = Magick::Draw.new
|
199
|
-
|
200
|
-
plot_axis_x_line.stroke_antialias(axis_antialias)
|
201
|
-
plot_axis_x_text.text_antialias(axis_antialias)
|
202
|
-
|
203
|
-
plot_axis_x_line.fill_opacity(0)
|
204
|
-
plot_axis_x_line.stroke(options[:axis_color])
|
205
|
-
plot_axis_x_line.stroke_opacity(1.0)
|
206
|
-
plot_axis_x_line.stroke_width(1.0)
|
207
|
-
plot_axis_x_line.stroke_linecap('square')
|
208
|
-
plot_axis_x_line.stroke_linejoin('miter')
|
209
|
-
|
210
|
-
plot_axis_x_text.pointsize(options[:axis_font_size])
|
211
|
-
plot_axis_x_text.font_family('helvetica')
|
212
|
-
plot_axis_x_text.font_style(Magick::NormalStyle)
|
213
|
-
plot_axis_x_text.text_align(Magick::LeftAlign)
|
214
|
-
plot_axis_x_text.text_undercolor(options[:background_color])
|
215
|
-
|
216
|
-
parameter_axis.each do |x|
|
217
|
-
bx = image_drawer.calc_bitmap_x(x)
|
218
|
-
plot_axis_x_line.line(
|
219
|
-
bx.round, 0,
|
220
|
-
bx.round, @image.rows-1
|
221
|
-
)
|
222
|
-
|
223
|
-
string_label = "#{truncate_string % x}"
|
224
|
-
|
225
|
-
plot_axis_x_text.text(
|
226
|
-
bx.round + 5,
|
227
|
-
@image.rows - 15,
|
228
|
-
string_label
|
229
|
-
)
|
230
|
-
end
|
231
|
-
|
232
|
-
t = Time.now
|
233
|
-
plot_axis_x_line.draw(@image)
|
234
|
-
puts "X axis time #{Time.now - t}, drawing lines"
|
235
|
-
t = Time.now
|
236
|
-
plot_axis_x_text.draw(@image)
|
237
|
-
puts "X axis time #{Time.now - t}, drawing lines"
|
238
|
-
|
239
|
-
end
|
240
|
-
|
241
|
-
# TODO: make it DRY
|
242
|
-
def render_values_zero_axis
|
243
|
-
plot_axis_y_line = Magick::Draw.new
|
244
|
-
plot_axis_y_text = Magick::Draw.new
|
245
|
-
|
246
|
-
plot_axis_y_line.stroke_antialias(axis_antialias)
|
247
|
-
plot_axis_y_text.text_antialias(image_drawer.font_antialias)
|
248
|
-
|
249
|
-
plot_axis_y_line.fill_opacity(0)
|
250
|
-
plot_axis_y_line.stroke(options[:axis_color])
|
251
|
-
plot_axis_y_line.stroke_opacity(1.0)
|
252
|
-
plot_axis_y_line.stroke_width(2.0)
|
253
|
-
plot_axis_y_line.stroke_linecap('square')
|
254
|
-
plot_axis_y_line.stroke_linejoin('miter')
|
255
|
-
|
256
|
-
plot_axis_y_text.pointsize(options[:axis_font_size])
|
257
|
-
plot_axis_y_text.font_family('helvetica')
|
258
|
-
plot_axis_y_text.font_style(Magick::NormalStyle)
|
259
|
-
plot_axis_y_text.text_align(Magick::LeftAlign)
|
260
|
-
plot_axis_y_text.text_undercolor(options[:background_color])
|
261
|
-
|
262
|
-
y = 0.0
|
263
|
-
by = image_drawer.calc_bitmap_y(y)
|
264
|
-
plot_axis_y_line.line(
|
265
|
-
0, by.round,
|
266
|
-
@image.columns-1, by.round
|
267
|
-
)
|
268
|
-
|
269
|
-
plot_axis_y_text.text(
|
270
|
-
5,
|
271
|
-
by.round + 15,
|
272
|
-
"#{y}"
|
160
|
+
def antialias
|
161
|
+
options[:antialias] == true
|
162
|
+
end
|
163
|
+
|
164
|
+
# Render normal axis
|
165
|
+
def render_axis
|
166
|
+
drawer.axis(
|
167
|
+
# X
|
168
|
+
parameter_axis.collect { |x| image.calc_bitmap_x(x).to_i },
|
169
|
+
# Y
|
170
|
+
value_axis.collect { |y| image.calc_bitmap_y(y).to_i },
|
171
|
+
# options
|
172
|
+
{ :color => options[:axis_color], :width => 1 },
|
173
|
+
# draw labels
|
174
|
+
options[:axis_value_and_param_labels],
|
175
|
+
# X axis labels
|
176
|
+
parameter_axis,
|
177
|
+
# Y axis labels
|
178
|
+
value_axis
|
273
179
|
)
|
274
|
-
|
275
|
-
# TODO: why normal axis does not need it?
|
276
|
-
plot_axis_y_line.draw(@image)
|
277
|
-
plot_axis_y_text.draw(@image)
|
278
180
|
end
|
279
181
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
plot_axis_x_text.pointsize(options[:axis_font_size])
|
296
|
-
plot_axis_x_text.font_family('helvetica')
|
297
|
-
plot_axis_x_text.font_style(Magick::NormalStyle)
|
298
|
-
plot_axis_x_text.text_align(Magick::LeftAlign)
|
299
|
-
plot_axis_x_text.text_undercolor(options[:background_color])
|
300
|
-
|
301
|
-
x = 0.0
|
302
|
-
bx = image_drawer.calc_bitmap_x(x)
|
303
|
-
plot_axis_x_line.line(
|
304
|
-
bx.round, 0,
|
305
|
-
bx.round, @image.rows-1
|
182
|
+
# Render axis for zeros
|
183
|
+
def render_zero_axis
|
184
|
+
drawer.axis(
|
185
|
+
# X - 0
|
186
|
+
image.calc_bitmap_x(0.0).to_i,
|
187
|
+
# Y - 0
|
188
|
+
image.calc_bitmap_y(0.0).to_i,
|
189
|
+
# options, slightly wider
|
190
|
+
{ :color => options[:axis_color], :width => 2 },
|
191
|
+
# draw label
|
192
|
+
options[:axis_zero_labels],
|
193
|
+
# X label,
|
194
|
+
[0.0],
|
195
|
+
# Y label
|
196
|
+
[0.0]
|
306
197
|
)
|
307
|
-
|
308
|
-
plot_axis_x_text.text(
|
309
|
-
bx.round + 15,
|
310
|
-
@image.rows - 15,
|
311
|
-
"#{x}"
|
312
|
-
)
|
313
|
-
|
314
|
-
# TODO: why normal axis does not need it?
|
315
|
-
plot_axis_x_line.draw(@image)
|
316
|
-
plot_axis_x_text.draw(@image)
|
317
198
|
end
|
318
199
|
|
319
|
-
def render_axis_labels
|
320
|
-
if options[:x_axis_label].to_s.size > 0
|
321
|
-
plot_axis_text = Magick::Draw.new
|
322
|
-
plot_axis_text.text_antialias(image_drawer.font_antialias)
|
323
|
-
|
324
|
-
plot_axis_text.pointsize(options[:axis_label_font_size])
|
325
|
-
plot_axis_text.font_family('helvetica')
|
326
|
-
plot_axis_text.font_style(Magick::NormalStyle)
|
327
|
-
#plot_axis_text.text_align(Magick::LeftAlign)
|
328
|
-
plot_axis_text.text_align(Magick::CenterAlign)
|
329
|
-
plot_axis_text.text_undercolor(options[:background_color])
|
330
|
-
|
331
|
-
plot_axis_text.text(
|
332
|
-
(@image.columns / 2).to_i,
|
333
|
-
@image.rows - 40,
|
334
|
-
options[:x_axis_label].to_s
|
335
|
-
)
|
336
|
-
plot_axis_text.draw(@image)
|
337
|
-
end
|
338
200
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
plot_axis_text = plot_axis_text.rotate(90)
|
351
|
-
plot_axis_text.text(
|
352
|
-
(@image.rows / 2).to_i,
|
353
|
-
-40,
|
354
|
-
options[:y_axis_label].to_s
|
355
|
-
)
|
356
|
-
|
357
|
-
plot_axis_text.draw(@image)
|
358
|
-
end
|
201
|
+
def render_axis_labels
|
202
|
+
drawer.axis_labels(
|
203
|
+
options[:x_axis_label].to_s,
|
204
|
+
options[:y_axis_label].to_s,
|
205
|
+
{
|
206
|
+
:color => options[:axis_color],
|
207
|
+
:width => 1,
|
208
|
+
:size => options[:axis_label_font_size],
|
209
|
+
}
|
210
|
+
)
|
359
211
|
end
|
360
212
|
|
361
213
|
end
|
@@ -6,7 +6,8 @@ require 'singleton'
|
|
6
6
|
|
7
7
|
class GraphColorLibrary
|
8
8
|
include Singleton
|
9
|
-
|
9
|
+
|
10
|
+
# rock solid colors
|
10
11
|
# http://www.imagemagick.org/script/color.php
|
11
12
|
BASIC_COLORS = [
|
12
13
|
'blue',
|
@@ -15,37 +16,52 @@ class GraphColorLibrary
|
|
15
16
|
'purple'
|
16
17
|
]
|
17
18
|
|
19
|
+
# other random picked up, SVG need #RGB and I'm too lazy :]
|
18
20
|
ADDITIONAL_COLORS = [
|
19
|
-
'tomato',
|
20
|
-
'sienna1',
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
21
|
+
'#8B3626', #'tomato',
|
22
|
+
'#FF8247', #'sienna1',
|
23
|
+
#'chocolate2',
|
24
|
+
#'DarkGoldenrod4',
|
25
|
+
#'OliveDrab4',
|
26
|
+
#'ForestGreen',
|
27
|
+
#'turquoise',
|
28
|
+
#'DarkCyan',
|
29
|
+
#'CadetBlue4',
|
30
|
+
#'DeepSkyBlue4',
|
31
|
+
#'DodgerBlue2',
|
32
|
+
#'CornflowerBlue',
|
33
|
+
#'MidnightBlue',
|
34
|
+
#'MediumPurple3',
|
35
|
+
#'magenta4',
|
36
|
+
#'orchid4',
|
37
|
+
#'DeepPink3',
|
38
|
+
#'PaleVioletRed4',
|
39
|
+
#'firebrick3'
|
38
40
|
]
|
39
41
|
|
40
42
|
FAIL_COLOR = 'black'
|
41
43
|
|
42
44
|
def initialize
|
43
|
-
@colors = BASIC_COLORS + ADDITIONAL_COLORS.sort {rand}
|
45
|
+
@colors = BASIC_COLORS + ADDITIONAL_COLORS.sort { rand }
|
46
|
+
end
|
47
|
+
|
48
|
+
# not too bright
|
49
|
+
MAX_INTENSITY = 0xbb
|
50
|
+
|
51
|
+
# Best solution, create random color JIT
|
52
|
+
def random_color
|
53
|
+
str = "#"
|
54
|
+
3.times do
|
55
|
+
str += colour = "%02x" % (rand * MAX_INTENSITY)
|
56
|
+
end
|
57
|
+
return str
|
44
58
|
end
|
45
59
|
|
46
60
|
def get_color
|
47
61
|
color = @colors.shift
|
48
|
-
return FAIL_COLOR if color.nil?
|
62
|
+
#return FAIL_COLOR if color.nil?
|
63
|
+
# better, create random colors just in time
|
64
|
+
return random_color if color.nil?
|
49
65
|
return color
|
50
66
|
end
|
51
67
|
|
@@ -17,6 +17,10 @@ class GraphDataProcessor
|
|
17
17
|
@technical_graph.layers
|
18
18
|
end
|
19
19
|
|
20
|
+
def logger
|
21
|
+
@technical_graph.logger
|
22
|
+
end
|
23
|
+
|
20
24
|
def initialize(technical_graph)
|
21
25
|
@technical_graph = technical_graph
|
22
26
|
|
@@ -121,7 +125,7 @@ class GraphDataProcessor
|
|
121
125
|
# ranges are set, can't change
|
122
126
|
return if fixed?
|
123
127
|
|
124
|
-
# updating ranges
|
128
|
+
# updating ranges, enlarging
|
125
129
|
self.raw_y_max = data_layer.y_max if not data_layer.y_max.nil? and data_layer.y_max > self.raw_y_max
|
126
130
|
self.raw_x_max = data_layer.x_max if not data_layer.x_max.nil? and data_layer.x_max > self.raw_x_max
|
127
131
|
|
@@ -149,6 +153,8 @@ class GraphDataProcessor
|
|
149
153
|
|
150
154
|
# Calculate zoomed X position for Array of X'es
|
151
155
|
def calc_x_zoomed(old_xes)
|
156
|
+
t = Time.now
|
157
|
+
|
152
158
|
# default zoom
|
153
159
|
if self.zoom_x == 1.0
|
154
160
|
return old_xes
|
@@ -162,11 +168,15 @@ class GraphDataProcessor
|
|
162
168
|
new_xes << (a + d * self.zoom_x)
|
163
169
|
end
|
164
170
|
|
171
|
+
logger.debug "calc_x_zoomed for #{old_xes.size}"
|
172
|
+
logger.debug " TIME COST #{Time.now - t}"
|
165
173
|
return new_xes
|
166
174
|
end
|
167
175
|
|
168
176
|
# Calculate zoomed Y position for Array of Y'es
|
169
177
|
def calc_y_zoomed(old_yes)
|
178
|
+
t = Time.now
|
179
|
+
|
170
180
|
# default zoom
|
171
181
|
if self.zoom_y == 1.0
|
172
182
|
return old_yes
|
@@ -180,6 +190,8 @@ class GraphDataProcessor
|
|
180
190
|
new_yes << (a + d * self.zoom_y)
|
181
191
|
end
|
182
192
|
|
193
|
+
logger.debug "calc_y_zoomed for #{old_yes.size}"
|
194
|
+
logger.debug " TIME COST #{Time.now - t}"
|
183
195
|
return new_yes
|
184
196
|
end
|
185
197
|
|