dyi 0.0.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|