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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Changes +4 -0
  3. data/Gemfile +8 -2
  4. data/README.md +4 -2
  5. data/lib/write_xlsx/chart/axis.rb +69 -96
  6. data/lib/write_xlsx/chart/bar.rb +18 -21
  7. data/lib/write_xlsx/chart/caption.rb +1 -1
  8. data/lib/write_xlsx/chart/column.rb +1 -5
  9. data/lib/write_xlsx/chart/line.rb +2 -16
  10. data/lib/write_xlsx/chart/pie.rb +18 -40
  11. data/lib/write_xlsx/chart/radar.rb +2 -5
  12. data/lib/write_xlsx/chart/scatter.rb +24 -32
  13. data/lib/write_xlsx/chart/series.rb +218 -236
  14. data/lib/write_xlsx/chart/stock.rb +15 -27
  15. data/lib/write_xlsx/chart.rb +303 -392
  16. data/lib/write_xlsx/chartsheet.rb +22 -20
  17. data/lib/write_xlsx/colors.rb +9 -15
  18. data/lib/write_xlsx/drawing.rb +26 -28
  19. data/lib/write_xlsx/format.rb +15 -15
  20. data/lib/write_xlsx/package/comments.rb +1 -1
  21. data/lib/write_xlsx/package/conditional_format.rb +8 -8
  22. data/lib/write_xlsx/package/relationships.rb +4 -15
  23. data/lib/write_xlsx/package/styles.rb +9 -16
  24. data/lib/write_xlsx/shape.rb +1 -15
  25. data/lib/write_xlsx/sparkline.rb +1 -1
  26. data/lib/write_xlsx/utility.rb +69 -13
  27. data/lib/write_xlsx/version.rb +1 -1
  28. data/lib/write_xlsx/workbook.rb +19 -7
  29. data/lib/write_xlsx/worksheet/cell_data.rb +1 -1
  30. data/lib/write_xlsx/worksheet/hyperlink.rb +39 -37
  31. data/lib/write_xlsx/worksheet.rb +44 -72
  32. data/lib/write_xlsx/zip_file_utils.rb +99 -0
  33. data/test/chart/test_add_series.rb +5 -5
  34. data/test/chart/test_write_d_lbls.rb +1 -1
  35. data/test/chart/test_write_major_gridlines.rb +1 -1
  36. data/test/chart/test_write_marker.rb +1 -1
  37. data/test/chart/test_write_number_format.rb +1 -1
  38. data/test/helper.rb +7 -4
  39. data/test/regression/klt.csv +4 -0
  40. data/test/regression/test_chart_column07.rb +44 -0
  41. data/test/regression/test_chart_column08.rb +46 -0
  42. data/test/regression/test_chart_date01.rb +57 -0
  43. data/test/regression/test_chart_date02.rb +59 -0
  44. data/test/regression/test_chart_date03.rb +59 -0
  45. data/test/regression/test_chart_date04.rb +61 -0
  46. data/test/regression/test_chart_stock01.rb +1 -6
  47. data/test/regression/test_chart_title02.rb +44 -0
  48. data/test/regression/test_escapes01.rb +1 -1
  49. data/test/regression/test_escapes02.rb +1 -1
  50. data/test/regression/test_escapes03.rb +1 -1
  51. data/test/regression/test_escapes04.rb +1 -1
  52. data/test/regression/test_escapes05.rb +1 -1
  53. data/test/regression/test_escapes06.rb +1 -1
  54. data/test/regression/test_escapes07.rb +1 -1
  55. data/test/regression/test_escapes08.rb +1 -1
  56. data/test/regression/test_set_column09.rb +31 -0
  57. data/test/regression/test_shared_strings_encoding.rb +103 -0
  58. data/test/regression/xlsx_files/chart_column07.xlsx +0 -0
  59. data/test/regression/xlsx_files/chart_column08.xlsx +0 -0
  60. data/test/regression/xlsx_files/chart_date01.xlsx +0 -0
  61. data/test/regression/xlsx_files/chart_date02.xlsx +0 -0
  62. data/test/regression/xlsx_files/chart_date03.xlsx +0 -0
  63. data/test/regression/xlsx_files/chart_date04.xlsx +0 -0
  64. data/test/regression/xlsx_files/chart_title02.xlsx +0 -0
  65. data/test/regression/xlsx_files/set_column09.xlsx +0 -0
  66. data/test/regression/xlsx_files/shared_strings_encoding.xlsx +0 -0
  67. data/test/worksheet/test_write_hyperlink.rb +10 -15
  68. data/write_xlsx.gemspec +0 -3
  69. metadata +48 -39
  70. data/test/worksheet/test_set_column.rb +0 -25
@@ -1,281 +1,263 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- class Series
4
- include Writexlsx::Utility
5
-
6
- attr_reader :values, :categories, :name, :name_formula, :name_id
7
- attr_reader :cat_data_id, :val_data_id, :fill
8
- attr_reader :trendline, :smooth, :labels, :invert_if_neg
9
- attr_reader :x2_axis, :y2_axis, :error_bars, :points
10
- attr_accessor :line, :marker
11
-
12
- def initialize(chart, params = {})
13
- @values = aref_to_formula(params[:values])
14
- @categories = aref_to_formula(params[:categories])
15
- @name, @name_formula =
16
- chart.process_names(params[:name], params[:name_formula])
17
- @cat_data_id = chart.get_data_id(@categories, params[:categories_data])
18
- @val_data_id = chart.get_data_id(@values, params[:values_data])
19
- @name_id = chart.get_data_id(@name_formula, params[:name_data])
20
- if params[:border]
21
- @line = line_properties(params[:border])
22
- else
23
- @line = line_properties(params[:line])
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
- def ==(other)
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
- private
34
+ class Gridline < Chartline
35
+ attr_reader :visible
52
36
 
53
- #
54
- # Convert and aref of row col values to a range formula.
55
- #
56
- def aref_to_formula(data) # :nodoc:
57
- # If it isn't an array ref it is probably a formula already.
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
- # Convert user defined fill properties to the structure required internally.
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
- fill[:_defined] = 1
46
+ def initialize(params)
47
+ super(params)
69
48
 
70
- fill
71
- end
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
- # Convert user defined marker properties to the structure required internally.
75
- #
76
- def marker_properties(marker) # :nodoc:
77
- return unless marker
78
-
79
- types = {
80
- :automatic => 'automatic',
81
- :none => 'none',
82
- :square => 'square',
83
- :diamond => 'diamond',
84
- :triangle => 'triangle',
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
- # Set the line properties for the marker..
105
- line = line_properties(marker[:line])
71
+ class Marker < Chartline
72
+ attr_reader :size
106
73
 
107
- # Allow 'border' as a synonym for 'line'.
108
- line = line_properties(marker[:border]) if marker[:border]
74
+ def initialize(params)
75
+ super(params)
109
76
 
110
- # Set the fill properties for the marker.
111
- fill = fill_properties(marker[:fill])
77
+ if params[:type]
78
+ @type = value_or_raise(types, params[:type], 'maker type')
79
+ end
112
80
 
113
- marker[:_line] = line
114
- marker[:_fill] = fill
81
+ @size = params[:size]
82
+ @automatic = false
83
+ @automatic = true if @type == 'automatic'
84
+ end
115
85
 
116
- marker
117
- end
86
+ def automatic?
87
+ @automatic
88
+ end
118
89
 
119
- #
120
- # Convert user defined trendline properties to the structure required internally.
121
- #
122
- def trendline_properties(trendline) # :nodoc:
123
- return unless trendline
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
- types = {
126
- :exponential => 'exp',
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
- # Check the trendline type.
135
- trend_type = trendline[:type]
115
+ attr_reader :type, :direction, :endcap, :value, :line, :fill
116
+ attr_reader :plus_values, :minus_values, :plus_data, :minus_data
136
117
 
137
- trendline[:type] = value_or_raise(types, trend_type, 'trendline type')
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
- # Set the line properties for the trendline..
140
- line = line_properties(trendline[:line])
123
+ # Set the error bar direction.
124
+ @direction = error_bar_direction(params[:direction])
141
125
 
142
- # Allow 'border' as a synonym for 'line'.
143
- line = line_properties(trendline[:border]) if trendline[:border]
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
- # Set the fill properties for the trendline.
146
- fill = fill_properties(trendline[:fill])
132
+ # Set the line properties for the error bars.
133
+ @line = line_properties(params[:line])
134
+ @fill = params[:fill]
135
+ end
147
136
 
148
- trendline[:_line] = line
149
- trendline[:_fill] = fill
137
+ private
150
138
 
151
- return trendline
152
- end
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
- # Convert user defined error bars properties to structure required
156
- # internally.
157
- #
158
- def error_bars_properties(params = {})
159
- return if !ptrue?(params) || params.empty?
160
-
161
- # Default values.
162
- error_bars = {
163
- :_type => 'fixedVal',
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
- # Set the value for error types that require it.
191
- if params.key?(:value)
192
- error_bars[:_value] = params[:value]
193
- end
161
+ class Series
162
+ include Writexlsx::Utility
194
163
 
195
- # Set the end-cap style.
196
- if params.key?(:end_style)
197
- error_bars[:_endcap] = params[:end_style]
198
- end
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
- # Set the error bar direction.
201
- if params.key?(:direction)
202
- if params[:direction] == 'minus'
203
- error_bars[:_direction] = 'minus'
204
- elsif params[:direction] == 'plus'
205
- error_bars[:_direction] = 'plus'
206
- else
207
- # Default to 'both'
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
- # Set any custom values
212
- error_bars[:_plus_values] = params[:plus_values] if params[:plus_values]
213
- error_bars[:_minus_values] = params[:minus_values] if params[:minus_values]
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
- # Set the line properties for the error bars.
218
- error_bars[:_line] = line_properties(params[:line])
206
+ private
219
207
 
220
- error_bars
221
- end
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
- # Convert user defined points properties to structure required internally.
225
- #
226
- def points_properties(user_points = nil)
227
- return unless user_points
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
- points = []
230
- user_points.each do |user_point|
231
- if user_point
232
- # Set the lline properties for the point.
233
- line = line_properties(user_point[:line])
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
- # Allow 'border' as a synonym for 'line'.
236
- if user_point[:border]
237
- line = line_properties(user_point[:border])
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
- # Set the fill properties for the chartarea.
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
- # Convert user defined labels properties to the structure required internally.
254
- #
255
- def labels_properties(labels) # :nodoc:
256
- return nil unless labels
257
-
258
- position = labels[:position]
259
- if position.nil? || position.empty?
260
- labels.delete(:position)
261
- else
262
- # Map user defined label positions to Excel positions.
263
- positions = {
264
- :center => 'ctr',
265
- :right => 'r',
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 = false
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.each do |series|
89
+ @series.each_with_index do |series, index|
97
90
  if index % 4 != 3
98
- if series.line[:_defined].nil? || series.line[:_defined] == 0
99
- series.line = {
100
- :width => 2.25,
101
- :none => 1,
102
- :_defined => 1
103
- }
104
- end
105
-
106
- if series.marker.nil? || series.marker == 0
107
- if index % 4 == 2
108
- series.marker = { :type => 'dot', :size => 3 }
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