dyi 0.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/COPYING +674 -0
- data/README +28 -0
- data/examples/class_diagram.rb +151 -0
- data/examples/data/03311056.xlsx +0 -0
- data/examples/data/currency.xlsx +0 -0
- data/examples/data/money.csv +12 -0
- data/examples/line_and_bar.rb +26 -0
- data/examples/line_chart.rb +30 -0
- data/examples/logo.rb +68 -0
- data/examples/pie_chart.rb +19 -0
- data/examples/simple_shapes.rb +15 -0
- data/lib/dyi.rb +49 -0
- data/lib/dyi/chart.rb +34 -0
- data/lib/dyi/chart/array_reader.rb +136 -0
- data/lib/dyi/chart/base.rb +580 -0
- data/lib/dyi/chart/csv_reader.rb +93 -0
- data/lib/dyi/chart/excel_reader.rb +100 -0
- data/lib/dyi/chart/line_chart.rb +468 -0
- data/lib/dyi/chart/pie_chart.rb +141 -0
- data/lib/dyi/chart/table.rb +201 -0
- data/lib/dyi/color.rb +218 -0
- data/lib/dyi/coordinate.rb +224 -0
- data/lib/dyi/drawing.rb +32 -0
- data/lib/dyi/drawing/canvas.rb +100 -0
- data/lib/dyi/drawing/clipping.rb +61 -0
- data/lib/dyi/drawing/color_effect.rb +118 -0
- data/lib/dyi/drawing/filter.rb +74 -0
- data/lib/dyi/drawing/pen.rb +231 -0
- data/lib/dyi/drawing/pen_3d.rb +270 -0
- data/lib/dyi/font.rb +132 -0
- data/lib/dyi/formatter.rb +36 -0
- data/lib/dyi/formatter/base.rb +245 -0
- data/lib/dyi/formatter/emf_formatter.rb +253 -0
- data/lib/dyi/formatter/eps_formatter.rb +397 -0
- data/lib/dyi/formatter/svg_formatter.rb +260 -0
- data/lib/dyi/formatter/svg_reader.rb +113 -0
- data/lib/dyi/formatter/xaml_formatter.rb +317 -0
- data/lib/dyi/length.rb +399 -0
- data/lib/dyi/matrix.rb +122 -0
- data/lib/dyi/painting.rb +177 -0
- data/lib/dyi/shape.rb +1332 -0
- data/lib/dyi/svg_element.rb +149 -0
- data/lib/dyi/type.rb +104 -0
- data/lib/ironruby.rb +326 -0
- data/lib/util.rb +231 -0
- data/test/path_command_test.rb +217 -0
- data/test/test_length.rb +91 -0
- metadata +114 -0
@@ -0,0 +1,136 @@
|
|
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
|
+
module Chart #:nodoc:
|
24
|
+
|
25
|
+
class ArrayReader
|
26
|
+
|
27
|
+
def [](i, j)
|
28
|
+
@data[i][j]
|
29
|
+
end
|
30
|
+
|
31
|
+
def row_title(i)
|
32
|
+
@row_titles[i]
|
33
|
+
end
|
34
|
+
|
35
|
+
def column_title(j)
|
36
|
+
@col_titles[j]
|
37
|
+
end
|
38
|
+
|
39
|
+
def row_values(i)
|
40
|
+
@data[i].dup
|
41
|
+
end
|
42
|
+
|
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
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear_data
|
56
|
+
@data.clear
|
57
|
+
@col_titles.clear
|
58
|
+
@row_titles.clear
|
59
|
+
end
|
60
|
+
|
61
|
+
def values
|
62
|
+
@data.dup
|
63
|
+
end
|
64
|
+
|
65
|
+
def column_titles
|
66
|
+
@col_titles.dup
|
67
|
+
end
|
68
|
+
|
69
|
+
def row_titles
|
70
|
+
@row_titles.dup
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize
|
74
|
+
@data = []
|
75
|
+
@col_titles = []
|
76
|
+
@row_titles = []
|
77
|
+
end
|
78
|
+
|
79
|
+
def read(array_of_array, options={})
|
80
|
+
clear_data
|
81
|
+
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])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def primitive_value(value, type=nil)
|
122
|
+
value
|
123
|
+
end
|
124
|
+
|
125
|
+
def primitive_title_value(value, type=nil)
|
126
|
+
primitive_value(value, type)
|
127
|
+
end
|
128
|
+
|
129
|
+
class << self
|
130
|
+
def read(array_of_array, options={})
|
131
|
+
new.read(array_of_array, options)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,580 @@
|
|
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 Legend
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def draw_legend(names, shapes=nil, records=nil, colors=nil) #:nodoc:
|
32
|
+
legend_canvas.translate(legend_point.x, legend_point.y)
|
33
|
+
if show_legend?
|
34
|
+
pen = Drawing::Pen.black_pen(:font => legend_font)
|
35
|
+
brush = Drawing::Brush.new
|
36
|
+
names.each_with_index do |name, index|
|
37
|
+
y = legend_font_size * (1.2 * (index + 1))
|
38
|
+
group = Shape::ShapeGroup.draw_on(legend_canvas)
|
39
|
+
case shapes && shapes[index]
|
40
|
+
when Shape::Base
|
41
|
+
shapes[index].draw_on(group)
|
42
|
+
when NilClass
|
43
|
+
brush.color = colors && colors[index] || chart_color(index)
|
44
|
+
brush.draw_rectangle(
|
45
|
+
group,
|
46
|
+
Coordinate.new(legend_font_size * 0.2, y - legend_font_size * 0.8),
|
47
|
+
legend_font_size * 0.8,
|
48
|
+
legend_font_size * 0.8)
|
49
|
+
end
|
50
|
+
pen.draw_text(
|
51
|
+
group,
|
52
|
+
Coordinate.new(legend_font_size * 0.2 + legend_font_size, y),
|
53
|
+
name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def legend_font_size #:nodoc:
|
59
|
+
legend_font ? legend_font.draw_size : Font::DEFAULT_SIZE
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_legend_point #:nodoc:
|
63
|
+
Coordinate.new(0,0)
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_legend_format #:nodoc:
|
67
|
+
"{name}"
|
68
|
+
end
|
69
|
+
|
70
|
+
class << self
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def included(klass) #:nodoc:
|
75
|
+
klass.__send__(:opt_accessor, :show_legend, :type => :boolean, :default => true)
|
76
|
+
klass.__send__(:opt_accessor, :legend_font, :type => :font)
|
77
|
+
klass.__send__(:opt_accessor, :legend_format, :type => :string, :default_method => :default_legend_format)
|
78
|
+
klass.__send__(:opt_accessor, :legend_point, :type => :point, :default_method => :default_legend_point)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module AxisUtil
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def moderate_axis(data, axis_length, min=nil, max=nil, scale_count_limit=nil)
|
88
|
+
raise ArgumentError, 'no data' if (data = data.flatten.compact).empty?
|
89
|
+
|
90
|
+
axis_length = Length.new(axis_length)
|
91
|
+
data_min, data_max = chart_range(data, min, max)
|
92
|
+
|
93
|
+
base_value = base_value(data_min, data_max, min.nil?, max.nil?)
|
94
|
+
scale_count_limit ||= (axis_length / Length.new(30)).to_i
|
95
|
+
scale_count_limit = 10 if 10 < scale_count_limit
|
96
|
+
scale_count_limit = 2 if scale_count_limit < 2
|
97
|
+
scale_interval = scale_interval(base_value, data_min, data_max, scale_count_limit)
|
98
|
+
min_scale_value = nil
|
99
|
+
(base_value + scale_interval).step(data_min, -scale_interval) {|n| min_scale_value = n}
|
100
|
+
min ||= (min_scale_value.nil? ? base_value : min_scale_value - scale_interval)
|
101
|
+
min_scale_value ||= min + scale_interval
|
102
|
+
unless max
|
103
|
+
base_value.step(data_max, scale_interval) {|n| max = n}
|
104
|
+
max += scale_interval if max < data_max
|
105
|
+
end
|
106
|
+
{
|
107
|
+
:min => min || min_scale_value - scale_interval,
|
108
|
+
:max => max,
|
109
|
+
:axis_length => axis_length,
|
110
|
+
:min_scale_value => min_scale_value,
|
111
|
+
:scale_interval => scale_interval
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def top_digit(num, n=1) #:nodoc:
|
116
|
+
num.div(10 ** (figures_count(num) - n + 1))
|
117
|
+
end
|
118
|
+
|
119
|
+
def suitable_1digit_value(a, b) #:nodoc:
|
120
|
+
return a if a == b
|
121
|
+
a, b = b, a if a > b
|
122
|
+
return 0 if a == 0
|
123
|
+
return 5 if a <= 5 && 5 <= b
|
124
|
+
return 2 if a <= 2 && 2 <= b
|
125
|
+
return 4 if a <= 4 && 4 <= b
|
126
|
+
return 6 if a <= 6 && 6 <= b
|
127
|
+
8
|
128
|
+
end
|
129
|
+
|
130
|
+
def figures_count(num) #:nodoc:
|
131
|
+
Math.log10(num).floor
|
132
|
+
end
|
133
|
+
|
134
|
+
def base_value(a, b, allow_under=true, allow_over=true) #:nodoc:
|
135
|
+
return 0 if a * b <= 0 || a == b
|
136
|
+
a, b = -a, -b if negative = (a < 0)
|
137
|
+
a, b = b, a if a > b
|
138
|
+
return 0 if ((negative && allow_over) || (!negative && allow_under)) && a < b * 0.3
|
139
|
+
suitable_value_positive(a, b) * (negative ? -1 : 1)
|
140
|
+
end
|
141
|
+
|
142
|
+
def suitable_value_positive(a, b) #:nodoc:
|
143
|
+
if figures_count(a) != (dig = figures_count(b))
|
144
|
+
return 10 ** dig
|
145
|
+
end
|
146
|
+
n = 1
|
147
|
+
n += 1 while (dig_a = top_digit(a, n)) == (dig_b = top_digit(b, n))
|
148
|
+
(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)))
|
149
|
+
end
|
150
|
+
|
151
|
+
def scale_interval(base_value, data_min, data_max, scale_count_limit) #:nodoc:
|
152
|
+
if base_value - data_min < data_max - base_value
|
153
|
+
allocate_scale_count = (data_max - base_value).div((data_max - data_min).quo(scale_count_limit))
|
154
|
+
scale_interval_base2edge(base_value, data_max, allocate_scale_count)
|
155
|
+
else
|
156
|
+
allocate_scale_count = (base_value - data_min).div((data_max - data_min).quo(scale_count_limit))
|
157
|
+
scale_interval_base2edge(base_value, data_min, allocate_scale_count)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def scale_interval_base2edge(base_value, edge_value, scale_count_limit) #:nodoc:
|
162
|
+
raise ArgumentError, 'base_value should not equal edge_value' if edge_value == base_value
|
163
|
+
range = (base_value - edge_value).abs
|
164
|
+
|
165
|
+
top_2_digits = top_digit(range, 2)
|
166
|
+
case scale_count_limit.to_i
|
167
|
+
when 1
|
168
|
+
case top_2_digits
|
169
|
+
when 10 then label_range = 10
|
170
|
+
when 11..20 then label_range = 20
|
171
|
+
when 21..40 then label_range = 40
|
172
|
+
when 41..50 then label_range = 50
|
173
|
+
when 51..99 then label_range = 100
|
174
|
+
end
|
175
|
+
when 2
|
176
|
+
case top_2_digits
|
177
|
+
when 10 then label_range = 5
|
178
|
+
when 11..20 then label_range = 10
|
179
|
+
when 21..40 then label_range = 20
|
180
|
+
when 41..50 then label_range = 25
|
181
|
+
when 51..99 then label_range = 50
|
182
|
+
end
|
183
|
+
when 3
|
184
|
+
case top_2_digits
|
185
|
+
when 10 then label_range = 4
|
186
|
+
when 11..15 then label_range = 5
|
187
|
+
when 16..30 then label_range = 10
|
188
|
+
when 31..60 then label_range = 20
|
189
|
+
when 61..75 then label_range = 25
|
190
|
+
when 76..99 then label_range = 40
|
191
|
+
end
|
192
|
+
when 4
|
193
|
+
case top_2_digits
|
194
|
+
when 10 then label_range = 2.5
|
195
|
+
when 11..15 then label_range = 4
|
196
|
+
when 16..20 then label_range = 5
|
197
|
+
when 21..40 then label_range = 10
|
198
|
+
when 41..80 then label_range = 20
|
199
|
+
when 81..99 then label_range = 25
|
200
|
+
end
|
201
|
+
when 5
|
202
|
+
case top_2_digits
|
203
|
+
when 10 then label_range = 2
|
204
|
+
when 11..12 then label_range = 2.5
|
205
|
+
when 13..15 then label_range = 4
|
206
|
+
when 16..25 then label_range = 5
|
207
|
+
when 26..50 then label_range = 10
|
208
|
+
when 51..99 then label_range = 20
|
209
|
+
end
|
210
|
+
when 6
|
211
|
+
case top_2_digits
|
212
|
+
when 10..12 then label_range = 2
|
213
|
+
when 13..15 then label_range = 2.5
|
214
|
+
when 16..20 then label_range = 4
|
215
|
+
when 21..30 then label_range = 5
|
216
|
+
when 31..60 then label_range = 10
|
217
|
+
when 61..99 then label_range = 20
|
218
|
+
end
|
219
|
+
when 7
|
220
|
+
case top_2_digits
|
221
|
+
when 10..14 then label_range = 2
|
222
|
+
when 15..17 then label_range = 2.5
|
223
|
+
when 18..20 then label_range = 4
|
224
|
+
when 21..35 then label_range = 5
|
225
|
+
when 35..70 then label_range = 10
|
226
|
+
when 71..99 then label_range = 20
|
227
|
+
end
|
228
|
+
when 8
|
229
|
+
case top_2_digits
|
230
|
+
when 10..16 then label_range = 2
|
231
|
+
when 17..20 then label_range = 2.5
|
232
|
+
when 21..25 then label_range = 4
|
233
|
+
when 26..40 then label_range = 5
|
234
|
+
when 41..80 then label_range = 10
|
235
|
+
when 81..99 then label_range = 20
|
236
|
+
end
|
237
|
+
when 9
|
238
|
+
case top_2_digits
|
239
|
+
when 10..18 then label_range = 2
|
240
|
+
when 19..22 then label_range = 2.5
|
241
|
+
when 23..30 then label_range = 4
|
242
|
+
when 31..45 then label_range = 5
|
243
|
+
when 46..90 then label_range = 10
|
244
|
+
when 91..99 then label_range = 20
|
245
|
+
end
|
246
|
+
else
|
247
|
+
case top_2_digits
|
248
|
+
when 10 then label_range = 1
|
249
|
+
when 11..20 then label_range = 2
|
250
|
+
when 21..25 then label_range = 2.5
|
251
|
+
when 26..30 then label_range = 4
|
252
|
+
when 31..50 then label_range = 5
|
253
|
+
when 51..99 then label_range = 10
|
254
|
+
end
|
255
|
+
end
|
256
|
+
label_range * (10 ** (figures_count(range) - 1))
|
257
|
+
end
|
258
|
+
|
259
|
+
def moderate_sub_axis(data, main_axis_settings, min=nil, max=nil)
|
260
|
+
if min && max
|
261
|
+
axis_ratio = (max - min).quo(main_axis_settings[:max] - main_axis_settings[:min])
|
262
|
+
return {
|
263
|
+
:max => max,
|
264
|
+
:min => min,
|
265
|
+
:min_scale_value => min + (main_axis_settings[:min_scale_value] - main_axis_settings[:min]) * axis_ratio,
|
266
|
+
:axis_length => main_axis_settings[:axis_length],
|
267
|
+
:scale_interval => main_axis_settings[:scale_interval] * axis_ratio
|
268
|
+
}
|
269
|
+
end
|
270
|
+
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)
|
271
|
+
data_min, data_max = chart_range(data, min, max)
|
272
|
+
|
273
|
+
base_value = base_value(data_min, data_max, min.nil?, max.nil?)
|
274
|
+
|
275
|
+
scale_interval = scale_interval(base_value, data_min, data_max, scale_count)
|
276
|
+
min_scale_value = nil
|
277
|
+
(base_value + scale_interval).step(data_min, -scale_interval) {|n| min_scale_value = n}
|
278
|
+
min ||= (min_scale_value.nil? ? base_value : min_scale_value - scale_interval)
|
279
|
+
min_scale_value ||= min + scale_interval
|
280
|
+
max = scale_interval * scale_count + min
|
281
|
+
|
282
|
+
{
|
283
|
+
:min => min || min_scale_value - scale_interval,
|
284
|
+
:max => max,
|
285
|
+
:axis_length => main_axis_settings[:axis_length],
|
286
|
+
:min_scale_value => min_scale_value,
|
287
|
+
:scale_interval => scale_interval
|
288
|
+
}
|
289
|
+
end
|
290
|
+
|
291
|
+
def chart_range(data, min=nil, max=nil)
|
292
|
+
data = data.compact.flatten
|
293
|
+
if min.nil? && max.nil?
|
294
|
+
data_min, data_max =
|
295
|
+
data.inject([nil, nil]) do |(_min, _max), value|
|
296
|
+
[value < (_min ||= value) ? value : _min, (_max ||= value) < value ? value : _max]
|
297
|
+
end
|
298
|
+
elsif min && max && max < min
|
299
|
+
data_min, data_max = max, min
|
300
|
+
else
|
301
|
+
data_min = min || [data.min, max].min
|
302
|
+
data_max = max || [data.max, min].max
|
303
|
+
end
|
304
|
+
|
305
|
+
if data_min == data_max
|
306
|
+
if data_min > 0
|
307
|
+
data_min = 0
|
308
|
+
elsif data_max < 0
|
309
|
+
data_max = 0
|
310
|
+
else
|
311
|
+
data_min = 0
|
312
|
+
data_max = 100
|
313
|
+
end
|
314
|
+
end
|
315
|
+
[data_min, data_max]
|
316
|
+
end
|
317
|
+
|
318
|
+
def value_position_on_chart(chart_margin, axis_settings, value, reverse_direction = false)
|
319
|
+
axis_settings[:axis_length] *
|
320
|
+
((reverse_direction ? (axis_settings[:max] - value) : (value - axis_settings[:min])).to_f / (axis_settings[:max] - axis_settings[:min])) +
|
321
|
+
Length.new(chart_margin)
|
322
|
+
end
|
323
|
+
|
324
|
+
def order_position_on_chart(chart_margin, axis_length, count, index, type=:point, renge_width_ratio=0, reverse_direction=false)
|
325
|
+
chart_margin = Length.new(chart_margin)
|
326
|
+
pos =
|
327
|
+
case type
|
328
|
+
when :point then index.to_f / (count - 1)
|
329
|
+
when :range then (index + 0.5 - renge_width_ratio.to_f / 2) / count
|
330
|
+
else raise ArgumentError, "\"#{type}\" is invalid type"
|
331
|
+
end
|
332
|
+
axis_length * pos + chart_margin
|
333
|
+
end
|
334
|
+
|
335
|
+
def round_top_2_digit(max, min) #:nodoc:
|
336
|
+
digit = Math.log10([max.abs, min.abs].max).floor - 1
|
337
|
+
[max.quo(10 ** digit).ceil * (10 ** digit), min.quo(10 ** digit).floor * (10 ** digit)]
|
338
|
+
end
|
339
|
+
|
340
|
+
def min_scale_value(max, min, scale_interval) #:nodoc:
|
341
|
+
return scale_interval if min == 0
|
342
|
+
if (max_digit = Math.log10(max).to_i) != Math.log10(min).to_i
|
343
|
+
base_value = 10 ** max_digit
|
344
|
+
elsif max.div(10 ** max_digit) != min.div(10 ** max_digit)
|
345
|
+
base_value = 9 * 10 ** max_digit
|
346
|
+
else
|
347
|
+
range_digit = Math.log10(max - min).floor
|
348
|
+
base_value = max.div(10 ** range_digit) * (10 ** range_digit)
|
349
|
+
end
|
350
|
+
base_value - ((base_value - min).quo(scale_interval).ceil - 1) * scale_interval
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class Base
|
355
|
+
DEFAULT_CHART_COLOR = ['#ff0f00', '#ff6600', '#ff9e01', '#fcd202', '#f8ff01', '#b0de09', '#04d215', '#0d8ecf', '#0d52d1', '#2a0cd0', '#8a0ccf', '#cd0d74']
|
356
|
+
attr_reader :options, :data, :canvas
|
357
|
+
|
358
|
+
def initialize(width, height, options={})
|
359
|
+
@canvas = Drawing::Canvas.new(width, height)
|
360
|
+
@options = {}
|
361
|
+
options.each do |key, value|
|
362
|
+
__send__("#{key}=", value) if respond_to?("#{key}=")
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def width
|
367
|
+
@canvas.width
|
368
|
+
end
|
369
|
+
|
370
|
+
def width=(width)
|
371
|
+
@canvas.width = width
|
372
|
+
end
|
373
|
+
|
374
|
+
def height
|
375
|
+
@canvas.height
|
376
|
+
end
|
377
|
+
|
378
|
+
def height=(height)
|
379
|
+
@canvas.height = height
|
380
|
+
end
|
381
|
+
|
382
|
+
def set_real_size(width, height)
|
383
|
+
@canvas.real_width = Length.new(width)
|
384
|
+
@canvas.real_height = Length.new(height)
|
385
|
+
end
|
386
|
+
|
387
|
+
def clear_real_size
|
388
|
+
@canvas.real_width = nil
|
389
|
+
@canvas.real_height = nil
|
390
|
+
end
|
391
|
+
|
392
|
+
def load_data(reader)
|
393
|
+
@data = reader
|
394
|
+
create_vector_image
|
395
|
+
end
|
396
|
+
|
397
|
+
def save(file_name, format=nil, options={})
|
398
|
+
@canvas.save(file_name, format)
|
399
|
+
end
|
400
|
+
|
401
|
+
def puts_in_io(format=nil, io=$>)
|
402
|
+
@canvas.puts_in_io(format, io)
|
403
|
+
end
|
404
|
+
|
405
|
+
def string(format=nil)
|
406
|
+
@canvas.string(format)
|
407
|
+
end
|
408
|
+
|
409
|
+
private
|
410
|
+
|
411
|
+
def options #:nodoc:
|
412
|
+
@options
|
413
|
+
end
|
414
|
+
|
415
|
+
def chart_color(index) #:nodoc:
|
416
|
+
if respond_to?(:chart_colors) && chart_colors
|
417
|
+
result = chart_colors[index]
|
418
|
+
end
|
419
|
+
result || DEFAULT_CHART_COLOR[index % DEFAULT_CHART_COLOR.size]
|
420
|
+
end
|
421
|
+
|
422
|
+
class << self
|
423
|
+
|
424
|
+
private
|
425
|
+
|
426
|
+
def opt_reader(name, settings = {})
|
427
|
+
name = name.to_sym
|
428
|
+
getter_name = settings[:type] == :boolean ? name.to_s.gsub(/^(.*[^=\?])[=\?]*$/, '\1?') : name
|
429
|
+
if settings.key?(:default)
|
430
|
+
define_method(getter_name) {@options.key?(name) ? @options[name] : settings[:default]}
|
431
|
+
elsif settings.key?(:default_method)
|
432
|
+
define_method(getter_name) {@options.key?(name) ? @options[name] : __send__(settings[:default_method])}
|
433
|
+
elsif settings.key?(:default_proc)
|
434
|
+
define_method(getter_name) {@options.key?(name) ? @options[name] : settings[:default_proc].call(self)}
|
435
|
+
else
|
436
|
+
define_method(getter_name) {@options[name]}
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def opt_writer(name, settings = {})
|
441
|
+
name = name.to_sym
|
442
|
+
setter_name = name.to_s.gsub(/^(.*[^=\?])[=\?]*$/, '\1=')
|
443
|
+
|
444
|
+
convertor =
|
445
|
+
case settings[:type]
|
446
|
+
when :boolen then proc {|value| not not value}
|
447
|
+
when :string then proc {|value| value.to_s}
|
448
|
+
when :symbol then proc {|value| value.to_sym}
|
449
|
+
when :integer then proc {|value| value.to_i}
|
450
|
+
when :float then proc {|value| value.to_f}
|
451
|
+
when :length then proc {|value| Length.new(value)}
|
452
|
+
when :point then proc {|value| Coordinate.new(value)}
|
453
|
+
when :color then proc {|value| Color.new(value)}
|
454
|
+
when :font then proc {|value| Font.new(value)}
|
455
|
+
else proc {|value| value} if !settings.key?(:map_method) && !settings.key?(:mapper) && !settings.key?(:item_type)
|
456
|
+
end
|
457
|
+
|
458
|
+
validator =
|
459
|
+
case settings[:type]
|
460
|
+
when :symbol
|
461
|
+
if settings.key?(:valid_values)
|
462
|
+
proc {|value| raise ArgumentError, "\"#{value}\" is invalid value" unless settings[:valid_values].include?(convertor.call(value))}
|
463
|
+
end
|
464
|
+
when :integer, :float
|
465
|
+
if settings.key?(:range)
|
466
|
+
proc {|value| raise ArgumentError, "\"#{value}\" is invalid value" unless settings[:range].include?(convertor.call(value))}
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
case settings[:type]
|
471
|
+
when :hash
|
472
|
+
raise ArgumentError, "keys is not specified" unless settings.key?(:keys)
|
473
|
+
define_method(setter_name) {|values|
|
474
|
+
if values.nil? || values.empty?
|
475
|
+
@options.delete(name)
|
476
|
+
else
|
477
|
+
@options[name] =
|
478
|
+
settings[:keys].inject({}) do |hash, key|
|
479
|
+
hash[key] =
|
480
|
+
if convertor
|
481
|
+
convertor.call(values[key])
|
482
|
+
elsif settings.key?(:map_method)
|
483
|
+
__send__(settings[:map_method], values[key])
|
484
|
+
elsif settings.key?(:mapper)
|
485
|
+
settings[:mapper].call(values[key], self)
|
486
|
+
elsif settings.key?(:item_type)
|
487
|
+
case settings[:item_type]
|
488
|
+
when :boolen then not not values[key]
|
489
|
+
when :string then values[key].to_s
|
490
|
+
when :symbol then values[key].to_sym
|
491
|
+
when :integer then values[key].to_i
|
492
|
+
when :float then values[key].to_f
|
493
|
+
when :length then Length.new(values[key])
|
494
|
+
when :point then Coordinate.new(values[key])
|
495
|
+
when :color then value[key].respond_to?(:format) ? value[key] : Color.new(values[key])
|
496
|
+
when :font then Font.new(values[key])
|
497
|
+
else values[key]
|
498
|
+
end
|
499
|
+
end if values[key]
|
500
|
+
hash
|
501
|
+
end
|
502
|
+
end
|
503
|
+
values
|
504
|
+
}
|
505
|
+
when :array
|
506
|
+
define_method(setter_name) {|values|
|
507
|
+
if values.nil? || values.empty?
|
508
|
+
@options.delete(name)
|
509
|
+
else
|
510
|
+
@options[name] =
|
511
|
+
Array(values).to_a.map {|item|
|
512
|
+
if convertor
|
513
|
+
convertor.call(item)
|
514
|
+
elsif settings.key?(:map_method)
|
515
|
+
__send__(settings[:map_method], item)
|
516
|
+
elsif settings.key?(:mapper)
|
517
|
+
settings[:mapper].call(item, self)
|
518
|
+
elsif settings.key?(:item_type)
|
519
|
+
case settings[:item_type]
|
520
|
+
when :boolen then not not item
|
521
|
+
when :string then item.to_s
|
522
|
+
when :symbol then item.to_sym
|
523
|
+
when :integer then item.to_i
|
524
|
+
when :float then item.to_f
|
525
|
+
when :length then Length.new(item)
|
526
|
+
when :point then Coordinate.new(item)
|
527
|
+
when :color then item.respond_to?(:write_as) ? item : Color.new(item)
|
528
|
+
when :font then Font.new(item)
|
529
|
+
else item
|
530
|
+
end
|
531
|
+
else
|
532
|
+
item
|
533
|
+
end
|
534
|
+
}
|
535
|
+
end
|
536
|
+
values
|
537
|
+
}
|
538
|
+
else
|
539
|
+
define_method(setter_name) {|value|
|
540
|
+
if value.nil?
|
541
|
+
@options.delete(name)
|
542
|
+
else
|
543
|
+
validator && validator.call(value)
|
544
|
+
@options[name] =
|
545
|
+
if convertor
|
546
|
+
convertor.call(value)
|
547
|
+
elsif settings.key?(:map_method)
|
548
|
+
__send__(settings[:map_method], value)
|
549
|
+
elsif ettings.key?(:mapper)
|
550
|
+
settings[:mapper].call(value, self)
|
551
|
+
elsif settings.key?(:item_type)
|
552
|
+
case settings[:item_type]
|
553
|
+
when :boolen then not not value
|
554
|
+
when :string then value.to_s
|
555
|
+
when :symbol then value.to_sym
|
556
|
+
when :integer then value.to_i
|
557
|
+
when :float then value.to_f
|
558
|
+
when :length then Length.new(value)
|
559
|
+
when :point then Coordinate.new(value)
|
560
|
+
when :color then Color.new(value)
|
561
|
+
when :font then Font.new(value)
|
562
|
+
else value
|
563
|
+
end
|
564
|
+
else
|
565
|
+
value
|
566
|
+
end
|
567
|
+
end
|
568
|
+
value
|
569
|
+
}
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
def opt_accessor(name, settings = {})
|
574
|
+
opt_reader(name, settings)
|
575
|
+
opt_writer(name, settings)
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|