write_xlsx 0.75.0 → 0.76.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.
- checksums.yaml +4 -4
- data/Changes +4 -0
- data/Gemfile +8 -2
- data/README.md +4 -2
- data/lib/write_xlsx/chart/axis.rb +69 -96
- data/lib/write_xlsx/chart/bar.rb +18 -21
- data/lib/write_xlsx/chart/caption.rb +1 -1
- data/lib/write_xlsx/chart/column.rb +1 -5
- data/lib/write_xlsx/chart/line.rb +2 -16
- data/lib/write_xlsx/chart/pie.rb +18 -40
- data/lib/write_xlsx/chart/radar.rb +2 -5
- data/lib/write_xlsx/chart/scatter.rb +24 -32
- data/lib/write_xlsx/chart/series.rb +218 -236
- data/lib/write_xlsx/chart/stock.rb +15 -27
- data/lib/write_xlsx/chart.rb +303 -392
- data/lib/write_xlsx/chartsheet.rb +22 -20
- data/lib/write_xlsx/colors.rb +9 -15
- data/lib/write_xlsx/drawing.rb +26 -28
- data/lib/write_xlsx/format.rb +15 -15
- data/lib/write_xlsx/package/comments.rb +1 -1
- data/lib/write_xlsx/package/conditional_format.rb +8 -8
- data/lib/write_xlsx/package/relationships.rb +4 -15
- data/lib/write_xlsx/package/styles.rb +9 -16
- data/lib/write_xlsx/shape.rb +1 -15
- data/lib/write_xlsx/sparkline.rb +1 -1
- data/lib/write_xlsx/utility.rb +69 -13
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook.rb +19 -7
- data/lib/write_xlsx/worksheet/cell_data.rb +1 -1
- data/lib/write_xlsx/worksheet/hyperlink.rb +39 -37
- data/lib/write_xlsx/worksheet.rb +44 -72
- data/lib/write_xlsx/zip_file_utils.rb +99 -0
- data/test/chart/test_add_series.rb +5 -5
- data/test/chart/test_write_d_lbls.rb +1 -1
- data/test/chart/test_write_major_gridlines.rb +1 -1
- data/test/chart/test_write_marker.rb +1 -1
- data/test/chart/test_write_number_format.rb +1 -1
- data/test/helper.rb +7 -4
- data/test/regression/klt.csv +4 -0
- data/test/regression/test_chart_column07.rb +44 -0
- data/test/regression/test_chart_column08.rb +46 -0
- data/test/regression/test_chart_date01.rb +57 -0
- data/test/regression/test_chart_date02.rb +59 -0
- data/test/regression/test_chart_date03.rb +59 -0
- data/test/regression/test_chart_date04.rb +61 -0
- data/test/regression/test_chart_stock01.rb +1 -6
- data/test/regression/test_chart_title02.rb +44 -0
- data/test/regression/test_escapes01.rb +1 -1
- data/test/regression/test_escapes02.rb +1 -1
- data/test/regression/test_escapes03.rb +1 -1
- data/test/regression/test_escapes04.rb +1 -1
- data/test/regression/test_escapes05.rb +1 -1
- data/test/regression/test_escapes06.rb +1 -1
- data/test/regression/test_escapes07.rb +1 -1
- data/test/regression/test_escapes08.rb +1 -1
- data/test/regression/test_set_column09.rb +31 -0
- data/test/regression/test_shared_strings_encoding.rb +103 -0
- data/test/regression/xlsx_files/chart_column07.xlsx +0 -0
- data/test/regression/xlsx_files/chart_column08.xlsx +0 -0
- data/test/regression/xlsx_files/chart_date01.xlsx +0 -0
- data/test/regression/xlsx_files/chart_date02.xlsx +0 -0
- data/test/regression/xlsx_files/chart_date03.xlsx +0 -0
- data/test/regression/xlsx_files/chart_date04.xlsx +0 -0
- data/test/regression/xlsx_files/chart_title02.xlsx +0 -0
- data/test/regression/xlsx_files/set_column09.xlsx +0 -0
- data/test/regression/xlsx_files/shared_strings_encoding.xlsx +0 -0
- data/test/worksheet/test_write_hyperlink.rb +10 -15
- data/write_xlsx.gemspec +0 -3
- metadata +48 -39
- data/test/worksheet/test_set_column.rb +0 -25
@@ -1,281 +1,263 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
3
|
+
module Writexlsx
|
4
|
+
class Chart
|
5
|
+
class Chartline
|
6
|
+
include Writexlsx::Utility
|
7
|
+
|
8
|
+
attr_reader :line, :fill, :type
|
9
|
+
|
10
|
+
def initialize(params)
|
11
|
+
@line = params[:line]
|
12
|
+
@fill = params[:fill]
|
13
|
+
# Set the line properties for the marker..
|
14
|
+
@line = line_properties(@line)
|
15
|
+
# Allow 'border' as a synonym for 'line'.
|
16
|
+
@line = line_properties(params[:border]) if params[:border]
|
17
|
+
|
18
|
+
# Set the fill properties for the marker.
|
19
|
+
@fill = fill_properties(@fill)
|
20
|
+
end
|
21
|
+
|
22
|
+
def line_defined?
|
23
|
+
line && ptrue?(line[:_defined])
|
24
|
+
end
|
25
|
+
|
26
|
+
def fill_defined?
|
27
|
+
fill && ptrue?(fill[:_defined])
|
28
|
+
end
|
24
29
|
end
|
25
|
-
@fill = fill_properties(params[:fill])
|
26
|
-
@marker = marker_properties(params[:marker])
|
27
|
-
@trendline = trendline_properties(params[:trendline])
|
28
|
-
@smooth = params[:smooth]
|
29
|
-
@error_bars = {
|
30
|
-
:_x_error_bars => error_bars_properties(params[:x_error_bars]),
|
31
|
-
:_y_error_bars => error_bars_properties(params[:y_error_bars])
|
32
|
-
}
|
33
|
-
@points = points_properties(params[:points])
|
34
|
-
@labels = labels_properties(params[:data_labels])
|
35
|
-
@invert_if_neg = params[:invert_if_negative]
|
36
|
-
@x2_axis = params[:x2_axis]
|
37
|
-
@y2_axis = params[:y2_axis]
|
38
|
-
end
|
39
30
|
|
40
|
-
|
41
|
-
methods = %w[categories values name name_formula name_id
|
42
|
-
cat_data_id val_data_id
|
43
|
-
line fill marker trendline
|
44
|
-
smooth labels invert_if_neg x2_axis y2_axis error_bars points ]
|
45
|
-
methods.each do |method|
|
46
|
-
return false unless self.instance_variable_get("@#{method}") == other.instance_variable_get("@#{method}")
|
31
|
+
class Point < Chartline
|
47
32
|
end
|
48
|
-
true
|
49
|
-
end
|
50
33
|
|
51
|
-
|
34
|
+
class Gridline < Chartline
|
35
|
+
attr_reader :visible
|
52
36
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
return data unless data.kind_of?(Array)
|
59
|
-
xl_range_formula(*data)
|
60
|
-
end
|
37
|
+
def initialize(params)
|
38
|
+
super(params)
|
39
|
+
@visible = params[:visible]
|
40
|
+
end
|
41
|
+
end
|
61
42
|
|
62
|
-
|
63
|
-
|
64
|
-
#
|
65
|
-
def fill_properties(fill) # :nodoc:
|
66
|
-
return { :_defined => 0 } unless fill
|
43
|
+
class Trendline < Chartline
|
44
|
+
attr_reader :name, :forward, :backward, :order, :period
|
67
45
|
|
68
|
-
|
46
|
+
def initialize(params)
|
47
|
+
super(params)
|
69
48
|
|
70
|
-
|
71
|
-
|
49
|
+
@name = params[:name]
|
50
|
+
@forward = params[:forward]
|
51
|
+
@backward = params[:backward]
|
52
|
+
@order = params[:order]
|
53
|
+
@period = params[:period]
|
54
|
+
@type = value_or_raise(types, params[:type], 'trendline type')
|
55
|
+
end
|
72
56
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
:x => 'x',
|
86
|
-
:star => 'star',
|
87
|
-
:dot => 'dot',
|
88
|
-
:short_dash => 'dot',
|
89
|
-
:dash => 'dash',
|
90
|
-
:long_dash => 'dash',
|
91
|
-
:circle => 'circle',
|
92
|
-
:plus => 'plus',
|
93
|
-
:picture => 'picture'
|
94
|
-
}
|
95
|
-
|
96
|
-
# Check for valid types.
|
97
|
-
marker_type = marker[:type]
|
98
|
-
|
99
|
-
if marker_type
|
100
|
-
marker[:automatic] = 1 if marker_type == 'automatic'
|
101
|
-
marker[:type] = value_or_raise(types, marker_type, 'maker type')
|
57
|
+
private
|
58
|
+
|
59
|
+
def types
|
60
|
+
{
|
61
|
+
:exponential => 'exp',
|
62
|
+
:linear => 'linear',
|
63
|
+
:log => 'log',
|
64
|
+
:moving_average => 'movingAvg',
|
65
|
+
:polynomial => 'poly',
|
66
|
+
:power => 'power'
|
67
|
+
}
|
68
|
+
end
|
102
69
|
end
|
103
70
|
|
104
|
-
|
105
|
-
|
71
|
+
class Marker < Chartline
|
72
|
+
attr_reader :size
|
106
73
|
|
107
|
-
|
108
|
-
|
74
|
+
def initialize(params)
|
75
|
+
super(params)
|
109
76
|
|
110
|
-
|
111
|
-
|
77
|
+
if params[:type]
|
78
|
+
@type = value_or_raise(types, params[:type], 'maker type')
|
79
|
+
end
|
112
80
|
|
113
|
-
|
114
|
-
|
81
|
+
@size = params[:size]
|
82
|
+
@automatic = false
|
83
|
+
@automatic = true if @type == 'automatic'
|
84
|
+
end
|
115
85
|
|
116
|
-
|
117
|
-
|
86
|
+
def automatic?
|
87
|
+
@automatic
|
88
|
+
end
|
118
89
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
90
|
+
private
|
91
|
+
|
92
|
+
def types
|
93
|
+
{
|
94
|
+
:automatic => 'automatic',
|
95
|
+
:none => 'none',
|
96
|
+
:square => 'square',
|
97
|
+
:diamond => 'diamond',
|
98
|
+
:triangle => 'triangle',
|
99
|
+
:x => 'x',
|
100
|
+
:star => 'star',
|
101
|
+
:dot => 'dot',
|
102
|
+
:short_dash => 'dot',
|
103
|
+
:dash => 'dash',
|
104
|
+
:long_dash => 'dash',
|
105
|
+
:circle => 'circle',
|
106
|
+
:plus => 'plus',
|
107
|
+
:picture => 'picture'
|
108
|
+
}
|
109
|
+
end
|
110
|
+
end
|
124
111
|
|
125
|
-
|
126
|
-
|
127
|
-
:linear => 'linear',
|
128
|
-
:log => 'log',
|
129
|
-
:moving_average => 'movingAvg',
|
130
|
-
:polynomial => 'poly',
|
131
|
-
:power => 'power'
|
132
|
-
}
|
112
|
+
class Errorbars
|
113
|
+
include Writexlsx::Utility
|
133
114
|
|
134
|
-
|
135
|
-
|
115
|
+
attr_reader :type, :direction, :endcap, :value, :line, :fill
|
116
|
+
attr_reader :plus_values, :minus_values, :plus_data, :minus_data
|
136
117
|
|
137
|
-
|
118
|
+
def initialize(params)
|
119
|
+
@type = types[params[:type].to_sym] || 'fixedVal'
|
120
|
+
@value = params[:value] || 1 # value for error types that require it.
|
121
|
+
@endcap = params[:end_style] || 1 # end-cap style.
|
138
122
|
|
139
|
-
|
140
|
-
|
123
|
+
# Set the error bar direction.
|
124
|
+
@direction = error_bar_direction(params[:direction])
|
141
125
|
|
142
|
-
|
143
|
-
|
126
|
+
# Set any custom values
|
127
|
+
@plus_values = params[:plus_values] || [1]
|
128
|
+
@minus_values = params[:minus_values] || [1]
|
129
|
+
@plus_data = params[:plus_data] || []
|
130
|
+
@minus_data = params[:minus_data] || []
|
144
131
|
|
145
|
-
|
146
|
-
|
132
|
+
# Set the line properties for the error bars.
|
133
|
+
@line = line_properties(params[:line])
|
134
|
+
@fill = params[:fill]
|
135
|
+
end
|
147
136
|
|
148
|
-
|
149
|
-
trendline[:_fill] = fill
|
137
|
+
private
|
150
138
|
|
151
|
-
|
152
|
-
|
139
|
+
def types
|
140
|
+
{
|
141
|
+
:fixed => 'fixedVal',
|
142
|
+
:percentage => 'percentage',
|
143
|
+
:standard_deviation => 'stdDev',
|
144
|
+
:standard_error => 'stdErr',
|
145
|
+
:custom => 'cust'
|
146
|
+
}
|
147
|
+
end
|
153
148
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
:_value => 1,
|
165
|
-
:_endcap => 1,
|
166
|
-
:_direction => 'both',
|
167
|
-
:_plus_values => [1],
|
168
|
-
:_minus_values => [1],
|
169
|
-
:_plus_data => [],
|
170
|
-
:_minus_data => []
|
171
|
-
}
|
172
|
-
|
173
|
-
types = {
|
174
|
-
:fixed => 'fixedVal',
|
175
|
-
:percentage => 'percentage',
|
176
|
-
:standard_deviation => 'stdDev',
|
177
|
-
:standard_error => 'stdErr',
|
178
|
-
:custom => 'cust'
|
179
|
-
}
|
180
|
-
|
181
|
-
# Check the error bars type.
|
182
|
-
error_type = params[:type].to_sym
|
183
|
-
|
184
|
-
if types.key?(error_type)
|
185
|
-
error_bars[:_type] = types[error_type]
|
186
|
-
else
|
187
|
-
raise "Unknown error bars type '#{error_type}'\n"
|
149
|
+
def error_bar_direction(direction)
|
150
|
+
case direction
|
151
|
+
when 'minus'
|
152
|
+
'minus'
|
153
|
+
when 'plus'
|
154
|
+
'plus'
|
155
|
+
else
|
156
|
+
'both'
|
157
|
+
end
|
158
|
+
end
|
188
159
|
end
|
189
160
|
|
190
|
-
|
191
|
-
|
192
|
-
error_bars[:_value] = params[:value]
|
193
|
-
end
|
161
|
+
class Series
|
162
|
+
include Writexlsx::Utility
|
194
163
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
164
|
+
attr_reader :values, :categories, :name, :name_formula, :name_id
|
165
|
+
attr_reader :cat_data_id, :val_data_id, :fill
|
166
|
+
attr_reader :trendline, :smooth, :labels, :invert_if_negative
|
167
|
+
attr_reader :x2_axis, :y2_axis, :error_bars, :points
|
168
|
+
attr_accessor :line, :marker
|
169
|
+
|
170
|
+
def initialize(chart, params = {})
|
171
|
+
@values = aref_to_formula(params[:values])
|
172
|
+
@categories = aref_to_formula(params[:categories])
|
173
|
+
@name, @name_formula =
|
174
|
+
chart.process_names(params[:name], params[:name_formula])
|
175
|
+
|
176
|
+
set_data_ids(chart, params)
|
177
|
+
|
178
|
+
@line = line_properties(params[:border] || params[:line])
|
179
|
+
@fill = fill_properties(params[:fill])
|
199
180
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
181
|
+
@marker = Marker.new(params[:marker]) if params[:marker]
|
182
|
+
@trendline = Trendline.new(params[:trendline]) if params[:trendline]
|
183
|
+
@error_bars = errorbars(params[:x_error_bars], params[:y_error_bars])
|
184
|
+
@points = params[:points].collect { |p| p ? Point.new(p) : p } if params[:points]
|
185
|
+
@labels = labels_properties(params[:data_labels])
|
186
|
+
|
187
|
+
[:smooth, :invert_if_negative, :x2_axis, :y2_axis].
|
188
|
+
each { |key| instance_variable_set("@#{key}", params[key]) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def ==(other)
|
192
|
+
methods = %w[categories values name name_formula name_id
|
193
|
+
cat_data_id val_data_id
|
194
|
+
line fill marker trendline
|
195
|
+
smooth labels invert_if_neg x2_axis y2_axis error_bars points ]
|
196
|
+
methods.each do |method|
|
197
|
+
return false unless self.instance_variable_get("@#{method}") == other.instance_variable_get("@#{method}")
|
198
|
+
end
|
199
|
+
true
|
208
200
|
end
|
209
|
-
end
|
210
201
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
error_bars[:_plus_data] = params[:plus_data] if params[:plus_data]
|
215
|
-
error_bars[:_minus_data] = params[:minus_data] if params[:minus_data]
|
202
|
+
def line_defined?
|
203
|
+
line && ptrue?(line[:_defined])
|
204
|
+
end
|
216
205
|
|
217
|
-
|
218
|
-
error_bars[:_line] = line_properties(params[:line])
|
206
|
+
private
|
219
207
|
|
220
|
-
|
221
|
-
|
208
|
+
#
|
209
|
+
# Convert and aref of row col values to a range formula.
|
210
|
+
#
|
211
|
+
def aref_to_formula(data) # :nodoc:
|
212
|
+
# If it isn't an array ref it is probably a formula already.
|
213
|
+
return data unless data.kind_of?(Array)
|
214
|
+
xl_range_formula(*data)
|
215
|
+
end
|
222
216
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
217
|
+
def set_data_ids(chart, params)
|
218
|
+
@cat_data_id = chart.data_id(@categories, params[:categories_data])
|
219
|
+
@val_data_id = chart.data_id(@values, params[:values_data])
|
220
|
+
@name_id = chart.data_id(@name_formula, params[:name_data])
|
221
|
+
end
|
228
222
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
223
|
+
def errorbars(x, y)
|
224
|
+
{
|
225
|
+
:_x_error_bars => x ? Errorbars.new(x) : nil,
|
226
|
+
:_y_error_bars => y ? Errorbars.new(y) : nil
|
227
|
+
}
|
228
|
+
end
|
234
229
|
|
235
|
-
|
236
|
-
|
237
|
-
|
230
|
+
#
|
231
|
+
# Convert user defined labels properties to the structure required internally.
|
232
|
+
#
|
233
|
+
def labels_properties(labels) # :nodoc:
|
234
|
+
return nil unless labels
|
235
|
+
|
236
|
+
position = labels[:position]
|
237
|
+
if position.nil? || position.empty?
|
238
|
+
labels.delete(:position)
|
239
|
+
else
|
240
|
+
# Map user defined label positions to Excel positions.
|
241
|
+
labels[:position] = value_or_raise(positions, position, 'label position')
|
238
242
|
end
|
239
243
|
|
240
|
-
|
241
|
-
fill = fill_properties(user_point[:fill])
|
242
|
-
|
243
|
-
point = {}
|
244
|
-
point[:_line] = line
|
245
|
-
point[:_fill] = fill
|
244
|
+
labels
|
246
245
|
end
|
247
|
-
points << point
|
248
|
-
end
|
249
|
-
points
|
250
|
-
end
|
251
246
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
:left => 'l',
|
267
|
-
:top => 't',
|
268
|
-
:above => 't',
|
269
|
-
:bottom => 'b',
|
270
|
-
:below => 'b',
|
271
|
-
:inside_end => 'inEnd',
|
272
|
-
:outside_end => 'outEnd',
|
273
|
-
:best_fit => 'bestFit'
|
274
|
-
}
|
275
|
-
|
276
|
-
labels[:position] = value_or_raise(positions, position, 'label position')
|
247
|
+
def positions
|
248
|
+
{
|
249
|
+
:center => 'ctr',
|
250
|
+
:right => 'r',
|
251
|
+
:left => 'l',
|
252
|
+
:top => 't',
|
253
|
+
:above => 't',
|
254
|
+
:bottom => 'b',
|
255
|
+
:below => 'b',
|
256
|
+
:inside_end => 'inEnd',
|
257
|
+
:outside_end => 'outEnd',
|
258
|
+
:best_fit => 'bestFit'
|
259
|
+
}
|
260
|
+
end
|
277
261
|
end
|
278
|
-
|
279
|
-
labels
|
280
262
|
end
|
281
263
|
end
|
@@ -26,8 +26,9 @@ module Writexlsx
|
|
26
26
|
|
27
27
|
def initialize(subtype)
|
28
28
|
super(subtype)
|
29
|
-
@show_crosses
|
30
|
-
@hi_low_lines
|
29
|
+
@show_crosses = false
|
30
|
+
@hi_low_lines = Chartline.new({})
|
31
|
+
@date_category = true
|
31
32
|
|
32
33
|
# Override and reset the default axis values.
|
33
34
|
@x_axis.defaults[:num_format] = 'dd/mm/yyyy'
|
@@ -80,38 +81,25 @@ module Writexlsx
|
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
83
|
-
#
|
84
|
-
# Overridden to use write_date_axis() instead of write_cat_axis().
|
85
|
-
#
|
86
|
-
def write_plot_area
|
87
|
-
write_plot_area_base(:stock)
|
88
|
-
end
|
89
|
-
|
90
84
|
#
|
91
85
|
# Add default formatting to the series data.
|
92
86
|
#
|
93
87
|
def modify_series_formatting
|
94
|
-
index = 0
|
95
88
|
array = []
|
96
|
-
@series.
|
89
|
+
@series.each_with_index do |series, index|
|
97
90
|
if index % 4 != 3
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
else
|
110
|
-
series.marker = { :type => 'none' }
|
111
|
-
end
|
112
|
-
end
|
91
|
+
series.line = {
|
92
|
+
:width => 2.25,
|
93
|
+
:none => 1,
|
94
|
+
:_defined => 1
|
95
|
+
} unless series.line_defined?
|
96
|
+
|
97
|
+
if index % 4 == 2
|
98
|
+
series.marker = Marker.new(:type => 'dot', :size => 3)
|
99
|
+
else
|
100
|
+
series.marker = Marker.new(:type => 'none')
|
101
|
+
end unless ptrue?(series.marker)
|
113
102
|
end
|
114
|
-
index += 1
|
115
103
|
array << series
|
116
104
|
end
|
117
105
|
@series = array
|