dyi 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dyi/canvas.rb ADDED
@@ -0,0 +1,202 @@
1
+ # -*- encoding: UTF-8 -*-
2
+
3
+ # Copyright (c) 2009-2011 Sound-F Co., Ltd. All rights reserved.
4
+ #
5
+ # Author:: Mamoru Yuo
6
+ #
7
+ # This file is part of DYI.
8
+ #
9
+ # DYI is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # DYI is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with DYI. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ module DYI #:nodoc:
23
+
24
+ class Canvas < GraphicalElement
25
+ IMPLEMENT_ATTRIBUTES = [:view_box, :preserve_aspect_ratio]
26
+ attr_length :width, :height
27
+ attr_reader *IMPLEMENT_ATTRIBUTES
28
+ attr_reader :child_elements
29
+ # @since 1.0.0
30
+ attr_reader :event_listeners, :stylesheets
31
+
32
+ def initialize(width, height,
33
+ real_width = nil, real_height = nil,
34
+ preserve_aspect_ratio='none', options={})
35
+ self.width = width
36
+ self.height = height
37
+ @view_box = "0 0 #{width} #{height}"
38
+ @preserve_aspect_ratio = preserve_aspect_ratio
39
+ @child_elements = []
40
+ @scripts = []
41
+ @event_listeners = {}
42
+ @stylesheets = []
43
+ @seed_of_id = -1
44
+ @receive_event = false
45
+ self.css_class = options[:css_class]
46
+ self.real_width = real_width
47
+ self.real_height = real_height
48
+ end
49
+
50
+ def real_width
51
+ @real_width || width
52
+ end
53
+
54
+ def real_width=(width)
55
+ @real_width = Length.new_or_nil(width)
56
+ end
57
+
58
+ def real_height
59
+ @real_height || height
60
+ end
61
+
62
+ def real_height=(height)
63
+ @real_height = Length.new_or_nil(height)
64
+ end
65
+
66
+ # This method is depricated; use Canvas#root_element?
67
+ # @deprecated
68
+ def root_node?
69
+ msg = [__FILE__, __LINE__, ' waring']
70
+ msg << ' DYI::Canvas#root_node? is depricated; use DYI::Canvas#root_element?'
71
+ warn(msg.join(':'))
72
+ true
73
+ end
74
+
75
+ # @since 1.0.0
76
+ def root_element?
77
+ true
78
+ end
79
+
80
+ # Returns the canvas where the shape is drawn
81
+ # @return [Canvas] the canvas where the shape is drawn
82
+ # @since 1.0.0
83
+ def canvas
84
+ self
85
+ end
86
+
87
+ # @since 1.0.0
88
+ def event_listeners
89
+ @event_listeners ||= {}
90
+ end
91
+
92
+ def write_as(formatter, io=$>)
93
+ formatter.write_canvas(self, io)
94
+ end
95
+
96
+ def save(file_name, format=nil, options={})
97
+ get_formatter(format).save(file_name, options)
98
+ end
99
+
100
+ def puts_in_io(format=nil, io=$>)
101
+ get_formatter(format).puts(io)
102
+ end
103
+
104
+ def string(format=nil)
105
+ get_formatter(format).string
106
+ end
107
+
108
+ def attributes #:nodoc:
109
+ IMPLEMENT_ATTRIBUTES.inject({}) do |hash, attribute|
110
+ variable_name = '@' + attribute.to_s.split(/(?=[A-Z])/).map{|str| str.downcase}.join('_')
111
+ value = instance_variable_get(variable_name)
112
+ hash[attribute] = value.to_s if value
113
+ hash
114
+ end
115
+ end
116
+
117
+ # Create a new id for a descendant element
118
+ # @return [String] new id for a descendant element
119
+ # @since 1.0.0
120
+ def publish_shape_id
121
+ 'elm%04d' % (@seed_of_id += 1)
122
+ end
123
+
124
+ # @since 1.0.0
125
+ def set_event(event)
126
+ super
127
+ @receive_event = true
128
+ end
129
+
130
+ # @return [Boolean] whether event is set to the shape
131
+ # @since 1.0.0
132
+ def receive_event?
133
+ @receive_event
134
+ end
135
+
136
+ # @since 1.0.0
137
+ def scripts
138
+ @init_script ? [@init_script].push(*@scripts) : @scripts
139
+ end
140
+
141
+ # @since 1.0.0
142
+ def add_script(script_body, content_type = 'application/ecmascript')
143
+ @scripts << Script::SimpleScript.new(script_body, content_type)
144
+ end
145
+
146
+ # @since 1.0.0
147
+ def reference_script_file(reference_path, content_type = 'application/ecmascript')
148
+ @scripts << Script::ScriptReference.new(reference_path, content_type)
149
+ end
150
+
151
+ # @since 1.0.0
152
+ def add_stylesheet(style_body, content_type = 'text/css')
153
+ @stylesheets << Stylesheet::Style.new(style_body, content_type)
154
+ end
155
+
156
+ # @since 1.0.0
157
+ def reference_stylesheet_file(reference_path, content_type = 'text/css')
158
+ @stylesheets << Stylesheet::StyleReference.new(reference_path, content_type)
159
+ end
160
+
161
+ # @since 1.0.0
162
+ def add_initialize_script(script_body)
163
+ if @init_script
164
+ @init_script = Script::EcmaScript::EventListener.new(
165
+ @init_script.instance_variable_get(:@body) + script_body, 'init')
166
+ else
167
+ @init_script = Script::EcmaScript::EventListener.new(script_body, 'init')
168
+ add_event_listener(:load, @init_script)
169
+ end
170
+ end
171
+
172
+ # @since 1.0.0
173
+ def add_event_listener(event_name, event_listener)
174
+ if event_listeners.key?(event_name)
175
+ unless event_listeners[event_name].include?(event_listener)
176
+ event_listeners[event_name] << event_listener
177
+ end
178
+ else
179
+ event_listeners[event_name] = [event_listener]
180
+ end
181
+ end
182
+
183
+ # @since 1.0.0
184
+ def remove_event_listener(event_name, event_listener)
185
+ if event_listeners.key?(event_name)
186
+ event_listeners[event_name].delete(event_listener)
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ def get_formatter(format=nil) #:nodoc:
193
+ case format
194
+ when :svg, nil then Formatter::SvgFormatter.new(self, 2)
195
+ when :xaml then Formatter::XamlFormatter.new(self, 2)
196
+ when :eps then Formatter::EpsFormatter.new(self)
197
+ when :png then Formatter::PngFormatter.new(self)
198
+ else raise ArgumentError, "`#{format}' is unknown format"
199
+ end
200
+ end
201
+ end
202
+ end
data/lib/dyi/chart.rb CHANGED
@@ -22,6 +22,8 @@
22
22
  %w(
23
23
 
24
24
  base
25
+ legend
26
+ axis_util
25
27
  line_chart
26
28
  pie_chart
27
29
  table
@@ -23,95 +23,94 @@ module DYI #:nodoc:
23
23
  module Chart #:nodoc:
24
24
 
25
25
  class ArrayReader
26
+ include Enumerable
26
27
 
27
28
  def [](i, j)
28
- @data[i][j]
29
+ @records[i].values[j]
29
30
  end
30
31
 
31
- def row_title(i)
32
- @row_titles[i]
32
+ # @return [Array] array of a struct
33
+ # @since 1.0.0
34
+ def records
35
+ @records.dup
33
36
  end
34
37
 
35
- def column_title(j)
36
- @col_titles[j]
38
+ # @return [Integer] number of the records
39
+ # @since 1.0.0
40
+ def records_size
41
+ @records.size
37
42
  end
38
43
 
39
- def row_values(i)
40
- @data[i].dup
44
+ # @return [Integer] number of the values
45
+ # @since 1.0.0
46
+ def values_size
47
+ @records.first.values.size rescue 0
41
48
  end
42
49
 
43
- def column_values(j)
44
- @data.map{|r| r[j]}
45
- end
46
-
47
- def row_count
48
- @data.size
49
- end
50
-
51
- def column_count
52
- @data.first.size
50
+ def clear_data
51
+ @records.clear
53
52
  end
54
53
 
55
- def clear_data
56
- @data.clear
57
- @col_titles.clear
58
- @row_titles.clear
54
+ # @return [void]
55
+ # @since 1.0.0
56
+ def values_each(&block)
57
+ @records.each do |record|
58
+ yield record.values
59
+ end
59
60
  end
60
61
 
61
- def values
62
- @data.dup
62
+ # @param [Integer] index index of series
63
+ # @return [Array]
64
+ # @since 1.0.0
65
+ def series(index)
66
+ @records.map do |record|
67
+ record.values[index]
68
+ end
63
69
  end
64
70
 
65
- def column_titles
66
- @col_titles.dup
71
+ # @since 1.0.0
72
+ def has_field?(field_name)
73
+ @schema.members.include?(field_name.to_s)
67
74
  end
68
75
 
69
- def row_titles
70
- @row_titles.dup
76
+ # @return [void]
77
+ # @since 1.0.0
78
+ def each(&block)
79
+ @records.each(&block)
71
80
  end
72
81
 
73
82
  def initialize
74
- @data = []
75
- @col_titles = []
76
- @row_titles = []
83
+ @records = []
77
84
  end
78
85
 
79
86
  def read(array_of_array, options={})
80
87
  clear_data
88
+ row_range = options[:row_range] || (0..-1)
89
+ col_range = options[:column_range] || (0..-1)
90
+ schema = options[:schema] || [:value]
81
91
  data_types = options[:data_types] || []
82
- row_skip = (options[:row_skip].to_i rescue 0)
83
- col_skip = (options[:column_skip].to_i rescue 0)
84
- title_row = ((options[:title_row] ? options[:title_row].to_i : nil) rescue nil)
85
- title_col = ((options[:title_column] ? options[:title_column].to_i : nil) rescue nil)
86
- row_limit = ((options[:row_limit] ? options[:row_limit].to_i : nil) rescue nil)
87
- row_proc = options[:row_proc]
88
- array_of_array.each_with_index do |row, i|
89
- unless options[:transposed]
90
- if i == title_row
91
- @col_titles.replace(row[col_skip..-1].map{|v| primitive_title_value(v)})
92
- end
93
- next if i < row_skip
94
- break if row_limit && row_limit + row_skip <= i
95
- next if row_proc.respond_to?(:call) && !row_proc.call(*row[col_skip..-1])
96
- @row_titles << primitive_title_value(row[title_col]) if title_col
97
- vals = []
98
- row[col_skip..-1].each_with_index do |value, j|
99
- vals << primitive_value(value, data_types[j])
100
- end
101
- @data << vals
102
- else
103
- row_limit_number = row_limit ? row_limit + row_skip - 1 : -1
104
- if i == title_col
105
- @row_titles.replace(row[row_skip..row_limit_number].map{|v| primitive_title_value(v)})
106
- end
107
- next if i < col_skip
108
- vals = row[row_skip..row_limit_number]
109
- @data.replace(vals.map{|value| []}) if @data.empty?
110
- @col_titles << primitive_title_value(row[title_row]) if title_row
111
- vals.each_with_index do |value, j|
112
- @data[j] << primitive_value(value, data_types[i])
92
+ # row_proc = options[:row_proc]
93
+ @schema = record_schema(schema)
94
+ array_of_array = transpose(array_of_array) if options[:transposed]
95
+
96
+ array_of_array[row_range].each do |row|
97
+ record_source = []
98
+ values = []
99
+ has_set_value = false
100
+ row[col_range].each_with_index do |cell, i|
101
+ cell = primitive_value(cell, data_types[i])
102
+ if schema[i].nil? || schema[i].to_sym == :value
103
+ unless has_set_value
104
+ record_source << cell
105
+ has_set_value = true
106
+ end
107
+ values << cell
108
+ else
109
+ record_source << cell
113
110
  end
114
111
  end
112
+ record_source << values
113
+ @records << @schema.new(*record_source)
115
114
  end
116
115
  self
117
116
  end
@@ -122,8 +121,58 @@ module DYI #:nodoc:
122
121
  value
123
122
  end
124
123
 
125
- def primitive_title_value(value, type=nil)
126
- primitive_value(value, type)
124
+ # Transposes row and column of array-of-array.
125
+ # @example
126
+ # transpose([[0,1,2],[3,4,5]]) => [[0,3],[1,4],[2,5]]
127
+ # @param [Array] array_of_array array of array
128
+ # @return [Array] transposed array
129
+ # @since 1.0.0
130
+ def transpose(array_of_array)
131
+ transposed_array = []
132
+ array_of_array.each_with_index do |row, i|
133
+ row.each_with_index do |cell, j|
134
+ transposed_array[j] ||= Array.new(i)
135
+ transposed_array[j] << cell
136
+ end
137
+ end
138
+ transposed_array
139
+ end
140
+
141
+ # @param [Array] schema of the record
142
+ # @return [Class] subclass of Struct class
143
+ # @since 1.0.0
144
+ def record_schema(schema)
145
+ struct_schema =
146
+ schema.inject([]) do |result, name|
147
+ if result.include?(name.to_sym)
148
+ if name.to_sym == :value
149
+ next result
150
+ else
151
+ raise ArgumentError, "schema option has a duplicate name: `#{name}'"
152
+ end
153
+ end
154
+ if name.to_sym == :values
155
+ raise ArgumentError, "schema option may not contain `:values'"
156
+ end
157
+ result << name.to_sym
158
+ end
159
+ struct_schema << :values
160
+ Struct.new(*struct_schema)
161
+ end
162
+
163
+ # Makes the instance respond to xxx_values method.
164
+ # @example
165
+ # data = ArrayReader.read([['Smith', 20, 3432], ['Thomas', 25, 9721]],
166
+ # :schema => [:name, :age, :value])
167
+ # data.name_vlaues # => ['Smith', 'Thomas']
168
+ # data.age_values # => [20, 25]
169
+ # @since 1.0.0
170
+ def method_missing(name, *args)
171
+ if args.size == 0 && name.to_s =~ /_values\z/ && @schema.members.include?($`)
172
+ @records.map{|r| r.__send__($`)}
173
+ else
174
+ super
175
+ end
127
176
  end
128
177
 
129
178
  class << self
@@ -0,0 +1,298 @@
1
+ # -*- encoding: UTF-8 -*-
2
+
3
+ # Copyright (c) 2009-2011 Sound-F Co., Ltd. All rights reserved.
4
+ #
5
+ # Author:: Mamoru Yuo
6
+ #
7
+ # This file is part of DYI.
8
+ #
9
+ # DYI is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # DYI is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with DYI. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ require 'csv'
23
+
24
+ module DYI #:nodoc:
25
+ module Chart #:nodoc:
26
+
27
+ module AxisUtil
28
+
29
+ private
30
+
31
+ def moderate_axis(data, axis_length, min=nil, max=nil, scale_count_limit=nil)
32
+ raise ArgumentError, 'no data' if (data = data.flatten.compact).empty?
33
+
34
+ axis_length = Length.new(axis_length)
35
+ data_min, data_max = chart_range(data, min, max)
36
+
37
+ base_value = base_value(data_min, data_max, min.nil?, max.nil?)
38
+ scale_count_limit ||= (axis_length / Length.new(30)).to_i
39
+ scale_count_limit = 10 if 10 < scale_count_limit
40
+ scale_count_limit = 2 if scale_count_limit < 2
41
+ scale_interval = scale_interval(base_value, data_min, data_max, scale_count_limit)
42
+ min_scale_value = nil
43
+ (base_value + scale_interval).step(data_min, -scale_interval) {|n| min_scale_value = n}
44
+ min ||= (min_scale_value.nil? ? base_value : min_scale_value - scale_interval)
45
+ min_scale_value ||= min + scale_interval
46
+ unless max
47
+ base_value.step(data_max, scale_interval) {|n| max = n}
48
+ max += scale_interval if max < data_max
49
+ end
50
+ {
51
+ :min => min || min_scale_value - scale_interval,
52
+ :max => max,
53
+ :axis_length => axis_length,
54
+ :min_scale_value => min_scale_value,
55
+ :scale_interval => scale_interval
56
+ }
57
+ end
58
+
59
+ def top_digit(num, n=1) #:nodoc:
60
+ num.div(10 ** (figures_count(num) - n + 1))
61
+ end
62
+
63
+ def suitable_1digit_value(a, b) #:nodoc:
64
+ return a if a == b
65
+ a, b = b, a if a > b
66
+ return 0 if a == 0
67
+ return 5 if a <= 5 && 5 <= b
68
+ return 2 if a <= 2 && 2 <= b
69
+ return 4 if a <= 4 && 4 <= b
70
+ return 6 if a <= 6 && 6 <= b
71
+ 8
72
+ end
73
+
74
+ def figures_count(num) #:nodoc:
75
+ Math.log10(num).floor
76
+ end
77
+
78
+ def base_value(a, b, allow_under=true, allow_over=true) #:nodoc:
79
+ return 0 if a * b <= 0 || a == b
80
+ a, b = -a, -b if negative = (a < 0)
81
+ a, b = b, a if a > b
82
+ return 0 if ((negative && allow_over) || (!negative && allow_under)) && a < b * 0.3
83
+ suitable_value_positive(a, b) * (negative ? -1 : 1)
84
+ end
85
+
86
+ def suitable_value_positive(a, b) #:nodoc:
87
+ if figures_count(a) != (dig = figures_count(b))
88
+ return 10 ** dig
89
+ end
90
+ n = 1
91
+ n += 1 while (dig_a = top_digit(a, n)) == (dig_b = top_digit(b, n))
92
+ (suitable_1digit_value(dig_a - dig_a.div(10) * 10 + (dig_a == dig_a.div(10) * 10 ? 0 : 1), dig_b - dig_b.div(10) * 10) + dig_a.div(10) * 10) * (10 ** (dig - figures_count(dig_a)))
93
+ end
94
+
95
+ def scale_interval(base_value, data_min, data_max, scale_count_limit) #:nodoc:
96
+ if base_value - data_min < data_max - base_value
97
+ allocate_scale_count = (data_max - base_value).div((data_max - data_min).quo(scale_count_limit))
98
+ scale_interval_base2edge(base_value, data_max, allocate_scale_count)
99
+ else
100
+ allocate_scale_count = (base_value - data_min).div((data_max - data_min).quo(scale_count_limit))
101
+ scale_interval_base2edge(base_value, data_min, allocate_scale_count)
102
+ end
103
+ end
104
+
105
+ def scale_interval_base2edge(base_value, edge_value, scale_count_limit) #:nodoc:
106
+ raise ArgumentError, 'base_value should not equal edge_value' if edge_value == base_value
107
+ range = (base_value - edge_value).abs
108
+
109
+ top_2_digits = top_digit(range, 2)
110
+ case scale_count_limit.to_i
111
+ when 1
112
+ case top_2_digits
113
+ when 10 then label_range = 10
114
+ when 11..20 then label_range = 20
115
+ when 21..40 then label_range = 40
116
+ when 41..50 then label_range = 50
117
+ when 51..99 then label_range = 100
118
+ end
119
+ when 2
120
+ case top_2_digits
121
+ when 10 then label_range = 5
122
+ when 11..20 then label_range = 10
123
+ when 21..40 then label_range = 20
124
+ when 41..50 then label_range = 25
125
+ when 51..99 then label_range = 50
126
+ end
127
+ when 3
128
+ case top_2_digits
129
+ when 10 then label_range = 4
130
+ when 11..15 then label_range = 5
131
+ when 16..30 then label_range = 10
132
+ when 31..60 then label_range = 20
133
+ when 61..75 then label_range = 25
134
+ when 76..99 then label_range = 40
135
+ end
136
+ when 4
137
+ case top_2_digits
138
+ when 10 then label_range = 2.5
139
+ when 11..15 then label_range = 4
140
+ when 16..20 then label_range = 5
141
+ when 21..40 then label_range = 10
142
+ when 41..80 then label_range = 20
143
+ when 81..99 then label_range = 25
144
+ end
145
+ when 5
146
+ case top_2_digits
147
+ when 10 then label_range = 2
148
+ when 11..12 then label_range = 2.5
149
+ when 13..15 then label_range = 4
150
+ when 16..25 then label_range = 5
151
+ when 26..50 then label_range = 10
152
+ when 51..99 then label_range = 20
153
+ end
154
+ when 6
155
+ case top_2_digits
156
+ when 10..12 then label_range = 2
157
+ when 13..15 then label_range = 2.5
158
+ when 16..20 then label_range = 4
159
+ when 21..30 then label_range = 5
160
+ when 31..60 then label_range = 10
161
+ when 61..99 then label_range = 20
162
+ end
163
+ when 7
164
+ case top_2_digits
165
+ when 10..14 then label_range = 2
166
+ when 15..17 then label_range = 2.5
167
+ when 18..20 then label_range = 4
168
+ when 21..35 then label_range = 5
169
+ when 35..70 then label_range = 10
170
+ when 71..99 then label_range = 20
171
+ end
172
+ when 8
173
+ case top_2_digits
174
+ when 10..16 then label_range = 2
175
+ when 17..20 then label_range = 2.5
176
+ when 21..25 then label_range = 4
177
+ when 26..40 then label_range = 5
178
+ when 41..80 then label_range = 10
179
+ when 81..99 then label_range = 20
180
+ end
181
+ when 9
182
+ case top_2_digits
183
+ when 10..18 then label_range = 2
184
+ when 19..22 then label_range = 2.5
185
+ when 23..30 then label_range = 4
186
+ when 31..45 then label_range = 5
187
+ when 46..90 then label_range = 10
188
+ when 91..99 then label_range = 20
189
+ end
190
+ else
191
+ case top_2_digits
192
+ when 10 then label_range = 1
193
+ when 11..20 then label_range = 2
194
+ when 21..25 then label_range = 2.5
195
+ when 26..30 then label_range = 4
196
+ when 31..50 then label_range = 5
197
+ when 51..99 then label_range = 10
198
+ end
199
+ end
200
+ label_range * (10 ** (figures_count(range) - 1))
201
+ end
202
+
203
+ def moderate_sub_axis(data, main_axis_settings, min=nil, max=nil)
204
+ if min && max
205
+ axis_ratio = (max - min).quo(main_axis_settings[:max] - main_axis_settings[:min])
206
+ return {
207
+ :max => max,
208
+ :min => min,
209
+ :min_scale_value => min + (main_axis_settings[:min_scale_value] - main_axis_settings[:min]) * axis_ratio,
210
+ :axis_length => main_axis_settings[:axis_length],
211
+ :scale_interval => main_axis_settings[:scale_interval] * axis_ratio
212
+ }
213
+ end
214
+ scale_count = (main_axis_settings[:max] - main_axis_settings[:min_scale_value]).div(main_axis_settings[:scale_interval]) + (main_axis_settings[:min_scale_value] == main_axis_settings[:min] ? 0 : 1)
215
+ data_min, data_max = chart_range(data, min, max)
216
+
217
+ base_value = base_value(data_min, data_max, min.nil?, max.nil?)
218
+
219
+ scale_interval = scale_interval(base_value, data_min, data_max, scale_count)
220
+ min_scale_value = nil
221
+ (base_value + scale_interval).step(data_min, -scale_interval) {|n| min_scale_value = n}
222
+ min ||= (min_scale_value.nil? ? base_value : min_scale_value - scale_interval)
223
+ min_scale_value ||= min + scale_interval
224
+ max = scale_interval * scale_count + min
225
+
226
+ {
227
+ :min => min || min_scale_value - scale_interval,
228
+ :max => max,
229
+ :axis_length => main_axis_settings[:axis_length],
230
+ :min_scale_value => min_scale_value,
231
+ :scale_interval => scale_interval
232
+ }
233
+ end
234
+
235
+ def chart_range(data, min=nil, max=nil)
236
+ data = data.compact.flatten
237
+ if min.nil? && max.nil?
238
+ data_min, data_max =
239
+ data.inject([nil, nil]) do |(_min, _max), value|
240
+ [value < (_min ||= value) ? value : _min, (_max ||= value) < value ? value : _max]
241
+ end
242
+ elsif min && max && max < min
243
+ data_min, data_max = max, min
244
+ else
245
+ data_min = min || [data.min, max].min
246
+ data_max = max || [data.max, min].max
247
+ end
248
+
249
+ if data_min == data_max
250
+ if data_min > 0
251
+ data_min = 0
252
+ elsif data_max < 0
253
+ data_max = 0
254
+ else
255
+ data_min = 0
256
+ data_max = 100
257
+ end
258
+ end
259
+ [data_min, data_max]
260
+ end
261
+
262
+ def value_position_on_chart(chart_margin, axis_settings, value, reverse_direction = false)
263
+ axis_settings[:axis_length] *
264
+ ((reverse_direction ? (axis_settings[:max] - value) : (value - axis_settings[:min])).to_f / (axis_settings[:max] - axis_settings[:min])) +
265
+ Length.new(chart_margin)
266
+ end
267
+
268
+ def order_position_on_chart(chart_margin, axis_length, count, index, type=:point, renge_width_ratio=0, reverse_direction=false)
269
+ chart_margin = Length.new(chart_margin)
270
+ pos =
271
+ case type
272
+ when :point then index.to_f / (count - 1)
273
+ when :range then (index + 0.5 - renge_width_ratio.to_f / 2) / count
274
+ else raise ArgumentError, "\"#{type}\" is invalid type"
275
+ end
276
+ axis_length * pos + chart_margin
277
+ end
278
+
279
+ def round_top_2_digit(max, min) #:nodoc:
280
+ digit = Math.log10([max.abs, min.abs].max).floor - 1
281
+ [max.quo(10 ** digit).ceil * (10 ** digit), min.quo(10 ** digit).floor * (10 ** digit)]
282
+ end
283
+
284
+ def min_scale_value(max, min, scale_interval) #:nodoc:
285
+ return scale_interval if min == 0
286
+ if (max_digit = Math.log10(max).to_i) != Math.log10(min).to_i
287
+ base_value = 10 ** max_digit
288
+ elsif max.div(10 ** max_digit) != min.div(10 ** max_digit)
289
+ base_value = 9 * 10 ** max_digit
290
+ else
291
+ range_digit = Math.log10(max - min).floor
292
+ base_value = max.div(10 ** range_digit) * (10 ** range_digit)
293
+ end
294
+ base_value - ((base_value - min).quo(scale_interval).ceil - 1) * scale_interval
295
+ end
296
+ end
297
+ end
298
+ end