technical_graph 0.1.1 → 0.1.2

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/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
-