technical_graph 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -5,8 +5,6 @@ gem 'rmagick'
5
5
  # Add dependencies to develop your gem here.
6
6
  # Include everything needed to run rake, tests, features, etc.
7
7
  group :development do
8
- gem 'jeweler'
9
-
10
8
  gem "shoulda"
11
9
  gem "bundler", "~> 1.0.0"
12
10
  gem "rspec"
data/README.md CHANGED
@@ -121,6 +121,15 @@ size will be enlarged to maintain set distanced between axis.
121
121
  * options[:x_axis_min_distance] - minimum distance between X axis, default 30 pixels
122
122
  * options[:y_axis_min_distance] - minimum distance between X axis, default 50 pixels
123
123
 
124
+ Legend options:
125
+
126
+ * options[:legend] - draw legend, default false
127
+ * options[:legend_auto] - let legend position to be chosen by algorithm, default true
128
+ * options[:legend_width] - width used for setting proper distance while drawing on right, default 100, legend height is calculated
129
+ * options[:legend_margin] - graph margin used not to draw legend on border, default 50
130
+ * options[:legend_x] - legend X position, default 50
131
+ * options[:legend_y] - legend Y position, default 50
132
+
124
133
 
125
134
  Layer options Hash
126
135
  ------------------
data/Rakefile CHANGED
@@ -24,10 +24,23 @@ Jeweler::Tasks.new do |gem|
24
24
  gem.authors = ["Aleksander Kwiatkowski"]
25
25
 
26
26
  #gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
27
+ gem.files = FileList[
28
+ "[A-Z]*", "{bin,generators,lib,test}/**/*"
29
+ ]
27
30
  # dependencies defined in Gemfile
28
31
  end
29
32
  Jeweler::RubygemsDotOrgTasks.new
30
33
 
34
+ desc "Remove all images created during tests"
35
+ task :clean do
36
+ `rm *.svg`
37
+ `rm *.png`
38
+ end
39
+
40
+ desc "Clean and release"
41
+ task :clean_and_release => [:clean, :release] do
42
+ end
43
+
31
44
  require 'rake/testtask'
32
45
  Rake::TestTask.new(:test) do |test|
33
46
  test.libs << 'lib' << 'test'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -40,6 +40,10 @@ class DataLayer
40
40
  return @data_params[:antialias]
41
41
  end
42
42
 
43
+ def label
44
+ return @data_params[:label] || '' #'data'
45
+ end
46
+
43
47
  # Clear data
44
48
  def clear_data
45
49
  @data = Array.new
@@ -71,6 +71,17 @@ class GraphImageDrawer
71
71
  options[:axis_font_size] ||= 10
72
72
  options[:layers_font_size] ||= 10
73
73
  options[:axis_label_font_size] ||= 10
74
+
75
+ # legend
76
+ options[:legend] = false if options[:legend].nil?
77
+ options[:legend_auto] = true if options[:legend_auto].nil?
78
+ options[:legend_x] ||= 50
79
+ options[:legend_y] ||= 50
80
+ options[:legend_width] ||= 100
81
+ options[:legend_margin] ||= 50
82
+
83
+ # array of all points drawn on graph, used for auto positioning of legend
84
+ @drawn_points = Array.new
74
85
  end
75
86
 
76
87
  def width
@@ -93,6 +104,30 @@ class GraphImageDrawer
93
104
  options[:font_antialias] == true
94
105
  end
95
106
 
107
+ def draw_legend?
108
+ options[:legend]
109
+ end
110
+
111
+ def legend_x
112
+ options[:legend_x]
113
+ end
114
+
115
+ def legend_y
116
+ options[:legend_y]
117
+ end
118
+
119
+ def legend_auto_position
120
+ options[:legend_auto]
121
+ end
122
+
123
+ def legend_width
124
+ options[:legend_width]
125
+ end
126
+
127
+ def legend_margin
128
+ options[:legend_margin]
129
+ end
130
+
96
131
  # Calculate image X position
97
132
  def calc_bitmap_x(_x)
98
133
  l = data_processor.x_max - data_processor.x_min
@@ -206,11 +241,101 @@ class GraphImageDrawer
206
241
  c[:ax], c[:ay],
207
242
  c[:bx], c[:by]
208
243
  )
244
+
245
+ # used for auto positioning of legend
246
+ if legend_auto_position
247
+ @drawn_points << {:x => c[:ax], :y => c[:ay]}
248
+ @drawn_points << {:x => c[:bx], :y => c[:by]}
249
+ end
209
250
  end
210
251
  layer_line.draw(@image)
211
252
 
212
253
  end
213
254
 
255
+ # height of 1 layer
256
+ ONE_LAYER_LEGEND_HEIGHT = 15
257
+
258
+ # Choose best location
259
+ def recalculate_legend_position
260
+ return unless legend_auto_position
261
+ puts "Auto position calculation, drawn points #{@drawn_points.size}"
262
+
263
+ legend_height = layers.size * ONE_LAYER_LEGEND_HEIGHT
264
+
265
+ # check 8 places:
266
+ positions = [
267
+ {:x => legend_margin, :y => 0 + legend_margin}, # top-left
268
+ {:x => width/2, :y => 0 + legend_margin}, # top-center
269
+ {:x => width - legend_margin - legend_width, :y => 0 + legend_margin}, # top-right
270
+ {:x => legend_margin, :y => height/2}, # middle-left
271
+ {:x => width - legend_margin - legend_width, :y => height/2}, # middle-right
272
+ {:x => legend_margin, :y => height - legend_margin - legend_height}, # bottom-left
273
+ {:x => width/2, :y => height - legend_margin - legend_height}, # bottom-center
274
+ {:x => width - legend_margin - legend_width, :y => height - legend_margin - legend_height}, # bottom-right
275
+ ]
276
+
277
+ # calculate nearest distance of all drawn points
278
+ positions.each do |p|
279
+ p[:distance] = (width ** 2 + height ** 2) ** 0.5 # max distance, diagonal of graph
280
+ @drawn_points.each do |dp|
281
+ # calculate drawn point distance to being checked now legend position
282
+ two_points_distance = ( (p[:x] - dp[:x]) ** 2 + (p[:y] - dp[:y]) ** 2 ) ** 0.5
283
+ # modify only if distance is closer
284
+ if p[:distance] > two_points_distance
285
+ p[:distance] = two_points_distance
286
+ end
287
+ end
288
+ end
289
+
290
+ # chose position with hihest distance
291
+ positions.sort!{|a,b| a[:distance] <=> b[:distance]}
292
+ best_position = positions.last
293
+ options[:legend_x] = best_position[:x]
294
+ options[:legend_y] = best_position[:y]
295
+
296
+ puts "Best position x #{options[:legend_x]}, y #{options[:legend_y]}, distance #{best_position[:distance]}"
297
+ # puts positions.to_yaml
298
+ end
299
+
300
+ # Render legend on graph
301
+ def render_data_legend
302
+ return unless draw_legend?
303
+
304
+ recalculate_legend_position
305
+
306
+ legend_text = Magick::Draw.new
307
+ legend_text_antialias = options[:layers_font_size]
308
+ legend_text.stroke_antialias(legend_text_antialias)
309
+ legend_text.text_antialias(legend_text_antialias)
310
+ legend_text.pointsize(options[:axis_font_size])
311
+ legend_text.font_family('helvetica')
312
+ legend_text.font_style(Magick::NormalStyle)
313
+ legend_text.text_align(Magick::LeftAlign)
314
+ legend_text.text_undercolor(options[:background_color])
315
+
316
+ x = legend_x
317
+ y = legend_y
318
+
319
+ layers.each do |l|
320
+ legend_text.fill(l.color)
321
+
322
+ string_label = l.label
323
+ legend_text.text(
324
+ x, y,
325
+ string_label
326
+ )
327
+
328
+ # little dot
329
+ legend_text.circle(
330
+ x - 10, y,
331
+ x - 10 + 3, y
332
+ )
333
+
334
+ y += ONE_LAYER_LEGEND_HEIGHT
335
+ end
336
+ legend_text.draw(@image)
337
+ end
338
+
214
339
  # Save output to file
215
340
  def save_to_file(file)
216
341
  @image.write(file)
@@ -51,5 +51,7 @@ class TechnicalGraph
51
51
  @layers.each do |l|
52
52
  @image_drawer.render_data_layer(l)
53
53
  end
54
+ # draw legend
55
+ @image_drawer.render_data_legend
54
56
  end
55
57
  end
@@ -0,0 +1,95 @@
1
+ require 'helper'
2
+
3
+ class TestTechnicalMultilayer < Test::Unit::TestCase
4
+ context 'initial options' do
5
+ should 'draw multilayer graph' do
6
+ @tg = TechnicalGraph.new(
7
+ {
8
+ :truncate_string => "%.1f",
9
+
10
+ :x_axis_label => 'x',
11
+ :y_axis_label => 'y',
12
+
13
+ :axis_antialias => true,
14
+ :layers_antialias => true,
15
+ :font_antialias => true,
16
+
17
+ :layers_font_size => 11,
18
+ :axis_font_size => 11,
19
+ :axis_label_font_size => 20,
20
+
21
+ #:x_axis_count => 20,
22
+ #:y_axis_count => 20,
23
+ #:x_axis_interval => 1.0,
24
+ #:y_axis_interval => 1.0,
25
+ #:x_axis_fixed_interval => false,
26
+ #:y_axis_fixed_interval => false,
27
+
28
+ #:x_min => -10.0,
29
+ #:x_max => 10.0,
30
+ #:y_min => -10.0,
31
+ #:y_max => 10.0,
32
+
33
+ #:width => 4000,
34
+ #:height => 3000,
35
+
36
+ :legend => true,
37
+ :legend_auto => true,
38
+ :legend_width => 90,
39
+ :legend_margin => 60,
40
+ :legend_x => 50,
41
+ :legend_y => 50,
42
+ }
43
+ )
44
+
45
+ max = 50
46
+
47
+ # adding simple layer
48
+ layer_params_a = {
49
+ :antialias => true,
50
+ :color => 'red',
51
+ :label => 'first'
52
+ }
53
+ layer_params_b = {
54
+ :antialias => true,
55
+ :color => 'green',
56
+ :label => 'second'
57
+ }
58
+ layer_params_c = {
59
+ :antialias => true,
60
+ :color => 'blue',
61
+ :label => 'third'
62
+ }
63
+ layer_params_d = {
64
+ :antialias => true,
65
+ :color => 'purple',
66
+ :label => 'fourth'
67
+ }
68
+ layer_data_a = Array.new
69
+ layer_data_b = Array.new
70
+ layer_data_c = Array.new
71
+ layer_data_d = Array.new
72
+ (0..max).each do |i|
73
+ layer_data_a << { :x => -10.0 + i.to_f, :y => 10.0 * Math.cos(i.to_f * (4.0 * 3.14 / max.to_f)) }
74
+ layer_data_b << { :x => -10.0 + i.to_f, :y => 10.0 * Math.cos(0.3 + i.to_f * (4.0 * 3.14 / max.to_f)) }
75
+ layer_data_c << { :x => -10.0 + i.to_f, :y => 10.0 * Math.cos(0.6 + i.to_f * (4.0 * 3.14 / max.to_f)) }
76
+ layer_data_d << { :x => -10.0 + i.to_f, :y => 10.0 * Math.cos(0.9 + i.to_f * (4.0 * 3.14 / max.to_f)) }
77
+ end
78
+ @tg.add_layer(layer_data_a, layer_params_a)
79
+ @tg.add_layer(layer_data_b, layer_params_b)
80
+ @tg.add_layer(layer_data_c, layer_params_c)
81
+ @tg.add_layer(layer_data_d, layer_params_d)
82
+
83
+
84
+ @tg.layers.last.data.size.should > 0
85
+ @tg.layers.size.should == 4
86
+
87
+ @tg.render
88
+
89
+ @tg.image_drawer.save_to_file('test_multilayer.png')
90
+ @tg.image_drawer.to_png.class.should == String
91
+
92
+ end
93
+ end
94
+
95
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: technical_graph
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aleksander Kwiatkowski
@@ -43,26 +43,12 @@ dependencies:
43
43
  segments:
44
44
  - 0
45
45
  version: "0"
46
- name: jeweler
46
+ name: shoulda
47
47
  version_requirements: *id002
48
48
  prerelease: false
49
49
  - !ruby/object:Gem::Dependency
50
50
  type: :development
51
51
  requirement: &id003 !ruby/object:Gem::Requirement
52
- none: false
53
- requirements:
54
- - - ">="
55
- - !ruby/object:Gem::Version
56
- hash: 3
57
- segments:
58
- - 0
59
- version: "0"
60
- name: shoulda
61
- version_requirements: *id003
62
- prerelease: false
63
- - !ruby/object:Gem::Dependency
64
- type: :development
65
- requirement: &id004 !ruby/object:Gem::Requirement
66
52
  none: false
67
53
  requirements:
68
54
  - - ~>
@@ -74,11 +60,11 @@ dependencies:
74
60
  - 0
75
61
  version: 1.0.0
76
62
  name: bundler
77
- version_requirements: *id004
63
+ version_requirements: *id003
78
64
  prerelease: false
79
65
  - !ruby/object:Gem::Dependency
80
66
  type: :development
81
- requirement: &id005 !ruby/object:Gem::Requirement
67
+ requirement: &id004 !ruby/object:Gem::Requirement
82
68
  none: false
83
69
  requirements:
84
70
  - - ">="
@@ -88,11 +74,11 @@ dependencies:
88
74
  - 0
89
75
  version: "0"
90
76
  name: rspec
91
- version_requirements: *id005
77
+ version_requirements: *id004
92
78
  prerelease: false
93
79
  - !ruby/object:Gem::Dependency
94
80
  type: :development
95
- requirement: &id006 !ruby/object:Gem::Requirement
81
+ requirement: &id005 !ruby/object:Gem::Requirement
96
82
  none: false
97
83
  requirements:
98
84
  - - ">="
@@ -102,11 +88,11 @@ dependencies:
102
88
  - 0
103
89
  version: "0"
104
90
  name: jeweler
105
- version_requirements: *id006
91
+ version_requirements: *id005
106
92
  prerelease: false
107
93
  - !ruby/object:Gem::Dependency
108
94
  type: :development
109
- requirement: &id007 !ruby/object:Gem::Requirement
95
+ requirement: &id006 !ruby/object:Gem::Requirement
110
96
  none: false
111
97
  requirements:
112
98
  - - ">="
@@ -116,7 +102,7 @@ dependencies:
116
102
  - 0
117
103
  version: "0"
118
104
  name: rcov
119
- version_requirements: *id007
105
+ version_requirements: *id006
120
106
  prerelease: false
121
107
  description: Purpose of this gem is to create neat, simple, technical graphs. This is alternative to most new libraries which create small, candy graphs using JavaScript.
122
108
  email: bobikx@poczta.fm
@@ -128,9 +114,6 @@ extra_rdoc_files:
128
114
  - LICENSE.txt
129
115
  - README.md
130
116
  files:
131
- - .document
132
- - .idea/dictionaries/olek.xml
133
- - .rvmrc
134
117
  - Gemfile
135
118
  - Gemfile.lock
136
119
  - LICENSE.txt
@@ -142,18 +125,11 @@ files:
142
125
  - lib/technical_graph/graph_axis.rb
143
126
  - lib/technical_graph/graph_data_processor.rb
144
127
  - lib/technical_graph/graph_image_drawer.rb
145
- - lib/technical_graph/refactoring_backup/lib/technical_graph.rb
146
- - lib/technical_graph/refactoring_backup/lib/technical_graph/axis_layer.rb
147
- - lib/technical_graph/refactoring_backup/lib/technical_graph/axis_layer_draw_module.rb
148
- - lib/technical_graph/refactoring_backup/test/test_technical_graph.rb
149
- - lib/technical_graph/refactoring_backup/test/test_technical_graph_axis.rb
150
- - samples/1.png
151
- - samples/home_io_batt_voltage.png
152
- - technical_graph.gemspec
153
128
  - test/helper.rb
154
129
  - test/test_technical_axis_enlarge.rb
155
130
  - test/test_technical_graph.rb
156
131
  - test/test_technical_graph_axis.rb
132
+ - test/test_technical_multilayer.rb
157
133
  - test/test_technical_simple_graph.rb
158
134
  has_rdoc: true
159
135
  homepage: http://github.com/akwiatkowski/technical_graph
data/.document DELETED
@@ -1,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt
@@ -1,8 +0,0 @@
1
- <component name="ProjectDictionaryState">
2
- <dictionary name="olek">
3
- <words>
4
- <w>antialias</w>
5
- <w>magick</w>
6
- </words>
7
- </dictionary>
8
- </component>
data/.rvmrc DELETED
@@ -1 +0,0 @@
1
- rvm use 1.8.7@technical_graph --create
@@ -1,151 +0,0 @@
1
- #encoding: utf-8
2
-
3
- require 'technical_graph/axis_layer_draw_module'
4
-
5
- # Decide min/max values, recalculate all points and draw axis
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 axis
21
- @options[:y_axis_count] ||= 10
22
- @options[:x_axis_count] ||= 10
23
- # interval
24
- @options[:y_axis_interval] ||= 1.0
25
- @options[:x_axis_interval] ||= 1.0
26
- # when false then axis are generated to meet 'count'
27
- # when true then axis are generated every X from lowest
28
- @options[:x_axis_fixed_interval] = true if @options[:x_axis_fixed_interval].nil?
29
- @options[:y_axis_fixed_interval] = true if @options[:y_axis_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
@@ -1,145 +0,0 @@
1
- module AxisLayerDrawModule
2
- def x_axis_fixed?
3
- @options[:x_axis_fixed_interval] == true
4
- end
5
-
6
- # Value axis has fixed count
7
- def y_axis_fixed?
8
- @options[:y_axis_fixed_interval] == true
9
- end
10
-
11
- # Where to put axis values
12
- def value_axis
13
- return calc_axis(self.y_min, self.y_max, @options[:y_axis_interval], @options[:y_axis_count], y_axis_fixed?)
14
- end
15
-
16
- # Where to put axis values
17
- def parameter_axis
18
- return calc_axis(self.x_min, self.x_max, @options[:x_axis_interval], @options[:x_axis_count], x_axis_fixed?)
19
- end
20
-
21
- # Calculate axis using 2 methods
22
- def calc_axis(from, to, interval, count, fixed_interval)
23
- axis = Array.new
24
- l = to - from
25
- current = from
26
-
27
- if fixed_interval
28
- while current < to
29
- axis << current
30
- current += interval
31
- end
32
- return axis
33
-
34
- else
35
- (0...count).each do |i|
36
- axis << from + (l.to_f * i.to_f) / count.to_f
37
- end
38
- return axis
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_axis.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_axis.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
@@ -1,55 +0,0 @@
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
@@ -1,104 +0,0 @@
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
@@ -1,306 +0,0 @@
1
- require 'helper'
2
-
3
- class TestTechnicalGraphAxis < Test::Unit::TestCase
4
- context 'ranges calculation' do
5
- setup do
6
- end
7
-
8
- should 'provide defined ranges' do
9
- x_min = 0.0
10
- x_max = 10.0
11
- y_min = -5.0
12
- y_max = 5.0
13
-
14
- @tg = TechnicalGraph.new(
15
- {
16
- :x_min => x_min,
17
- :x_max => x_max,
18
- :y_min => y_min,
19
- :y_max => y_max,
20
- :xy_behaviour => :fixed
21
- }
22
- )
23
- @tg.render
24
-
25
- @tg.axis.x_min.should == x_min
26
- @tg.axis.x_max.should == x_max
27
- @tg.axis.y_min.should == y_min
28
- @tg.axis.y_max.should == y_max
29
- end
30
-
31
- should 'calculate ranges per layer' do
32
- # adding simple layer
33
- layer_data = [
34
- { :x => -1, :y => 3.0 },
35
- { :x => 3, :y => -8.0 },
36
- { :x => 0, :y => 3.0 },
37
- { :x => 10, :y => 10.0 }
38
- ]
39
- dl = DataLayer.new(layer_data)
40
-
41
- dl.x_min.should == -1
42
- dl.x_max.should == 10
43
- dl.y_min.should == -8.0
44
- dl.y_max.should == 10.0
45
- end
46
-
47
-
48
- should 'provide ranges calculated using data layer, and multiple layers' do
49
- x_min = 0.0
50
- x_max = 1.0
51
- y_min = -1.0
52
- y_max = 1.0
53
-
54
- @tg = TechnicalGraph.new(
55
- {
56
- :x_min => x_min,
57
- :x_max => x_max,
58
- :y_min => y_min,
59
- :y_max => y_max,
60
- :xy_behaviour => :default
61
- }
62
- )
63
-
64
- # adding simple layer
65
- layer_data = [
66
- { :x => -1, :y => 3.0 },
67
- { :x => 3, :y => -8.0 },
68
- { :x => 0, :y => 3.0 },
69
- { :x => 10, :y => 10.0 }
70
- ]
71
- @tg.add_layer(layer_data)
72
- # should be added
73
- @tg.layers.last.data.size > 0
74
- # checking ranger for layer
75
-
76
- @tg.render
77
-
78
-
79
- @tg.axis.x_min.should_not == x_min
80
- @tg.axis.x_max.should_not == x_max
81
- @tg.axis.y_min.should_not == y_min
82
- @tg.axis.y_max.should_not == y_max
83
-
84
- @tg.axis.x_min.should == -1
85
- @tg.axis.x_max.should == 10
86
- @tg.axis.y_min.should == -8.0
87
- @tg.axis.y_max.should == 10.0
88
-
89
-
90
-
91
- # adding another layer
92
-
93
- # adding simple layer
94
- layer_data = [
95
- { :x => -21, :y => -93.0 },
96
- { :x => -5, :y => 3.0 },
97
- { :x => 39, :y => -8.0 },
98
- { :x => 0, :y => 333.0 },
99
- { :x => 10, :y => 50.0 }
100
- ]
101
- @tg.add_layer(layer_data)
102
- # should be added
103
- @tg.layers.last.data.size > 1
104
-
105
- @tg.render
106
-
107
- @tg.axis.x_min.should_not == x_min
108
- @tg.axis.x_max.should_not == x_max
109
- @tg.axis.y_min.should_not == y_min
110
- @tg.axis.y_max.should_not == y_max
111
-
112
- @tg.axis.x_min.should_not == -1.0
113
- @tg.axis.x_max.should_not == 10.0
114
- @tg.axis.y_min.should_not == -8.0
115
- @tg.axis.y_max.should_not == 10.0
116
-
117
- @tg.axis.x_min.should == -21.0
118
- @tg.axis.x_max.should == 39.0
119
- @tg.axis.y_min.should == -93.0
120
- @tg.axis.y_max.should == 333.0
121
- end
122
-
123
- should 'provide ranges calculated with zoom' do
124
- x_min = 0.0
125
- x_max = 1.0
126
- y_min = -1.0
127
- y_max = 1.0
128
-
129
- @tg = TechnicalGraph.new(
130
- {
131
- :x_min => x_min,
132
- :x_max => x_max,
133
- :y_min => y_min,
134
- :y_max => y_max,
135
- :xy_behaviour => :default
136
- }
137
- )
138
-
139
- # adding simple layer
140
- layer_data = [
141
- { :x => -5, :y => 5.0 },
142
- { :x => 2, :y => -5.0 },
143
- { :x => 0, :y => 5.0 },
144
- { :x => 5, :y => 5.0 }
145
- ]
146
- @tg.add_layer(layer_data)
147
- # should be added
148
- @tg.layers.last.data.size > 0
149
- # checking ranger for layer
150
-
151
- @tg.render
152
-
153
-
154
- @tg.axis.x_min.should_not == x_min
155
- @tg.axis.x_max.should_not == x_max
156
- @tg.axis.y_min.should_not == y_min
157
- @tg.axis.y_max.should_not == y_max
158
-
159
- @tg.axis.x_min.should == -5
160
- @tg.axis.x_max.should == 5
161
- @tg.axis.y_min.should == -5.0
162
- @tg.axis.y_max.should == 5.0
163
-
164
- @tg.axis.zoom = 2.0
165
-
166
- @tg.axis.x_min.should == -10.0
167
- @tg.axis.x_max.should == 10.0
168
- @tg.axis.y_min.should == -10.0
169
- @tg.axis.y_max.should == 10.0
170
-
171
- @tg.axis.x_min.should_not == -5
172
- @tg.axis.x_max.should_not == 5
173
- @tg.axis.y_min.should_not == -5.0
174
- @tg.axis.y_max.should_not == 5.0
175
-
176
- @tg.axis.raw_x_min.should == -5
177
- @tg.axis.raw_x_max.should == 5
178
- @tg.axis.raw_y_min.should == -5.0
179
- @tg.axis.raw_y_max.should == 5.0
180
- end
181
-
182
-
183
- should 'calculate axis with fixed interval' do
184
- x_min = -5.0
185
- x_max = 5.0
186
- y_min = -5.0
187
- y_max = 5.0
188
-
189
- @tg = TechnicalGraph.new(
190
- {
191
- :x_min => x_min,
192
- :x_max => x_max,
193
- :y_min => y_min,
194
- :y_max => y_max,
195
- :xy_behaviour => :fixed,
196
-
197
- :y_axis_count => 10,
198
- :x_axis_count => 10,
199
- :y_axis_interval => 1.0,
200
- :x_axis_interval => 4.0,
201
- :x_axis_fixed_interval => true,
202
- :y_axis_fixed_interval => true
203
- }
204
- )
205
-
206
- # adding simple layer
207
- layer_data = [
208
- { :x => -1, :y => 2.0 },
209
- { :x => 1, :y => -2.0 },
210
- { :x => 0, :y => 2.0 },
211
- { :x => 1, :y => 2.0 }
212
- ]
213
- @tg.add_layer(layer_data)
214
- # should be added
215
- @tg.layers.last.data.size > 0
216
- # checking ranger for layer
217
-
218
- @tg.render
219
-
220
- @tg.axis.value_axis.should == [-5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0]
221
- @tg.axis.parameter_axis.should == [-5.0, -1.0, 3.0]
222
- end
223
-
224
-
225
- should 'calculate axis with fixed count' do
226
- x_min = -8.0
227
- x_max = 8.0
228
- y_min = -4.0
229
- y_max = 4.0
230
-
231
- @tg = TechnicalGraph.new(
232
- {
233
- :x_min => x_min,
234
- :x_max => x_max,
235
- :y_min => y_min,
236
- :y_max => y_max,
237
- :xy_behaviour => :fixed,
238
-
239
- :x_axis_count => 8,
240
- :y_axis_count => 4,
241
- :x_axis_interval => 2.0,
242
- :y_axis_interval => 1.0,
243
- :x_axis_fixed_interval => false,
244
- :y_axis_fixed_interval => false
245
- }
246
- )
247
-
248
- # adding simple layer
249
- layer_data = [
250
- { :x => -1, :y => 2.0 },
251
- { :x => 1, :y => -2.0 },
252
- { :x => 0, :y => 2.0 },
253
- { :x => 1, :y => 2.0 }
254
- ]
255
- @tg.add_layer(layer_data)
256
- # should be added
257
- @tg.layers.last.data.size > 0
258
- # checking ranger for layer
259
-
260
- @tg.render
261
-
262
- @tg.axis.parameter_axis.should == [-8.0, -6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0]
263
- @tg.axis.value_axis.should == [-4.0, -2.0, 0.0, 2.0]
264
-
265
- @tg.image.save_to_file('test1.png')
266
- end
267
-
268
-
269
- should 'draw simple graph' do
270
- @tg = TechnicalGraph.new(
271
- {
272
- :x_axis_count => 10,
273
- :y_axis_count => 10,
274
- :x_axis_interval => 1.0,
275
- :y_axis_interval => 1.0,
276
- :x_axis_fixed_interval => false,
277
- :y_axis_fixed_interval => false,
278
-
279
- :x_min => -10.0,
280
- :x_max => 10.0,
281
- :y_min => -10.0,
282
- :y_max => 10.0
283
- }
284
- )
285
-
286
-
287
-
288
- # adding simple layer
289
- layer_data = Array.new
290
- (0..20).each do |i|
291
- layer_data << {:x => Math.sin(i.to_f/10.0) * 10.0, :y => Math.cos(i.to_f/10.0) * 10.0 }
292
- end
293
- @tg.add_layer(layer_data)
294
- # should be added
295
- @tg.layers.last.data.size > 0
296
- # checking ranger for layer
297
-
298
- @tg.render
299
-
300
- @tg.image.save_to_file('test2.png')
301
- end
302
-
303
- end
304
-
305
- end
306
-
data/samples/1.png DELETED
Binary file
Binary file
@@ -1,84 +0,0 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
5
-
6
- Gem::Specification.new do |s|
7
- s.name = %q{technical_graph}
8
- s.version = "0.1.1"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Aleksander Kwiatkowski"]
12
- s.date = %q{2011-10-09}
13
- s.description = %q{Purpose of this gem is to create neat, simple, technical graphs. This is alternative to most new libraries which create small, candy graphs using JavaScript.}
14
- s.email = %q{bobikx@poczta.fm}
15
- s.extra_rdoc_files = [
16
- "LICENSE.txt",
17
- "README.md"
18
- ]
19
- s.files = [
20
- ".document",
21
- ".idea/dictionaries/olek.xml",
22
- ".rvmrc",
23
- "Gemfile",
24
- "Gemfile.lock",
25
- "LICENSE.txt",
26
- "README.md",
27
- "Rakefile",
28
- "VERSION",
29
- "lib/technical_graph.rb",
30
- "lib/technical_graph/data_layer.rb",
31
- "lib/technical_graph/graph_axis.rb",
32
- "lib/technical_graph/graph_data_processor.rb",
33
- "lib/technical_graph/graph_image_drawer.rb",
34
- "lib/technical_graph/refactoring_backup/lib/technical_graph.rb",
35
- "lib/technical_graph/refactoring_backup/lib/technical_graph/axis_layer.rb",
36
- "lib/technical_graph/refactoring_backup/lib/technical_graph/axis_layer_draw_module.rb",
37
- "lib/technical_graph/refactoring_backup/test/test_technical_graph.rb",
38
- "lib/technical_graph/refactoring_backup/test/test_technical_graph_axis.rb",
39
- "samples/1.png",
40
- "samples/home_io_batt_voltage.png",
41
- "technical_graph.gemspec",
42
- "test/helper.rb",
43
- "test/test_technical_axis_enlarge.rb",
44
- "test/test_technical_graph.rb",
45
- "test/test_technical_graph_axis.rb",
46
- "test/test_technical_simple_graph.rb"
47
- ]
48
- s.homepage = %q{http://github.com/akwiatkowski/technical_graph}
49
- s.licenses = ["LGPLv3"]
50
- s.require_paths = ["lib"]
51
- s.rubygems_version = %q{1.6.2}
52
- s.summary = %q{Create simple and neat graphs}
53
-
54
- if s.respond_to? :specification_version then
55
- s.specification_version = 3
56
-
57
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
- s.add_runtime_dependency(%q<rmagick>, [">= 0"])
59
- s.add_development_dependency(%q<jeweler>, [">= 0"])
60
- s.add_development_dependency(%q<shoulda>, [">= 0"])
61
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
62
- s.add_development_dependency(%q<rspec>, [">= 0"])
63
- s.add_development_dependency(%q<jeweler>, [">= 0"])
64
- s.add_development_dependency(%q<rcov>, [">= 0"])
65
- else
66
- s.add_dependency(%q<rmagick>, [">= 0"])
67
- s.add_dependency(%q<jeweler>, [">= 0"])
68
- s.add_dependency(%q<shoulda>, [">= 0"])
69
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
70
- s.add_dependency(%q<rspec>, [">= 0"])
71
- s.add_dependency(%q<jeweler>, [">= 0"])
72
- s.add_dependency(%q<rcov>, [">= 0"])
73
- end
74
- else
75
- s.add_dependency(%q<rmagick>, [">= 0"])
76
- s.add_dependency(%q<jeweler>, [">= 0"])
77
- s.add_dependency(%q<shoulda>, [">= 0"])
78
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
79
- s.add_dependency(%q<rspec>, [">= 0"])
80
- s.add_dependency(%q<jeweler>, [">= 0"])
81
- s.add_dependency(%q<rcov>, [">= 0"])
82
- end
83
- end
84
-