technical_graph 0.3.2 → 0.4.0
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.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
|
|