technical_graph 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.idea/dictionaries/olek.xml +8 -0
- data/.rvmrc +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/lib/technical_graph/data_layer.rb +87 -0
- data/lib/technical_graph/graph_axis.rb +248 -0
- data/lib/technical_graph/graph_data_processor.rb +175 -0
- data/lib/technical_graph/graph_image_drawer.rb +190 -0
- data/lib/technical_graph/refactoring_backup/lib/technical_graph/axis_layer.rb +151 -0
- data/lib/technical_graph/refactoring_backup/lib/technical_graph/axis_layer_draw_module.rb +145 -0
- data/lib/technical_graph/refactoring_backup/lib/technical_graph.rb +55 -0
- data/lib/technical_graph/refactoring_backup/test/test_technical_graph.rb +104 -0
- data/lib/technical_graph/refactoring_backup/test/test_technical_graph_axis.rb +306 -0
- data/lib/technical_graph.rb +55 -0
- data/samples/1.png +0 -0
- data/test/helper.rb +19 -0
- data/test/test_technical_graph.rb +104 -0
- data/test/test_technical_graph_axis.rb +306 -0
- data/test/test_technical_simple_graph.rb +46 -0
- metadata +190 -0
@@ -0,0 +1,190 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'RMagick'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
# Universal class for creating graphs/charts.
|
8
|
+
|
9
|
+
# options parameters:
|
10
|
+
# :width - width of image
|
11
|
+
# :height - height of image
|
12
|
+
# :x_min, :x_max, :y_min, :y_max - default or fixed ranges
|
13
|
+
# :xy_behaviour:
|
14
|
+
# * :default - use them as default ranges
|
15
|
+
# * :fixed - ranges will not be changed during addition of layers
|
16
|
+
|
17
|
+
class GraphImageDrawer
|
18
|
+
|
19
|
+
attr_reader :technical_graph
|
20
|
+
|
21
|
+
# Accessor for options Hash
|
22
|
+
def options
|
23
|
+
@technical_graph.options
|
24
|
+
end
|
25
|
+
|
26
|
+
# Accessor for DataLayer Array
|
27
|
+
def layers
|
28
|
+
@technical_graph.layers
|
29
|
+
end
|
30
|
+
|
31
|
+
# Calculate everything
|
32
|
+
def data_processor
|
33
|
+
@technical_graph.data_processor
|
34
|
+
end
|
35
|
+
|
36
|
+
def truncate_string
|
37
|
+
options[:truncate_string]
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# default sizes
|
42
|
+
DEFAULT_WIDTH = 1600
|
43
|
+
DEFAULT_HEIGHT = 1200
|
44
|
+
|
45
|
+
def initialize(technical_graph)
|
46
|
+
@technical_graph = technical_graph
|
47
|
+
|
48
|
+
options[:width] ||= DEFAULT_WIDTH
|
49
|
+
options[:height] ||= DEFAULT_HEIGHT
|
50
|
+
|
51
|
+
# colors
|
52
|
+
options[:background_color] ||= 'white'
|
53
|
+
options[:background_hatch_color] ||= 'lightcyan2'
|
54
|
+
options[:axis_color] ||= '#aaaaaa'
|
55
|
+
end
|
56
|
+
|
57
|
+
def width
|
58
|
+
options[:width].to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
def height
|
62
|
+
options[:height].to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
def width=(w)
|
66
|
+
options[:width] = w.to_i if w.to_i > 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def height=(h)
|
70
|
+
options[:height] = h.to_i if h.to_i > 0
|
71
|
+
end
|
72
|
+
|
73
|
+
def font_antialias
|
74
|
+
options[:font_antialias] == true
|
75
|
+
end
|
76
|
+
|
77
|
+
# Calculate image X position
|
78
|
+
def calc_bitmap_x(_x)
|
79
|
+
l = data_processor.x_max - data_processor.x_min
|
80
|
+
offset = _x - data_processor.x_min
|
81
|
+
return (offset.to_f * width.to_f) / l.to_f
|
82
|
+
end
|
83
|
+
|
84
|
+
# Calculate image Y position
|
85
|
+
def calc_bitmap_y(_y)
|
86
|
+
l = data_processor.y_max - data_processor.y_min
|
87
|
+
#offset = _y - data_processor.y_min
|
88
|
+
offset = data_processor.y_max - _y
|
89
|
+
return (offset.to_f * height.to_f) / l.to_f
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create background image
|
93
|
+
def crate_blank_graph_image
|
94
|
+
@image = Magick::ImageList.new
|
95
|
+
@image.new_image(
|
96
|
+
width,
|
97
|
+
height,
|
98
|
+
Magick::HatchFill.new(
|
99
|
+
options[:background_color],
|
100
|
+
options[:background_hatch_color]
|
101
|
+
)
|
102
|
+
)
|
103
|
+
|
104
|
+
return @image
|
105
|
+
end
|
106
|
+
|
107
|
+
attr_reader :image
|
108
|
+
|
109
|
+
# Render data layer
|
110
|
+
def render_data_layer(l)
|
111
|
+
layer_line = Magick::Draw.new
|
112
|
+
layer_text = Magick::Draw.new
|
113
|
+
|
114
|
+
layer_line.stroke_antialias(l.antialias)
|
115
|
+
layer_line.fill(l.color)
|
116
|
+
layer_line.fill_opacity(1)
|
117
|
+
layer_line.stroke(l.color)
|
118
|
+
layer_line.stroke_opacity(1.0)
|
119
|
+
layer_line.stroke_width(1.0)
|
120
|
+
layer_line.stroke_linecap('square')
|
121
|
+
layer_line.stroke_linejoin('miter')
|
122
|
+
|
123
|
+
layer_text.text_antialias(font_antialias)
|
124
|
+
layer_text.pointsize(10)
|
125
|
+
layer_text.font_family('helvetica')
|
126
|
+
layer_text.font_style(Magick::NormalStyle)
|
127
|
+
layer_text.text_align(Magick::LeftAlign)
|
128
|
+
layer_text.text_undercolor(options[:background_color])
|
129
|
+
|
130
|
+
# calculate coords, draw text, and then lines and circles
|
131
|
+
coords = Array.new
|
132
|
+
|
133
|
+
(0...(l.data.size - 1)).each do |i|
|
134
|
+
ax = l.data[i][:x]
|
135
|
+
ax = calc_bitmap_x(ax).round
|
136
|
+
ay = l.data[i][:y]
|
137
|
+
ay = calc_bitmap_y(ay).round
|
138
|
+
|
139
|
+
bx = l.data[i+1][:x]
|
140
|
+
bx = calc_bitmap_x(bx).round
|
141
|
+
by = l.data[i+1][:y]
|
142
|
+
by = calc_bitmap_y(by).round
|
143
|
+
|
144
|
+
coords << {
|
145
|
+
:ax => ax, :ay => ay,
|
146
|
+
:bx => bx, :by => by,
|
147
|
+
:dy => l.data[i][:y]
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
# labels
|
152
|
+
coords.each do |c|
|
153
|
+
string_label = "#{truncate_string % c[:dy]}"
|
154
|
+
layer_text.text(
|
155
|
+
c[:ax] + 5, c[:ay],
|
156
|
+
string_label
|
157
|
+
)
|
158
|
+
end
|
159
|
+
layer_text.draw(@image)
|
160
|
+
|
161
|
+
# lines and circles
|
162
|
+
coords.each do |c|
|
163
|
+
# additional circle
|
164
|
+
layer_line.circle(
|
165
|
+
c[:ax], c[:ay],
|
166
|
+
c[:ax] + 3, c[:ay]
|
167
|
+
)
|
168
|
+
layer_line.circle(
|
169
|
+
c[:bx], c[:by],
|
170
|
+
c[:bx] + 3, c[:by]
|
171
|
+
)
|
172
|
+
|
173
|
+
# line
|
174
|
+
layer_line.line(
|
175
|
+
c[:ax], c[:ay],
|
176
|
+
c[:bx], c[:by]
|
177
|
+
)
|
178
|
+
end
|
179
|
+
layer_line.draw(@image)
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
# Save output to file
|
184
|
+
def save_to_file(file)
|
185
|
+
@image.write(file)
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
end
|
190
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'technical_graph/axis_layer_draw_module'
|
4
|
+
|
5
|
+
# Decide min/max values, recalculate all points and draw axises
|
6
|
+
|
7
|
+
class AxisLayer
|
8
|
+
include AxisLayerDrawModule
|
9
|
+
|
10
|
+
def initialize(options = { })
|
11
|
+
@options = options
|
12
|
+
@options[:x_min] ||= (Time.now - 24 * 3600).to_f
|
13
|
+
@options[:x_max] ||= Time.now.to_f
|
14
|
+
@options[:y_min] ||= 0.0
|
15
|
+
@options[:y_max] ||= 1.0
|
16
|
+
# :default - coords are default
|
17
|
+
# :fixed or whatever else - min/max coords are fixed
|
18
|
+
@options[:xy_behaviour] ||= :default
|
19
|
+
|
20
|
+
# number of axises
|
21
|
+
@options[:y_axises_count] ||= 10
|
22
|
+
@options[:x_axises_count] ||= 10
|
23
|
+
# interval
|
24
|
+
@options[:y_axises_interval] ||= 1.0
|
25
|
+
@options[:x_axises_interval] ||= 1.0
|
26
|
+
# when false then axises are generated to meet 'count'
|
27
|
+
# when true then axises are generated every X from lowest
|
28
|
+
@options[:x_axises_fixed_interval] = true if @options[:x_axises_fixed_interval].nil?
|
29
|
+
@options[:y_axises_fixed_interval] = true if @options[:y_axises_fixed_interval].nil?
|
30
|
+
|
31
|
+
@zoom_x = 1.0
|
32
|
+
@zoom_y = 1.0
|
33
|
+
end
|
34
|
+
|
35
|
+
# Ranges are fixed
|
36
|
+
def fixed?
|
37
|
+
@options[:xy_behaviour] == :fixed
|
38
|
+
end
|
39
|
+
|
40
|
+
# Ranges without zoom
|
41
|
+
def raw_x_min
|
42
|
+
@options[:x_min]
|
43
|
+
end
|
44
|
+
|
45
|
+
def raw_x_max
|
46
|
+
@options[:x_max]
|
47
|
+
end
|
48
|
+
|
49
|
+
def raw_y_min
|
50
|
+
@options[:y_min]
|
51
|
+
end
|
52
|
+
|
53
|
+
def raw_y_max
|
54
|
+
@options[:y_max]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Ranges with zoom
|
58
|
+
def x_min
|
59
|
+
calc_x_zoomed([self.raw_x_min]).first
|
60
|
+
end
|
61
|
+
|
62
|
+
def x_max
|
63
|
+
calc_x_zoomed([self.raw_x_max]).first
|
64
|
+
end
|
65
|
+
|
66
|
+
def y_min
|
67
|
+
calc_y_zoomed([self.raw_y_min]).first
|
68
|
+
end
|
69
|
+
|
70
|
+
def y_max
|
71
|
+
calc_y_zoomed([self.raw_y_max]).first
|
72
|
+
end
|
73
|
+
|
74
|
+
# Accessors
|
75
|
+
private
|
76
|
+
def raw_x_min=(x)
|
77
|
+
@options[:x_min] = x
|
78
|
+
end
|
79
|
+
|
80
|
+
def raw_x_max=(x)
|
81
|
+
@options[:x_max] = x
|
82
|
+
end
|
83
|
+
|
84
|
+
def raw_y_min=(y)
|
85
|
+
@options[:y_min] = y
|
86
|
+
end
|
87
|
+
|
88
|
+
def raw_y_max=(y)
|
89
|
+
@options[:y_max] = y
|
90
|
+
end
|
91
|
+
|
92
|
+
public
|
93
|
+
|
94
|
+
# Consider changing ranges
|
95
|
+
def process_data_layer(data_layer)
|
96
|
+
# ranges are set, can't change sir
|
97
|
+
return if fixed?
|
98
|
+
|
99
|
+
# updating ranges
|
100
|
+
self.raw_y_max = data_layer.y_max if not data_layer.y_max.nil? and data_layer.y_max > self.raw_y_max
|
101
|
+
self.raw_x_max = data_layer.x_max if not data_layer.x_max.nil? and data_layer.x_max > self.raw_x_max
|
102
|
+
|
103
|
+
self.raw_y_min = data_layer.y_min if not data_layer.y_min.nil? and data_layer.y_min < self.raw_y_min
|
104
|
+
self.raw_x_min = data_layer.x_min if not data_layer.x_min.nil? and data_layer.x_min < self.raw_x_min
|
105
|
+
end
|
106
|
+
|
107
|
+
# Change overall image zoom
|
108
|
+
def zoom=(z = 1.0)
|
109
|
+
self.x_zoom = z
|
110
|
+
self.y_zoom = z
|
111
|
+
end
|
112
|
+
|
113
|
+
# Change X axis zoom
|
114
|
+
def x_zoom=(z = 1.0)
|
115
|
+
@zoom_x = z
|
116
|
+
end
|
117
|
+
|
118
|
+
# Change X axis zoom
|
119
|
+
def y_zoom=(z = 1.0)
|
120
|
+
@zoom_y = z
|
121
|
+
end
|
122
|
+
|
123
|
+
attr_reader :zoom_x, :zoom_y
|
124
|
+
|
125
|
+
# Calculate zoomed X position for Array of X'es
|
126
|
+
def calc_x_zoomed(old_xes)
|
127
|
+
a = (raw_x_max.to_f + raw_x_min.to_f) / 2.0
|
128
|
+
new_xes = Array.new
|
129
|
+
|
130
|
+
old_xes.each do |x|
|
131
|
+
d = x - a
|
132
|
+
new_xes << (a + d * self.zoom_x)
|
133
|
+
end
|
134
|
+
|
135
|
+
return new_xes
|
136
|
+
end
|
137
|
+
|
138
|
+
# Calculate zoomed Y position for Array of Y'es
|
139
|
+
def calc_y_zoomed(old_yes)
|
140
|
+
a = (raw_y_max.to_f + raw_y_min.to_f) / 2.0
|
141
|
+
new_yes = Array.new
|
142
|
+
|
143
|
+
old_yes.each do |y|
|
144
|
+
d = y - a
|
145
|
+
new_yes << (a + d * self.zoom_y)
|
146
|
+
end
|
147
|
+
|
148
|
+
return new_yes
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module AxisLayerDrawModule
|
2
|
+
def x_axis_fixed?
|
3
|
+
@options[:x_axises_fixed_interval] == true
|
4
|
+
end
|
5
|
+
|
6
|
+
# Value axis has fixed count
|
7
|
+
def y_axis_fixed?
|
8
|
+
@options[:y_axises_fixed_interval] == true
|
9
|
+
end
|
10
|
+
|
11
|
+
# Where to put axis values
|
12
|
+
def value_axises
|
13
|
+
return calc_axis(self.y_min, self.y_max, @options[:y_axises_interval], @options[:y_axises_count], y_axis_fixed?)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Where to put axis values
|
17
|
+
def parameter_axises
|
18
|
+
return calc_axis(self.x_min, self.x_max, @options[:x_axises_interval], @options[:x_axises_count], x_axis_fixed?)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Calculate axis using 2 methods
|
22
|
+
def calc_axis(from, to, interval, count, fixed_interval)
|
23
|
+
axises = Array.new
|
24
|
+
l = to - from
|
25
|
+
current = from
|
26
|
+
|
27
|
+
if fixed_interval
|
28
|
+
while current < to
|
29
|
+
axises << current
|
30
|
+
current += interval
|
31
|
+
end
|
32
|
+
return axises
|
33
|
+
|
34
|
+
else
|
35
|
+
(0...count).each do |i|
|
36
|
+
axises << from + (l.to_f * i.to_f) / count.to_f
|
37
|
+
end
|
38
|
+
return axises
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def calc_bitmap_position(array)
|
45
|
+
# TODO move calculatio of lenght here
|
46
|
+
end
|
47
|
+
|
48
|
+
def calc_bitmap_x(_x)
|
49
|
+
l = self.x_max - self.x_min
|
50
|
+
offset = _x - self.x_min
|
51
|
+
return (offset.to_f * @image.width.to_f) / l.to_f
|
52
|
+
end
|
53
|
+
|
54
|
+
def calc_bitmap_y(_y)
|
55
|
+
l = self.y_max - self.y_min
|
56
|
+
offset = _y - self.y_min
|
57
|
+
return (offset.to_f * @image.width.to_f) / l.to_f
|
58
|
+
end
|
59
|
+
|
60
|
+
# Render axis on image
|
61
|
+
def render_on_image(image)
|
62
|
+
@image = image
|
63
|
+
|
64
|
+
render_values_axis
|
65
|
+
render_parameters_axis
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_values_axis
|
69
|
+
plot_axis_y_line = Magick::Draw.new
|
70
|
+
plot_axis_y_text = Magick::Draw.new
|
71
|
+
|
72
|
+
plot_axis_y_line.fill_opacity(0)
|
73
|
+
plot_axis_y_line.stroke(@image.options[:axis_color])
|
74
|
+
plot_axis_y_line.stroke_opacity(1.0)
|
75
|
+
plot_axis_y_line.stroke_width(1.0)
|
76
|
+
plot_axis_y_line.stroke_linecap('square')
|
77
|
+
plot_axis_y_line.stroke_linejoin('miter')
|
78
|
+
|
79
|
+
plot_axis_y_text.font_family('helvetica')
|
80
|
+
plot_axis_y_text.font_style(Magick::NormalStyle)
|
81
|
+
plot_axis_y_text.text_align(Magick::LeftAlign)
|
82
|
+
plot_axis_y_text.text_undercolor(@image.options[:background_color])
|
83
|
+
|
84
|
+
value_axises.each do |y|
|
85
|
+
by = calc_bitmap_y(y)
|
86
|
+
plot_axis_y_line.line(
|
87
|
+
0, by.round,
|
88
|
+
@image.image.columns-1, by.round
|
89
|
+
)
|
90
|
+
|
91
|
+
plot_axis_y_text.text(
|
92
|
+
5,
|
93
|
+
by.round + 15,
|
94
|
+
"#{y}"
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
t = Time.now
|
99
|
+
plot_axis_y_line.draw(@image.image)
|
100
|
+
puts "#{Time.now - t} drawing lines"
|
101
|
+
plot_axis_y_text.draw(@image.image)
|
102
|
+
puts "#{Time.now - t} drawing text"
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
def render_parameters_axis
|
107
|
+
|
108
|
+
plot_axis_x_line = Magick::Draw.new
|
109
|
+
plot_axis_x_text = Magick::Draw.new
|
110
|
+
|
111
|
+
plot_axis_x_line.fill_opacity(0)
|
112
|
+
plot_axis_x_line.stroke(@image.options[:axis_color])
|
113
|
+
plot_axis_x_line.stroke_opacity(1.0)
|
114
|
+
plot_axis_x_line.stroke_width(1.0)
|
115
|
+
plot_axis_x_line.stroke_linecap('square')
|
116
|
+
plot_axis_x_line.stroke_linejoin('miter')
|
117
|
+
|
118
|
+
plot_axis_x_text.font_family('helvetica')
|
119
|
+
plot_axis_x_text.font_style(Magick::NormalStyle)
|
120
|
+
plot_axis_x_text.text_align(Magick::LeftAlign)
|
121
|
+
plot_axis_x_text.text_undercolor(@image.options[:background_color])
|
122
|
+
|
123
|
+
parameter_axises.each do |x|
|
124
|
+
bx = calc_bitmap_x(x)
|
125
|
+
plot_axis_x_line.line(
|
126
|
+
bx.round, 0,
|
127
|
+
bx.round, @image.image.rows-1
|
128
|
+
)
|
129
|
+
|
130
|
+
plot_axis_x_text.text(
|
131
|
+
bx.round + 15,
|
132
|
+
@image.image.rows - 15,
|
133
|
+
"#{x}"
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
t = Time.now
|
138
|
+
plot_axis_x_line.draw(@image.image)
|
139
|
+
puts "#{Time.now - t} drawing lines"
|
140
|
+
plot_axis_x_text.draw(@image.image)
|
141
|
+
puts "#{Time.now - t} drawing text"
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'technical_graph/data_processor'
|
5
|
+
#require 'technical_graph/graph_image'
|
6
|
+
#require 'technical_graph/data_layer'
|
7
|
+
#require 'technical_graph/axis_layer'
|
8
|
+
|
9
|
+
# Universal class for creating graphs/charts.
|
10
|
+
|
11
|
+
# options parameters:
|
12
|
+
# :width - width of image
|
13
|
+
# :height - height of image
|
14
|
+
# :x_min, :x_max, :y_min, :y_max - default or fixed ranges
|
15
|
+
# :xy_behaviour:
|
16
|
+
# * :default - use them as default ranges
|
17
|
+
# * :fixed - ranges will not be changed during addition of layers
|
18
|
+
|
19
|
+
class TechnicalGraph
|
20
|
+
|
21
|
+
def initialize(options = { })
|
22
|
+
@options = options
|
23
|
+
@data_processor = GraphDataProcessor.new(@options)
|
24
|
+
|
25
|
+
#@image = GraphImage.new(@options)
|
26
|
+
#@axis = AxisLayer.new(@options)
|
27
|
+
#@layers = []
|
28
|
+
end
|
29
|
+
attr_reader :options
|
30
|
+
#attr_reader :image
|
31
|
+
#attr_reader :layers
|
32
|
+
#attr_reader :axis
|
33
|
+
|
34
|
+
# Add new data layer to layer array
|
35
|
+
def add_layer(data = [], options = {})
|
36
|
+
@layers << DataLayer.new(data, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create graph
|
40
|
+
def render
|
41
|
+
@image.render_image
|
42
|
+
# recalculate ranges
|
43
|
+
@layers.each do |l|
|
44
|
+
@axis.process_data_layer(l)
|
45
|
+
end
|
46
|
+
# draw axis
|
47
|
+
@axis.render_on_image(@image)
|
48
|
+
# draw layers
|
49
|
+
@layers.each do |l|
|
50
|
+
# @xis used for calculation purpose
|
51
|
+
l.render_on_image(@image, @axis)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestTechnicalGraph < Test::Unit::TestCase
|
4
|
+
context 'initial options' do
|
5
|
+
setup do
|
6
|
+
@technical_graph = TechnicalGraph.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "has options with default values" do
|
10
|
+
@technical_graph.options.class.should == Hash
|
11
|
+
@technical_graph.options[:width] > 0
|
12
|
+
@technical_graph.options[:height] > 0
|
13
|
+
end
|
14
|
+
|
15
|
+
should "has options with custom values" do
|
16
|
+
s = 10
|
17
|
+
tg = TechnicalGraph.new({ :height => s, :width => s })
|
18
|
+
@technical_graph.options[:width] == s
|
19
|
+
@technical_graph.options[:height] == s
|
20
|
+
|
21
|
+
@technical_graph.image.width == s
|
22
|
+
@technical_graph.image.height == s
|
23
|
+
end
|
24
|
+
|
25
|
+
should "has changeable options" do
|
26
|
+
s = 20
|
27
|
+
tg = TechnicalGraph.new
|
28
|
+
tg.image.width = s
|
29
|
+
tg.image.height = s
|
30
|
+
@technical_graph.options[:width] == s
|
31
|
+
@technical_graph.options[:height] == s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'basic layer operation and saving file' do
|
36
|
+
setup do
|
37
|
+
@tg = TechnicalGraph.new
|
38
|
+
@data_size = 100
|
39
|
+
|
40
|
+
# sample data
|
41
|
+
@data = Array.new
|
42
|
+
@second_data = Array.new
|
43
|
+
(0...@data_size).each do |i|
|
44
|
+
@data << { :x => Time.now.to_i - 3600 + i, :y => Math.sin(i.to_f / 10.0) }
|
45
|
+
@second_data << { :x => Time.now.to_i - 1800 + i*2, :y => Math.cos(i.to_f / 10.0) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
should 'has ability do add new layer' do
|
50
|
+
layers = @tg.layers.size
|
51
|
+
@tg.add_layer(@data)
|
52
|
+
@tg.layers.size.should == layers + 1
|
53
|
+
|
54
|
+
layer = @tg.layers.last
|
55
|
+
layer.data.size.should == @data_size
|
56
|
+
end
|
57
|
+
|
58
|
+
should 'has ability to manipulate layers, add more data' do
|
59
|
+
@tg.add_layer(@data)
|
60
|
+
layer = @tg.layers.last
|
61
|
+
layer.class.should == DataLayer
|
62
|
+
|
63
|
+
layer.data.size.should == @data_size
|
64
|
+
|
65
|
+
# adding second data
|
66
|
+
layer.append_data(@second_data)
|
67
|
+
layer.data.size.should == 2 * @data_size
|
68
|
+
|
69
|
+
# @tg.render
|
70
|
+
# @tg.image.save_to_file('test1.png')
|
71
|
+
end
|
72
|
+
|
73
|
+
should 'has ability to filter records with similar x\'es' do
|
74
|
+
@tg.add_layer
|
75
|
+
layer = @tg.layers.last
|
76
|
+
layer.data.size.should == 0
|
77
|
+
layer.append_data([{ :x => 0, :y => 1 }])
|
78
|
+
layer.data.size.should == 1
|
79
|
+
|
80
|
+
# uniq check
|
81
|
+
layer.append_data([{ :x => 0, :y => 1 }])
|
82
|
+
layer.append_data([{ :x => 0, :y => 1 }])
|
83
|
+
layer.data.size.should == 1
|
84
|
+
layer.append_data([{ :x => 2, :y => 1 }])
|
85
|
+
layer.data.size.should == 2
|
86
|
+
end
|
87
|
+
|
88
|
+
should 'has ability to filter bad records' do
|
89
|
+
@tg.add_layer
|
90
|
+
layer = @tg.layers.last
|
91
|
+
layer.data.size.should == 0
|
92
|
+
layer.append_data([{ :x => 0, :y => 1 }])
|
93
|
+
layer.data.size.should == 1
|
94
|
+
|
95
|
+
# uniq check
|
96
|
+
layer.append_data([{ :z => 0, :y => 1 }])
|
97
|
+
layer.append_data([{}])
|
98
|
+
layer.data.size.should == 1
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|