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/CHANGES +13 -0
- data/examples/animation.rb +28 -0
- data/examples/class_diagram.rb +1 -1
- data/examples/css.rb +34 -0
- data/examples/line_and_bar.rb +1 -2
- data/examples/line_chart.rb +5 -6
- data/examples/logo.rb +1 -1
- data/examples/pie_chart.rb +11 -4
- data/examples/simple_shapes.rb +1 -1
- data/lib/dyi.rb +8 -2
- data/lib/dyi/animation.rb +259 -0
- data/lib/dyi/canvas.rb +202 -0
- data/lib/dyi/chart.rb +2 -0
- data/lib/dyi/chart/array_reader.rb +112 -63
- data/lib/dyi/chart/axis_util.rb +298 -0
- data/lib/dyi/chart/base.rb +188 -466
- data/lib/dyi/chart/csv_reader.rb +0 -4
- data/lib/dyi/chart/legend.rb +82 -0
- data/lib/dyi/chart/line_chart.rb +31 -23
- data/lib/dyi/chart/pie_chart.rb +237 -30
- data/lib/dyi/color.rb +1 -1
- data/lib/dyi/drawing.rb +8 -1
- data/lib/dyi/drawing/clipping.rb +14 -1
- data/lib/dyi/drawing/pen.rb +12 -2
- data/lib/dyi/element.rb +167 -0
- data/lib/dyi/event.rb +148 -0
- data/lib/dyi/formatter/base.rb +43 -3
- data/lib/dyi/formatter/eps_formatter.rb +2 -2
- data/lib/dyi/formatter/svg_formatter.rb +430 -74
- data/lib/dyi/painting.rb +6 -1
- data/lib/dyi/script.rb +29 -0
- data/lib/dyi/script/ecmascript.rb +273 -0
- data/lib/dyi/script/simple_script.rb +94 -0
- data/lib/dyi/shape.rb +5 -1331
- data/lib/dyi/shape/base.rb +612 -0
- data/lib/dyi/shape/path.rb +902 -0
- data/lib/dyi/stylesheet.rb +86 -0
- data/lib/ironruby.rb +11 -11
- metadata +17 -4
- data/lib/dyi/drawing/canvas.rb +0 -100
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
@@ -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
|
-
@
|
29
|
+
@records[i].values[j]
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
# @return [Array] array of a struct
|
33
|
+
# @since 1.0.0
|
34
|
+
def records
|
35
|
+
@records.dup
|
33
36
|
end
|
34
37
|
|
35
|
-
|
36
|
-
|
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
|
-
|
40
|
-
|
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
|
44
|
-
@
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@
|
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
|
-
|
62
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
70
|
-
|
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
|
-
@
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
126
|
-
|
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
|