write_xlsx 0.75.0 → 0.76.0

Sign up to get free protection for your applications and to get access to all the features.
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