technical_graph 0.0.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/.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
|